/* Every number here was read off pixelmapsConcept4k.png with a colour picker and
   a column scan, at the 1920x959 the mock was drawn at. Where a value looks odd
   - a 73px bar, an 8px label - that is the concept, not a rounding slip.
   docs/PIXELMAPS-DESIGN.md carries the sampled table and the short list of places
   the owner has since overruled it. */

/* Minecraft Rus does all of it, numerals included. Measuring the mock settled
   that: its coordinates are 102x14 for "-71 31 535" and its version is 38 wide
   for "15.0", which is Rus at 16px to the pixel.

   Minecraft Five held the 3D badge for a while. Its 7x7 cell only lands whole
   pixels at 10px and 20px, and neither fits a dock button: 10px reads as a
   mistake and 20px is 31px of glyph in a 34px square. The badge is Rus now and
   the second font is gone.

   It is a pixel font, so the only sizes allowed are the ones that land a whole
   number of screen pixels on a font pixel: 8 and 16. Anything between blurs. */
@font-face {
  font-family: "Minecraft Rus";
  src: url("fonts/minecraft-rus.ttf") format("truetype");
  font-weight: 400 700;
  font-display: block;
}

:root {
  --bar: #232323;
  --field: #323232;
  --edge: #474747;
  --line: #303030;
  --sink: #1a1a1a;
  --text: #bfbfbf;
  --bright: #e9e9e9;
  --green: #95ff5c;
  --blue: #0a82fd;
  --blue-btn: #274673;
  --blue-btn-hi: #325891;
  --map-void: #11131a;

  /* Two shadows, because the mock uses two. Panels floating over the map cast a
     third of black; anything sitting on a panel casts solid black. */
  /* Sampled, not chosen. Anything with a border - the rail, the download panel,
     the place card, a style swatch - casts a third of black. Anything without one
     - the search field, the avatar, the card's buttons, the map controls - casts
     solid black. */
  --drop: 2px 2px 0 rgba(0, 0, 0, .34);
  --hard: 2px 2px 0 #000;

  --top-h: 73px;
  --rail-w: 80px;
  --rail-x: 12px;
  --dock-b: 24px;

  --font: "Minecraft Rus", "Segoe UI", system-ui, sans-serif;
}

* { box-sizing: border-box; }

/* Components below set display explicitly, which silently beats the attribute. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: var(--map-void);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1;
  -webkit-font-smoothing: none;
  font-smooth: never;
}

img, .leaflet-tile, .leaflet-image-layer {
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

img.px { display: block; flex: none; image-rendering: pixelated; }

svg.ico {
  display: block;
  flex: none;
  color: var(--text);
  shape-rendering: crispEdges;
  overflow: visible;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

button:focus-visible,
input:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

.num { font-variant-numeric: tabular-nums; }

/* Every piece of 16px text in the mock carries the same 2px hard shadow, and no
   piece of 8px text does. */
.topbar, .place-card { text-shadow: 2px 2px 0 #000; }

/* Pressing a pixel button moves it onto its own shadow. It is most of the
   feedback vocabulary this style has, so everything clickable uses it. */
button:active:not(:disabled) { transform: translate(2px, 2px); box-shadow: none; }

/* Unavailable rather than broken: it keeps its shape and loses its contrast, so
   it reads as a thing that will work later. */
button:disabled { cursor: not-allowed; }
button:disabled, button:disabled .ico { color: #5e5e5e; }

/* ---------------- top bar ---------------- */

.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--top-h);
  display: flex;
  align-items: center;
  padding: 0 40px 0 25px;
  background: var(--bar);
  z-index: 900;
}

.brand { display: flex; align-items: center; margin-right: 34px; }
.brand img { display: block; width: 161px; height: 25px; margin-top: 2px; }

.sep {
  width: 2px;
  height: 9px;
  background: var(--line);
  flex: none;
}

.search {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 289px;
  height: 36px;
  margin: 0 0 1px 25px;
  padding: 0 11px;
  background: var(--field);
  box-shadow: var(--hard);
  flex: none;
}

