/* =====================================================================
   b2maps - shared application styles
   Mobile-first, no external CSS framework.
   ===================================================================== */

:root {
  --color-bg: #f2f4f8;
  --color-surface: #ffffff;
  --color-surface-alt: #f8f9fb;
  --color-primary: #4f6bf6;
  --color-primary-dark: #3a53d9;
  --color-primary-soft: rgba(79, 107, 246, 0.12);
  --color-text: #171b24;
  --color-muted: #6b7785;
  --color-border: #e3e7ee;
  --color-danger: #e5484d;
  --color-danger-dark: #c73338;
  --color-success: #23a566;
  --color-map-btn: #1877f2;
  --radius: 10px;
  --radius-lg: 16px;
  --shadow: 0 1px 2px rgba(16, 24, 32, 0.06), 0 4px 14px rgba(16, 24, 32, 0.06);
  --shadow-lg: 0 8px 24px rgba(16, 24, 32, 0.1), 0 2px 6px rgba(16, 24, 32, 0.06);
  --sidebar-width: 230px;
  --topbar-height: 60px;
}

html[data-theme="dark"] {
  --color-bg: #0f1520;
  --color-surface: #171d29;
  --color-surface-alt: #1d2432;
  --color-primary: #6c85ff;
  --color-primary-dark: #8fa2ff;
  --color-primary-soft: rgba(108, 133, 255, 0.16);
  --color-text: #e7eaf1;
  --color-muted: #8b95a5;
  --color-border: #2a3140;
  --color-danger: #f16368;
  --color-danger-dark: #ff8a8e;
  --color-success: #3ecf8e;
  --color-map-btn: #4d94ff;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 4px 14px rgba(0, 0, 0, 0.28);
  --shadow-lg: 0 10px 28px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
}

html { color-scheme: light; }
html[data-theme="dark"] { color-scheme: dark; }

body {
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--color-bg);
  color: var(--color-text);
  font-size: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  transition: background 0.2s ease, color 0.2s ease;
}

/* Plain text links - no classic "blue + underlined" look. Affordance
   comes from a color shift + a subtle animated underline that only
   appears on hover/focus (drawn as a background-size trick rather than
   text-decoration, so it doesn't clip descenders like "g"/"y"). */
a {
  color: var(--color-primary);
  text-decoration: none;
  font-weight: 600;
  background-image: linear-gradient(var(--color-primary-dark), var(--color-primary-dark));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0 1px;
  transition: color 0.15s ease, background-size 0.15s ease;
}
a:hover, a:focus-visible {
  color: var(--color-primary-dark);
  background-size: 100% 1px;
}

h1, h2, h3 { line-height: 1.2; margin: 0 0 0.6em; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 18px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--color-primary);
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(16, 24, 32, 0.08);
  transition: background 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;
}
.btn:hover { background: var(--color-primary-dark); text-decoration: none; transform: translateY(-1px); box-shadow: 0 4px 10px rgba(79, 107, 246, 0.25); }
.btn:active { transform: translateY(0); }
.btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; }
.btn.btn-secondary { background: var(--color-surface); color: var(--color-text); border-color: var(--color-border); box-shadow: none; }
.btn.btn-secondary:hover { background: var(--color-surface-alt); box-shadow: none; }
.btn.btn-danger { background: var(--color-danger); }
.btn.btn-danger:hover { background: var(--color-danger-dark); box-shadow: 0 4px 10px rgba(229, 72, 77, 0.25); }
.btn.btn-sm { padding: 5px 12px; font-size: 13px; }
.btn.btn-map { background: var(--color-map-btn); }
.btn.btn-map:hover { background: var(--color-map-btn); filter: brightness(0.9); box-shadow: 0 4px 10px rgba(24, 119, 242, 0.25); }

