/*
 * TileServer-PHP UI theme.
 *
 * Light theme in the Mintlify-inspired system documented in DESIGN.md:
 * white canvas, mint-green accent reserved for CTAs/active states, black
 * pill buttons, flat 12px-radius cards, Inter for UI text.
 *
 * Inter is self-hosted (inter-var.woff2, a variable font subsetted to
 * latin covering weights 100-900) instead of Google Fonts, so the app has
 * zero network dependency for text and still renders correctly on hosts
 * with limited connectivity - this server may run on cartography networks
 * without reliable internet access. Monospace stays on the system stack
 * (used only for short secrets/tokens/identifiers, not long-form code), so
 * only one webfont is self-hosted.
 */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/fonts/inter-var.woff2") format("woff2");
}

:root {
  --canvas: #ffffff;
  --surface: #f7f7f7;
  --surface-soft: #fafafa;
  --surface-code: #1c1c1e;

  --ink: #0a0a0a;
  --charcoal: #1c1c1e;
  --slate: #3a3a3c;
  --steel: #5a5a5c;
  --stone: #888888;
  --muted: #a8a8aa;

  --accent: #00d4a4;
  --accent-deep: #00b48a;
  --accent-soft: #7cebcb;

  --danger: #d45656;
  --danger-soft: rgba(212, 86, 86, 0.08);

  --hairline: #e5e5e5;
  --hairline-soft: #ededed;

  /* Text color that sits ON TOP of a --ink-colored surface (button-primary
     text, etc.) - separate from --ink itself because in dark mode --ink
     flips to white but the contrasting text on top of it must flip to
     black, not stay white. */
  --ink-contrast: #ffffff;
  --btn-hover: #1c1c1e;
  --btn-active: #000000;
  --accent-tint: rgba(0, 212, 164, 0.08);

  /* Card background - same as --canvas in light (a hairline border is
     enough to separate white-on-white), but lifted to --surface in dark
     (a hairline border alone is much harder to perceive near-black on
     near-black - see the dark theme block below). */
  --card-bg: var(--canvas);

  /* Numbered aliases kept for the many inline style="...var(--space-N)"
     call sites across app/templates/ - avoids a repo-wide find/replace. */
  --space-1: 8px;
  --space-2: 12px;
  --space-3: 16px;
  --space-4: 40px;

  /* Semantic scale used by this file going forward. */
  --space-xxs: 4px;
  --space-xs: 8px;
  --space-sm: 12px;
  --space-md: 16px;
  --space-lg: 20px;
  --space-xl: 24px;
  --space-xxl: 32px;
  --space-xxxl: 40px;

  --radius-card: 12px;
  --radius-input: 8px;
  --radius-sm: 6px;
  --radius-xs: 4px;
  --radius-full: 9999px;

  --font-ui: "Inter", -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", monospace;

  --text-hero: 48px;
  --text-h2: 36px;
  --text-h3: 28px;
  --text-body: 16px;
  --text-caption: 13px;

  --shadow-feature: rgba(0, 0, 0, 0.12) 0px 24px 48px -8px;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background-color: var(--canvas);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--text-body);
  line-height: 1.5;
  min-height: 100vh;
}

/* Subtle atmospheric moment reserved for the login flow - the one screen
   in this admin/viewer tool that isn't dense data, per DESIGN.md's
   atmospheric-hero guidance used sparingly. */
body.animate-grid {
  background-image:
    linear-gradient(180deg, var(--canvas) 0%, var(--surface) 100%),
    repeating-linear-gradient(0deg, rgba(0, 212, 164, 0.07) 0 1px, transparent 1px 64px),
    repeating-linear-gradient(90deg, rgba(0, 212, 164, 0.07) 0 1px, transparent 1px 64px);
  background-blend-mode: normal, normal, normal;
  animation: drift 40s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  body.animate-grid {
    animation: none;
  }
}

@keyframes drift {
  from {
    background-position: 0 0, 0 0, 0 0;
  }
  to {
    background-position: 0 0, 64px 64px, 64px 64px;
  }
}

.page {
  max-width: 1200px;
  margin: 0 auto;
  padding: var(--space-4) var(--space-3);
}

.page-narrow {
  max-width: 480px;
}

.page-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.eyebrow {
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--steel);
  margin: 0 0 var(--space-1);
}

