:root {
  --bg: #0f1216;
  --panel: #171c22;
  --panel-2: #1e242c;
  --border: #2a323c;
  --text: #e6ebf1;
  --muted: #93a1b1;
  --accent: #4f9cf9;
  --gt: #ffffff;
  --cv: #ff9f43;
  /* Custom Vision's next iteration. Cyan: anything warmer drifts into the
     orange of Iteration 18 or the red of --bad, and it has to stay legible as
     a box over a night-time frame like the others. */
  --cv19: #3dd6f5;
  --sn: #35d07f;
  /* Third engine. Violet, because it has to stay apart from the orange and
     green at a glance and survive being drawn as a box over a night-time
     trail-cam frame. */
  --df: #b18aff;
  --bad: #ff6b6b;
  --warn: #ffd166;
  --ok: #35d07f;
  --radius: 8px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.5 "Segoe UI", system-ui, -apple-system, sans-serif;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

header.top {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 12px 24px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 20;
}
header.top .brand { font-weight: 600; color: var(--text); letter-spacing: .2px; }
header.top .brand span { color: var(--muted); font-weight: 400; }
header.top nav { display: flex; gap: 16px; margin-left: auto; align-items: center; }

main { max-width: 1400px; margin: 0 auto; padding: 24px; }
main.wide { max-width: 1800px; }

h1 { font-size: 22px; margin: 0 0 4px; }
h2 { font-size: 16px; margin: 28px 0 12px; }
h3 { font-size: 14px; margin: 0 0 8px; }
p.sub { color: var(--muted); margin: 0 0 20px; }

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
}
.card h3 { color: var(--muted); text-transform: uppercase; font-size: 11px; letter-spacing: .8px; }

.row { display: flex; gap: 16px; flex-wrap: wrap; }
.row > .card { flex: 1; min-width: 280px; }
.spread { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }

label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 4px; }

input[type=text], input[type=password], input[type=number], textarea, select {
  width: 100%;
  padding: 8px 10px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
}
input[type=file] { color: var(--muted); font: inherit; }
textarea { resize: vertical; min-height: 60px; }
input:focus, select:focus, textarea:focus { outline: 1px solid var(--accent); border-color: var(--accent); }