/* Small square icon-only action buttons (e.g. table row actions) - a
   plain single-color glyph rather than a labeled button, for compact
   rows with several actions. */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border: none; border-radius: var(--radius);
  padding: 0; cursor: pointer; background: transparent; color: var(--color-muted);
  transition: background 0.15s ease, color 0.15s ease;
}
.icon-btn + .icon-btn { margin-left: 2px; }
.icon-btn svg { width: 15px; height: 15px; display: block; pointer-events: none; }
.icon-btn:hover { background: var(--color-surface-alt); color: var(--color-text); }
.icon-btn.icon-btn-danger:hover { background: #fde2e2; color: var(--color-danger); }

/* Icon-only buttons that (unlike .icon-btn above) always carry a solid
   color background, e.g. the Projects page's row actions - each action
   type gets its own color so several adjacent icon buttons stay easy to
   tell apart without any text label. */
.icon-btn-solid {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border: none; border-radius: var(--radius);
  padding: 0; cursor: pointer; color: #fff; flex-shrink: 0;
  box-shadow: 0 1px 2px rgba(16, 24, 32, 0.08);
  transition: filter 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;
}
.icon-btn-solid + .icon-btn-solid { margin-left: 4px; }
.icon-btn-solid svg { width: 16px; height: 16px; display: block; pointer-events: none; }
/* Some of these (e.g. Projects page's "Open"/"Map" row actions) are <a>
   tags, not <button>s - without this explicit color, the generic
   `a:hover { color: var(--color-primary-dark) }` rule above (which wins
   simply because .icon-btn-solid:hover never declared `color` at all,
   regardless of its higher selector specificity) took over on hover and
   turned the currentColor-based SVG icon a dark/near-black color instead
   of staying its normal white. */
.icon-btn-solid:hover { filter: brightness(0.9); text-decoration: none; transform: translateY(-1px); box-shadow: 0 4px 10px rgba(16, 24, 32, 0.18); color: #fff; }
.icon-btn-solid:active { transform: translateY(0); }
.icon-btn-solid:disabled { opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; }
.icon-btn-solid.icon-btn-primary { background: var(--color-primary); }
.icon-btn-solid.icon-btn-secondary { background: var(--color-muted); }
.icon-btn-solid.icon-btn-danger { background: var(--color-danger); }
.icon-btn-solid.icon-btn-map { background: var(--color-map-btn); }

/* "Layer categories" admin page (categories.php/categories.js) - a flat,
   already depth-first-ordered list rendered as an indented tree (each
   row's own inline margin-left handles the indentation, see
   categories.js's renderTree()). */
.cat-node {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 10px; border-radius: 6px;
}
.cat-node:hover { background: var(--color-surface-alt); }
.cat-node-name { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-node-actions { display: flex; gap: 4px; flex-shrink: 0; }
.cat-node-actions .icon-btn-solid { width: 26px; height: 26px; }
.cat-node-actions .icon-btn-solid svg { width: 13px; height: 13px; }
.catAddBtn { font-weight: 700; font-size: 15px; line-height: 1; }

.form-group { margin-bottom: 16px; }
.form-group label {
  display: block;
  margin-bottom: 6px;
  font-weight: 600;
  font-size: 13px;
  color: var(--color-muted);
}
.form-control {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-size: 15px;
  background: var(--color-surface);
  color: var(--color-text);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.form-control:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-soft);
}
/* A plain <input type="color"> inheriting .form-control's generous
   text-input padding (10px 12px) squishes the browser's own color swatch
   down to a thin sliver, making it unclear it's even a color picker or
   what's currently selected (seen in the Layer style / Notes style
   modals). Match the already-established look used elsewhere
   (.theme-grid-row input[type="color"]) instead: a proper, clearly
   visible swatch block. */
.form-control[type="color"] {
  width: 64px;
  height: 38px;
  padding: 3px;
  cursor: pointer;
}

.mini-editor {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  overflow: hidden;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.mini-editor:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-soft);
}
.mini-editor-toolbar {
  display: flex; align-items: center; gap: 2px; padding: 5px 6px;
  background: var(--color-surface-alt); border-bottom: 1px solid var(--color-border);
}
.mini-editor-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border: none; border-radius: 5px; padding: 0; cursor: pointer;
  background: transparent; color: var(--color-text); font-size: 13px;
}
.mini-editor-btn:hover { background: var(--color-border); }
.mini-editor-sep { width: 1px; height: 18px; background: var(--color-border); margin: 0 4px; }
.mini-editor-content {
  min-height: 70px; padding: 10px 12px; font-size: 14px; line-height: 1.5;
  color: var(--color-text);
}
.mini-editor-content:focus { outline: none; }
.mini-editor-content a { color: var(--color-primary); }

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 22px;
  transition: background 0.2s ease, border-color 0.2s ease;
}