.hero {
  font-size: var(--text-hero);
  line-height: 1.1;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-2);
  color: var(--ink);
}

.hero-md {
  font-size: 40px;
}

.hero-sm {
  font-size: var(--text-h3);
}

.subhead {
  color: var(--steel);
  font-size: 17px;
  margin: 0 0 var(--space-4);
}

.section-title {
  font-size: var(--text-h2);
  font-weight: 600;
  margin: var(--space-4) 0 var(--space-3);
  color: var(--ink);
}

.section-title-sm {
  font-size: 20px;
  margin-top: 0;
}

p {
  margin: 0 0 var(--space-3);
}

a {
  color: var(--ink);
  text-decoration: none;
}

a:hover {
  color: var(--accent-deep);
  text-decoration: underline;
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-xs);
  padding: 2px 6px;
  color: var(--charcoal);
}

.card {
  background: var(--card-bg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  padding: var(--space-xl);
}

.card-narrow {
  margin-bottom: var(--space-3);
}

/* Only the login card gets the reserved "feature moment" shadow - every
   other surface in the app stays flat per DESIGN.md's elevation guidance. */
body.animate-grid .card {
  box-shadow: var(--shadow-feature);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--ink);
  color: var(--ink-contrast);
  border: none;
  border-radius: var(--radius-full);
  padding: 10px 20px;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.btn:hover {
  background: var(--btn-hover);
}

.btn:active {
  background: var(--btn-active);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--space-3);
}

.field label {
  font-size: 12px;
  color: var(--steel);
  font-family: var(--font-ui);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.field input,
.field select {
  background: var(--canvas);
  color: var(--ink);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-input);
  padding: 10px 16px;
  font-size: var(--text-body);
  font-family: var(--font-ui);
  width: 100%;
  height: 40px;
}

.field input:focus,
.field select:focus {
  outline: none;
  border: 2px solid var(--accent);
  padding: 9px 15px;
}

.field input[name="code"] {
  font-family: var(--font-mono);
  letter-spacing: 0.16em;
}

.field {
  position: relative;
  /* Chevron drawn with a background-color + mask instead of a baked-in
     background-image, so it picks up --steel's theme-flipped value
     instead of staying stuck at the light-theme color in dark mode.
     Declared on .field (not .field select) so the sibling ::after
     pseudo-element below can inherit it. */
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.field select {
  appearance: none;
  padding-right: 40px;
}

.field:has(select)::after {
  content: "";
  position: absolute;
  right: 14px;
  bottom: 13px;
  width: 16px;
  height: 16px;
  pointer-events: none;
  background-color: var(--steel);
  -webkit-mask-image: var(--chevron);
  mask-image: var(--chevron);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.field input[type="file"] {
  height: auto;
  padding: 8px;
}

.field input[type="file"]::file-selector-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--hairline);
  color: var(--steel);
  background: var(--canvas);
  border-radius: var(--radius-full);
  padding: 8px 16px;
  margin-right: var(--space-sm);
  font-size: var(--text-caption);
  font-family: var(--font-ui);
  font-weight: 500;
  cursor: pointer;
}

.field input[type="file"]::file-selector-button:hover {
  border-color: var(--accent-deep);
  color: var(--accent-deep);
}

/* Drag-and-drop wrapper around a plain input[type=file], see
   public/assets/dropzone.js. The native input stays visible inside it -
   this only adds a drop target and a dashed-border affordance. */
.dropzone {
  border: 1px dashed var(--hairline);
  border-radius: var(--radius-input);
  padding: var(--space-3);
  text-align: center;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.dropzone.is-dragover {
  border-color: var(--accent);
  background: var(--accent-tint);
}

.dropzone-hint {
  margin: var(--space-2) 0 0;
  font-size: var(--text-caption);
  color: var(--steel);
}

.upload-progress {
  margin-bottom: var(--space-3);
}

.upload-progress-bar {
  height: 8px;
  background: var(--hairline-soft);
  border-radius: var(--radius-full);
  overflow: hidden;
}

.upload-progress-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  border-radius: var(--radius-full);
  transition: width 0.2s ease;
}

.upload-progress-label {
  margin: 6px 0 0;
  font-size: var(--text-caption);
  font-family: var(--font-ui);
  color: var(--steel);
}

