/* Visual language ported from remistats.net: one hue drives every surface,
   border and text colour, so re-theming the whole site means changing
   --theme-h alone. The formulas below are the reference site's own. */

@font-face {
  font-family: "RemiliaMincho";
  src: url("/fonts/RemiliaMincho-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "RemiliaWestern";
  src: url("/fonts/RemiliaWestern-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  --theme-h: 227;

  --bg: #e7eaee;
  --surface-1: hsl(var(--theme-h), 80%, 95%);
  --surface-2h: hsl(var(--theme-h), 70%, 89%);
  --surface-3h: hsl(var(--theme-h), 60%, 85%);
  --border-light: hsl(var(--theme-h), 50%, 75%);
  --border-light2: hsl(var(--theme-h), 50%, 90%);
  --border-dark: hsl(var(--theme-h), 40%, 40%);
  --accent: hsl(var(--theme-h), 60%, 45%);
  --highlight: hsl(var(--theme-h), 80%, 100%);

  --text: hsl(var(--theme-h), 35%, 18%);
  --text-muted: #6b7280;
  --on-accent-text: #fefefe;

  --input-bg: #ffffff;
  --hover: #eef1f5;

  --danger: #b4453a;
  --danger-soft: #f6dcd8;
  --pin: hsl(var(--theme-h), 60%, 45%);

  /* Ported from remistats.net's own --selection-bg formula. */
  --selection-bg: hsla(var(--theme-h), 65%, 31%, 0.4);
  --selection-color: inherit;

  --radius: 12px;
  --radius-btn: 7px;
  --border-width: 1.5px;
  --shadow: 0 8px 30px rgba(17, 24, 39, 0.08);
  --module-gap: 1rem;
  --page-margins: 1rem;

  --mono: "JetBrains Mono", ui-monospace, monospace;
  --sans: "RemiliaMincho", "Iowan Old Style", Georgia, serif;
  --display: "RemiliaWestern", "RemiliaMincho", Georgia, serif;
}

* { box-sizing: border-box; }

/* Panels below set an explicit `display`, which would otherwise outrank the
   UA stylesheet's `[hidden] { display: none }` and leave them always visible.
   app.js toggles the attribute, so this has to win. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
}

::selection {
  background: var(--selection-bg);
  color: var(--selection-color);
}

/* Firefox's standard property; the ::-webkit-scrollbar rules below cover
   Chromium/Safari, which don't honour this one. `thin` rather than `auto` to
   match the site's generally tight, dense chrome. */
html {
  scrollbar-color: var(--border-light) var(--surface-1);
  scrollbar-width: thin;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--surface-1); }
::-webkit-scrollbar-thumb {
  background: var(--border-light);
  border-radius: 6px;
  border: 2px solid var(--surface-1);
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--border-dark); }

/* Skeleton state for any image while its source is still loading — the
   element already carries its final background colour (var(--surface-*)),
   this just pulses it so an empty box doesn't read as broken or forgotten. */
@keyframes skeleton-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}
img.is-loading {
  animation: skeleton-pulse 1.1s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  img.is-loading { animation: none; opacity: 0.75; }
}

/* The reference page's faint corner washes over a flat grey field. */
#app {
  display: flex;
  flex-direction: column;
  gap: var(--module-gap);
  height: 100%;
  padding: var(--page-margins);
  background:
    radial-gradient(1200px 800px at 10% -10%, rgba(0, 0, 0, 0.02), rgba(0, 0, 0, 0)),
    radial-gradient(1000px 700px at 110% 10%, rgba(0, 0, 0, 0.016), rgba(0, 0, 0, 0)),
    var(--bg);
  /* Own stacking context so the texture below can sit under the content
     without escaping behind the page background. */
  position: relative;
  isolation: isolate;
}

/* remistats.net's plus texture — a 120px tile drawn at 40px, kept at the
   reference's 5% so it reads as grain rather than pattern. On a pseudo
   element because a background layer can't carry its own opacity. */
