/* =========================================================
   Workspace infrastructure CSS.

   Everything visual now lives as Tailwind utilities in the views. What remains
   here can't: the map markers are DOM injected at runtime by map_controller.js,
   the MapLibre overrides target the library's own elements, and the two display
   rules fix cases where a Tailwind `.hidden` toggle has to beat an inline or
   library `display`. The brand gradient is the lone shared design token.
   ========================================================= */
:root {
  --logo-gradient: linear-gradient(135deg, #BB5CCF 0%, #4768C8 100%);
}

/* ===== MapLibre custom markers (injected by map_controller.js) ===== */
.maplibregl-marker { will-change: transform; }
.ws-mk { width: 28px; height: 28px; position: relative; cursor: pointer; }
.ws-mk-dot {
  position: absolute; inset: 0; margin: auto;
  width: 21px; height: 21px; border-radius: 50%;
  background: var(--mk); border: 2px solid #fff;
  box-shadow: 0 1px 5px rgba(15, 23, 42, .45);
  transition: transform .15s;
  display: flex; align-items: center; justify-content: center;
}
/* The ocean / air / road glyph the controller draws inside the dot. */
.ws-mk-dot svg { display: block; width: 12px; height: 12px; color: #fff; }
.ws-mk-pulse {
  position: absolute; inset: 0; margin: auto;
  width: 21px; height: 21px; border-radius: 50%;
  background: var(--mk); opacity: .4;
  animation: mkpulse 2.4s ease-out infinite;
}
@keyframes mkpulse {
  0% { transform: scale(1); opacity: .45; }
  70% { transform: scale(2.2); opacity: 0; }
  100% { transform: scale(2.2); opacity: 0; }
}
.ws-mk.hover .ws-mk-dot { transform: scale(1.2); }
.ws-mk.current .ws-mk-dot { width: 25px; height: 25px; border-width: 3px; }
.ws-mk.current .ws-mk-dot svg { width: 14px; height: 14px; }
.ws-mk.current .ws-mk-pulse { width: 25px; height: 25px; }

/* ===== Shipment details popped from a marker (map_controller#detailsPopup) =====
   An instrument panel rather than a card: this thing floats over a live globe,
   and a white sheet reads as a document laid on top of the map instead of a
   readout coming off it. Dark in both themes for that reason — the day basemap
   is pale, so a dark panel is the one that stays legible against either.

   It redefines the app's own colour tokens for its subtree instead of hard-coding
   light-on-dark greys into the markup, so the popup's ERB goes on using text-fg /
   border-stroke / bg-surface-muted like every other view and simply resolves them
   darker in here.

   The font has to be named too: maplibre-gl.css puts `font: 12px/20px Helvetica
   Neue` on .maplibregl-map, and the popup is inside it, so everything in here
   inherited Helvetica while the rest of the app is in Space Grotesk.

   The tip is dropped rather than recoloured: its colour lives on a different
   border side per anchor, so the offset alone carries the connection to the
   marker. */
.ws-popup .maplibregl-popup-content {
  --color-surface: #0e1828;
  --color-surface-muted: #17253c;
  --color-fg: #e9f1fc;
  --color-fg-muted: #aabfd8;
  --color-fg-soft: #7590b0;
  --color-stroke: #263b55;

  font-family: var(--font-sans);
  font-size: 12px;
  line-height: 1.45;
  background: color-mix(in oklab, var(--color-surface) 92%, transparent);
  backdrop-filter: blur(10px);
  color: var(--color-fg);
  border: 1px solid var(--color-stroke);
  border-radius: 12px;
  padding: 0;
  /* A drop shadow to lift it off the globe, and a hairline highlight along the
     top edge so it catches light like a screen rather than sitting flat. */
  box-shadow:
    0 18px 40px rgba(2, 6, 23, .55),
    inset 0 1px 0 rgba(255, 255, 255, .06);
  max-height: 60vh;
  overflow: auto;
}
.ws-popup .maplibregl-popup-tip { display: none; }
.ws-popup .maplibregl-popup-close-button {
  top: 6px; right: 7px;
  color: var(--color-fg-soft);
  background: transparent;
  font-size: 16px;
  line-height: 1;
  padding: 0 5px;
  border-radius: 7px;
}
.ws-popup .maplibregl-popup-close-button:hover {
  background: var(--color-surface-muted);
  color: var(--color-fg);
}

/* A hovered peek (see map_controller#overviewPopup) never takes the pointer, so
   the marker under it stays clickable and the popup can't trigger the mouseleave
   that would close itself. The rule has to name the content: maplibre-gl.css
   already has the popup wrapper at pointer-events none and sets the content back
   to auto. Its "View journey" button goes too — on a pointer that hovers,
   clicking the marker is what opens the journey. */
.ws-popup-peek .maplibregl-popup-content { pointer-events: none; }
.ws-popup-peek .ws-popup-cta { display: none; }

/* A ping's timestamp, shown hovering one of the dots the route is drawn from
   (map_controller#peekTrace). It labels a 5px dot, so it stays a tight chip
   rather than the padded card a shipment's details get. */
.ws-popup-trace .maplibregl-popup-content { padding: 7px 10px; border-radius: 9px; }
.ws-popup-trace .ws-trace-peek { padding: 0; }
.ws-trace-peek { display: block; line-height: 1.35; white-space: nowrap; }
.ws-trace-peek b {
  display: block;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--color-fg-soft);
}

.ws-ep { width: 18px; height: 18px; position: relative; }
.ws-ep span { position: absolute; inset: 0; margin: auto; border-radius: 50%; box-shadow: 0 1px 5px rgba(15, 23, 42, .4); }
.ws-ep-from span { width: 13px; height: 13px; background: #fff; border: 3px solid var(--mk); }
.ws-ep-to span { width: 14px; height: 14px; background: var(--mk); border: 2.5px solid #fff; }

/* The modality badge (ApplicationHelper#modality_badge). Sized from variables
   because the box and glyph are arguments, and Tailwind only generates the
   utilities it can find spelled out in the source. */
.ws-modality {
  width: var(--modality-box);
  height: var(--modality-box);
}
.ws-modality svg {
  display: block;
  width: var(--modality-glyph);
  height: var(--modality-glyph);
}

/* ===== Map controls =====
   MapLibre ships its zoom and fullscreen buttons as a white pill with its own
   house glyphs — a hairline plus, a boxy fullscreen — which read as a different
   product beside the app's Lucide icons and glass panels. The chrome moves onto
   the app's surface tokens, and each glyph is redrawn as its Lucide counterpart.
   Masks rather than background images, so the glyph takes `currentColor` and
   follows the button through hover and both themes.

   Every selector repeats its class: the library's stylesheet is injected by
   map_controller.js and so always lands after this file, and at matching
   specificity the later rule wins. */
.maplibregl-ctrl-group.maplibregl-ctrl-group {
  background: var(--color-surface);
  border: 1px solid var(--color-stroke);
  border-radius: 12px;
  box-shadow: 0 6px 20px rgba(2, 6, 23, .28);
  overflow: hidden;
}
.maplibregl-ctrl-group.maplibregl-ctrl-group:not(:empty) { box-shadow: 0 6px 20px rgba(2, 6, 23, .28); }
.maplibregl-ctrl-group.maplibregl-ctrl-group button {
  width: 34px;
  height: 34px;
  color: var(--color-fg-soft);
  background: transparent;
  transition: background-color .15s, color .15s;
}
.maplibregl-ctrl-group.maplibregl-ctrl-group button:not(:disabled):hover {
  background: var(--color-surface-muted);
  color: var(--color-fg);
}
.maplibregl-ctrl-group.maplibregl-ctrl-group button:disabled { color: var(--color-fg-soft); opacity: .4; }
.maplibregl-ctrl-group.maplibregl-ctrl-group button + button { border-top: 1px solid var(--color-stroke); }
.maplibregl-ctrl-group.maplibregl-ctrl-group button .maplibregl-ctrl-icon {
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: 17px 17px;
          mask-size: 17px 17px;
}
.maplibregl-ctrl-group.maplibregl-ctrl-group button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon {
  background-image: none;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='M12 5v14'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='M12 5v14'/%3E%3C/svg%3E");
}
.maplibregl-ctrl-group.maplibregl-ctrl-group button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon {
  background-image: none;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3C/svg%3E");
}
.maplibregl-ctrl-group.maplibregl-ctrl-group button.maplibregl-ctrl-fullscreen .maplibregl-ctrl-icon {
  background-image: none;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 3H5a2 2 0 0 0-2 2v3'/%3E%3Cpath d='M21 8V5a2 2 0 0 0-2-2h-3'/%3E%3Cpath d='M3 16v3a2 2 0 0 0 2 2h3'/%3E%3Cpath d='M16 21h3a2 2 0 0 0 2-2v-3'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 3H5a2 2 0 0 0-2 2v3'/%3E%3Cpath d='M21 8V5a2 2 0 0 0-2-2h-3'/%3E%3Cpath d='M3 16v3a2 2 0 0 0 2 2h3'/%3E%3Cpath d='M16 21h3a2 2 0 0 0 2-2v-3'/%3E%3C/svg%3E");
}
.maplibregl-ctrl-group.maplibregl-ctrl-group button.maplibregl-ctrl-shrink .maplibregl-ctrl-icon {
  background-image: none;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 3v3a2 2 0 0 1-2 2H3'/%3E%3Cpath d='M21 8h-3a2 2 0 0 1-2-2V3'/%3E%3Cpath d='M3 16h3a2 2 0 0 1 2 2v3'/%3E%3Cpath d='M16 21v-3a2 2 0 0 1 2-2h3'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 3v3a2 2 0 0 1-2 2H3'/%3E%3Cpath d='M21 8h-3a2 2 0 0 1-2-2V3'/%3E%3Cpath d='M3 16h3a2 2 0 0 1 2 2v3'/%3E%3Cpath d='M16 21v-3a2 2 0 0 1 2-2h3'/%3E%3C/svg%3E");
}

/* The orbit pause/resume control (map_controller#setupOrbit) draws its glyph as
   a real <svg> rather than the background-image MapLibre's own buttons use, so
   it has to do its own centring — the selector is qualified to outrank
   maplibre-gl.css's `.maplibregl-ctrl-group button { display: block }`. */
.maplibregl-ctrl-group.maplibregl-ctrl-group button.ws-orbit-btn {
  display: flex;
  align-items: center;
  justify-content: center;
}
.ws-orbit-btn svg { display: block; }

/* The dialog is one fixed size so the workspace doesn't jump between the
   sections — but a task dialog (add a shipment reference, start a journey) is
   one short form, and at that size it floated in a mostly empty box. The
   surface is rendered before its content arrives, so it cannot size itself to
   it up front; :has() lets the content that lands say which it is.
   Sized here rather than in the markup because the class list belongs to a
   surface that does not yet know what it will hold. */
dialog:has([data-modal-kind="task"]) {
  width: min(620px, calc(100vw - 2rem));
  /* fit-content, not auto: the UA stylesheet pins a modal dialog to inset 0,
     and an auto height between two fixed edges STRETCHES rather than shrinks —
     which is how every short task form came to sit in a 620px box with a void
     under it. Short forms size to their content; a long one (creating a
     journey: a reference, two addresses, two dates and the modality list)
     stops at the same height the sections use and scrolls inside, rather than
     stretching to the full height of the screen. */
  height: fit-content;
  max-height: min(620px, calc(100vh - 2rem));
}

/* The chrome inside is a flex column that asks for the full height of the
   dialog (layouts/_modal_chrome), which is what lets its body scroll under a
   pinned footer. Against a dialog now sized to its content that percentage is
   circular, so the two boxes between the dialog and the chrome take the
   dialog's own max-height instead — a length, so the flex column can cap and
   scroll, while a short form still ends where its last field does. */
dialog:has([data-modal-kind="task"]) > turbo-frame,
dialog:has([data-modal-kind="task"]) > turbo-frame > [data-modal-kind="task"] {
  height: auto;
  max-height: inherit;
}

/* Keep the map's own controls clear of the floating panels. The variable is set
   from the same inset the camera is framed by (map_controller#insetPadding), so
   they sit beside whichever panel is open instead of under it — the list is on
   the left, the journey detail on the right, and a fixed corner is covered by
   one or the other. */
#company-map-host .maplibregl-ctrl-top-right,
#voyager-map-host .maplibregl-ctrl-top-right {
  right: var(--ws-inset-right, 0px);
  transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.maplibregl-canvas { outline: none; }
.maplibregl-ctrl-attrib { font-size: 10px; }

/* maplibre-gl.css forces the map element to position:relative, which defeats a
   Tailwind `absolute inset-0` and collapses the map to 0 height. Make every map
   target fill its parent regardless — the coverage globe is a second controller
   with its own target name, and it collapsed the same way. */
[data-map-target="map"],
[data-coverage-map-target="map"] { width: 100%; height: 100%; min-height: inherit; }

/* The fullscreen element is the map host (so the starfield goes fullscreen with
   the globe), and it is normally `absolute inset-0` inside the workspace stage —
   which would keep it at that size in the top layer. Fill the screen instead. */
#company-map-host:fullscreen,
#voyager-map-host:fullscreen { width: 100%; height: 100%; }

/* The detail tabs are flex panes (display:flex), so the JS toggling Tailwind's
   `.hidden` needs help winning over `flex`. Scoped so it can't defeat other
   conditional reveals. */
[data-tabs-target="pane"].hidden { display: none !important; }

/* Honor reduced-motion: still the pulsing map markers (the orbit, starfield
   twinkle and ant-line are gated in the controller; this covers the CSS pulse). */
@media (prefers-reduced-motion: reduce) {
  .ws-mk-pulse { animation: none; }
}