.basename-cell {
  cursor: text;
  border-radius: var(--radius-xs);
  padding: 2px 4px;
  margin: -2px -4px;
}

.basename-cell:hover,
.basename-cell:focus {
  background: var(--accent-tint);
  outline: none;
}

.basename-cell input {
  background: var(--canvas);
  color: var(--ink);
  border: 2px solid var(--accent);
  border-radius: var(--radius-xs);
  padding: 1px 3px;
  font-family: var(--font-mono);
  font-size: inherit;
  width: 100%;
  min-width: 160px;
}

.basename-cell-error {
  margin-top: 4px;
  color: var(--danger);
  font-size: var(--text-caption);
  font-family: var(--font-ui);
  white-space: normal;
  min-width: 160px;
  max-width: 260px;
}

input[type="checkbox"] {
  accent-color: var(--accent);
}

.checkbox-row {
  display: block;
  margin-bottom: 6px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--slate);
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--hairline);
  background: var(--canvas);
}

.topbar-brand {
  font-family: var(--font-ui);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.topbar-account {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-caption);
}

.topbar-account a {
  color: var(--steel);
}

.topbar-account a:hover {
  color: var(--ink);
}

.topbar-username {
  color: var(--steel);
  font-family: var(--font-ui);
  font-weight: 500;
}

.table {
  width: 100%;
  min-width: 640px;
  border-collapse: collapse;
  font-size: var(--text-caption);
}

.table th,
.table td {
  text-align: left;
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--hairline-soft);
  vertical-align: middle;
}

.table th {
  color: var(--steel);
  font-family: var(--font-ui);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 11px;
}

/* Sortable column headers (see templates/mapsheets-preview.php's
   $sortHeader) - keep the same muted tone as a plain header until
   hovered, so a sortable/non-sortable column reads consistently at rest. */
.table th a {
  color: inherit;
}

.table th a:hover {
  color: var(--accent-deep);
}

.table tbody tr:last-child td {
  border-bottom: none;
}

.admin-users-header {
  align-items: flex-start;
  margin-bottom: var(--space-lg);
}

.admin-users-subhead {
  margin: var(--space-1) 0 0;
}

.admin-users-nav,
.row-actions {
  display: flex;
  gap: 6px;
  flex-wrap: nowrap;
  align-items: flex-start;
}

.admin-users-nav {
  justify-content: flex-end;
}

.admin-users-stats {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--space-sm);
  margin-bottom: var(--space-3);
}

.admin-stat {
  display: block;
  min-width: 0;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  padding: var(--space-sm) var(--space-md);
  text-decoration: none;
  transition: border-color 0.15s ease, background-color 0.15s ease, transform 0.15s ease;
}

.admin-stat:hover {
  background: var(--accent-tint);
  border-color: var(--accent-deep);
  text-decoration: none;
  transform: translateY(-1px);
}

.admin-stat:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.admin-stat--active {
  background: var(--accent-tint);
  border-color: var(--accent-deep);
}

.admin-stat-value {
  display: block;
  color: var(--ink);
  font-size: 22px;
  line-height: 1.1;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.admin-stat-label {
  display: block;
  margin-top: 2px;
  color: var(--steel);
  font-size: 12px;
  font-weight: 500;
}

.admin-stat--danger {
  background: var(--danger-soft);
  border-color: var(--danger);
}

.admin-stat--danger:hover,
.admin-stat--danger.admin-stat--active {
  background: var(--danger-soft);
  border-color: var(--danger);
}

.admin-stat--danger .admin-stat-value,
.admin-stat--danger .admin-stat-label {
  color: var(--danger);
}

.admin-users-card {
  overflow: visible;
  padding: 0;
}

.admin-users-table {
  min-width: 920px;
}

.admin-users-table th,
.admin-users-table td {
  padding: var(--space-lg) var(--space-md);
}

.admin-users-table tbody tr {
  transition: background-color 0.15s ease;
}

.admin-users-table tbody tr:hover {
  background: var(--surface-soft);
}

.admin-users-table tbody tr.admin-user-row--attention {
  background:
    linear-gradient(90deg, var(--danger-soft), transparent 140px),
    var(--card-bg);
}

.admin-users-table tbody tr.admin-user-row--attention:hover {
  background:
    linear-gradient(90deg, var(--danger-soft), transparent 140px),
    var(--surface-soft);
}

.admin-user-cell {
  min-width: 250px;
}

.admin-user-main {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-sm);
}

