:root {
  color-scheme: light dark;
  --bg: #fafafa;
  --bg-panel: #ffffff;
  --border: #d0d0d0;
  --accent: #2057a0;
  --accent-fg: #ffffff;
  --muted: #6b7280;
  --tile-memory: #4ade80;
  --tile-disk: #60a5fa;
  --tile-network: #f59e0b;
  --tile-missing: #6b7280;
  --tile-pending: #e5e7eb;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1c1f24;
    --bg-panel: #262a33;
    --border: #3a414d;
    --accent: #6aa6f5;
    --accent-fg: #0b0f14;
    --muted: #9aa3b2;
    --tile-pending: #2f343d;
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 1.5rem;
  font-family: system-ui, sans-serif;
  background: var(--bg);
  color: inherit;
  line-height: 1.4;
  max-width: 1280px;
  margin-left: auto;
  margin-right: auto;
}

header h1 {
  margin: 0 0 0.25rem;
}

header p {
  margin: 0 0 1.5rem;
  color: var(--muted);
}

section {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1rem 1.25rem;
  margin-bottom: 1rem;
}

section h2 {
  margin-top: 0;
  font-size: 1.1rem;
}

.row {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  align-items: end;
  margin-bottom: 0.75rem;
}

.row:last-child {
  margin-bottom: 0;
}

label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.9rem;
}

label.checkbox,
label.color-toggle {
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
}

label.grow {
  flex: 1 1 auto;
}

input,
select,
textarea {
  font: inherit;
  padding: 0.35rem 0.5rem;
  background: var(--bg);
  color: inherit;
  border: 1px solid var(--border);
  border-radius: 4px;
}

textarea {
  font-family: ui-monospace, monospace;
  resize: vertical;
  min-height: 4rem;
}

input[type="color"] {
  padding: 0;
  height: 2rem;
  width: 3rem;
}

/* Shared saved-colour palette panel. */
.saved-colors-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.5rem 0;
}