.btn {
  display: inline-block;
  padding: 8px 14px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.btn:hover { background: #262e38; text-decoration: none; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #06121f; font-weight: 600; }
.btn.primary:hover { background: #67aaff; }
.btn.cv { background: var(--cv); border-color: var(--cv); color: #221200; font-weight: 600; }
.btn.cv19 { background: var(--cv19); border-color: var(--cv19); color: #04232a; font-weight: 600; }
.btn.sn { background: var(--sn); border-color: var(--sn); color: #042314; font-weight: 600; }
.btn.df { background: var(--df); border-color: var(--df); color: #1a0f33; font-weight: 600; }
.btn.danger { color: var(--bad); border-color: #4a2b2b; }
.btn.small { padding: 4px 9px; font-size: 12px; }
.btn:disabled, .btn[aria-disabled=true] { opacity: .45; cursor: not-allowed; }

table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--border); }
th { color: var(--muted); font-weight: 500; font-size: 12px; text-transform: uppercase; letter-spacing: .5px; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
tbody tr:hover { background: rgba(255,255,255,.02); }

.pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--muted);
}
.pill.ok { color: var(--ok); border-color: #1d4634; background: #10241b; }
.pill.bad { color: var(--bad); border-color: #4a2b2b; background: #241313; }
.pill.warn { color: var(--warn); border-color: #4a412b; background: #241f13; }
.pill.cv { color: var(--cv); border-color: #4a3a20; background: #241b0f; }
.pill.cv19 { color: var(--cv19); border-color: #1f4450; background: #0e2127; }
.pill.sn { color: var(--sn); border-color: #1d4634; background: #10241b; }
.pill.df { color: var(--df); border-color: #3a2b56; background: #1b1430; }

.muted { color: var(--muted); }
.mono { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: 12px; }
/* SpeciesNet's taxon strings are one unbroken token of semicolons. */
.wrap { overflow-wrap: anywhere; }
.small { font-size: 12px; }
.right { text-align: right; }

.flash {
  background: #16283d;
  border: 1px solid #24456b;
  color: #cfe3ff;
  padding: 10px 14px;
  border-radius: 6px;
  margin-bottom: 12px;
}

/* --- metric tiles --------------------------------------------------------- */
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; }
.metric {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px 12px;
}
.metric .k { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; }
.metric .v { font-size: 20px; font-variant-numeric: tabular-nums; margin-top: 2px; }
.metric .n { font-size: 11px; color: var(--muted); }
.metric.win { border-color: #1d4634; }
.metric.win .v { color: var(--ok); }

/* --- image grid ----------------------------------------------------------- */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
.tile {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.tile img { width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; background: #000; }
.tile .meta { padding: 8px 10px; display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.tile .name { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile.todo { border-color: #4a412b; }

/* --- box overlays --------------------------------------------------------- */
.stage { position: relative; display: inline-block; max-width: 100%; line-height: 0; }
.stage img { max-width: 100%; height: auto; display: block; border-radius: 6px; }
.stage .layer { position: absolute; inset: 0; }

.bx {
  position: absolute;
  border: 2px solid var(--gt);
  border-radius: 2px;
  pointer-events: none;
}
.bx > .tag {
  position: absolute;
  top: -20px;
  left: -2px;
  font: 11px/16px "Segoe UI", sans-serif;
  padding: 1px 5px;
  border-radius: 3px;
  white-space: nowrap;
  background: var(--gt);
  color: #000;
}
.bx.gt { border-color: var(--gt); }
.bx.gt > .tag { background: var(--gt); color: #000; }
.bx.cv { border-color: var(--cv); }
.bx.cv > .tag { background: var(--cv); color: #221200; }
.bx.cv19 { border-color: var(--cv19); }
.bx.cv19 > .tag { background: var(--cv19); color: #04232a; }
.bx.sn { border-color: var(--sn); }
.bx.sn > .tag { background: var(--sn); color: #042314; }
.bx.df { border-color: var(--df); }
.bx.df > .tag { background: var(--df); color: #1a0f33; }
/* A prediction that matched no annotated animal. Faded, not hidden - it is a
   false positive and you need to see where it landed to judge whether the
   model was wrong or the ground truth was. At .35 the small ones were hard to
   read against a night frame. */
.bx.dim { opacity: 0.70; }
.bx.sel { border-width: 3px; box-shadow: 0 0 0 1px #000; }
.bx.sel > .tag { font-weight: 700; }

.legend { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; font-size: 12px; color: var(--muted); }
.legend i { display: inline-block; width: 11px; height: 11px; border: 2px solid; border-radius: 2px; margin-right: 5px; vertical-align: -1px; }
.legend i.gt { border-color: var(--gt); }
.legend i.cv { border-color: var(--cv); }
.legend i.cv19 { border-color: var(--cv19); }
.legend i.sn { border-color: var(--sn); }
.legend i.df { border-color: var(--df); }

/* --- zoom ----------------------------------------------------------------- */
.stage-scroll {
  overflow: auto;
  max-height: 78vh;
  border-radius: 6px;
  background: #0a0d11;
  border: 1px solid var(--border);
}
/* At fit, the image is capped to the container. Zoomed, it must be allowed to
   exceed it so the container scrolls instead of squashing the image. */
.stage-scroll.zoomed .stage,
.stage-scroll.zoomed .stage img { max-width: none; }

.zoom-bar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.zoom-bar .zoom-label {
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--muted);
  min-width: 74px;
}
.zoom-bar .zoom-label.one-to-one { color: var(--ok); font-weight: 600; }

/* A tag above a box at the very top of the frame would be clipped by the
   scroll container, so those sit just inside the box instead. */
.bx.tag-inside > .tag { top: 1px; left: 1px; }

/* --- annotate ------------------------------------------------------------- */
.annotate-wrap { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 16px; align-items: start; }
@media (max-width: 1000px) { .annotate-wrap { grid-template-columns: 1fr; } }

#canvas-stage { cursor: crosshair; user-select: none; touch-action: none; }
#canvas-stage .bx { pointer-events: auto; cursor: pointer; }
#canvas-stage .bx .handle {
  position: absolute; right: -6px; bottom: -6px;
  width: 12px; height: 12px; border-radius: 2px;
  background: var(--accent); cursor: nwse-resize;
}
#drag-preview { position: absolute; border: 2px dashed var(--accent); background: rgba(79,156,249,.12); pointer-events: none; }

.box-list { list-style: none; padding: 0; margin: 0; max-height: 380px; overflow-y: auto; }
.box-list li {
  display: flex; align-items: center; gap: 8px;
  padding: 6px; border: 1px solid var(--border); border-radius: 6px; margin-bottom: 6px;
  background: var(--panel-2);
}
.box-list li.sel { border-color: var(--accent); }
.box-list li .idx { width: 22px; text-align: center; color: var(--muted); font-size: 12px; }
.box-list li select { flex: 1; padding: 4px 6px; font-size: 12px; }

.kbd {
  display: inline-block; padding: 1px 6px; border-radius: 4px;
  border: 1px solid var(--border); background: var(--panel-2);
  font-family: ui-monospace, Consolas, monospace; font-size: 11px;
}

/* --- progress ------------------------------------------------------------- */
.bar { height: 6px; background: var(--panel-2); border-radius: 999px; overflow: hidden; }
.bar > i { display: block; height: 100%; background: var(--accent); width: 0; transition: width .3s; }

/* --- comparison ----------------------------------------------------------- */
.compare { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 1100px) { .compare { grid-template-columns: 1fr; } }

/* 600px is what the detection table needs (six columns plus the match pill).
   Any narrower and four engines squeeze into one row with their tables spilling
   into the neighbouring card; at this width they sit two by two. */
.pairs { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(600px, 100%), 1fr)); gap: 16px; }

/* A section heading that folds away. The <summary> is styled to sit exactly
   where its <h2> did, so folding changes what you see and not the layout. */
/* --- comparison chart ----------------------------------------------------- */
/* Every metric charted is a share of something, so one 0-100% scale serves all
   of them and a bar's length means the same thing in every row. */
.chart { display: flex; flex-direction: column; gap: 14px; }
.chart-row { display: flex; align-items: center; gap: 14px; }
.chart-label { width: 190px; flex: 0 0 190px; font-size: 13px; }
.chart-label .small { line-height: 1.3; margin-top: 1px; }
.chart-bars { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.chart-bar {
  position: relative;
  display: flex; align-items: center; gap: 8px;
  height: 18px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 3px;
  overflow: hidden;
}
.chart-bar > i {
  display: block; height: 100%;
  /* A zero-width bar is invisible and reads as missing data rather than as a
     real zero, so every bar keeps a sliver. */
  min-width: 2px;
  transition: width .2s ease;
}
.chart-bar > i.cv { background: var(--cv); }
.chart-bar > i.cv19 { background: var(--cv19); }
.chart-bar > i.sn { background: var(--sn); }
.chart-bar > i.df { background: var(--df); }
.chart-bar > b {
  position: absolute; right: 8px;
  font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  /* The label sits over the track, which can be either the bar colour or the
     background, so it carries its own backdrop rather than guessing. */
  background: rgba(15, 18, 22, .72);
  padding: 0 4px; border-radius: 3px;
}

.chart-axis {
  display: flex; justify-content: space-between;
  margin: 10px 0 0 204px;
  font-size: 11px; color: var(--muted);
}

@media (max-width: 720px) {
  .chart-row { flex-direction: column; align-items: stretch; gap: 4px; }
  .chart-label { width: auto; flex: none; }
  .chart-axis { margin-left: 0; }
}

/* The "a run is going, this page will refresh itself" line. The spinner is
   what distinguishes waiting from hung - a static message leaves you guessing
   whether anything is still happening. */
.watching {
  display: flex; align-items: center; gap: 8px;
  margin-top: 10px; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--panel-2);
}
.watching .spin {
  width: 12px; height: 12px; flex: 0 0 12px;
  border: 2px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
.watching .watch-text { color: var(--text); }
@keyframes spin { to { transform: rotate(360deg); } }

/* Someone who has asked not to see motion still needs to know it is alive, so
   the spin becomes a pulse rather than disappearing. */
@media (prefers-reduced-motion: reduce) {
  .watching .spin { animation: pulse 1.4s ease-in-out infinite; }
  @keyframes pulse { 50% { opacity: 0.25; } }
}

details.section > summary {
  font-size: 16px; font-weight: 600; margin: 28px 0 12px;
  cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: 8px;
}
details.section > summary::-webkit-details-marker { display: none; }
details.section > summary::before {
  content: 'b8'; color: var(--muted); font-size: 11px; width: 9px;
}
details.section[open] > summary::before { content: 'be'; }
details.section > summary:hover { color: var(--accent); }
details.section > summary .pill { font-weight: 400; }

details.raw { margin-top: 10px; }
details.raw summary { cursor: pointer; color: var(--muted); font-size: 12px; }
details.raw pre {
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 10px; overflow: auto; max-height: 320px; font-size: 11px;
}

.login-wrap { max-width: 340px; margin: 14vh auto; }
.err { color: var(--bad); font-size: 13px; margin-top: 10px; }

.scroll-x { overflow-x: auto; }