.admin-user-primary {
  color: var(--ink);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.admin-user-token {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  color: var(--steel);
  font-size: 11px;
  white-space: nowrap;
}

.admin-user-token code {
  font-size: 11px;
}

.admin-account-cell {
  min-width: 260px;
}

.admin-account-stack {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.role-badge {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-full);
  padding: 2px 10px;
  color: var(--steel);
  font-size: 12px;
  font-weight: 500;
}

.role-badge--admin {
  color: var(--ink);
  border-color: var(--ink);
}

.admin-maps-pill {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-full);
  padding: 2px 10px;
  color: var(--steel);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}

.admin-maps-count {
  color: var(--ink);
  font-weight: 600;
}

.admin-usage-cell {
  min-width: 340px;
}

.admin-actions-cell {
  width: 1%;
  min-width: 250px;
}

.admin-actions-cell .row-actions {
  justify-content: flex-end;
}

/* Removes the native <summary> disclosure marker so a <details> reads as
   a custom control (a meter, a "More" button) instead of a text line with
   a triangle - used by both the usage-meter rows and the row-actions
   overflow menu in admin/users.php. Their panels are positioned as
   floating "bocadillos" so opening them does not change row height. */
.summary-plain {
  cursor: pointer;
  list-style: none;
}

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

/* "Estado" / a color-dot + label, read at a glance down the column. */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-caption);
}

.status-badge::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  background: var(--accent);
}

.status-badge--off::before {
  background: var(--danger);
}

/* "Plan" pill - filled when assigned, dashed/muted when not. */
.plan-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border-radius: var(--radius-full);
  font-size: 12px;
  font-weight: 500;
  background: var(--accent-tint);
  color: var(--accent-deep);
}

.plan-pill--none {
  background: none;
  border: 1px dashed var(--hairline);
  color: var(--steel);
}

/* "Consumo" cell - one .usage-meter-row per metric (teselas/impresiones).
   The bar + numbers are the <summary>; opening it reveals the manual
   block/reset controls below, so they cost a click instead of permanent
   row height for the common case (no plan, nothing to manage). */
.usage-meter-row {
  position: relative;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-input);
  background: var(--surface-soft);
  padding: 8px 10px;
}

.usage-meter-row + .usage-meter-row {
  margin-top: 8px;
}

.usage-meter-row--danger {
  border-color: var(--danger);
  background: var(--danger-soft);
}

.usage-meter-row summary {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 300px;
}

.usage-meter-row summary::before {
  content: '';
  width: 0;
  height: 0;
  flex: none;
  border-left: 4px solid var(--steel);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform 0.15s ease;
}

.usage-meter-row[open] summary::before {
  transform: rotate(90deg);
}

.usage-meter-head {
  display: flex;
  flex: 0 0 84px;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.usage-meter-label {
  flex: none;
  color: var(--ink);
  font-size: 12px;
  font-weight: 600;
}

.usage-meter-row summary::before {
  margin-right: -2px;
  order: -1;
}

.usage-meter-row--danger .usage-meter-label {
  color: var(--danger);
}

.usage-meter--empty {
  background: linear-gradient(90deg, var(--hairline) 0 45%, transparent 45% 100%);
  opacity: 0.65;
}

.usage-meter-label,
.usage-meter-flag {
  line-height: 1.1;
}

.usage-meter-head .usage-meter-flag {
  align-self: flex-start;
}

.usage-meter-head + .usage-meter {
  margin-left: 0;
}

.usage-meter-row .meta {
  margin: 0;
  font-size: 11px;
}

.usage-meter-row summary:hover .usage-meter-label,
.usage-meter-row summary:hover .usage-meter-value {
  color: var(--accent-deep);
}

.usage-meter-row--danger summary:hover .usage-meter-label,
.usage-meter-row--danger summary:hover .usage-meter-value {
  color: var(--danger);
}

.usage-meter-row summary .usage-meter-head {
  order: 0;
}

.usage-meter-row summary .usage-meter {
  order: 1;
}

.usage-meter-row summary .usage-meter-value {
  order: 2;
}

.usage-meter-head .usage-meter-flag {
  margin-top: 0;
}

.usage-meter-row .usage-meter-flag {
  font-size: 9px;
}

.usage-meter {
  flex: 1 1 70px;
  min-width: 50px;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--hairline);
  overflow: hidden;
}