.search:focus-within { background: #3a3a3a; }
/* The mock draws the magnifier in the same #BFBFBF as everything else, not the
   dimmer grey a placeholder icon usually gets. */
.search .ico { color: var(--text); }
.search:focus-within .ico { color: var(--bright); }

.search input {
  flex: 1;
  min-width: 0;
  height: 16px;
  background: none;
  border: 0;
  color: var(--bright);
  font: inherit;
  outline: none;
}

.search input::placeholder { color: var(--text); opacity: 1; }
.search input::-webkit-search-cancel-button { display: none; }

.categories {
  display: flex;
  align-items: center;
  margin: 0 0 1px 55px;
  overflow: hidden;
}

.categories button {
  color: var(--text);
  white-space: nowrap;
  padding: 10px 0;
}

.categories button:hover { color: var(--bright); }

.categories button[aria-pressed="true"] {
  color: var(--bright);
  text-decoration: underline;
  text-underline-offset: 5px;
}

/* 36px between words, with the 3px dot sitting in the middle of the gap. */
.categories .dot {
  width: 3px;
  height: 3px;
  margin: 0 15.5px;
  background: var(--line);
  flex: none;
}

.grow { flex: 1; min-width: 24px; }

.version { color: var(--green); margin: 0 14px 1px 0; }
.avatar { margin-left: 15px; }

.avatar {
  width: 32px;
  height: 32px;
  flex: none;
  background: var(--field);
  box-shadow: var(--hard);
  display: grid;
  place-items: center;
  overflow: hidden;
}

.avatar:hover { background: #3a3a3a; }
.avatar img { display: block; }
#avatar-img { width: 100%; height: 100%; }

/* ---------------- floating panels ---------------- */

/* The floating panels carry a 2px #474747 border; the buttons do not. Both facts
   are in the mock, and an earlier read of it had this backwards. */
.panel {
  background: var(--bar);
  border: 2px solid var(--edge);
  box-shadow: var(--drop);
  color: var(--text);
}

/* ---------------- left rail ---------------- */

.rail {
  position: fixed;
  top: calc(var(--top-h) + 12px);
  bottom: 135px;
  left: var(--rail-x);
  width: var(--rail-w);
  padding: 31px 0 20px;
  background: var(--bar);
  border: 2px solid var(--edge);
  box-shadow: var(--drop);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 33px;
  overflow: hidden auto;
  scrollbar-width: none;
  z-index: 890;
}

.rail::-webkit-scrollbar { display: none; }

.rail-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 19px;
  width: 100%;
  color: var(--text);
}

/* The bare list icon is the only rail item with no label, so it needs the space
   its missing caption would have taken. */
#rail-places { margin-bottom: 4px; }

.rail-btn em { font-style: normal; font-size: 8px; line-height: 8px; }
.rail-btn:hover, .rail-btn[aria-pressed="true"] { color: var(--bright); }
.rail-btn:hover .ico, .rail-btn[aria-pressed="true"] .ico { color: var(--bright); }

.rail-sep {
  width: 9px;
  height: 2px;
  background: var(--line);
  flex: none;
  margin: -8px 0;
}

/* Recents need an account, so until there is one the rail has nothing below the
   rule and the rule has nothing to separate. */
.rail-sep:has(+ .pins:empty) { display: none; }

.pins {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: 100%;
}

.pin {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  width: 100%;
  color: var(--text);
}

.pin:hover { color: var(--bright); }

/* Recents read as a stack of photos in the mock: the thumbnail sits on a second
   square offset behind it rather than being a plain image. */
.pin-thumb {
  position: relative;
  width: 28px;
  height: 28px;
  margin-bottom: 6px;
}

.pin-thumb::after {
  content: "";
  position: absolute;
  inset: 6px -6px -6px 6px;
  background: var(--sink);
}

.pin-thumb img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  background: var(--sink);
}

.pin em {
  font-style: normal;
  font-size: 8px;
  line-height: 8px;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  /* A long name runs off the rail in the mock rather than being cut hard. */
  mask-image: linear-gradient(90deg, #000 78%, transparent);
}

/* ---------------- map ---------------- */

#map {
  position: fixed;
  inset: var(--top-h) 0 0 0;
  background: var(--map-void);
}

.leaflet-container {
  background: var(--map-void);
  font-family: var(--font);
  outline: none;
}

.leaflet-marker-icon { cursor: pointer; }

/* ---------------- bottom left ---------------- */

.dock-left {
  position: fixed;
  left: var(--rail-x);
  bottom: var(--dock-b);
  display: flex;
  align-items: flex-end;
  z-index: 890;
}

