/*
 * ApexMaps styles.
 *
 * Custom properties are the theming surface: a host dashboard overrides
 * `--apexmaps-*` on the container (or globally) and the map follows, which is how
 * theme inheritance works without configuration. Dark mode is a class rather than
 * only a media query so a dashboard can force it independently of the OS.
 */

.apexmaps {
  --apexmaps-fg: #1f2933;
  --apexmaps-fg-muted: #6b7280;
  --apexmaps-bg: transparent;
  --apexmaps-surface: #ffffff;
  --apexmaps-border: rgba(0, 0, 0, 0.12);
  --apexmaps-shadow: 0 4px 16px rgba(15, 23, 42, 0.14);
  --apexmaps-focus: #2563eb;
  /* The label halo, so it flips with the theme rather than staying white over a
     dark map. */
  --apexmaps-halo: rgba(255, 255, 255, 0.85);
  --apexmaps-radius: 6px;
  --apexmaps-font-size: 12px;

  position: relative;
  color: var(--apexmaps-fg);
  background: var(--apexmaps-bg);
  font-size: var(--apexmaps-font-size);
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;

  /* The root lays out the furniture: plot, legend, breadcrumb. Flex rather than
     normal flow because `legend.position` is then a one-line change of axis or
     order instead of a DOM move, and the breadcrumb keeps its place in the tab
     order whichever side the legend lands on. Column by default, so the stack is
     the same as it always was. */
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  align-items: stretch;
}

/* The breadcrumb is "you are here", so it stays above everything whatever the
   legend does. Ordered rather than moved, so it keeps its place in the tab
   order as the first thing after the container. */
.apexmaps > .apexmaps-breadcrumb {
  order: -2;
}

/* A side legend turns the root into a row. The breadcrumb takes a full line of
   its own above both, which is where a reader looks for "you are here". */
.apexmaps--legend-left,
.apexmaps--legend-right {
  flex-direction: row;
  align-items: flex-start;
}

.apexmaps--legend-left > .apexmaps-breadcrumb,
.apexmaps--legend-right > .apexmaps-breadcrumb {
  width: 100%;
}

/* Dark mode paints its own background, unlike the light default which stays
   transparent. Forcing dark on a page that is still light is the whole point of
   this being a class, and light text over the host's white card is not a theme,
   it is an unreadable map. A dashboard that is already dark can hand the
   background back with `--apexmaps-bg: transparent`. */
.apexmaps--dark {
  --apexmaps-fg: #e5e7eb;
  --apexmaps-fg-muted: #9ca3af;
  --apexmaps-bg: #111827;
  --apexmaps-surface: #1f2937;
  --apexmaps-border: rgba(255, 255, 255, 0.16);
  --apexmaps-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  --apexmaps-focus: #60a5fa;
  --apexmaps-halo: rgba(17, 24, 39, 0.85);
}

.apexmaps-plot {
  position: relative;
  overflow: hidden;
  /* Sized in pixels by `_measure`; nothing about the legend may shrink it, or
     the SVG viewBox and the box it is drawn in would disagree. */
  flex: 0 0 auto;
  /* A drag on a map is a pan or a selection box, never a text selection. Without
     this, dragging highlights the surrounding page copy, which looks like a bug and
     leaves stray highlight behind after the gesture. */
  user-select: none;
  -webkit-user-select: none;
}

.apexmaps-svg {
  display: block;
  /* Prevents the browser's own pan/zoom from fighting our pointer handlers. */
  touch-action: none;
}

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

/* --- features ------------------------------------------------------------- */

/* Value transitions make `updateSeries` read as a chart update rather than a
   page reload. Durations come from `chart.animations` via two variables the
   engine writes before each draw: `--apexmaps-anim` for cheap properties
   (fill, stroke), `--apexmaps-anim-geom` for geometry (r, stroke-width),
   which the motion budget zeroes first when the mark count grows. Camera-
   driven attributes (cx, cy, transform, d) are deliberately absent: they are
   written per frame while panning, and a transition there would fight every
   one of those frames. */

/* `stroke-opacity` is here for one caller: a drilldown developing the level it
   arrived at brings each feature's boundary up with its fill, so that the parent
   shape reads as dividing rather than as being replaced by an already-divided
   one. See `renderers/LevelReveal`. Nothing else writes it, so it costs nothing
   the rest of the time. */