.usage-meter-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-full);
}

.usage-meter-fill--danger {
  background: var(--danger);
}

.usage-meter-value {
  flex: 0 0 78px;
  color: var(--ink);
  font-size: 12px;
  font-weight: 600;
  text-align: right;
  white-space: nowrap;
}

.usage-meter-flag {
  flex: none;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--danger);
}

.usage-meter-detail {
  position: absolute;
  top: calc(100% + 8px);
  left: 28px;
  z-index: 30;
  min-width: 260px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
  background: var(--card-bg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  box-shadow: rgba(0, 0, 0, 0.12) 0 16px 32px -16px;
  padding: var(--space-sm);
}

.usage-meter-detail::before {
  content: '';
  position: absolute;
  top: -6px;
  left: 18px;
  width: 10px;
  height: 10px;
  background: var(--card-bg);
  border-left: 1px solid var(--hairline);
  border-top: 1px solid var(--hairline);
  transform: rotate(45deg);
}

/* Row-actions overflow ("⋯ Más") - groups the infrequent/irreversible
   actions (new token, reset TOTP, delete) behind one click, leaving only
   Editar/Deshabilitar visible at rest. */
.row-menu {
  position: relative;
  display: inline-block;
}

.row-menu-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  min-width: 170px;
  margin-top: 0;
  background: var(--card-bg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  box-shadow: rgba(0, 0, 0, 0.12) 0 16px 32px -16px;
  padding: var(--space-sm);
}

.row-menu-panel::before {
  content: '';
  position: absolute;
  top: -6px;
  right: 22px;
  width: 10px;
  height: 10px;
  background: var(--card-bg);
  border-left: 1px solid var(--hairline);
  border-top: 1px solid var(--hairline);
  transform: rotate(45deg);
}

.usage-meter-row[open],
.row-menu[open] {
  z-index: 20;
}

.row-menu-panel .pill-link,
.usage-meter-detail .pill-link {
  justify-content: center;
  white-space: nowrap;
}

.alert {
  border: 1px solid var(--danger);
  background: var(--danger-soft);
  color: var(--ink);
  border-radius: var(--radius-card);
  padding: 12px 16px;
  font-size: var(--text-caption);
  margin-bottom: var(--space-3);
}

.alert-success {
  border: 1px solid var(--accent);
  background: var(--accent-tint);
  color: var(--ink);
  border-radius: var(--radius-card);
  padding: 12px 16px;
  font-size: var(--text-caption);
  margin-bottom: var(--space-3);
}

.terminal {
  background: var(--surface-code);
  border: 1px solid #1f1f1f;
  border-radius: var(--radius-input);
  padding: 16px;
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  color: var(--accent-soft);
  overflow-x: auto;
}

.pill-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
  padding: 0;
  list-style: none;
}

.pill-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--hairline);
  color: var(--steel);
  background: var(--canvas);
  border-radius: var(--radius-full);
  padding: 8px 16px;
  font-size: var(--text-caption);
  font-family: var(--font-ui);
  font-weight: 500;
  cursor: pointer;
}

.pill-link:hover {
  background: none;
  border-color: var(--accent-deep);
  color: var(--accent-deep);
  text-decoration: none;
}

.pill-link--muted {
  border-color: var(--hairline);
  color: var(--steel);
  background: none;
}

.pill-link--danger {
  border-color: var(--danger);
  color: var(--danger);
  background: none;
}

.pill-link--danger:hover {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
}

.grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}

@media (min-width: 768px) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 992px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.dataset-card h3 {
  font-family: var(--font-mono);
  font-size: 16px;
  margin: 0 0 var(--space-2);
  word-break: break-all;
  color: var(--ink);
}

.coords {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 var(--space-2);
  padding: 0;
  list-style: none;
}

.coords li {
  font-family: var(--font-mono);
  font-size: 12px;
  background: var(--surface);
  border-radius: var(--radius-xs);
  padding: 2px 6px;
  color: var(--steel);
}

.meta {
  font-size: var(--text-caption);
  color: var(--steel);
  margin: 0 0 var(--space-2);
}