.alert {
  padding: 10px 14px;
  border-radius: var(--radius);
  font-size: 14px;
  margin-bottom: 16px;
}
.alert-error { background: #fdecec; color: #a02525; border: 1px solid #f3c6c6; }
.alert-success { background: #e9f7ef; color: #1d7a45; border: 1px solid #bfe8d0; }
.alert-info { background: #eaf2fe; color: #1e4e8c; border: 1px solid #c6d9f3; }
html[data-theme="dark"] .alert-error { background: rgba(241, 99, 104, 0.14); color: #ff9a9d; border-color: rgba(241, 99, 104, 0.3); }
html[data-theme="dark"] .alert-success { background: rgba(62, 207, 142, 0.14); color: #7fe6b8; border-color: rgba(62, 207, 142, 0.3); }
html[data-theme="dark"] .alert-info { background: rgba(58, 130, 224, 0.14); color: #8bb8f5; border-color: rgba(58, 130, 224, 0.3); }

.view-as-banner { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }

/* ---------------------------------------------------------------------
   Login page
   --------------------------------------------------------------------- */
.login-page {
  min-height: 100%;
  display: flex;
}
.login-visual {
  flex: 0 0 70%;
  position: relative;
  overflow: hidden;
  background: linear-gradient(160deg, #1f6feb 0%, #123a73 100%);
}
.login-visual-slide {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 1.2s ease;
}
.login-visual-slide.active { opacity: 1; }
.login-visual::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0) 40%);
}
.login-form-panel {
  flex: 0 0 30%;
  min-width: 320px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: var(--color-bg);
}
.login-card {
  width: 100%;
  max-width: 380px;
}
.login-card h1 {
  font-size: 22px;
  text-align: center;
  margin-bottom: 4px;
}
.login-card .subtitle {
  text-align: center;
  color: var(--color-muted);
  font-size: 13px;
  margin-bottom: 24px;
}
.login-card .btn { width: 100%; padding: 11px; font-size: 15px; }
.login-links {
  text-align: center;
  margin-top: 14px;
  font-size: 13px;
}
.login-links a { color: var(--color-primary); }

@media (max-width: 800px) {
  .login-page { flex-direction: column; }
  .login-visual { flex: 0 0 220px; }
  .login-form-panel { flex: 1 1 auto; min-width: 0; }
}

/* ---------------------------------------------------------------------
   Dropzone (bulk drag & drop upload)
   --------------------------------------------------------------------- */
.dropzone {
  border: 2px dashed var(--color-border);
  border-radius: var(--radius);
  padding: 36px 20px;
  text-align: center;
  color: var(--color-muted);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.dropzone:hover { border-color: var(--color-primary); }
.dropzone.dragover {
  border-color: var(--color-primary);
  background: var(--color-primary-soft);
  color: var(--color-primary);
}
.dropzone.disabled { pointer-events: none; opacity: 0.6; }
.dropzone-icon { font-size: 28px; margin-bottom: 8px; }

.image-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 14px;
}
.image-gallery-item {
  position: relative;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--color-surface-alt);
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.image-gallery-item:hover { box-shadow: var(--shadow); transform: translateY(-2px); }
.image-gallery-item img {
  width: 100%;
  height: 120px;
  object-fit: cover;
  display: block;
}
.image-gallery-meta {
  font-size: 12px;
  color: var(--color-muted);
  padding: 6px 8px;
}
.image-gallery-item .btnDeleteImage {
  position: absolute;
  top: 6px;
  right: 6px;
}

/* ---------------------------------------------------------------------
   Dashboard shell
   --------------------------------------------------------------------- */
.app-topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--topbar-height);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  z-index: 100;
  box-shadow: var(--shadow);
}
.app-topbar .brand {
  font-weight: 800;
  font-size: 18px;
  letter-spacing: -0.01em;
  background: linear-gradient(135deg, var(--color-primary) 0%, #8f6bff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-decoration: none;
  cursor: pointer;
}
.app-topbar .brand:hover { text-decoration: none; opacity: 0.85; }
.app-topbar .user-menu { display: flex; align-items: center; gap: 10px; font-size: 14px; }
.app-topbar .nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  background: none;
  border: none;
  border-radius: var(--radius);
  font-size: 20px;
  cursor: pointer;
  color: var(--color-text);
  transition: background 0.15s ease;
}
.app-topbar .nav-toggle:hover { background: var(--color-surface-alt); }

.theme-toggle .icon-light { display: none; }
html[data-theme="dark"] .theme-toggle .icon-dark { display: none; }
html[data-theme="dark"] .theme-toggle .icon-light { display: inline; }

/* ---------------------------------------------------------------------
   Sidebar collapse handles - small tabs anchored to each sidebar's
   outer edge (not inside the sidebar itself, so they stay reachable
   even when the sidebar is fully collapsed off-screen).
   --------------------------------------------------------------------- */
.sidebar-handle {
  position: fixed;
  top: calc(var(--topbar-height) + 14px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 40px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-muted);
  cursor: pointer;
  font-size: 12px;
  z-index: 95;
  transition: left 0.2s ease, right 0.2s ease, background 0.15s ease, color 0.15s ease;
}
.sidebar-handle:hover { background: var(--color-surface-alt); color: var(--color-text); }

.sidebar-handle-left {
  left: 0;
  border-radius: 0 999px 999px 0;
  border-left: none;
}
body.left-open .sidebar-handle-left { left: var(--sidebar-width); }
body.left-open .sidebar-handle-left::before { content: '\2039'; }
.sidebar-handle-left::before { content: '\203A'; }

.sidebar-handle-right {
  right: 0;
  border-radius: 999px 0 0 999px;
  border-right: none;
}
body.right-open .sidebar-handle-right { right: var(--sidebar-width); }
body.right-open .sidebar-handle-right::before { content: '\203A'; }
.sidebar-handle-right::before { content: '\2039'; }

@media (max-width: 800px) {
  .sidebar-handle { top: calc(var(--topbar-height) + 10px); }
}

.app-sidebar {
  position: fixed;
  top: var(--topbar-height);
  bottom: 28px;
  left: 0;
  width: var(--sidebar-width);
  background: var(--color-surface);
  border-right: 1px solid var(--color-border);
  overflow-y: auto;
  transform: translateX(-100%);
  transition: transform 0.2s ease, background 0.2s ease;
  z-index: 90;
}
.app-sidebar.open { transform: translateX(0); box-shadow: var(--shadow-lg); }
/* Same effect as .open, but keyed off <body>'s class instead of the
   sidebar's own - set synchronously before first paint (see the inline
   script right after <body> in includes/layout.php) so the sidebar is
   already in its final position on first paint, with no slide-in flash. */
body.left-open .app-sidebar:not(.app-sidebar-right) { transform: translateX(0); box-shadow: var(--shadow-lg); }
body.right-open .app-sidebar-right { transform: translateX(0); box-shadow: var(--shadow-lg); }
.app-sidebar nav { padding: 10px; }
.app-sidebar nav a {
  display: block;
  padding: 10px 14px;
  margin-bottom: 2px;
  border-radius: var(--radius);
  color: var(--color-muted);
  font-size: 14px;
  font-weight: 500;
  border-left: none;
  transition: background 0.15s ease, color 0.15s ease;
}
.app-sidebar nav a:hover { background: var(--color-surface-alt); color: var(--color-text); text-decoration: none; }
.app-sidebar nav a.active {
  color: var(--color-primary);
  font-weight: 700;
  background: var(--color-primary-soft);
}

.app-sidebar-right {
  left: auto;
  right: 0;
  border-right: none;
  border-left: 1px solid var(--color-border);
  transform: translateX(100%);
}
.app-sidebar-heading {
  padding: 16px 18px 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
}
/* Sub-groups within the admin sidebar's own nav (see includes/layout.php)
   - a small uppercase label (same look as .app-sidebar-heading, just
   tighter/nested one level in) above each related cluster of links, with
   a thin divider before every group after the first so the whole menu
   reads as clearly separated sections rather than one long flat list. */
.app-nav-group + .app-nav-group {
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--color-border);
}
.app-nav-group-heading {
  padding: 2px 14px 6px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
  opacity: 0.7;
}

/* Small crown badge marking something visible/available to admins only
   (see includes/layout.php's admin_crown() helper). Plain single-color
   SVG, colored to match the existing "admin" badge's gold/amber tone. */
.admin-crown {
  display: inline-flex;
  vertical-align: middle;
  cursor: default;
  color: #c9891a;
}
.admin-crown svg { width: 18px; height: 18px; display: block; }
html[data-theme="dark"] .admin-crown { color: #f5b862; }
h2 .admin-crown, h3 .admin-crown { margin-left: 8px; }
h2 .admin-crown svg, h3 .admin-crown svg { width: 22px; height: 22px; }
th .admin-crown { margin-left: 4px; }
th .admin-crown svg { width: 15px; height: 15px; }

/* Admin-only content used to get a yellow background highlight here
   (paired with the admin_crown() badge) - removed per request, the crown
   badge alone now marks admin-only titles/columns. Class kept (still
   applied in several PHP/JS files) but intentionally left with no visual
   effect, so none of those call sites need touching. */
.admin-only-bg { background: transparent !important; }
.admin-only-bg .card, .admin-only-bg.card, .admin-only-bg.stat-card { background: var(--color-surface) !important; }
td.admin-only-bg, th.admin-only-bg { background: transparent !important; }

.app-content {
  position: fixed;
  top: var(--topbar-height);
  bottom: 28px;
  left: 0;
  right: 0;
  padding: 24px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  transition: left 0.2s ease, right 0.2s ease;
}
body.left-open .app-content { left: var(--sidebar-width); }
/* Only reserve room on the right for the admin sidebar when it actually
   exists (has-admin-sidebar, set on <body> only for admins) - otherwise
   "right-open" (the default state) left a permanent blank gap on the
   right edge for non-admin users, who never get an .app-sidebar-right
   rendered at all. */
body.has-admin-sidebar.right-open .app-content { right: var(--sidebar-width); }

.app-footer {
  position: fixed; left: 0; right: 0; bottom: 0; height: 28px; z-index: 50;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 0 16px; font-size: 11px; color: var(--color-muted);
  background: var(--color-surface); border-top: 1px solid var(--color-border);
}
.app-footer-version { color: var(--color-muted); font-weight: 700; }
.app-footer-version:hover { color: var(--color-primary); text-decoration: none; }

@media (max-width: 800px) {
  /* Sidebars are always an overlay on small screens, never push the content. */
  .app-content { left: 0 !important; right: 0 !important; }
}

/* ---------------------------------------------------------------------
   Fullscreen preloader - covers the whole page from the very first paint
   (see the inline <div> right after <body> in includes/layout.php) so
   the sidebar/content layout settling into place is never visible, then
   fades out once the page has fully loaded (assets/js/app.js).
   --------------------------------------------------------------------- */
#pagePreloader {
  position: fixed; inset: 0; z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-bg);
  opacity: 1; visibility: visible;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}
