/* gate login page, tuned for safari on an iphone

   safari keeps a page between its status bar and its toolbar and paints the strips under both with
   the page colour. the document runs on below the viewport, so the picture really continues under
   the toolbar. under the status bar it can't - only scrolled content shows there - so that strip is
   --void, the colour of bg.avif's top rows, and the picture sits --top px higher than the viewport,
   where it was framed for the whole screen

   the page rests at its top and scrolls only to meet the keyboard - up by --rise while the
   field has the focus, form, picture and brand together, and back. safari blurs what passes under
   the status bar itself, no fade of ours on top, and the field ends up on the fog instead of the
   tower. app.js does the scrolling and says why not safari. nothing here is fixed or sticky -
   safari displaces such a layer for as long as a scroll to a field runs

   dark-mode extensions rewrite stylesheets. keep var() out of any declaration with a url() - noir
   turns that into an invalid value and the picture is gone */

/* the page's own mono - light for the dim lines, regular for the rest. its sizes and tracking are
   the ones it was drawn with - bigger letters, less air, each line within 6% of its width in the
   system mono */
@font-face {
  font-family: "BSQ Mono";
  src: url(BSQMono-Light.woff2?v=7406a745d5a1) format("woff2");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "BSQ Mono";
  src: url(BSQMono-Regular.woff2?v=c4975f9801ce) format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  --void: #0b0b0d;
  --ink: #e9e9ec;
  --dim: #8a8a91;
  --faint: #5a5a61;
  --rule: #2c2c32;
  --red: #cf4f55; /* the light strip's own red - its core on the tower, rows 850 to 1300 */
  --mono: "BSQ Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --edge: clamp(24px, 8vw, 40px); /* the page's side margin - 32 px on a 402 px iphone */
  /* how far the page scrolls up while the field has the focus - only where a keyboard covers the
     screen. 55svh - 64px puts the field 56 px above 45% of the viewport and the line under it at
     45% - all of the form above every iphone keyboard */
  --rise: 0px;
  color-scheme: dark;
}
@media (pointer: coarse) {
  :root { --rise: calc(55svh - 64px); }
}

* { box-sizing: border-box; margin: 0; }
::-webkit-scrollbar { display: none; }
[hidden] { display: none !important; }

html {
  overflow: hidden; /* the document is longer than the viewport, on purpose, and moves only when app.js scrolls it */
  background: var(--void);
  overscroll-behavior: none;
  scrollbar-width: none; /* only app.js scrolls the page, a scroll bar on the way has nothing to say */
  touch-action: none; /* no pan and no pinch, wherever the finger lands - the caret and its loupe still work */
  -webkit-text-size-adjust: 100%;
}
body {
  position: relative;
  height: 100svh;
  color: var(--ink);
  font: 13px/1.4 var(--mono);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
}

/* the picture is three layers of one render - bg.avif is the sky, the city and the fog on a canvas
   two phone screens wide, tower.avif the tower with its light off on the middle half of it, and
   light.avif the light strip. .scene is as wide as the viewport and as tall as the picture, and
   cuts off what the picture has beyond the sides

   --b is the picture's width - the viewport's, or more where it takes more to bring the fog up to
   the form. the form's top edge, 196 px above the viewport's bottom, stands on the row 68.364%
   down the picture, the point just under where the tower is gone. a wide window loses sky above
   that row, never the fog under the form
   --tw is the tower's width. from its own half of the picture it shrinks to 60% of that, no
   further, to stay about half a viewport tall while the picture behind it grows with the window.
   it scales about 49.502% 68.364% of its box and no other point - there its baked fog lands on
   fog of its own tone, anywhere else a smudge shows under the foot. top and left of .tower are
   that scale written out in lengths, calc() does not divide a length by a length everywhere
   the horizon, 48.18% down the picture, never goes higher than 63 px under the viewport's top -
   where a 1100x500 window has it. a window shorter or wider than that keeps the horizon and lets
   the form climb up the picture instead, and once the form's top is more than 3% of the
   picture above the tower's foot the tower is gone (--gone, 0 or far off to the left, nothing
   in between) - it can step aside by a third of its canvas at most, too little to clear the
   form in a narrow window, and the picture stands without it */