.saved-colors-list:empty::before {
  content: "No saved colours yet.";
  color: var(--muted, #888);
  font-style: italic;
}

.saved-color {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.35rem;
  border: 1px solid var(--border, #ccc);
  border-radius: 4px;
}

.saved-color-chip {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  border: 1px solid var(--border, #ccc);
  border-radius: 2px;
}

.saved-color-remove {
  padding: 0 0.4rem;
  line-height: 1.4;
  background: transparent;
  color: inherit;
  border-radius: 2px;
}

/* Save/load themes panel. */
.themes > summary {
  cursor: pointer;
}

#theme_status {
  display: inline-block;
  margin-top: 0.25rem;
  color: var(--muted, #888);
}

/* Read-only theme settings table in the library's View modal. */
.theme-detail td:first-child {
  font-weight: 600;
  white-space: nowrap;
  padding-right: 1rem;
}

.theme-detail code {
  vertical-align: middle;
}

button,
.button {
  cursor: pointer;
  padding: 0.5rem 1rem;
  border: 0;
  border-radius: 4px;
  background: var(--accent);
  color: var(--accent-fg);
  font: inherit;
  text-decoration: none;
  display: inline-block;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.tabs {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0;
  background: transparent;
  border: 0;
  padding: 0;
}

.tab {
  background: var(--bg-panel);
  color: inherit;
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: 8px 8px 0 0;
}

.tab.active {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
}

.tab-panel {
  display: none;
  border-top-left-radius: 0;
}

.tab-panel.active {
  display: block;
}

.subtabs,
.glw-tabs {
  display: flex;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
  border-bottom: 1px solid var(--border);
  padding: 0;
}

.subtab,
.glw-tab {
  background: transparent;
  color: inherit;
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: 6px 6px 0 0;
  padding: 0.35rem 0.75rem;
  cursor: pointer;
}

.subtab.active,
.glw-tab.active {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
}

.subtab-panel,
.glw-tab-panel {
  display: none;
  margin-bottom: 0.5rem;
}

.subtab-panel.active,
.glw-tab-panel.active {
  display: block;
}

.borders {
  border: 1px solid var(--border);
  border-radius: 6px;
  margin: 0.75rem 0;
}

.borders legend {
  font-size: 0.9rem;
  font-weight: 600;
}

.borders .per-side {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.note {
  margin: 0.25rem 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}

.status {
  font-family: ui-monospace, monospace;
  font-size: 0.9rem;
  color: var(--muted);
  min-height: 1.25rem;
  margin: 0 0 0.5rem;
}

.map-container {
  position: relative;
  width: 100%;
  overflow: hidden;
  border: 1px solid var(--border);
  background: #000;
  min-height: 4rem;
}

/* Wrapper around the preview so the extend/shrink controls can be positioned
   on the edges of the map box. The padding reserves room for the half-overlapping
   edge buttons so they are not clipped by the surrounding layout. */
.preview-viewport-wrap {
  position: relative;
  margin: 18px 18px 18px 18px;
}

/* A cluster of extend/shrink buttons anchored to one edge (or the corner) of
   the preview box. */
.preview-viewport-wrap .extend-group {
  position: absolute;
  display: flex;
  gap: 2px;
  z-index: 5;
}

.preview-viewport-wrap .extend-group.north {
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
}

.preview-viewport-wrap .extend-group.south {
  bottom: 0;
  left: 50%;
  transform: translate(-50%, 50%);
}

.preview-viewport-wrap .extend-group.west {
  top: 50%;
  left: 0;
  flex-direction: column;
  transform: translate(-50%, -50%);
}

.preview-viewport-wrap .extend-group.east {
  top: 50%;
  right: 0;
  flex-direction: column;
  transform: translate(50%, -50%);
}

.preview-viewport-wrap .extend-group.corner {
  top: 0;
  left: 0;
  flex-direction: column;
  transform: translate(-50%, -50%);
}

/* Match the icon-only buttons used on the slot overlay. */
.preview-viewport-wrap .icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  cursor: pointer;
}

.preview-viewport-wrap .icon-btn:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
}

/* Drag-select rectangle drawn inside the viewport while choosing a zoom area
   (grid rectangle mode only). Mirrors the dashed bounds-rect look. */
.map-container .selection-rect {
  position: absolute;
  box-sizing: border-box;
  border: 2px dashed #ff2d2d;
  background: rgba(255, 45, 45, 0.18);
  pointer-events: none;
  z-index: 6;
}

.map-container .viewport {
  position: relative;
  /* the JS sets explicit width/height/transform; this only ensures
     children stack with absolute positioning */
}

.map-container .tiles {
  position: relative;
}

.map-container img.tile {
  position: absolute;
  width: 256px;
  height: 256px;
  display: block;
}

/* Replaces a tile <img> that failed to load (SL CDN 403 = no map data) when
   "Fill missing map tiles" is enabled, painted in the chosen colour (set
   inline by JS) so the preview mirrors the final render's missing-tile fill. */
.map-container .tiles .missing-tile {
  position: absolute;
  width: 256px;
  height: 256px;
}

.map-container svg.route-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* GLW overlay PNG composited over the tiles, under the route SVG. JS sets the
   explicit left/top/width/height to match the final-image bounds rectangle. */
.map-container img.glw-overlay {
  position: absolute;
  pointer-events: none;
}

/* Per-region annotation overlay PNG (rectangles, names, grid coordinates),
   rendered at the final-image resolution and dropped into the bounds rectangle.
   Bottom-most overlay — the final render draws it just above the bare tiles,
   below the GLW overlay, route and labels. JS inserts it right after the tiles
   so DOM order keeps it under the others, and sets the explicit
   left/top/width/height. */
.map-container img.region-overlay {
  position: absolute;
  pointer-events: none;
}

/* Route PNG, rendered server-side at the final-image resolution with the very
   same spline + arrows code the output uses, and dropped into the bounds
   rectangle. Sits above the GLW shapes and below the bounds-guide SVG (so the
   dashed rectangle stays visible) and the legend/labels overlays. JS sets the
   explicit left/top/width/height. */
.map-container img.route-overlay {
  position: absolute;
  pointer-events: none;
}

/* GLW base legend PNG, rendered at the final-image resolution and dropped into
   the bounds rectangle. z-index keeps it above the route SVG and the free-slot
   overlay so it reads as the legend the final render will place. */
.map-container img.glw-legend {
  position: absolute;
  pointer-events: none;
  z-index: 2;
}

/* Text labels + logos PNG, rendered at the final-image resolution and dropped
   into the bounds rectangle. Topmost: the final render draws labels/logos
   above the route, GLW overlay and legend. */
.map-container img.placement-overlay {
  position: absolute;
  pointer-events: none;
  z-index: 3;
}

/* Free-placement-slot overlay, drawn ABOVE all preview content (GLW overlay,
   legend, labels/logos) so its buttons stay usable. Each free slot is a
   translucent green box (red when its content does not fit); occupied slots get
   a red tag at their cell centre. The layer is click-through except for the
   buttons, which re-enable pointer events. JS sets the explicit positions. */
.map-container .slots-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 4;
}

.map-container .slot-box {
  position: absolute;
  box-sizing: border-box;
  border: 1.5px solid #2e8b57;
  background: rgba(46, 139, 87, 0.18);
}

/* A slot whose configured content cannot be generated (e.g. too much text to
   fit) turns red. */
.map-container .slot-box.error {
  border-color: #c0392b;
  background: rgba(192, 57, 43, 0.22);
}

.map-container .slot-label,
.map-container .slot-marker {
  position: absolute;
  font-size: 10px;
  line-height: 1.3;
  padding: 0 3px;
  white-space: nowrap;
  color: #fff;
}

.map-container .slot-label {
  left: 0;
  top: 0;
  background: #2e8b57;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  /* receive hover so the title tooltip shows the full text when clipped */
  pointer-events: auto;
}

.map-container .slot-box.error .slot-label {
  background: #c0392b;
}

.map-container .slot-marker.occupied {
  background: rgba(192, 57, 43, 0.9);
  border-radius: 2px;
  transform: translate(-50%, -50%);
}

/* Per-slot button cluster, just under the slot label. */
.map-container .slot-buttons {
  position: absolute;
  left: 0;
  top: 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: 2px;
}

/* Icon-only buttons used on the slot overlay (radios, split, combine). */
.map-container .icon-btn {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: pointer;
}

.map-container .icon-btn:hover {
  background: rgba(0, 0, 0, 0.75);
}

.map-container .icon-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
}

/* Combine button centred on the shared edge of two adjacent free slots. */
.map-container .slot-combine {
  position: absolute;
  transform: translate(-50%, -50%);
  background: rgba(46, 139, 87, 0.9);
  border-color: #fff;
}

/* While a copy/swap is pending, candidate target boxes accept clicks and are
   highlighted. */
.map-container .slots-overlay.picking .slot-box.pick-target {
  pointer-events: auto;
  cursor: pointer;
  border-color: #f4c430;
  background: rgba(244, 196, 48, 0.22);
}

/* Slot-editor modal form: stack the fields vertically. */
.modal-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.modal-form .lm-lines {
  width: 100%;
}

.tile-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  margin-top: 0.5rem;
  font-size: 0.9rem;
}