#pagePreloader.hide { opacity: 0; visibility: hidden; pointer-events: none; }
.preloader-spinner {
  width: 46px; height: 46px; border-radius: 50%;
  border: 4px solid var(--color-border);
  border-top-color: var(--color-primary);
  animation: preloaderSpin 0.8s linear infinite;
}
@keyframes preloaderSpin { to { transform: rotate(360deg); } }

/* Small inline spinner for use inside buttons/alert boxes (e.g. the
   "Creating droplet..." feedback on servers.php) - uses currentColor so
   it automatically matches whatever text color it's placed in, on any
   background, light or dark. */
.inline-spinner {
  display: inline-block; width: 13px; height: 13px; margin-right: 6px; vertical-align: -2px;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  opacity: 0.85; animation: preloaderSpin 0.7s linear infinite;
}

/* ---------------------------------------------------------------------
   Tables
   --------------------------------------------------------------------- */
table.data-table { width: 100%; border-collapse: collapse; font-size: 14px; }
table.data-table th, table.data-table td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--color-border);
  text-align: left;
  vertical-align: middle;
}
table.data-table th { color: var(--color-muted); font-weight: 600; font-size: 13px; }
table.data-table tr:hover td { background: var(--color-surface-alt); }
.table-responsive { overflow-x: auto; }
.text-right { text-align: right; }