.apexmaps-feature {
  transition:
    fill var(--apexmaps-anim, 220ms) ease-out,
    stroke-opacity var(--apexmaps-anim, 220ms) ease-out,
    opacity 150ms ease-out;
}

.apexmaps-bubble {
  transition:
    fill var(--apexmaps-anim, 220ms) ease-out,
    r var(--apexmaps-anim-geom, 220ms) ease-out,
    opacity 150ms ease-out;
}

.apexmaps-mark-shape {
  transition: fill var(--apexmaps-anim, 220ms) ease-out;
}

.apexmaps-arc,
.apexmaps-line {
  transition:
    stroke var(--apexmaps-anim, 220ms) ease-out,
    stroke-width var(--apexmaps-anim-geom, 220ms) ease-out,
    opacity 150ms ease-out;
}

/* Beads travelling along a route (`series/flow`). The dash pattern, the width and
   the colour are attributes written by the renderer; the only thing here is the
   travel, and all that travels is the dash offset, by one period of the pattern,
   which is why nothing on this side needs to know how long any route is.

   No transition on `d`: the beads share their path data with the route, and that is
   camera-driven like every other geometry attribute. The travel is unaffected,
   being a function of time rather than of position. */
.apexmaps-flow--moving {
  animation-name: apexmaps-flow;
  animation-duration: var(--apexmaps-flow-duration, 1s);
  animation-delay: var(--apexmaps-flow-delay, 0s);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

/* Negative, because a positive dash offset walks the pattern back towards the
   start of the path. The beads have to leave `from` and arrive at `to`, which is
   the direction the data was given in. */
@keyframes apexmaps-flow {
  to {
    stroke-dashoffset: calc(-1 * var(--apexmaps-flow-travel, 0px));
  }
}

/* Hover feedback has to feel attached to the pointer: the data-update pace
   would read as lag here. */
.apexmaps-feature.is-hovered,
.apexmaps-bubble.is-hovered,
.apexmaps-mark.is-hovered .apexmaps-mark-shape {
  transition-duration: 90ms;
}

.apexmaps-feature.is-selected {
  transition: none;
}

/* One fade of the mark and symbol layers on first paint, when
   `chart.animations.entrance` asks for it (`context: 'story'` turns it on).
   Layer-level, so it costs two animations regardless of mark count. */
.apexmaps--enter .apexmaps-layer-marks,
.apexmaps--enter .apexmaps-layer-symbols {
  animation: apexmaps-enter var(--apexmaps-anim, 350ms) ease-out;
}

@keyframes apexmaps-enter {
  from {
    opacity: 0;
  }
}

/* Everything not selected steps back. A CSS property rather than an attribute, so
   it wins over the series' own opacity attribute and uncovers it again when the
   class goes, with no per-mark bookkeeping. */
.apexmaps-feature.is-muted,
.apexmaps-bubble.is-muted,
.apexmaps-mark.is-muted {
  opacity: var(--apexmaps-muted-opacity, 0.25);
}

/* The level a drilldown is fading out. Its marks gave up their `apexmaps-*`
   classes so that nothing resolves a hit test, a query or an export to a copy
   (see `renderers/LevelGhost`), which takes the rule above with them. This is
   the same step-back, keyed on what the copy still carries: without it a
   legend-filtered level flashes back to full opacity as the fade begins. */
.apexmaps-ghost .is-muted {
  opacity: var(--apexmaps-muted-opacity, 0.25);
}

.apexmaps-select-box {
  fill: var(--apexmaps-focus);
  fill-opacity: 0.1;
  stroke: var(--apexmaps-focus);
  stroke-width: 1;
  stroke-dasharray: 3 2;
  pointer-events: none;
}

/* The JS motion budget already zeroes the variables under reduced motion; this
   is the belt-and-braces layer for anything that misses that path. */
@media (prefers-reduced-motion: reduce) {
  .apexmaps-feature,
  .apexmaps-feature.is-hovered,
  .apexmaps-bubble,
  .apexmaps-mark-shape,
  .apexmaps-arc,
  .apexmaps-line {
    transition: none;
  }

  .apexmaps--enter .apexmaps-layer-marks,
  .apexmaps--enter .apexmaps-layer-symbols {
    animation: none;
  }

  /* The beads stay, spaced along the route, and stop travelling. Removing them
     would take a mark off the map because the reader asked for less motion. */
  .apexmaps-flow--moving {
    animation: none;
  }
}

.apexmaps-label {
  pointer-events: none;
  user-select: none;
}

/* --- annotations ---------------------------------------------------------- */

/* Inert to the pointer at the layer level as well as per group: an annotation
   sits over data by definition, and a chip that swallowed the hover of the
   country it explains would make the map worse than an unannotated one. */
.apexmaps-annotations,
.apexmaps-annotation-regions {
  pointer-events: none;
  user-select: none;
}

.apexmaps-annotation-text {
  font-variant-numeric: tabular-nums;
}

.apexmaps-sphere,
.apexmaps-graticule {
  pointer-events: none;
}

/* --- tooltip -------------------------------------------------------------- */

.apexmaps-tooltip {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  max-width: 260px;
  padding: 8px 10px;
  border: 1px solid var(--apexmaps-border);
  border-radius: var(--apexmaps-radius);
  background: var(--apexmaps-surface);
  box-shadow: var(--apexmaps-shadow);
  font-size: var(--apexmaps-font-size);
  color: var(--apexmaps-fg);
}

.apexmaps-tooltip-title {
  font-weight: 600;
  margin-bottom: 2px;
}

.apexmaps-tooltip-key {
  color: var(--apexmaps-fg-muted);
  font-size: 0.9em;
  font-variant-numeric: tabular-nums;
  margin-bottom: 4px;
}

.apexmaps-tooltip-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.apexmaps-tooltip-label {
  color: var(--apexmaps-fg-muted);
}

.apexmaps-tooltip-value {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.apexmaps-tooltip-extra {
  font-size: 0.92em;
  opacity: 0.8;
}

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

.apexmaps-legend {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 4px 2px;
}

/* `legend.position`. The root is the layout container and the legend is one of
   its flex items, so a position is an `order` (top/bottom) or a change of main
   axis (left/right); see the `.apexmaps--legend-*` rules at the top of the file.
   The class stays on the legend itself for `--top` because that is the one case
   the legend can settle on its own. */
.apexmaps-legend--top {
  order: -1;
}

/* A side legend is a column of fixed width, so the plot can be measured against
   what is left over rather than fighting it for the same pixels. */
.apexmaps-legend--left,
.apexmaps-legend--right {
  width: var(--apexmaps-legend-width, 180px);
  flex: 0 0 var(--apexmaps-legend-width, 180px);
  padding: 2px 6px;
}

.apexmaps-legend--left {
  order: -1;
}

.apexmaps-legend-title {
  font-weight: 600;
  color: var(--apexmaps-fg);
}

.apexmaps-legend-items {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 12px;
}

.apexmaps-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: opacity 150ms ease-out;
}

.apexmaps-legend-item[role='listitem'] {
  cursor: default;
}

.apexmaps-legend-item:hover {
  background: var(--apexmaps-border);
}

.apexmaps-legend-item:focus-visible {
  outline: 2px solid var(--apexmaps-focus);
  outline-offset: 1px;
}

.apexmaps-legend-item.is-muted {
  opacity: 0.4;
}

.apexmaps-legend-swatch {
  width: 14px;
  height: 14px;
  border-radius: 3px;
  border: 1px solid var(--apexmaps-border);
  flex: 0 0 auto;
}

/* Holds a 12px inline tile rather than a background colour, so the radius has to
   clip it and the border must not push it off centre. */
.apexmaps-legend-swatch.is-patterned {
  overflow: hidden;
  line-height: 0;
}

.apexmaps-legend-label {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* An explicit width, not just a cap: the bar itself has no intrinsic width and
   both the marker and the tick labels are absolutely positioned, so under a
   centred (shrink-to-fit) legend the whole thing would collapse to nothing. */
.apexmaps-legend-gradient-wrap {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: min(320px, 100%);
  max-width: 320px;
}

.apexmaps-legend-gradient-track {
  position: relative;
  /* Room above the bar for the marker arrow and its value. */
  padding-top: 22px;
}

.apexmaps-legend-gradient {
  height: 10px;
  border-radius: 3px;
  border: 1px solid var(--apexmaps-border);
}

.apexmaps-legend-gradient-labels {
  display: flex;
  justify-content: space-between;
  color: var(--apexmaps-fg-muted);
  font-variant-numeric: tabular-nums;
}

/* Boundary numbers sit *at* the break they describe rather than inside a band,
   so each is centred on its own tick and allowed to overlap nothing. */
.apexmaps-legend-gradient-labels.is-ticks {
  position: relative;
  display: block;
  height: 1.2em;
}

.apexmaps-legend-tick {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
}

/* --- legend hover marker --- */

.apexmaps-legend-marker {
  position: absolute;
  top: 0;
  left: 0;
  height: 22px;
  /* `left` is the value's position on the scale, so the arrow's tip has to sit
     on it, not its left edge. */
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  pointer-events: none;
  opacity: 0;
  /* Opacity fades; the travel along the bar is a transition on `left`, which is
     what makes the arrow read as one marker moving rather than several blinking. */
  transition:
    left 140ms ease-out,
    opacity 120ms ease-out;
}

.apexmaps-legend-marker.is-visible {
  opacity: 1;
}

.apexmaps-legend-marker-label {
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  color: var(--apexmaps-fg);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  order: -1;
  margin-bottom: 3px;
}

.apexmaps-legend-marker-arrow {
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid var(--apexmaps-fg);
}

/* --- vertical bar, for a legend on the left or the right ------------------- */

/* Same parts, rotated: the bar runs bottom-to-top because that is how a reader
   expects magnitude stacked vertically, the marker points sideways into it, and
   the numbers sit beside their break instead of under it. */
.apexmaps-legend--vertical .apexmaps-legend-gradient-wrap {
  flex-direction: row;
  gap: 6px;
  width: auto;
  max-width: none;
}

.apexmaps-legend--vertical .apexmaps-legend-gradient-track {
  padding-top: 0;
  padding-left: 30px;
}

.apexmaps-legend--vertical .apexmaps-legend-gradient {
  width: 12px;
  height: var(--apexmaps-legend-bar, 140px);
}

.apexmaps-legend--vertical .apexmaps-legend-marker {
  top: auto;
  bottom: 0;
  left: 0;
  width: 30px;
  height: auto;
  flex-direction: row;
  justify-content: flex-end;
  align-items: center;
  transform: translateY(50%);
  transition:
    bottom 140ms ease-out,
    opacity 120ms ease-out;
}

.apexmaps-legend--vertical .apexmaps-legend-marker-label {
  margin: 0 3px 0 0;
}

.apexmaps-legend--vertical .apexmaps-legend-marker-arrow {
  border: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 6px solid var(--apexmaps-fg);
}

.apexmaps-legend--vertical .apexmaps-legend-gradient-labels {
  flex-direction: column-reverse;
  justify-content: space-between;
  height: var(--apexmaps-legend-bar, 140px);
}

.apexmaps-legend--vertical .apexmaps-legend-gradient-labels.is-ticks {
  height: var(--apexmaps-legend-bar, 140px);
  width: auto;
}

.apexmaps-legend--vertical .apexmaps-legend-tick {
  top: auto;
  left: 0;
  transform: translateY(50%);
}

@media (prefers-reduced-motion: reduce) {
  .apexmaps-legend-marker,
  .apexmaps-legend--vertical .apexmaps-legend-marker {
    transition: none;
  }
}

/* A vertical bar is as tall as the column allows; the swatch list beside it
   wraps to one item per line rather than spilling out of the column. */
.apexmaps-legend--left .apexmaps-legend-items,
.apexmaps-legend--right .apexmaps-legend-items {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

/* legend.align. The declared default is 'center', matching the rest of the
   Apex family; 'left' and 'right' are the overrides. */
.apexmaps-legend--align-center {
  align-items: center;
  text-align: center;
}
.apexmaps-legend--align-center .apexmaps-legend-items {
  justify-content: center;
}
.apexmaps-legend--align-right,
.apexmaps-legend--align-end {
  align-items: flex-end;
  text-align: right;
}
.apexmaps-legend--align-right .apexmaps-legend-items,
.apexmaps-legend--align-end .apexmaps-legend-items {
  justify-content: flex-end;
}

/* --- breadcrumb ----------------------------------------------------------- */

.apexmaps-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
  padding: 0 2px 8px;
  font-size: var(--apexmaps-font-size);
}

.apexmaps-breadcrumb-item {
  padding: 2px 6px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--apexmaps-fg-muted);
  font: inherit;
  cursor: pointer;
}

.apexmaps-breadcrumb-item:hover {
  background: var(--apexmaps-border);
  color: var(--apexmaps-fg);
}

.apexmaps-breadcrumb-item:focus-visible {
  outline: 2px solid var(--apexmaps-focus);
  outline-offset: 1px;
}

.apexmaps-breadcrumb-sep {
  color: var(--apexmaps-fg-muted);
  opacity: 0.7;
}

.apexmaps-breadcrumb-current {
  padding: 2px 6px;
  font-weight: 600;
}

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

/* One card of stacked cells, divided rather than spaced: the group is a single
   piece of furniture, and separate floating buttons read as two unrelated
   controls. Inside the plot, which is `position: relative`, so the corner offsets
   are the plot's corners and not the container's. Absolutely positioned, so it is
   out of flow and takes nothing off the map: the plot is sized in pixels by
   `_measure`, and furniture that claimed layout would shrink the geography. */
.apexmaps-zoom {
  position: absolute;
  z-index: 4;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--apexmaps-border);
  border-radius: var(--apexmaps-radius);
  background: var(--apexmaps-surface);
  box-shadow: var(--apexmaps-shadow);
  /* Recessive until wanted: the map is the content, and a control sitting at full
     strength over the geography competes with it. Overridable per instance with
     `--apexmaps-zoom-idle-opacity`, and 1 turns the behaviour off. */
  opacity: var(--apexmaps-zoom-idle-opacity, 0.7);
  transition: opacity var(--apexmaps-anim, 180ms) ease-out;
}