.scene {
  --b: max(100vw, (100svh - 196px) * 1.3456);
  --h: calc(var(--b) * 2622 / 2412);
  --tw: clamp(.3 * var(--b), 52.9svh, .5 * var(--b));
  --gone: clamp(0px, (var(--h) * .172 + 259px - 100svh) * 1000000, 300vw);
  position: absolute;
  top: calc(-1 * max(0px, min(var(--h) - 100svh, var(--h) * .68364 + 196px - 100svh, var(--h) * .4818 - 63px)));
  left: 0;
  right: 0;
  height: var(--h);
  overflow-x: clip;
}
/* room to scroll into below the picture - its last row's fog, thinning out */
.scene::after {
  content: "";
  position: absolute;
  inset: 100% 0 auto;
  height: 60svh;
  background: linear-gradient(to bottom, #0f0f10, var(--void));
}
.sky {
  position: absolute;
  top: 0;
  left: calc(50% - var(--b) / 2);
  width: var(--b);
  height: 100%;
  background: url(bg.avif?v=cc48cdbc38c7) 0 0 / 100% 100% no-repeat;
}
/* the strip at rest is the overlay at full strength on the tower with its light off - the pair the
   render was cut into, the overlay fits no other tower */
.tower {
  position: absolute;
  top: calc(.68364 * (var(--h) - var(--tw) * 2622 / 1206));
  left: calc(50% - .00249 * var(--b) - .49502 * var(--tw) - var(--gone));
  width: var(--tw);
  height: calc(var(--tw) * 2622 / 1206);
  background: url(light.avif?v=54003effc830) 0 0 / 100% 100% no-repeat, url(tower.avif?v=0c4a421dd17e) 0 0 / 100% 100% no-repeat;
  transition: opacity .6s ease-out;
}
/* the tower never stands on nothing - bg.avif is the heaviest layer and arrives last, so on the
   login page (.wait) the tower and its strip stay out until app.js has every layer decoded and the
   sky painted, then rise out of the fog together. the denied page runs no script and has no .wait */
.scene.wait:not(.ready) .tower { opacity: 0; }
/* more light on the strip while the page waits - the overlay again, in the tower's own box, added
   to what is there and drawn twice over itself, which lifts its soft edge into a glow without a
   filter. the light runs up the strip: .stream is a column of windows, a bright head and a fading tail
   every --run px, that climbs by one --run per cycle, and .strip sinks inside it at the same pace.
   the strip stays where it is and only the light moves - two transforms, nothing is repainted */
.light {
  --run: 120px;
  position: absolute;
  inset: 0;
  overflow: hidden;
  mix-blend-mode: plus-lighter;
  opacity: 0;
}
.stream {
  position: absolute;
  inset: 0 0 calc(-1 * var(--run));
  -webkit-mask-image: repeating-linear-gradient(to bottom, transparent, #000 calc(.06 * var(--run)), transparent calc(.62 * var(--run)), transparent var(--run));
  mask-image: repeating-linear-gradient(to bottom, transparent, #000 calc(.06 * var(--run)), transparent calc(.62 * var(--run)), transparent var(--run));
}
.strip {
  position: absolute;
  inset: 0 0 var(--run);
  background: url(light.avif?v=54003effc830) 0 0 / 100% 100% no-repeat, url(light.avif?v=54003effc830) 0 0 / 100% 100% no-repeat;
}

/* a phone held upright - the middle half of the picture is one iphone screen, so that half spans
   the width, the tower is full size and the scene starts the status bar's height above the
   viewport. that height can't be measured from a page, --top holds it per screen width. in a
   full-screen web view the safe area inset is the same number and the scene starts at the
   viewport's own top */
@media (pointer: coarse) and (orientation: portrait) and (max-width: 480px) {
  :root { --top: 54px; }
  @media (width: 402px), (width: 420px), (width: 440px) { :root { --top: 62px; } }
  @media (width: 393px), (width: 430px) { :root { --top: 59px; } }
  @media (width: 390px), (width: 428px) { :root { --top: 47px; } }
  @media (width: 375px), (width: 414px) { :root { --top: 46px; } }
  .scene {
    --b: 200vw;
    --tw: 100vw;
    --gone: 0px;
    top: calc(env(safe-area-inset-top, 0px) - var(--top));
  }
}

/* the brand's ink, not its box, sits --edge from the side and 1.5 times that under the status
   bar - on a 402x874 iphone the b's stem 32 px in and its top 48 px under the 62 px bar, 110 from
   the screen's top. top over side is 3:2, the page canon's. the box starts 2 px left of the stem
   and 4.6 px above the b - bsq mono's side bearing and the room over its ascender at 22 px */
.brand {
  position: absolute;
  left: calc(var(--edge) - 2px);
  top: calc(env(safe-area-inset-top, 0px) + 1.5 * var(--edge) - 4.6px);
}
/* a phone on its side has its island left or right and reports the same safe area inset on both
   sides either way. app.js says when it is the left one, only then the brand moves 20 px clear */
.island-left .brand { left: max(var(--edge) - 2px, env(safe-area-inset-left, 0px) + 20px); }
.name { font-size: 22px; line-height: 1.2; letter-spacing: .12em; }
.tagline {
  max-width: 16em;
  margin-top: 8px;
  color: var(--dim); /* 5.7:1 on the void - thin strokes look dimmer than their ratio */
  font-size: 11px;
  font-weight: 300;
  line-height: 1.13;
  letter-spacing: .23em;
  text-transform: uppercase;
  word-spacing: 100vw; /* one word per line */
}
.tagline:first-child { margin-top: 0; } /* no brand above it - GATE_BRAND set and empty */

form { position: absolute; inset: 0; }

/* a column that ends 50 px above the toolbar. the slots have fixed heights, so the centre of the
   primary button, or of the field in its place, is always 120 px above the toolbar - under
   the thumb - whatever else the view shows */
.dock {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 50px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.note {
  max-width: 300px;
  min-height: 16px;
  margin-bottom: 18px;
  color: var(--dim);
  font-size: 13px;
  font-weight: 300;
  line-height: 16px;
  letter-spacing: .03em;
  text-align: center;
  text-wrap: balance;
  overflow-wrap: anywhere;
  transition: opacity .2s;
}
.note[data-kind="err"] { color: #cf7f83; }

button {
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  touch-action: manipulation;
}
/* red as on the focused field, 4.2:1 on the fog - --faint was 2.9:1, under the 3:1 a focus ring needs */
button:focus-visible { outline: 2px solid var(--red); outline-offset: -4px; }

/* the field, in the primary button's slot - a view has one or the other. the input lets
   touches through to the slot until it has the focus, app.js says why */
.field {
  position: relative;
  width: 262px;
  height: 52px;
  cursor: text;
}
.field::after {
  content: "";
  position: absolute;
  inset: auto 0 7px;
  height: 1px;
  background: var(--rule);
  transition: background .2s;
}
.field:focus-within::after { background: var(--red); }
#entry {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  outline: 0;
  background: transparent;
  color: var(--ink);
  font: 16px/52px var(--mono); /* 16px - anything smaller makes safari zoom in on focus */
  letter-spacing: .06em;
  text-align: center;
  text-overflow: ellipsis;
  caret-color: var(--red);
  pointer-events: none;
  touch-action: none; /* a finger that starts on the focused field must not pan or pinch the page either */
  -webkit-appearance: none;
  appearance: none;
  -webkit-user-select: text;
  user-select: text;
}
#entry:focus { pointer-events: auto; }
#entry::placeholder { color: var(--faint); opacity: 1; }
#entry:-webkit-autofill { -webkit-text-fill-color: var(--ink); transition: background-color 9999s; }

.primary {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  height: 52px;
  padding: 0 20px;
  font-size: 15px;
  letter-spacing: .32em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: opacity .2s;
}
/* the tracking trails the last letter - the indent centres the word between the rules again */
.primary span { padding-left: .32em; }
.primary::before, .primary::after {
  content: "";
  flex: 0 1 73px;
  min-width: 20px;
  height: 1px;
  background: linear-gradient(to right, rgb(207 79 85 / .25), var(--red) 45%);
}
.primary::after { transform: scaleX(-1); }
.primary:active { opacity: .55; }
.busy .primary { pointer-events: none; }

/* .pulse - the page waits for something. light streams up the tower, swelling and fading in
   breaths of 3.6 s, three runs of the stream each - nothing else on the page moves. app.js takes
   the class off at the end of a breath, never inside one */
.pulse .light { animation: swell 3.6s ease-in-out infinite; }
.pulse .stream { animation: climb 1.2s linear infinite; }
.pulse .strip { animation: sink 1.2s linear infinite; }
@keyframes swell { 30%, 70% { opacity: 1; } }
@keyframes climb { to { transform: translateY(calc(-1 * var(--run))); } }
@keyframes sink { to { transform: translateY(var(--run)); } }

.secondary {
  height: 44px;
  padding: 0 20px;
  color: var(--dim);
  font-size: 13px;
  font-weight: 300;
  letter-spacing: .06em;
  transition: opacity .3s;
}
.secondary:empty { visibility: hidden; }
.secondary:active { opacity: .55; }

.noscript {
  position: absolute;
  inset: auto 0 170px;
  color: var(--dim);
  font-size: 11px;
  letter-spacing: .14em;
  text-align: center;
}

/* the page /verify answers a signed-in user without access to the host with */
.denied .note { max-width: 320px; }
.denied a.primary { color: inherit; text-decoration: none; }

@media (prefers-reduced-motion: reduce) {
  .note, .secondary, .primary, .field::after, .tower { transition: none; }
  .pulse .light { animation: none; opacity: .6; }
  .pulse .stream, .pulse .strip { animation: none; }
  .stream { -webkit-mask-image: none; mask-image: none; }
}