/* In dark mode, --color-border (#2a3140) has very low contrast against
   --color-surface (#171d29), making the row/header divider lines in
   every data table nearly invisible. Use a light/white line instead,
   specifically for tables, in dark mode only - scoped to table.data-table
   (present on every app table whether or not DataTables has enhanced it)
   rather than changing --color-border itself, which is also used by
   cards/inputs/sidebars elsewhere and should stay subtle there. */
html[data-theme="dark"] table.data-table th,
html[data-theme="dark"] table.data-table td {
  border-bottom-color: rgba(255, 255, 255, 0.22);
}

/* Drag-and-drop reorderable table rows (e.g. Project statuses) - a
   dedicated narrow leftmost column holding just a grip icon, the row
   itself is draggable="true" (see the relevant page's .js). */
th.drag-handle-col { width: 32px; padding-left: 16px !important; padding-right: 0 !important; }
td.drag-handle-cell {
  width: 32px; padding-left: 16px !important; padding-right: 0 !important;
  color: var(--color-muted); cursor: grab;
}
td.drag-handle-cell svg { width: 16px; height: 16px; display: block; }
tr.draggable-row.dragging { opacity: 0.4; }
tr.draggable-row:active { cursor: grabbing; }

/* DataTables auto-detects "numeric"/"date"-looking columns (e.g. Layers/
   Features/Members/Projects counts, Created dates) and right-aligns them
   via its own dt-type-numeric/dt-type-date classes - overridden back to
   left here, GLOBALLY, for every DataTable in the app (not just one
   table id), so only a column that explicitly opts into .text-right (the
   Actions column, in every table) is ever right-aligned. The vendor
   stylesheet's own matching selector (table.dataTable th.dt-type-numeric
   etc.) has equal specificity to a plain `table.data-table th.dt-type-
   numeric` rule, and which one wins would then depend on load order -
   app.css loads BEFORE the vendor CSS on most pages but AFTER it on
   map.php, so a plain equal-specificity override would win on some pages
   and lose on others. The `html[data-theme]` prefix (present on every
   page) adds one extra class-level selector, making ours strictly more
   specific than the vendor's regardless of load order - same technique
   already used below for the dark-mode paging-button fix. */
html[data-theme] table.data-table th.dt-type-numeric, html[data-theme] table.data-table td.dt-type-numeric,
html[data-theme] table.data-table th.dt-type-date, html[data-theme] table.data-table td.dt-type-date {
  text-align: left;
}
html[data-theme] table.data-table th.text-right, html[data-theme] table.data-table td.text-right {
  text-align: right;
}

/* Project page: Versions / Static layers side by side (50/50) instead of
   stacked, so the page fits without unnecessary vertical scrolling.
   Stacks back to full-width columns on narrow screens.
   NOTE: basis/max-width use calc(50% - 8px) (half the 16px gap), not a
   bare 50% - with flex-shrink:0, two bare-50% children plus a 16px gap
   overflow their flex container by 16px (gap isn't shrunk away when
   shrink is 0), which pushed the right-hand card 16px further right than
   intended - i.e. too close to / overlapping the space before the right
   sidebar. calc(50% - 8px) makes both cards + the gap add up to exactly
   100%, so the right card's edge lines up correctly again. */
.project-columns { display: flex; gap: 16px; align-items: stretch; margin-bottom: 16px; }
.project-columns .card { margin-bottom: 0; }
.project-col-50 { flex: 0 0 calc(50% - 8px); max-width: calc(50% - 8px); min-width: 0; }
@media (max-width: 900px) {
  .project-columns { flex-direction: column; }
  .project-col-50 { flex-basis: auto; max-width: 100%; width: 100%; }
}