.list {
  margin: 0 0 var(--space-3);
  padding: 0;
  list-style: none;
}

.list li {
  padding: var(--space-1) 0 var(--space-1) 28px;
  border-bottom: 1px solid var(--hairline-soft);
  position: relative;
}

.list li:last-child {
  border-bottom: none;
}

.list li::before {
  content: "\2192";
  position: absolute;
  left: 0;
  color: var(--accent-deep);
}

.logo-preview {
  display: inline-block;
  /* Always white, regardless of theme - like the TOTP QR box, this is a
     functional requirement (many uploaded logos are transparent PNGs
     designed to sit on white), not a theming choice. */
  background: #ffffff;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  padding: 12px;
  margin-bottom: var(--space-3);
}

.viewer-panel {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  overflow: hidden;
  min-height: 480px;
}

.viewer-panel-map {
  height: 70vh;
}

/* Route preview mini-map, see mapsheets-preview.php / mapsheet-route-preview.js */
.route-preview-map {
  height: 220px;
  min-height: 0;
}

/* Leaflet's stock control chrome is already light - just align its
   hairlines/radii with the rest of the app instead of forcing it dark. */
.leaflet-container {
  background: var(--surface);
  font-family: var(--font-ui);
}

.leaflet-bar,
.leaflet-control-layers {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-input);
  box-shadow: none;
}

.leaflet-bar a {
  color: var(--ink);
  border-bottom: 1px solid var(--hairline);
}

.leaflet-bar a:hover {
  background: var(--surface);
}

.leaflet-control-layers-expanded {
  border-radius: var(--radius-input);
  padding: var(--space-2);
}

/* Fullscreen toggle, see public/assets/map-controls.js - the icon needs
   flex centering since (unlike Leaflet's own text glyphs) an inline SVG
   isn't centered by line-height alone. */
.leaflet-control a:has(svg) {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* :fullscreen is applied straight to the Leaflet container element (the
   div the map is created on), which the Fullscreen API already resizes
   to fill the screen - this just drops the card chrome that no longer
   makes sense full-bleed and keeps the background themed instead of
   flashing to the browser's default fullscreen backdrop. */
.viewer-panel:fullscreen,
.leaflet-container:fullscreen {
  width: 100vw;
  height: 100vh;
  border: none;
  border-radius: 0;
  background: var(--canvas);
}

.leaflet-control-attribution {
  background: rgba(255, 255, 255, 0.85);
  color: var(--steel);
}

.leaflet-control-attribution a {
  color: var(--ink);
}

/* Zoom level + approximate map scale readout, see
   public/assets/map-controls.js. Sits bottomleft, next to L.control.scale(). */
.zoom-scale-indicator {
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 11px;
  line-height: 1;
  padding: 4px 8px;
  white-space: nowrap;
}

/* "Go to coordinates" control, see public/assets/map-controls.js. Sits in
   the map's topright corner, above/alongside the layer switcher. */
.coord-search {
  display: flex;
  background: var(--surface);
}

.coord-search-input {
  border: none;
  background: none;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 13px;
  padding: 0 8px;
  width: 110px;
  height: 30px;
}

.coord-search-input:focus {
  outline: none;
}

.coord-search-btn {
  border: none;
  border-left: 1px solid var(--hairline);
  background: none;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  padding: 0 12px;
}

.coord-search-btn:hover {
  background: var(--surface-soft);
}

.footnote {
  color: var(--steel);
  font-size: var(--text-caption);
  margin-top: var(--space-4);
}

.theme-toggle {
  display: inline-flex;
  gap: 2px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-full);
  padding: 2px;
}

.theme-toggle button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--steel);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}

.theme-toggle button.is-active {
  background: var(--ink);
  color: var(--ink-contrast);
}

.theme-toggle-floating {
  position: fixed;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 10;
}