.download {
  width: var(--rail-w);
  height: 100px;
  padding: 25px 0 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

/* The only icon whose art grid is not 1 unit per pixel: it is 7x8 drawn at 3x,
   so a baked two-unit shadow would come out 6px. Keep the 2px the rest of the UI
   uses by putting it in CSS instead. */
.download .ico {
  color: var(--text);
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, .55));
}
.download em { font-style: normal; font-size: 8px; line-height: 8px; }
.download:hover:not(:disabled) { color: var(--bright); }
.download:hover:not(:disabled) .ico { color: var(--bright); }
.download:disabled { border-color: #383838; }

/* 12px clears the panel's shadow, then the resting swatch. The alternatives sit
   36px further right, which is the gap the mock drew them at. */
.styles { display: flex; align-items: flex-end; margin-left: 14px; }

.style-fan {
  display: flex;
  gap: 8px;
  margin-left: 36px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s steps(3);
}

.styles:hover .style-fan,
.styles:focus-within .style-fan,
.styles.open .style-fan {
  opacity: 1;
  pointer-events: auto;
}

.swatch {
  width: 68px;
  height: 68px;
  flex: none;
  padding: 0;
  background: var(--bar);
  border: 2px solid var(--bar);
  /* Bordered, so it takes the soft shadow. The solid one put 2px of #232323 hard
     against 2px of #000 and read as a doubled outline down the right and bottom. */
  box-shadow: var(--drop);
  display: block;
}

/* The file is authored at exactly the 64px shown, so this only matters on a
   hi-dpi screen, where the upscale should stay square rather than blur. */
.swatch img { width: 100%; height: 100%; display: block; image-rendering: pixelated; }
.swatch:hover { border-color: var(--edge); }
.swatch:disabled { cursor: not-allowed; }
.swatch:disabled img { filter: grayscale(1) brightness(.45); }

/* ---------------- bottom right ---------------- */

.dock-right {
  position: fixed;
  /* The compass is the widest child and the mock centres the column on it, so
     this offset is measured from the compass rather than from the buttons. */
  right: 10px;
  bottom: var(--dock-b);
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 890;
}

/* The game's own compass texture at exactly 3x. Any other factor shimmers. */
.compass {
  width: 48px;
  height: 48px;
  display: block;
  margin-bottom: 8px;
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, .34));
}

/* Unlike the rail, the map controls are unbordered #323232 on a solid shadow.
   The mock draws them at 27px, which turned out to be too small to read once the
   3D badge was set at a size that lands on whole pixels. 34px keeps the mock's
   proportion of glyph to padding. */
.pill {
  width: 34px;
  background: var(--field);
  box-shadow: var(--hard);
  color: var(--bright);
  display: grid;
  place-items: center;
}

.pill:hover { background: #3d3d3d; }
.pill[aria-pressed="true"] { background: var(--blue-btn); }
.pill[aria-pressed="true"]:hover { background: var(--blue-btn-hi); }

#toggle-3d {
  height: 34px;
  margin-bottom: 30px;
  font-size: 16px;
}

#streetview { height: 34px; }
#streetview .ico { color: var(--bright); }

/* The pegman is dragged, so it must not start a text selection or a native image
   drag on the way out of the button. */
.pegman { cursor: grab; touch-action: none; user-select: none; }
.pegman:active { cursor: grabbing; }
.pegman .ico { pointer-events: none; }

.pegman-ghost {
  position: fixed;
  z-index: 950;
  pointer-events: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  background: var(--blue-btn);
  box-shadow: var(--hard);
  transform: translate(-17px, -17px);
}

.pegman-ghost .ico { color: var(--bright); }

/* Only while the pegman is in the air, the way Google Maps does it. */
.sv-layer { opacity: 0; pointer-events: none; transition: opacity .12s steps(3); }
body.sv-mode .sv-layer { opacity: 1; pointer-events: auto; }
body.sv-mode { cursor: grabbing; }

.zoom {
  height: 68px;
  margin-bottom: 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* Drawn rather than typeset. The mock's plus is an 11x11 cross of 2px bars and
   its minus is the same bar alone, which no cut of either font produces. */
.zoom button {
  position: relative;
  width: 34px;
  height: 33px;
  font-size: 0;
}

.zoom button::before,
.zoom button::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  background: var(--bright);
  transform: translate(-50%, -50%);
}