/* DataTables integration - blend the plugin's default markup with app styling. */
.dt-container { width: 100%; font-size: 14px; color: var(--color-text); }
.dt-container .dt-layout-row:first-child { padding-bottom: 12px; }
.dt-container .dt-search input,
.dt-container .dt-length select {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 6px 10px;
  font-size: 14px;
  color: var(--color-text);
  background: var(--color-surface);
}
.dt-container .dt-search input:focus,
.dt-container .dt-length select:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-soft);
}
.dt-container .dt-info, .dt-container .dt-paging { color: var(--color-muted); font-size: 13px; }
.dt-container .dt-paging .dt-paging-button {
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 4px 10px;
  margin-left: 4px;
  cursor: pointer;
  color: var(--color-text);
}
.dt-container .dt-paging .dt-paging-button.current {
  background: var(--color-primary);
  color: #fff;
}
.dt-container .dt-paging .dt-paging-button.disabled { color: var(--color-muted); opacity: 0.5; cursor: default; }
.dt-container .dt-paging .dt-paging-button:hover:not(.disabled):not(.current) { background: var(--color-surface-alt); }
/* The vendor stylesheet (dataTables.dataTables.min.css) hardcodes
   !important colors on paging buttons (color:inherit, and - critically -
   color:rgba(0,0,0,0.5) on .disabled) that ignore our theme variables,
   and it's loaded AFTER app.css, so with EQUAL specificity it would win
   regardless. In light mode that accidentally still looks fine (inherit
   resolves to our own text color), but in dark mode it's badly wrong:
   disabled Previous/Next buttons render near-black text on a dark
   background (invisible), and .current/hover text can also fail to pick
   up our palette. Fix: re-declare every paging button state with a
   `html[data-theme]` prefix (present on every page, either "light" or
   "dark") to make our selectors STRICTLY more specific than the vendor's
   `div.dt-container ...` ones, so ours always wins on specificity alone
   - not just by source order (which vendor's, loaded later, would win by
   default at equal specificity). */