#app::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.05;
  background-size: 40px;
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADDSURBVHgB7d2xCoMwFEDR2P//Z0vXOrSgqLmeszmGS5C8IVnGb+vX9zLaUut9DdIEjhM4TuA4geMEjhM47nPGW8ex7n5ufNR67eA4geMEjvvn/2EWPTE7OE7gOIHjBI4TOE7gOIEBAAAAAAAAAAAAAAAAAAAAAACA07kvest90cxD4DiB4wSOEzhO4DiB47xduJ+3C7mOwHECx5lFb5lFMw+B4wSOEzhO4DiB4wQGAAAAAAAAAAAAAAAAAAAAAAAATvcGAL8MS1OvnhwAAAAASUVORK5CYII=");
}

/* ---- top bar ---------------------------------------------------------- */

#bar {
  display: flex;
  gap: 14px;
  align-items: center;
  flex-wrap: wrap;
  flex: 0 0 auto;
}
.bar-spacer { flex: 1; }

.wordmark {
  font-family: var(--display);
  font-size: 26px;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--text);
  padding: 6px 16px;
  border: var(--border-width) solid var(--border-light);
  border-radius: var(--radius-btn);
  background: linear-gradient(180deg, var(--highlight), var(--surface-2h));
  box-shadow: 0 2px 0 0 var(--border-light), 0 5px 2.5px 0 rgba(0, 0, 0, 0.12);
}

#count {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-muted);
}

/* ---- modules ---------------------------------------------------------- */

/* The reference's 4px dot grid over the panel surface. Its opacity is baked
   into fill-opacity (0.5 there, faded by a 0.25 pseudo-element) so this can
   stay a plain background layer rather than another stacking-context overlay. */
.module {
  border: var(--border-width) solid var(--border-light);
  border-radius: var(--radius);
  background-color: var(--surface-1);
  background-image: url("data:image/svg+xml,%3Csvg width='4' height='4' viewBox='0 0 4 4' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.5 0h.5v.5h-.5zm0 3.5v.5h.5v-.5zm-3 0h.5v.5h-.5zm0-3v.5h.5v-.5z' fill='%23cccccc' fill-opacity='0.5' fill-rule='evenodd'/%3E%3C/svg%3E");
  box-shadow: var(--shadow);
  overflow: hidden;
}

/* Map and screenshot sit side by side, equal halves. `flex: 1 1 0` with
   min-width 0 is what makes them actually equal — `1 1 auto` would let the
   map's content bias the split. */
/* Grid rather than flex: two `1fr` tracks are exactly equal, where flex
   `1 1 0` left them a couple of pixels apart through subpixel rounding. */
#workspace {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--module-gap);
  flex: 1 1 auto;
  min-height: 0;
}

#mapModule,
#side {
  min-width: 0;
  min-height: 0;
}

#mapModule {
  display: flex;
  flex-direction: column;
}

#side { display: flex; }
#side > .module {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
}
#side > .module > .module-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 14px;
}

.empty-hint {
  margin: 0;
  color: var(--text-muted);
  line-height: 1.7;
}

/* ---- show on map + nearby --------------------------------------------- */

#showOnMapBtn {
  width: 100%;
  margin-top: 14px;
}

/* Deliberately the plain button styling, matching Show on map above it —
   these are a pair of map-movement actions and reading as one costs nothing. */
#randomBtn {
  width: 100%;
  margin-top: 12px;
}

#nearby {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--border-light2);
}

.nearby-title {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

/* One row that divides the panel evenly however many there are, rather than a
   fixed-width strip that scrolls. */
#nearbyList {
  display: flex;
  gap: 8px;
}

/* `flex: 1 1 0` with min-width 0 is what makes the columns equal — `1 1 auto`
   would let each thumbnail's own width bias the split. */
.nearby-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1 1 0;
  min-width: 0;
  padding: 4px;
  background: var(--surface-2h);
  border: 1px solid var(--border-light2);
  border-radius: 7px;
  box-shadow: none;
  cursor: pointer;
}
.nearby-item:hover {
  background: var(--surface-3h);
  border-color: var(--border-light);
  filter: none;
}
.nearby-item:active { transform: none; box-shadow: none; }

/* Height follows the column width, so they scale with the panel instead of
   staying 78px whatever the space. */
.nearby-thumb {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 4px;
  background: var(--surface-1);
  -webkit-user-drag: none;
  user-select: none;
}