.tile-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 14px);
  gap: 2px;
  margin-top: 0.5rem;
}

.tile-cell {
  width: 14px;
  height: 14px;
  border-radius: 2px;
  background: var(--tile-pending);
}

.tile-cell.memory {
  background: var(--tile-memory);
}

.tile-cell.disk {
  background: var(--tile-disk);
}

.tile-cell.network {
  background: var(--tile-network);
}

.tile-cell.missing {
  background: var(--tile-missing);
}

.tile-cell.active {
  outline: 1px solid var(--accent);
}

#result-image {
  max-width: 100%;
  display: block;
  margin-top: 0.5rem;
  border: 1px solid var(--border);
}

.hidden {
  display: none !important;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.25rem;
}

.topbar h1 {
  margin: 0;
}

.topbar-user {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.95rem;
}

.topbar-logout {
  padding: 0.3rem 0.75rem;
  font-size: 0.85rem;
}

body.auth-page {
  max-width: 520px;
}

.auth-card form label {
  margin-bottom: 0.75rem;
}

.auth-card form input {
  width: 100%;
}

.primary-nav {
  display: flex;
  gap: 0.75rem;
  margin: 0.25rem 0 0.75rem;
  font-size: 0.95rem;
}

.primary-nav a {
  color: var(--accent);
  text-decoration: none;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
}

.primary-nav a:hover {
  background: rgba(0, 0, 0, 0.05);
}

.primary-nav a.active {
  background: var(--accent);
  color: var(--accent-fg);
}

.badge {
  display: inline-block;
  background: var(--accent);
  color: var(--accent-fg);
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0 0.4rem;
  min-width: 1.2rem;
  text-align: center;
  vertical-align: middle;
}

.library-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
}

.library-table th,
.library-table td {
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}

.library-table th {
  font-weight: 600;
  background: rgba(0, 0, 0, 0.03);
}

.library-table tbody tr:hover {
  background: rgba(0, 0, 0, 0.02);
}

/* A button rendered as an in-page link, used for cross-references between
   library tabs (e.g. a render's notecard / GLW data cell). */