html[data-theme] .dt-container .dt-paging .dt-paging-button,
html[data-theme] .dt-container .dt-paging .dt-paging-button:hover:not(.disabled):not(.current) {
  color: var(--color-text) !important;
}
html[data-theme] .dt-container .dt-paging .dt-paging-button.disabled,
html[data-theme] .dt-container .dt-paging .dt-paging-button.disabled:hover,
html[data-theme] .dt-container .dt-paging .dt-paging-button.disabled:active {
  color: var(--color-muted) !important;
  opacity: 0.5;
  background: transparent !important;
}
html[data-theme] .dt-container .dt-paging .dt-paging-button.current,
html[data-theme] .dt-container .dt-paging .dt-paging-button.current:hover {
  color: #fff !important;
  background: var(--color-primary) !important;
  border-color: var(--color-primary) !important;
}
html[data-theme] .dt-container .dt-paging .dt-paging-button:hover:not(.disabled):not(.current) {
  background: var(--color-surface-alt) !important;
  border-color: var(--color-border) !important;
}
table.dataTable thead th, table.dataTable thead td {
  border-bottom: 1px solid var(--color-border);
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control::before {
  background-color: var(--color-primary);
  top: 50%;
  transform: translateY(-50%);
}
table.dataTable > tbody > tr.child ul.dtr-details { width: 100%; }
table.dataTable > tbody > tr.child span.dtr-title { color: var(--color-muted); font-weight: 600; }


.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}
.badge-admin { background: #fde6cf; color: #93540c; }
.badge-user { background: #e4e9f0; color: #45505e; }
.badge-active { background: #e9f7ef; color: #1d7a45; }
.badge-inactive { background: #f0f0f0; color: #8a8a8a; }
html[data-theme="dark"] .badge-admin { background: rgba(253, 176, 88, 0.18); color: #f5b862; }
html[data-theme="dark"] .badge-user { background: rgba(180, 190, 205, 0.14); color: #b7c0cd; }
html[data-theme="dark"] .badge-active { background: rgba(62, 207, 142, 0.16); color: #7fe6b8; }
html[data-theme="dark"] .badge-inactive { background: rgba(140, 148, 160, 0.16); color: #9aa3af; }

/* Layer/template "characterization" badge (civil/network/topography) -
   see App.characterizationBadgeHtml() in app.js, used by the map viewer's
   own layer list, the Attribute Table page's layer list, and the admin
   Team layers catalog table. Deliberately tiny/compact (used inline next
   to a layer's name in a narrow sidebar row, not just in a wider table
   cell) - smaller font/padding than the general-purpose .badge above. */
.char-badge {
  display: inline-block; padding: 1px 6px; border-radius: 999px;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .02em;
  vertical-align: middle; line-height: 1.5;
}
.char-badge-civil { background: #fde6cf; color: #93540c; }
.char-badge-network { background: #e0ecff; color: #1c4f9c; }
.char-badge-topography { background: #e3f5e6; color: #226b3a; }
html[data-theme="dark"] .char-badge-civil { background: rgba(253, 176, 88, 0.2); color: #f5b862; }
html[data-theme="dark"] .char-badge-network { background: rgba(90, 150, 255, 0.2); color: #8fb8ff; }
html[data-theme="dark"] .char-badge-topography { background: rgba(70, 200, 120, 0.18); color: #7fe6a0; }

/* Project status badge - background color is admin-defined (project_statuses.color) */
.status-badge { color: #fff; }

/* ---------------------------------------------------------------------
   Dashboard stat cards
   --------------------------------------------------------------------- */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.stat-card {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 78px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 16px 18px;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.2s ease, border-color 0.2s ease;
}
.stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
.stat-icon {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-primary-soft);
  color: var(--color-primary);
}
.stat-icon svg { width: 22px; height: 22px; }
.stat-icon.stat-icon-teams { background: rgba(143, 107, 255, 0.14); color: #8f6bff; }
html[data-theme="dark"] .stat-icon.stat-icon-teams { background: rgba(143, 107, 255, 0.2); color: #b09dff; }
.stat-icon.stat-icon-users { background: rgba(253, 176, 88, 0.16); color: #c9891a; }
html[data-theme="dark"] .stat-icon.stat-icon-users { background: rgba(253, 176, 88, 0.2); color: #f5b862; }
.stat-icon.stat-icon-success { background: rgba(35, 165, 102, 0.14); color: #1d7a45; }
html[data-theme="dark"] .stat-icon.stat-icon-success { background: rgba(62, 207, 142, 0.18); color: #7fe6b8; }
.stat-icon.stat-icon-layers { background: rgba(8, 145, 178, 0.14); color: #0891b2; }
html[data-theme="dark"] .stat-icon.stat-icon-layers { background: rgba(34, 211, 238, 0.18); color: #67e8f9; }
.stat-icon.stat-icon-objects { background: rgba(219, 39, 119, 0.12); color: #db2777; }
html[data-theme="dark"] .stat-icon.stat-icon-objects { background: rgba(244, 114, 182, 0.18); color: #f9a8d4; }
.stat-info { min-width: 0; }
.stat-card .stat-value { font-size: 26px; font-weight: 800; line-height: 1.15; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stat-card .stat-label { color: var(--color-muted); font-size: 13px; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Paired 50/50 dashboard cards (Recent projects/Projects by status, All
   projects by status/Recent activity) should always match height, even
   when one side's content is much shorter - .project-columns already
   stretches (see above) and a flex-column card body here lets a trailing
   link ("View all projects", "Manage statuses") pin to the bottom via
   margin-top:auto instead of leaving an awkward empty gap underneath a
   short widget. */
.project-columns .card { display: flex; flex-direction: column; }
.card-footer-link { margin-top: auto; padding-top: 14px; margin-bottom: 0; }

/* Status breakdown widget - a small horizontal-bar "distribution" list
   (colored dot + name + count, with a proportional track/fill bar
   underneath) rather than a loose wrap of badges, so it reads as a real
   mini chart and fills the card's height sensibly regardless of how many
   statuses there are. */
.status-list { display: flex; flex-direction: column; gap: 12px; }
.status-list-row { }
.status-list-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; font-size: 13px; }
.status-list-name { display: flex; align-items: center; gap: 7px; font-weight: 600; min-width: 0; }
.status-list-name span.label-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.status-list-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
.status-list-count { color: var(--color-muted); font-weight: 700; flex: 0 0 auto; }
.status-list-track { height: 8px; border-radius: 999px; background: var(--color-surface-alt); overflow: hidden; }
.status-list-fill { height: 100%; border-radius: 999px; transition: width 0.3s ease; }

/* Generic flex-wrap-with-gap row, reused for the project detail page's
   Sub-projects quick-link buttons. */
.status-breakdown { display: flex; flex-wrap: wrap; gap: 8px; }

/* Admin "Activity log" page - hand-rolled 24-bar hour-of-day chart (no
   charting library in this app) - see assets/js/pages/activity.js. */
.hour-chart-inner { display: flex; align-items: flex-end; gap: 3px; height: 140px; }
.hour-bar-col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; min-width: 0; }
.hour-bar-value { font-size: 10px; color: var(--color-muted); margin-bottom: 2px; height: 12px; }
.hour-bar { width: 100%; background: var(--color-primary); border-radius: 3px 3px 0 0; min-height: 1px; transition: height 0.3s ease, background 0.15s ease; }
.hour-bar-col:hover .hour-bar { background: var(--color-primary-dark); }
.hour-bar-label { font-size: 10px; color: var(--color-muted); margin-top: 4px; }

/* ---------------------------------------------------------------------
   Toggle switch (used for view/edit permission grid - no lock icons)
   --------------------------------------------------------------------- */
.toggle-group { display: inline-flex; border: 1px solid var(--color-border); border-radius: 999px; overflow: hidden; }
.toggle-group button {
  border: none;
  background: var(--color-surface);
  padding: 5px 12px;
  font-size: 12px;
  cursor: pointer;
  color: var(--color-muted);
}
.toggle-group button.active { background: var(--color-primary); color: #fff; }
.toggle-group button + button { border-left: 1px solid var(--color-border); }

.switch { position: relative; display: inline-block; width: 42px; height: 24px; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .slider {
  position: absolute; cursor: pointer; inset: 0;
  background-color: #c7cdd6; transition: 0.2s; border-radius: 999px;
}
html[data-theme="dark"] .switch .slider { background-color: #394254; }
.switch .slider::before {
  position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px;
  background-color: white; transition: 0.2s; border-radius: 50%;
}
.switch input:checked + .slider { background-color: var(--color-success); }
.switch input:checked + .slider::before { transform: translateX(18px); }

.toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.toolbar .search { max-width: 260px; }

.modal-backdrop {
  position: fixed; inset: 0; background: rgba(10, 14, 20, 0.6);
  display: flex; align-items: center; justify-content: center; z-index: 2000; padding: 16px;
}
.modal {
  background: var(--color-surface); border-radius: var(--radius-lg); padding: 24px; width: 100%; max-width: 460px;
  box-shadow: var(--shadow-lg); max-height: calc(100vh - 32px); overflow-y: auto;
}
.modal h3 { margin-bottom: 16px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }

/* Legend editor's static text fields (label/value pairs - see
   openLegendEditor() in report_legends.js, also reused by report_edit.js's
   own "Manage legends" shortcut). */
.legend-static-field-row { display: flex; gap: 6px; margin-bottom: 4px; }
.legend-static-field-row input { flex: 1; }

/* Compact variant for small option-only modals (e.g. "Layer style" - see
   openStyleModal() in map_viewer.js) that would otherwise look sparse/
   oversized with the default modal's generous width and each field
   stacked (label above input) with a full 16px gap - here every field is
   one tight label-left/control-right row instead, and the modal itself
   is narrower and less padded. */
.modal-compact { max-width: 320px; padding: 18px 20px; }
.modal-compact h3 { margin-bottom: 12px; }
.modal-compact .form-group {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px;
}
.modal-compact .form-group label { margin-bottom: 0; flex-shrink: 0; }
.modal-compact .form-group input[type="number"] { width: 80px; flex: 0 0 auto; }
.modal-compact .form-group input[type="color"] { flex: 0 0 auto; }
.modal-compact .form-group select.form-control { width: auto; flex: 1; min-width: 0; }
.modal-compact .modal-actions { margin-top: 16px; }
/* The "Classify by column" section has a label + select + button all in
   one form-group (more than the simple label/single-control rows above
   are designed for) - keep it as a normal stacked block instead of a
   compact row, so the button never gets squeezed/overflows. */
.modal-compact #categorizeSection .form-group { display: block; }
.modal-compact #categorizeSection .form-group label { display: block; margin-bottom: 4px; }
.modal-compact #categorizeSection select.form-control { width: auto; }

/* Wide variant for data-heavy modals (e.g. the attribute table - see
   openAttributeTableForLayers() in map_viewer.js), which also makes its
   own title bar a drag handle (see makeModalDraggable()) so the modal can
   be moved out of the way of the map underneath instead of always
   sitting fixed dead-center. */
.modal-wide { max-width: 900px; padding: 16px 20px; }
.modal-wide h3 { margin-bottom: 10px; cursor: move; user-select: none; }

/* "Show coordinates" modal (Attribute Table page's per-row Actions button
   - see openCoordinatesModal() in attribute_table.js). Sized in vw/vh
   (viewport-relative), NOT a fixed max-width like the other modal
   variants above, so it scales with the browser window: a left ~40%
   column with the raw coordinate list + a Copy button, and a right
   column with a small read-only Leaflet map of the same geometry. */
.modal-coords { width: 92vw; max-width: 92vw; height: 85vh; padding: 0; display: flex; flex-direction: column; }
.modal-coords-header {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 14px 18px; border-bottom: 1px solid var(--color-border); flex-shrink: 0;
}
.modal-coords-header h3 { margin: 0; font-size: 16px; }
.modal-coords-body { flex: 1; display: flex; min-height: 0; }
.modal-coords-left {
  width: 40%; flex: 0 0 40%; max-width: 40%; min-width: 0;
  display: flex; flex-direction: column; padding: 14px 18px; border-right: 1px solid var(--color-border);
}
.modal-coords-left-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; flex-shrink: 0; }
.modal-coords-text {
  flex: 1; margin: 0; overflow: auto; white-space: pre; font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 12px; line-height: 1.6; background: var(--color-surface-alt); border-radius: var(--radius); padding: 10px;
}
.modal-coords-right { flex: 1; min-width: 0; padding: 14px 18px; }
.modal-coords-map { width: 100%; height: 100%; border-radius: var(--radius); overflow: hidden; }

.muted { color: var(--color-muted); font-size: 13px; }
.hidden { display: none !important; }

/* ---------------------------------------------------------------------
   Theme color settings (admin)
   --------------------------------------------------------------------- */
.theme-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}
.theme-grid-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid var(--color-border);
}
.theme-grid-row label { flex: 1 1 auto; font-size: 14px; }
.theme-grid-row input[type="color"] {
  width: 42px;
  height: 30px;
  padding: 2px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  cursor: pointer;
}
.theme-grid-hex { font-family: monospace; font-size: 12px; width: 70px; }
