@charset "UTF-8";
/* ═══════════════════════════════════════════════════════════════════════
   Five ways to open the front door.

   The first version of this page opened with the vehicle's trajectory drawn
   as hairlines. Igor, correctly: *"some lines and some purple dots, like we
   are in 1995"*. There are 755 million classified points on this server,
   3,256 street panoramas and 1,589 photographed objects; a wireframe was the
   one thing on the page that showed none of it.

   So the hero is now a slot, and what goes in it is real data:

     drive      a hyperlapse down the street, from the survey's own panoramas
     wall       1,589 objects the machine found, each a crop it aimed itself
     dissolve   the photograph melting into the classified cloud of that view
     classes    the mission from above, every point coloured by its class
     live       the actual viewer, running in the page
     route      the trajectory, kept so the difference is visible

   One is chosen with ?intro=, and only that one's bytes are fetched: the
   video has no src until it is picked, and neither do the images.
   ═══════════════════════════════════════════════════════════════════════ */

.hero { position: relative; overflow: hidden; }

/* every backdrop fills the hero and only the chosen one is visible */
.back {
  position: absolute; inset: 0; z-index: 0;
  opacity: 0; visibility: hidden;
  transition: opacity .6s ease;
}
.hero[data-intro="drive"]    #back-drive,
.hero[data-intro="wall"]     #back-wall,
.hero[data-intro="dissolve"] #back-dissolve,
.hero[data-intro="classes"]  #back-classes,
.hero[data-intro="live"]     #back-live,
.hero[data-intro="route"]    #back-route { opacity: 1; visibility: visible; }

.back video, .back img, .back iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; border: 0; display: block;
}

/* A photograph is not a background: text over one needs its own ground. The
   scrim is a gradient rather than a flat wash so the picture survives on the
   side the words are not. */
.hero[data-intro]:not([data-intro="route"])::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(100deg, rgba(10,11,14,.96) 0%, rgba(10,11,14,.9) 26%,
                            rgba(10,11,14,.55) 52%, rgba(10,11,14,.3) 100%),
    linear-gradient(to top, rgba(10,11,14,.85) 0%, rgba(10,11,14,0) 38%);
}
.say { z-index: 2; }

/* ── 1. the drive ────────────────────────────────────────────────────── */
#back-drive video { object-position: 50% 58%; }

/* ── 2. the wall ─────────────────────────────────────────────────────── */
/* it drifts, slowly, because 1,589 tiles standing still read as wallpaper
   and moving they read as a count */
#back-wall .tiles {
  position: absolute; top: -6%; left: -6%; width: 112%; height: 112%;
  background-size: cover; background-position: 50% 50%;
  animation: drift 90s linear infinite alternate;
}
/* The mosaic is named only once the wall is the chosen opening. A background
   image on a hidden element is fetched like any other: for as long as this
   url sat on the unconditional rule, every visitor downloaded 1.66 MB of
   wall whichever opening they were looking at -- and the lazy-loading check
   could not see it, because it only ever looked at elements with data-src. */
#back-wall[data-awake] .tiles { background-image: url(/app/site/intro/wall.jpg); }
@media (max-width: 900px) {
  #back-wall[data-awake] .tiles { background-image: url(/app/site/intro/wall-small.jpg); }
}

@keyframes drift {
  from { transform: translate3d(0, 0, 0) scale(1.02); }
  to   { transform: translate3d(-4%, -2.5%, 0) scale(1.1); }
}

/* ── 3. the dissolve ─────────────────────────────────────────────────── */
#back-dissolve .a, #back-dissolve .b { position: absolute; inset: 0; }
#back-dissolve .b { animation: dissolve 11s ease-in-out infinite; }
@keyframes dissolve {
  0%, 34%   { opacity: 0; }
  50%, 84%  { opacity: 1; }
  100%      { opacity: 0; }
}

/* ── 4. the mission from above ───────────────────────────────────────── */
#back-classes img { animation: creep 70s ease-in-out infinite alternate; }
@keyframes creep {
  from { transform: scale(1.04) translate3d(1%, 1%, 0); }
  to   { transform: scale(1.16) translate3d(-2%, -1.5%, 0); }
}

/* ── 5. live ─────────────────────────────────────────────────────────── */
#back-live { background: #07080b; }
#back-live iframe { pointer-events: none; }        /* the hero is not a toy */
#back-live .wake {
  position: absolute; z-index: 3; right: var(--gut); bottom: 22px;
  font-family: var(--mono); font-size: 11.5px; color: var(--dim);
}

@media (prefers-reduced-motion: reduce) {
  #back-wall .tiles, #back-classes img, #back-dissolve .b { animation: none; }
  #back-dissolve .b { opacity: .55; }
}

/* ── the chooser, for picking one ────────────────────────────────────── */
.pick {
  position: absolute; z-index: 3; left: var(--gut); bottom: 18px;
  display: flex; flex-wrap: wrap; gap: 6px;
  font-family: var(--mono); font-size: 11px;
}
.pick a {
  padding: 5px 9px; text-decoration: none; color: var(--dim);
  border: 1px solid var(--hair); background: rgba(10, 11, 14, .55);
  backdrop-filter: blur(4px);
}
.pick a:hover { color: var(--ink); border-color: var(--ink); }
.pick a[aria-current="true"] {
  color: var(--accent-ink); background: var(--accent); border-color: var(--accent);
}
.pick b { color: var(--faint); font-weight: 400; align-self: center;
          padding-right: 4px; }
@media (max-width: 900px) { .pick { display: none; } }

/* ═══════════════════════════════════════════════════════════════════════
   The three that run.

   `city`, `finds` and `through` are WebGL: a canvas filling the hero, two
   lines of type over it, and nothing else. The readout says what the scene
   is showing at that instant -- how much of the point file has landed, which
   find the pointer is on, how far down the street you have driven -- and the
   hint says what the pointer can do. Both are in the mono face, both small,
   because the picture is the thing.
   ═══════════════════════════════════════════════════════════════════════ */
.hero[data-intro="city"]    #back-city,
.hero[data-intro="finds"]   #back-finds,
.hero[data-intro="through"] #back-through { opacity: 1; visibility: visible; }

.back canvas.gl {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  touch-action: pan-y;                 /* the page keeps its vertical scroll */
  cursor: grab;
}
.back canvas.gl.grabbing { cursor: grabbing; }

/* the readout sits above the chooser, on the same baseline grid */
.glread, .glhint {
  position: absolute; z-index: 3; pointer-events: none;
  font-family: var(--mono); font-size: 11.5px; line-height: 1.5;
  color: var(--dim); text-shadow: 0 1px 12px rgba(10, 11, 14, .9);
}
.glread {
  left: var(--gut); bottom: 52px; max-width: min(62ch, 72vw);
  font-variant-numeric: tabular-nums;
}
.glread b { color: var(--ink); font-weight: 500; }
.glhint {
  right: var(--gut); bottom: 22px; color: var(--faint);
  transition: opacity .35s ease;
}
.glhint b { color: var(--dim); font-weight: 500;
  border: 1px solid var(--hair); border-radius: 3px; padding: 0 4px; }
.glhint.gone { opacity: 0; }
@media (max-width: 900px) {
  .glread { bottom: 20px; font-size: 11px; }
  .glhint { display: none; }
}

/* a scene that could not start says so where its readout would have been */
.glfail {
  position: absolute; z-index: 3; left: var(--gut); bottom: 52px;
  font-family: var(--mono); font-size: 11.5px; color: var(--faint);
}