.nearby-dist {
  font-size: 10px;
  color: var(--text-muted);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- screenshot navigation -------------------------------------------- */

#shotNav {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: center;
  margin-top: 10px;
}

.nav-btn {
  width: 38px;
  padding: 4px 0;
  font-size: 18px;
  line-height: 1.1;
}

#shotPos {
  min-width: 6ch;
  text-align: center;
  color: var(--text-muted);
}

.module-header {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  padding: 7px 10px;
  background: var(--surface-2h);
  border-bottom: var(--border-width) solid var(--border-light);
}

/* Flex rather than an inline ::before nudged with vertical-align: the diamond
   and the text have very different font sizes, so baseline alignment left the
   title sitting low in the header strip. As flex items both centre on the
   same axis, and line-height: 1 stops RemiliaMincho's ascent padding the row. */
.module-title {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  font-size: 14px;
  line-height: 1;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--text);
}
.module-title::before {
  content: "◆";
  font-size: 10px;
  line-height: 1;
  color: var(--accent);
}

#mapModule > .module-body {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  padding: 0;
}

/* ---- map stage -------------------------------------------------------- */

#stage {
  position: absolute;
  inset: 0;
  cursor: grab;
  touch-action: none;
  overflow: hidden;
  background: var(--surface-2h);
}
#stage.dragging { cursor: grabbing; }
#stage.picking { cursor: crosshair; }

#canvas {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  /* No `will-change: transform` here. It pins this into a permanently
     promoted compositing layer, and with the map's extent that layer is
     enormous — at high zoom the browser starts discarding and re-rasterising
     it, which showed up as the whole map blinking out on drag and even
     unrelated images (the screenshot panel) flickering. Letting the browser
     promote on its own during the gesture is cheaper and steadier. */
}

#backdrop, #tiles {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
}
#backdrop { z-index: 0; }
#tiles { z-index: 1; }

.tile {
  position: absolute;
  display: block;
  image-rendering: pixelated;
  user-select: none;
}

#pins { position: absolute; top: 0; left: 0; z-index: 2; }

.pin {
  position: absolute;
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  padding: 0;
  border: 2.5px solid #fff;
  border-radius: 50%;
  background: var(--pin);
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.55);
  cursor: pointer;
  transform: scale(var(--inv, 1));
  transform-origin: center;
}
/* No invisible halo to enlarge the hit area — it worked for a lone pin but
   covered its neighbours' circles wherever pins cluster, so at low zoom a pin
   you were pointing straight at belonged to the pin next to it. Near-misses
   are resolved by distance in app.js (`nearestPin`) instead, which can't take
   a click away from a pin the cursor is actually over. */

.pin:hover, .pin:focus-visible {
  background: var(--highlight);
  outline: none;
  box-shadow: 0 0 0 3px hsla(var(--theme-h), 60%, 45%, 0.45);
}
.pin[aria-expanded="true"] { background: var(--border-dark); }

#ghost {
  position: absolute;
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  border: 2px dashed var(--accent);
  border-radius: 50%;
  transform: scale(var(--inv, 1));
  pointer-events: none;
}

/* ---- pin hover preview ------------------------------------------------ */

/* `pointer-events: none` is load-bearing, not tidiness: the card sits over
   the cursor's approach path, so without it the pointer would leave the pin
   the moment the card appeared, hide it, re-enter the pin, and flicker. */
#pinPreview {
  position: absolute;
  z-index: 4;
  width: 220px;
  pointer-events: none;
}
#pinPreview .module-header { padding: 5px 9px; }
#pinPreview .module-title {
  font-size: 12px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block; /* ellipsis needs a block box, not the flex row */
}
#pinPreview .module-title::before { content: none; }
#pinPreview .module-body { padding: 6px; }

#pinPreviewImg {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 4px;
  background: var(--surface-2h);
  -webkit-user-drag: none;
}
#pinPreviewCoords {
  margin: 5px 0 1px;
  font-size: 10px;
  text-align: center;
}

#mapError {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: 34ch;
  margin: 0;
  text-align: center;
  color: var(--text-muted);
  line-height: 1.7;
}
#mapError code {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text);
}