/* Full strength once the reader is anywhere near it, and on keyboard focus, which
   is the same intent arriving by another route. `:focus-within` on the group rather
   than `:focus` on a button, so tabbing to `+` does not leave `-` half faded. */
.apexmaps-plot:hover .apexmaps-zoom,
.apexmaps-zoom:hover,
.apexmaps-zoom:focus-within {
  opacity: 1;
}

/* A touch screen has no hover, so the idle state would be the only state and the
   controls would sit permanently faded. `hover: hover` is the test for "this
   pointer can express intent before committing to a tap". */
@media (hover: none) {
  .apexmaps-zoom {
    opacity: 1;
  }
}

/* Fading is decoration; a reader who has asked for less motion still gets both
   states, just without the crossfade. */
@media (prefers-reduced-motion: reduce) {
  .apexmaps-zoom {
    transition: none;
  }
}

.apexmaps-zoom--top-left {
  top: 8px;
  left: 8px;
}

.apexmaps-zoom--top-right {
  top: 8px;
  right: 8px;
}

.apexmaps-zoom--bottom-left {
  bottom: 8px;
  left: 8px;
}

/* Clear of the attribution, which owns the bottom-right corner and is not
   allowed to be covered. */
.apexmaps-zoom--bottom-right {
  right: 8px;
  bottom: 22px;
}