@media (max-width: 767px) {
  :root {
    --text-hero: 34px;
    --text-h2: 22px;
  }

  .btn {
    width: 100%;
  }

  /* Each topbar link on its own line instead of an unpredictable
     multi-line wrap - the flex-wrap row reads as a jumble at this width. */
  .topbar {
    flex-direction: column;
    align-items: stretch;
  }

  .topbar-account {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
  }

  /* Fixed positioning puts this on top of (not above) the page content -
     on narrow screens the eyebrow text is full-width and long enough to
     collide with it. In flow, at the top of the page, instead. */
  .theme-toggle-floating {
    position: static;
    display: flex;
    justify-content: flex-end;
    margin-bottom: var(--space-2);
  }

  .admin-users-nav {
    width: 100%;
    flex-wrap: wrap;
    justify-content: flex-start;
  }

  .admin-users-nav .btn {
    width: auto;
  }

  .admin-users-stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/*
 * Dark theme.
 *
 * Two blocks with identical token values, on purpose:
 *  - the @media block covers "system preference is dark" for both the
 *    no-JS case (data-theme is never set) and the JS case while the user's
 *    saved choice is "system" (guarded to yield to an explicit "light"
 *    choice via :not([data-theme="light"])).
 *  - the [data-theme="dark"] block covers an explicit "dark" choice even
 *    when the OS itself is set to light.
 * See app/templates/_theme-init.php for the script that sets data-theme.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --canvas: #0a0a0a;
    --surface: #161616;
    --surface-soft: #1c1c1c;
    --surface-code: #000000;

    --ink: #ffffff;
    --ink-contrast: #0a0a0a;
    --charcoal: #e5e5e5;
    --slate: #c7c7c9;
    --steel: #a8a8aa;
    --stone: #8a8a8a;
    --muted: #6b6b6d;

    --danger: #ff7a7a;
    --danger-soft: rgba(255, 122, 122, 0.14);

    --hairline: #262626;
    --hairline-soft: #2a2a2a;

    --btn-hover: #e5e5e5;
    --btn-active: #f2f2f2;
    --accent-tint: rgba(0, 212, 164, 0.16);

    --shadow-feature: rgba(0, 212, 164, 0.18) 0px 24px 48px -8px;

    /* Lift cards off the canvas - a hairline border alone is much harder
       to perceive near-black-on-near-black than light-gray-on-white. */
    --card-bg: var(--surface);
  }

  :root:not([data-theme="light"]) .leaflet-container {
    background: var(--surface);
  }

  :root:not([data-theme="light"]) .leaflet-bar,
  :root:not([data-theme="light"]) .leaflet-control-layers {
    background: var(--surface);
  }

  :root:not([data-theme="light"]) .leaflet-bar a {
    background: var(--surface);
    color: var(--ink);
  }

  :root:not([data-theme="light"]) .leaflet-control-layers-toggle {
    filter: invert(1);
  }

  :root:not([data-theme="light"]) .leaflet-control-layers-expanded {
    background: var(--surface);
    color: var(--ink);
  }

  :root:not([data-theme="light"]) .leaflet-control-attribution {
    background: rgba(0, 0, 0, 0.85);
    color: var(--steel);
  }

  :root:not([data-theme="light"]) .leaflet-control-attribution a {
    color: var(--ink);
  }

  :root:not([data-theme="light"]) .leaflet-control-scale-line {
    background: rgba(22, 22, 22, 0.85);
    border-color: var(--ink);
    color: var(--ink);
  }
}

:root[data-theme="dark"] {
  --canvas: #0a0a0a;
  --surface: #161616;
  --surface-soft: #1c1c1c;
  --surface-code: #000000;

  --ink: #ffffff;
  --ink-contrast: #0a0a0a;
  --charcoal: #e5e5e5;
  --slate: #c7c7c9;
  --steel: #a8a8aa;
  --stone: #8a8a8a;
  --muted: #6b6b6d;

  --danger: #ff7a7a;
  --danger-soft: rgba(255, 122, 122, 0.14);

  --hairline: #262626;
  --hairline-soft: #2a2a2a;

  --btn-hover: #e5e5e5;
  --btn-active: #f2f2f2;
  --accent-tint: rgba(0, 212, 164, 0.16);

  --shadow-feature: rgba(0, 212, 164, 0.18) 0px 24px 48px -8px;

  --card-bg: var(--surface);
}

:root[data-theme="dark"] .leaflet-container {
  background: var(--surface);
}

:root[data-theme="dark"] .leaflet-bar,
:root[data-theme="dark"] .leaflet-control-layers {
  background: var(--surface);
}

:root[data-theme="dark"] .leaflet-bar a {
  background: var(--surface);
  color: var(--ink);
}