/* ---- readout + layer switcher (both live in the map header) ----------- */

#readout {
  display: flex;
  gap: 6px;
  align-items: baseline;
  margin-left: auto;
  padding: 5px 10px;
  border: var(--border-width) solid var(--border-light);
  border-radius: var(--radius-btn);
  background: var(--input-bg);
  font-family: var(--mono);
  font-size: 12px;
}
#readout .k {
  color: var(--text-muted);
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.08em;
}
#readout .v {
  min-width: 5ch;
  margin-right: 8px;
  color: var(--accent);
}
#readout .v:last-child { margin-right: 0; }

/* Just a row — the bordered tray it used to sit in read as a box inside the
   header's box. The buttons carry their own edges instead. */
#layerSwitch {
  display: flex;
  gap: 6px;
}

/* ---- buttons ---------------------------------------------------------- */

/* The signature tactile look: a hard offset edge plus a soft drop, so the
   button reads as sitting slightly above the surface. */
button {
  font-family: var(--sans);
  font-size: 14px;
  color: var(--text);
  background: linear-gradient(180deg, var(--highlight), var(--surface-2h));
  border: 1px solid var(--border-light);
  border-radius: var(--radius-btn);
  padding: 6px 14px;
  cursor: pointer;
  box-shadow: 0 2px 0 0 var(--border-light), 0 5px 2.5px 0 rgba(0, 0, 0, 0.15);
}
button:hover { filter: brightness(1.03); }
button:active {
  transform: translateY(2px);
  box-shadow: 0 0 0 0 var(--border-light), 0 2px 1.5px 0 rgba(0, 0, 0, 0.12);
}
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button:disabled { opacity: 0.55; cursor: default; }

button.primary {
  color: var(--on-accent-text);
  background: var(--accent);
  border-color: var(--border-dark);
  box-shadow: 0 2px 0 0 var(--border-dark), 0 5px 2.5px 0 rgba(0, 0, 0, 0.15);
}
button.primary:active {
  box-shadow: 0 0 0 0 var(--border-dark), 0 2px 1.5px 0 rgba(0, 0, 0, 0.12);
}

button.ghost {
  color: var(--text);
  background: var(--surface-1);
  border-color: var(--border-light);
}
button.ghost[aria-pressed="true"] {
  color: var(--on-accent-text);
  background: var(--accent);
  border-color: var(--border-dark);
  box-shadow: 0 2px 0 0 var(--border-dark), 0 5px 2.5px 0 rgba(0, 0, 0, 0.15);
}

button.danger {
  width: 100%;
  margin-top: 14px;
  color: var(--danger);
  background: var(--danger-soft);
  border-color: var(--danger);
  box-shadow: 0 2px 0 0 var(--danger), 0 5px 2.5px 0 rgba(0, 0, 0, 0.12);
}
button.danger:hover { color: #fff; background: var(--danger); filter: none; }
button.danger:active {
  box-shadow: 0 0 0 0 var(--danger), 0 2px 1.5px 0 rgba(0, 0, 0, 0.12);
}

/* Small square-ish toggles, flush inside their own bordered group. */
/* Standing on their own now, so they take the normal tactile treatment at a
   smaller size rather than looking like bare text. */
.layerBtn {
  padding: 3px 11px;
  font-size: 12px;
  border-radius: 5px;
  box-shadow: 0 2px 0 0 var(--border-light), 0 4px 2px 0 rgba(0, 0, 0, 0.12);
}
.layerBtn[aria-pressed="true"] {
  color: var(--on-accent-text);
  background: var(--accent);
  border-color: var(--border-dark);
  box-shadow: 0 2px 0 0 var(--border-dark), 0 4px 2px 0 rgba(0, 0, 0, 0.12);
}
.layerBtn:active {
  transform: translateY(2px);
  box-shadow: 0 0 0 0 var(--border-light), 0 1px 1px 0 rgba(0, 0, 0, 0.1);
}
.layerBtn[aria-pressed="true"]:active {
  box-shadow: 0 0 0 0 var(--border-dark), 0 1px 1px 0 rgba(0, 0, 0, 0.1);
}

button.close {
  margin-left: auto;
  width: 24px;
  height: 24px;
  padding: 0;
  font-size: 17px;
  line-height: 1;
  color: var(--text-muted);
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}
button.close:hover {
  color: var(--text);
  background: var(--hover);
  border-color: var(--border-light);
  filter: none;
}
button.close:active { transform: none; box-shadow: none; }

/* ---- screenshot panel ------------------------------------------------- */

/* Crossfade when stepping between screenshots: app.js adds .is-loading (which
   drops opacity to 0) before setting a new src, then removes it once the new
   image has actually loaded — so this transition is what makes the swap fade
   rather than pop, without needing a second stacked <img>. No pulse here
   (unlike the thumbnails' img.is-loading): animating opacity for a pulse
   would fight the fade this transition is doing on the same property. */
#detailImg {
  display: block;
  width: 100%;
  border: var(--border-width) solid var(--border-light);
  border-radius: 6px;
  background: var(--surface-2h);
  -webkit-user-drag: none;
  user-select: none;
  opacity: 1;
  transition: opacity 0.18s ease;
}
/* animation: none overrides the global img.is-loading pulse (a running
   animation would otherwise win the cascade on opacity and fight the fade
   below, even though #detailImg is more specific). */