.apexmaps-zoom-button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  background: none;
  /* Full foreground rather than the muted tone the breadcrumb uses. The group
     already spends contrast on being faded while idle, and an icon is a non-text
     UI component: 3:1 against its own background is the floor, and muted plus
     faded lands on the wrong side of it. */
  color: var(--apexmaps-fg);
  cursor: pointer;
  /* iOS paints a grey block over a tapped control, which on a 28px button in the
     corner of a map reads as a rendering fault. The hover rule is the feedback. */
  -webkit-tap-highlight-color: transparent;
}

.apexmaps-zoom-button + .apexmaps-zoom-button {
  border-top: 1px solid var(--apexmaps-border);
}

/* The cell, not the group, so a hover says which control is under the pointer.
   The group's own fade is a different signal: "the whole thing is live now". */
.apexmaps-zoom-button:hover:not(:disabled) {
  background: var(--apexmaps-border);
}

.apexmaps-zoom-button:focus-visible {
  outline: 2px solid var(--apexmaps-focus);
  outline-offset: -2px;
}

/* At the end of the range the control is spent, and it has to look it: an
   enabled `+` that cannot zoom is a control that lies. */
.apexmaps-zoom-button:disabled {
  opacity: 0.35;
  cursor: default;
}

/* --- attribution ---------------------------------------------------------- */

.apexmaps-attribution {
  position: absolute;
  right: 4px;
  bottom: 2px;
  z-index: 3;
  padding: 1px 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.72);
  color: #4b5563;
  font-size: 10px;
  pointer-events: none;
}

.apexmaps--dark .apexmaps-attribution {
  background: rgba(17, 24, 39, 0.72);
  color: #9ca3af;
}

/* --- accessibility -------------------------------------------------------- */

.apexmaps-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