.linklike {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: var(--accent);
  text-align: left;
  cursor: pointer;
}

.linklike:hover {
  text-decoration: underline;
}

/* Brief flash applied to a row jumped to from another tab so the eye can find
   it after the tab switch. */
.library-table tbody tr.row-highlight {
  animation: row-highlight-flash 2s ease-out;
}

@keyframes row-highlight-flash {
  from {
    background: var(--accent);
  }
  to {
    background: transparent;
  }
}

/* Logo preview thumbnail in the library table, kept small and on a
   checkerboard so transparent logo images are legible. */
.logo-thumb {
  max-width: 96px;
  max-height: 48px;
  vertical-align: middle;
  background-image:
    linear-gradient(45deg, #ccc 25%, transparent 25%),
    linear-gradient(-45deg, #ccc 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, #ccc 75%),
    linear-gradient(-45deg, transparent 75%, #ccc 75%);
  background-size: 12px 12px;
  background-position:
    0 0,
    0 6px,
    6px -6px,
    -6px 0;
}

/* A checkbox sitting inline with its label text (placement "fill" toggle),
   rather than the default stacked label/control layout. */
.checkbox-inline {
  flex-direction: row;
  align-items: center;
  gap: 0.3rem;
}

.row-action {
  display: inline-block;
  margin-right: 0.5rem;
  padding: 0.2rem 0.6rem;
  font-size: 0.8rem;
  background: var(--accent);
  color: var(--accent-fg);
  border: 0;
  border-radius: 4px;
  text-decoration: none;
  cursor: pointer;
}

.row-action.danger {
  background: #b22a2a;
}

.status-pill {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  font-size: 0.78rem;
  font-weight: 600;
  border-radius: 999px;
  text-transform: uppercase;
}

.status-pill.status-in_progress {
  background: #f59e0b;
  color: #fff;
}

.status-pill.status-done {
  background: #16a34a;
  color: #fff;
}

.status-pill.status-failed {
  background: #b22a2a;
  color: #fff;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 1rem;
}

.modal-dialog {
  background: var(--bg-panel);
  color: inherit;
  border: 1px solid var(--border);
  border-radius: 8px;
  max-width: min(32rem, 100%);
  width: 100%;
  padding: 1rem 1.25rem;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.modal-title {
  font-size: 1.1rem;
  margin: 0;
}

.modal-message {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.modal-input {
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: inherit;
  font: inherit;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}

.modal-btn {
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.modal-btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
}

.modal-btn.primary.danger,
.modal-btn.danger {
  background: #b22a2a;
  border-color: #b22a2a;
  color: #fff;
}

.metadata-list {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 1rem;
  row-gap: 0.25rem;
  margin: 0;
}

.metadata-list dt {
  font-weight: bold;
}

.metadata-list dd {
  margin: 0;
}

.metadata-subheading {
  margin: 0.5rem 0 0;
  font-size: 1rem;
}

.metadata-pps {
  width: 100%;
  font-family: monospace;
  background: var(--bg);
  color: inherit;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.4rem 0.5rem;
  resize: vertical;
}

.metadata-copy-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.metadata-copy-status {
  font-size: 0.9rem;
  color: var(--muted, inherit);
}

.metadata-steps {
  margin: 0;
  padding-left: 1.25rem;
}

/* Wider dialog for the image viewer — the default 32rem cap is too
 * narrow for a rendered map; the rule below targets the dialog only
 * when an image-modal-wrap is present so other modals are unaffected. */
.modal-dialog:has(.image-modal-wrap) {
  max-width: min(64rem, 100%);
}

.image-modal-tabs {
  display: flex;
  gap: 0.5rem;
  margin: 0;
  border-bottom: 1px solid var(--border);
}

.image-modal-wrap {
  display: flex;
  justify-content: center;
  align-items: center;
  max-height: 70vh;
  overflow: auto;
}

.image-modal-img {
  display: block;
  max-width: 100%;
  height: auto;
}

.image-modal-img.hidden {
  display: none;
}

.muted {
  color: var(--muted, #888);
  font-style: italic;
}

/* --- placement slots & text labels --- */

.field-error {
  color: #c0392b;
  font-size: 0.85em;
  align-self: center;
}

.label-row {
  border: 1px solid var(--border, #ccc);
  border-radius: 4px;
  padding: 0.5rem 0.75rem;
  margin-bottom: 0.75rem;
}

.label-measure {
  margin: 0.25rem 0 0;
}