:root[data-theme="dark"] .leaflet-control-layers-toggle {
  filter: invert(1);
}

:root[data-theme="dark"] .leaflet-control-layers-expanded {
  background: var(--surface);
  color: var(--ink);
}

:root[data-theme="dark"] .leaflet-control-attribution {
  background: rgba(0, 0, 0, 0.85);
  color: var(--steel);
}

:root[data-theme="dark"] .leaflet-control-attribution a {
  color: var(--ink);
}

:root[data-theme="dark"] .leaflet-control-scale-line {
  background: rgba(22, 22, 22, 0.85);
  border-color: var(--ink);
  color: var(--ink);
}

/* Lightbox - see public/assets/lightbox.js. Click a .lightbox-trigger to
   open, click the backdrop (not the image) or Escape to close. The dark
   backdrop is intentionally the same in both themes (a translucent black
   scrim is what makes the enlarged image pop, regardless of page theme). */
.lightbox-trigger {
  cursor: zoom-in;
}

.lightbox-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 100;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.82);
  padding: var(--space-xl);
  cursor: zoom-out;
}

.lightbox-overlay.is-open {
  display: flex;
}

.lightbox-overlay img {
  max-width: 90vw;
  max-height: 90vh;
  width: auto;
  height: auto;
  cursor: default;
  background: var(--card-bg);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-feature);
}

.lightbox-close {
  position: fixed;
  top: var(--space-lg);
  right: var(--space-lg);
  background: none;
  border: none;
  color: #fff;
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 10px;
}

.lightbox-close:hover {
  color: var(--accent);
}

/* "Mis mapas generados" row thumbnail - fixed box regardless of the sheet's
   own orientation (A3 portrait vs landscape), cropped via object-fit so
   every row in the table is the same height. */
.generated-map-thumbnail {
  width: 80px;
  height: 60px;
  object-fit: cover;
  display: block;
  border-radius: var(--radius-input);
  background: var(--surface);
}

/* Compact inline variant of the .field/.field select pattern (bordered
   box + hand-drawn chevron, see the block above) for a <select> that
   needs to sit in a horizontal toolbar instead of a full-width form
   field - used by the "Por página" picker below the table. */
.select-compact {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.select-compact select {
  appearance: none;
  background: var(--canvas);
  color: var(--ink);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-input);
  padding: 8px 32px 8px 14px;
  font-size: var(--text-body);
  font-family: var(--font-ui);
  height: 36px;
}

.select-compact select:focus {
  outline: none;
  border: 2px solid var(--accent);
  padding: 7px 31px 7px 13px;
}

.select-compact::after {
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  content: "";
  position: absolute;
  top: 50%;
  right: 12px;
  width: 14px;
  height: 14px;
  transform: translateY(-50%);
  pointer-events: none;
  background-color: var(--steel);
  -webkit-mask-image: var(--chevron);
  mask-image: var(--chevron);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* Service URL preview modal (TileJSON/WMTS/TMS) - see
   public/assets/service-url-modal.js. Same dark-backdrop language as
   .lightbox-overlay, but a titled content box instead of a bare image. */
.service-modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1000;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.82);
  padding: var(--space-xl);
}

.service-modal-overlay.is-open {
  display: flex;
}

.service-modal-box {
  display: flex;
  flex-direction: column;
  width: min(700px, 90vw);
  max-height: 80vh;
  background: var(--card-bg);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-feature);
  overflow: hidden;
}

.service-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-lg);
  border-bottom: 1px solid var(--hairline);
}

.service-modal-title {
  margin: 0;
  font-family: var(--font-ui);
  font-weight: 600;
}

/* Visual style for the header's icon-only (x) close button - the footer's
   text "Cerrar" button stays a plain .pill-link. Both share the
   behavior-only .service-modal-close class the JS binds its click
   handler to, so it must never carry visual styles itself. */
.service-modal-icon-close {
  background: none;
  border: none;
  color: var(--steel);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
}

.service-modal-icon-close:hover {
  color: var(--accent-deep);
}

.service-modal-body {
  margin: 0;
  padding: var(--space-lg);
  overflow: auto;
  font-family: var(--font-mono);
  font-size: 12px;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--ink);
}

.service-modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-lg);
  border-top: 1px solid var(--hairline);
}