.zoom button::before { width: 14px; height: 2px; }
#zoom-in::after { width: 2px; height: 14px; }
.zoom button:hover { background: #3d3d3d; }
.zoom button:active { transform: none; }
.zoom-sep { width: 14px; height: 2px; background: #4a4a4a; flex: none; }

/* ---------------- place card ---------------- */

.place-card {
  position: fixed;
  left: 50%;
  bottom: var(--dock-b);
  /* Wide enough for the mock's card, and no wider than a name plus a description
     actually need. A place with neither a still nor a nearby streetview drops the
     thumbnail entirely and hands its 114px back to the text. */
  width: max-content;
  min-width: 382px;
  max-width: min(470px, calc(100vw - 40px));
  min-height: 88px;
  display: flex;
  align-items: stretch;
  padding: 6px;
  background: var(--bar);
  border: 2px solid var(--edge);
  box-shadow: var(--drop);
  transform: translateX(-50%);
  z-index: 895;
  animation: card-in .16s steps(4);
}

@keyframes card-in {
  from { transform: translate(-50%, 14px); opacity: 0; }
  to { transform: translate(-50%, 0); opacity: 1; }
}

.thumb {
  position: relative;
  width: 98px;
  height: 70px;
  padding: 0;
  flex: none;
  background: var(--sink);
  box-shadow: 2px 2px 0 rgba(0, 0, 0, .26);
  overflow: hidden;
}

.thumb:hover { box-shadow: 2px 2px 0 rgba(0, 0, 0, .26), inset 0 0 0 2px var(--edge); }

.thumb-empty {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 8px;
  line-height: 8px;
  color: #6f6f6f;
}

/* Scoped, or it also swallows the label's own icon. */
#card-thumb { width: 100%; height: 100%; object-fit: cover; display: block; }

/* No plate behind it in the mock: the label sits straight on the photo and
   relies on its own shadow, which is why it keeps one at 8px. */
.thumb-label {
  position: absolute;
  left: 0;
  bottom: 3px;
  display: flex;
  align-items: center;
  gap: 18px;
  font-size: 8px;
  line-height: 8px;
  color: var(--text);
  text-shadow: 1px 1px 0 #000;
}

.thumb-label img { filter: drop-shadow(2px 2px 0 #000); }

.card-body {
  flex: 1;
  min-width: 0;
  /* 12 above the title and 13 below the coordinates, which is what the mock has.
     Without the bottom padding it was 12 and 9, and the coordinates looked like
     they had slipped out of the card. */
  padding: 6px 0 4px 12px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* These carry overflow:hidden for the ellipsis, which switches off the automatic
   minimum size a flex item normally gets. Without flex:none a stray default
   margin anywhere in the column silently squashes the title to nothing. */
.card-body > * { flex: none; }

.card-body h2 {
  margin: 0;
  font-size: 16px;
  line-height: 16px;
  font-weight: 400;
  color: var(--text);
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.card-sub {
  margin: 4px 0 0;
  font-size: 8px;
  line-height: 8px;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-shadow: none;
}

/* Two lines is the most this card can give a description without pushing the
   coordinates off the bottom of it. */
.card-desc {
  margin: 5px 0 0;
  max-width: 100%;
  font-size: 8px;
  line-height: 11px;
  color: #9a9a9a;
  text-shadow: none;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card-rule {
  width: 164px;
  max-width: 100%;
  height: 2px;
  background: var(--line);
  margin: 8px 0 7px;
}

.coords { margin: 0; color: var(--blue); line-height: 16px; }

.card-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 7px 1px 12px;
}

.btn {
  width: 33px;
  height: 33px;
  flex: none;
  background: var(--field);
  box-shadow: var(--hard);
  display: grid;
  place-items: center;
}

.btn .ico { color: var(--bright); }
.btn:hover { background: #3d3d3d; }
.btn.primary { background: var(--blue-btn); }
.btn.primary:hover { background: var(--blue-btn-hi); }

/* Not in the mock, which has no way to dismiss the card. Kept small and quiet so
   it reads as an affordance rather than a third action. */
.card-close {
  position: absolute;
  top: 4px;
  right: 4px;
  padding: 3px;
  opacity: .45;
  line-height: 0;
}

.card-close:hover { opacity: 1; }

/* ---------------- marks ---------------- */

/* Leaflet's own default div-icon chrome, which this replaces entirely. */
.leaflet-div-icon { background: none; border: 0; }

.mark img { display: block; }

/* Dropped in from above and squashed on landing, in whole steps so it reads as
   pixel art moving rather than as a CSS animation playing. */
.mark-selected img {
  transform-origin: 50% 100%;
  animation: mark-drop .34s steps(7, end);
}

@keyframes mark-drop {
  0%   { transform: translateY(-28px) scale(.5); opacity: 0; }
  55%  { transform: translateY(0) scale(1); opacity: 1; }
  75%  { transform: translateY(0) scale(1.2, .8); }
  100% { transform: translateY(0) scale(1); }
}

.mark-place img {
  transform-origin: 50% 50%;
  transition: transform .1s steps(2);
}

.mark-place:hover img { transform: scale(1.2); }

/* ---------------- toast ---------------- */

/* The mock has no permanent readout, so anything the map wants to say says it
   here and then gets out of the way. */
.toast {
  position: fixed;
  top: calc(var(--top-h) + 12px);
  left: 50%;
  padding: 9px 12px;
  background: var(--bar);
  border: 2px solid var(--edge);
  box-shadow: var(--drop);
  color: var(--text);
  font-size: 8px;
  line-height: 8px;
  transform: translateX(-50%);
  z-index: 940;
  animation: toast-in .16s steps(4);
}

@keyframes toast-in {
  from { transform: translate(-50%, -10px); opacity: 0; }
  to { transform: translate(-50%, 0); opacity: 1; }
}

@media (max-width: 1180px) {
  .categories { display: none; }
}

@media (max-width: 760px) {
  .rail, .dock-left { display: none; }
  .place-card { max-width: calc(100vw - 24px); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