#detailImg.is-loading { opacity: 0; animation: none; }
@media (prefers-reduced-motion: reduce) {
  #detailImg { transition: none; }
}
#detailCaption {
  margin: 12px 0 6px;
  line-height: 1.6;
}
/* Muted so the placeholder doesn't read as a title someone actually chose. */
#detailCaption.is-untitled { color: var(--text-muted); }
.mono {
  margin: 0;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-muted);
}
#detailTaken { margin-top: 4px; }

/* ---- form ------------------------------------------------------------- */

#form label {
  display: block;
  margin: 12px 0 5px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}
#form label:first-of-type { margin-top: 0; }

#form input {
  width: 100%;
  padding: 8px 10px;
  color: var(--text);
  background: var(--input-bg);
  border: var(--border-width) solid var(--border-light);
  border-radius: 6px;
  font-family: var(--mono);
  font-size: 13px;
}
#form input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px hsla(var(--theme-h), 60%, 45%, 0.18);
}
#form input[type="file"] {
  font-family: var(--sans);
  padding: 7px;
}
#form .row {
  display: flex;
  gap: 10px;
}
#form .row span { flex: 1; }
#form .row label { margin-top: 0; }

#coordHint {
  margin: 6px 0 0;
  color: var(--accent);
}

#form #pickBtn, #form #submitBtn {
  width: 100%;
  margin-top: 12px;
}

#formMsg {
  margin: 12px 0 0;
  line-height: 1.6;
}
#formMsg.err { color: var(--danger); }
#formMsg.ok { color: var(--accent); }

/* ---- footer ----------------------------------------------------------- */

#foot {
  flex: 0 0 auto;
  text-align: center;
  font-size: 12px;
  color: var(--text-muted);
}
#foot .heart { color: var(--accent); }

/* ---- narrow screens --------------------------------------------------- */

/* Side by side needs real width for both halves; below that they stack, map
   first, and the page scrolls rather than squeezing the map to nothing. */
@media (max-width: 900px) {
  #app { height: auto; min-height: 100%; }
  #workspace { grid-template-columns: 1fr; }
  #mapModule { height: 60vh; }
  #side { min-height: 40vh; }
}

@media (max-width: 620px) {
  :root { --page-margins: 0.5rem; --module-gap: 0.5rem; }
  .wordmark { font-size: 20px; padding: 5px 12px; }
  #bar { gap: 10px; }
  #addBtn { font-size: 13px; padding: 6px 11px; }
  /* Keep the map header on one line's worth of essentials. */
  #readout { display: none; }

  /* Six across a panel this narrow leaves them too small to read, so wrap to
     two rows of three. The 16px is the two 8px gaps between the three. */
  #nearbyList { flex-wrap: wrap; }
  .nearby-item { flex: 0 0 calc((100% - 16px) / 3); }
}

@media (prefers-reduced-motion: no-preference) {
  .pin { transition: background 0.12s ease; }
  button { transition: filter 0.12s ease, box-shadow 0.12s ease, transform 0.06s ease; }
}
