/* The side panel's pixel letters, kept on this server. Only ever at whole multiples of 8px */
@font-face {
  font-family: "Panel Pixel";
  src: url("../fonts/panel-pixel-3.ttf") format("truetype");
  font-display: block;
}

/* The panel's small pixel letters, kept on this server. Only ever used at 16px */
@font-face {
  font-family: "Panel Text";
  src: url("../fonts/panel-text-3.ttf") format("truetype");
  font-weight: 100 900;
  font-display: block;
}

/* Colour and size settings, all kept in one place */
:root {
  /* Page base, close to black with a hint of violet */
  --bg: #05030a;
  /* Inside of the box */
  --surface: #0d0820;
  /* Size of one chunky pixel */
  --px: 4px;
  /* Box border */
  --pixel: #6d4de6;
  /* Box border when active */
  --pixel-active: #c4b5fd;
  /* Hard shadow under the box */
  --pixel-drop: #1d1145;
  /* Button face */
  --button: #7c3aed;
  /* Button face on hover */
  --button-hover: #8b4ff0;
  /* Button highlight edge */
  --button-light: #a78bfa;
  /* Button shadow edge */
  --button-dark: #4c1d95;
  /* Disabled button face */
  --button-off: #241847;
  /* Brightest highlight */
  --flash: #f4f0ff;
  /* Main text colour */
  --text: #f1edfb;
  /* Placeholder text colour */
  --text-muted: #a79fc0;
  /* Error colour */
  --error: #ff4d6d;
  /* Monospaced system font, so no font is downloaded */
  --font-mono: "Panel Text", ui-monospace, "SF Mono", "Cascadia Mono", "Liberation Mono", Menlo, Consolas, monospace;
}

/* Sizes include padding and borders */
*, *::before, *::after {
  box-sizing: border-box;
}

/* Full-height page with no default margin */
html, body {
  height: 100%;
  margin: 0;
}

/* Base page look */
body {
  /* Fallback glow for when the animation cannot run */
  background: radial-gradient(120% 90% at 70% 15%, #1a0b3a 0%, var(--bg) 60%);
  /* Page colour behind the gradient */
  background-color: var(--bg);
  /* Default text colour */
  color: var(--text);
  /* Default font */
  font-family: var(--font-mono);
  /* Stops the enter animation's light line causing scrollbars */
  overflow-x: hidden;
  /* Stops phones resizing text by themselves */
  -webkit-text-size-adjust: 100%;
  /* Same, for other browsers */
  text-size-adjust: 100%;
}

/* Animated background layer */
#background {
  /* Stays in place behind everything */
  position: fixed;
  /* Covers the whole screen */
  inset: 0;
  /* Fills the width */
  width: 100%;
  /* Fills the height */
  height: 100%;
  /* Removes the gap under inline elements */
  display: block;
  /* Hidden until the first frame is drawn */
  opacity: 0;
  /* Slow fade in */
  transition: opacity 1.4s ease;
}

/* Set by background.js once the first frame is ready */
#background.is-ready {
  /* Fully visible */
  opacity: 1;
}

/* Centres everything on screen */
.login {
  /* Sits above the background */
  position: relative;
  /* At least the height of the screen, allowing for phone browser bars */
  min-height: 100dvh;
  /* Vertical stack */
  display: flex;
  /* Stack direction */
  flex-direction: column;
  /* Centre horizontally */
  align-items: center;
  /* Centre vertically */
  justify-content: center;
  /* Keeps content off the edges, and clear of phone notches */
  padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
}

/* Pixel ghost mascot */
.mascot {
  /* 5 times its 32 pixel width, a whole number, which keeps the pixels sharp */
  width: 160px;
  /* 5 times its 30 pixel height */
  height: 150px;
  /* Space above the box */
  margin-bottom: 26px;
  /* Soft violet glow */
  filter: drop-shadow(0 0 18px rgba(167, 139, 250, 0.45));
  /* Lets the body bend slightly past its edges as it moves */
  overflow: visible;
}

/* Holds the ghost and its speech bubble, without moving the ghost from where it was */
.ghost-stage {
  /* Anchor for the bubble */
  position: relative;
}

/* The ghost's speech bubble, beside its head, travelling with it pixel for pixel */
.speech {
  /* Taken out of the flow, so the ghost and the box stay exactly where they were */
  position: absolute;
  /* Level with its face */
  top: 6px;
  /* Beside the ghost, with room for the tail */
  left: calc(100% + 24px);
  /* As wide as the words */
  width: max-content;
  /* Follows the ghost */
  transform: translate(var(--ghost-x, 0), var(--ghost-y, 0));
  /* The same soft glow as the ghost */
  filter: drop-shadow(0 0 14px rgba(167, 139, 250, 0.35));
  /* Steps away in blocks */
  transition: opacity 0.16s steps(2);
}

/* Out of sight while a key is being typed or checked */
.speech.is-quiet,
.speech.is-gone {
  /* Unseen */
  opacity: 0;
  /* Cannot be clicked */
  pointer-events: none;
}

/* Built exactly like the key box: dark surface, notched pixel border, solid drop */
.speech-body {
  /* Anchor for the tail */
  position: relative;
  /* Inner spacing */
  padding: 10px 14px 12px;
  /* Same as the box */
  background: var(--surface);
  /* Same border and drop as the box */
  box-shadow:
    0 calc(var(--px) * -1) 0 0 var(--pixel),
    0 var(--px) 0 0 var(--pixel),
    calc(var(--px) * -1) 0 0 0 var(--pixel),
    var(--px) 0 0 0 var(--pixel),
    0 calc(var(--px) * 3) 0 0 var(--pixel-drop);
  /* Pops in like an old game */
  animation: speech-in 0.2s steps(3);
}

/* The tail, four blocks tall, pointing down at the ghost's microphone */
.speech-tail {
  /* Placed against the side */
  position: absolute;
  /* Its last column covers the box's border, joining them */
  left: -20px;
  /* Low on the side */
  bottom: 8px;
  /* 5 blocks of 4 pixels */
  width: 20px;
  /* 4 blocks of 4 pixels */
  height: 16px;
  /* Never squeezed */
  overflow: visible;
}

/* The inside of the tail */
.speech-tail-fill {
  /* Same as the bubble */
  fill: var(--surface);
}

/* The edge of the tail */
.speech-tail-line {
  /* Same as the border */
  fill: var(--pixel);
}

/* The words, in the same lettering and size as ENTER */
.speech-words {
  /* No browser spacing */
  margin: 0;
  /* Short enough to break into two neat lines */
  max-width: 190px;
  /* The side panel's letters */
  font-family: "Panel Pixel", var(--font-mono);
  /* Exactly 3 screen pixels per lettering pixel, the same as ENTER */
  font-size: 24px;
  /* Room between lines */
  line-height: 30px;
  /* Bright */
  color: var(--text);
}

/* Longer words, such as a message from the owner to someone blocked */
.speech.is-long .speech-words {
  /* Room for more on each line */
  max-width: 264px;
  /* Exactly 2 screen pixels per lettering pixel, the same as the side panel */
  font-size: 16px;
  /* Room between lines */
  line-height: 22px;
}

/* A smaller cursor to match */
.speech.is-long .speech-cursor::after {
  /* 2 lettering pixels wide */
  width: 6px;
  /* As tall as a capital */
  height: 14px;
}

/* The letters still to come */
.speech-rest {
  /* Unseen, but holding their place */
  visibility: hidden;
}

/* The cursor, which takes up no room so nothing shifts */
.speech-cursor {
  /* Anchor for the block */
  position: relative;
}

/* The block itself */
.speech-cursor::after {
  /* Drawn by itself */
  content: "";
  /* Just after the last letter */
  position: absolute;
  /* Clear of the letter */
  left: 2px;
  /* Level with the letters */
  bottom: -2px;
  /* 3 lettering pixels wide */
  width: 9px;
  /* As tall as a capital */
  height: 21px;
  /* Lit violet */
  background: var(--pixel-active);
  /* Blinks in steps */
  animation: speech-blink 1s steps(2, jump-none) infinite;
}

/* Key not accepted: the same bubble, lit the ghost's red, and the same red as the box */
.speech.is-error {
  /* The red of its horns */
  --pixel: var(--error);
  /* A dark red surface */
  --surface: #38071a;
  /* A deeper red drop */
  --pixel-drop: #5e0c28;
  /* A red glow in place of the violet */
  filter: drop-shadow(0 0 18px rgba(255, 77, 109, 0.55));
}

/* Shakes in step with the box and the ghost */
.speech.is-error .speech-body {
  /* The same shake */
  animation: shake 0.32s steps(4, jump-none);
}

/* The cursor goes red too */
.speech.is-error .speech-cursor::after {
  /* The red of its horns */
  background: var(--error);
}

/* Pops in */
@keyframes speech-in {
  /* From slightly smaller */
  from { opacity: 0; transform: translateY(4px); }
}

/* On, then off */
@keyframes speech-blink {
  /* Half the time */
  50% { opacity: 0; }
}

/* Wrong key: the ghost shakes */
.mascot.is-sad {
  /* Quick steps from side to side */
  animation: shake 0.32s steps(4, jump-none);
}

/* Holds the box and the light used by the enter animation */
.login-form {
  /* Anchor for the light */
  position: relative;
  /* Full width up to the maximum */
  width: 100%;
  /* Wide enough for the key */
  max-width: 560px;
}

/* The visible box */
.key-box {
  /* Side by side */
  display: flex;
  /* Keeps the pixel border inside the form's width */
  margin: var(--px);
  /* Space between the border and the contents */
  padding: 8px;
  /* Inside colour */
  background: var(--surface);
  /* Four straight shadows make a pixel border with notched corners, and the last is the hard drop shadow */
  box-shadow:
    0 calc(var(--px) * -1) 0 0 var(--pixel),
    0 var(--px) 0 0 var(--pixel),
    calc(var(--px) * -1) 0 0 0 var(--pixel),
    var(--px) 0 0 0 var(--pixel),
    0 calc(var(--px) * 3) 0 0 var(--pixel-drop);
}

/* Border lights up while the input has focus */
.key-box:focus-within {
  /* Brighter pixels */
  --pixel: var(--pixel-active);
}

/* The input area */
.key-field {
  /* Holds the dots and the input on top of each other */
  position: relative;
  /* Takes all space beside the button */
  flex: 1;
  /* Lets it shrink inside the box */
  min-width: 0;
  /* Comfortable to tap, and grows with a long key */
  min-height: 56px;
}

/* The dots, one per character */
.key-mask {
  /* Holds the line breaks */
  display: block;
  /* Vertically centres a single line */
  min-height: 56px;
  /* Inner spacing */
  padding: 14px 12px;
  /* Wraps anywhere, so a long key takes more lines instead of being cut off */
  overflow-wrap: anywhere;
  /* Dot colour */
  color: var(--text);
  /* Matches the rest of the page */
  font-family: var(--font-mono);
  /* Dot size */
  font-size: 16px;
  /* Lines close together */
  line-height: 1.8;
  /* Keeps the dots visually separate */
  letter-spacing: 0.1em;
  /* Clicks go to the input */
  pointer-events: none;
}

/* Blinking cursor after the last dot, while the box is in use */
.key-field:focus-within .key-mask::after {
  /* Required for the pseudo-element to exist */
  content: "";
  /* Sits in the text */
  display: inline-block;
  /* Thin */
  width: 2px;
  /* Line height */
  height: 1.1em;
  /* Lines up with the dots */
  vertical-align: -0.2em;
  /* Violet */
  background: var(--pixel-active);
  /* Hard blink */
  animation: caret 1s steps(2, jump-none) infinite;
}

/* Cursor blink */
@keyframes caret {
  /* On */
  from { opacity: 1; }
  /* Off */
  to { opacity: 0; }
}

/* The real input, invisible over the dots */
.key-input {
  /* Covers the whole area, so a click anywhere goes to it */
  position: absolute;
  /* Edge to edge */
  inset: 0;
  /* Full width */
  width: 100%;
  /* Full height */
  height: 100%;
  /* Inner spacing, matching the dots */
  padding: 0 12px;
  /* The box supplies the background */
  background: transparent;
  /* The box supplies the border */
  border: 0;
  /* The box supplies the focus style */
  outline: 0;
  /* Hides the browser's own masking, because the dots replace it */
  color: transparent;
  /* Same, for Safari and Chrome */
  -webkit-text-fill-color: transparent;
  /* The dots have their own cursor */
  caret-color: transparent;
  /* Matches the rest of the page */
  font-family: var(--font-mono);
  /* 16px stops phones zooming in on focus */
  font-size: 16px;
  /* Removes the default look on iPhones */
  appearance: none;
  /* Removes iPhone's rounded corners */
  border-radius: 0;
}

/* Edge and other browsers have a built-in eye button that reveals passwords, and it must never appear */
.key-input::-ms-reveal,
.key-input::-ms-clear {
  /* Removed */
  display: none;
}

/* Selecting the hidden text shows nothing */
.key-input::selection {
  /* No highlight */
  background: transparent;
}

/* Placeholder text */
.key-input::placeholder {
  /* Readable but quiet */
  color: var(--text-muted);
  /* Same, for Safari and Chrome */
  -webkit-text-fill-color: var(--text-muted);
  /* Normal spacing for words */
  letter-spacing: 0.02em;
}

/* Enter button */
.key-submit {
  /* Keeps its size beside the input */
  flex: none;
  /* Leaves room for the longest label, so the button never jumps */
  min-width: 170px;
  /* Leaves room for its own pixel border */
  margin: var(--px);
  /* Centres the label */
  display: flex;
  /* Horizontally */
  justify-content: center;
  /* Vertically */
  align-items: center;
  /* Horizontal space around the label */
  padding: 0 14px;
  /* Button colour */
  background: var(--button);
  /* The shadows are the border */
  border: 0;
  /* Square corners */
  border-radius: 0;
  /* Label colour */
  color: #ffffff;
  /* Hand cursor */
  cursor: pointer;
  /* Notched corners plus a light top edge and a dark bottom edge, like a game button */
  box-shadow:
    0 calc(var(--px) * -1) 0 0 var(--button),
    0 var(--px) 0 0 var(--button),
    calc(var(--px) * -1) 0 0 0 var(--button),
    var(--px) 0 0 0 var(--button),
    inset 0 var(--px) 0 0 var(--button-light),
    inset 0 calc(var(--px) * -1) 0 0 var(--button-dark);
  /* Removes the tap delay on phones */
  touch-action: manipulation;
  /* Removes the grey flash on phones */
  -webkit-tap-highlight-color: transparent;
  /* Removes the default look on iPhones */
  appearance: none;
}

/* Hover */
.key-submit:hover:not(:disabled) {
  /* Brighter face */
  --button: var(--button-hover);
}

/* Pressed: moves down, like a physical button */
.key-submit:active:not(:disabled) {
  /* Moves down one chunky pixel */
  transform: translateY(var(--px));
}

/* Visible keyboard focus */
.key-submit:focus-visible {
  /* Clear ring */
  outline: 2px solid var(--flash);
  /* Clears the pixel border */
  outline-offset: 6px;
}

/* Disabled state */
.key-submit:disabled {
  /* Dull face */
  --button: var(--button-off);
  /* Flat edges */
  --button-light: var(--button-off);
  /* Flat edges */
  --button-dark: var(--button-off);
  /* Dimmed label */
  color: var(--text-muted);
  /* Normal cursor */
  cursor: default;
}

/* Pixel lettering */
.pixel-label {
  /* Removes the gap under inline elements */
  display: block;
}

/* Hidden until the check starts */
.label-checking {
  /* Not shown */
  display: none;
}

/* During the check: hides ENTER */
.login-form[aria-busy="true"] .label-enter {
  /* Not shown */
  display: none;
}

/* During the check: shows CHECKING */
.login-form[aria-busy="true"] .label-checking {
  /* Shown */
  display: block;
}

/* Failed attempt */
.login-form.is-error .key-box {
  /* Red pixels */
  --pixel: var(--error);
  /* Quick, chunky shake */
  animation: shake 0.32s steps(4, jump-none);
}

/* Shake movement */
@keyframes shake {
  /* Start and end in place */
  0%, 100% { transform: translateX(0); }
  /* Left */
  25% { transform: translateX(-8px); }
  /* Right */
  75% { transform: translateX(8px); }
}

/* ENTER ANIMATION, 1.6 seconds in total, only played after a successful login */

/* Light used by the animation, hidden until then */
.key-flash {
  /* Covers the box */
  position: absolute;
  /* Edge to edge, including the pixel border */
  inset: 0;
  /* Bright fill */
  background: var(--flash);
  /* Violet glow */
  box-shadow: 0 0 34px rgba(196, 181, 253, 0.9);
  /* Invisible until needed */
  opacity: 0;
  /* Never blocks taps */
  pointer-events: none;
}

/* Step 1: the box disappears */
.login-form.is-entering .key-box {
  /* Fades quickly */
  animation: box-leave 0.18s ease both;
}

/* Box movement */
@keyframes box-leave {
  /* Gone */
  to { opacity: 0; }
}

/* Step 2: the box flashes, collapses into a line, and the line stretches past the screen edges */
.login-form.is-entering .key-flash {
  /* Runs across most of the animation */
  animation: flash-enter 1.15s cubic-bezier(0.7, 0, 0.3, 1) both;
}

/* Light movement */
@keyframes flash-enter {
  /* Starts invisible at the size of the box */
  0% { opacity: 0; transform: scale(1, 1); }
  /* Bright box */
  18% { opacity: 1; transform: scale(1, 1); }
  /* Thin line */
  45% { opacity: 1; transform: scale(1, 0.04); }
  /* Wider than any screen */
  80% { opacity: 1; transform: scale(8, 0.04); }
  /* Fades out */
  100% { opacity: 0; transform: scale(8, 0.04); }
}

/* Solid colour that covers the page at the very end */
.veil {
  /* Covers the screen */
  position: fixed;
  /* Edge to edge */
  inset: 0;
  /* Same colour the panel page starts on, so there is no flash between pages */
  background: var(--bg);
  /* Invisible until needed */
  opacity: 0;
  /* Never blocks taps */
  pointer-events: none;
}

/* Step 3: fades in once the background dive has built up */
.veil.is-active {
  /* Fades over the last part of the animation */
  animation: veil-in 0.55s ease 1.05s both;
}

/* Veil movement */
@keyframes veil-in {
  /* Fully covered */
  to { opacity: 1; }
}

/* Hidden on screen but still read by screen readers */
.visually-hidden {
  /* Taken out of the layout */
  position: absolute;
  /* Tiny width */
  width: 1px;
  /* Tiny height */
  height: 1px;
  /* Clips the contents away */
  overflow: hidden;
  /* Clips to nothing */
  clip-path: inset(50%);
  /* Keeps the text on one line */
  white-space: nowrap;
}

/* Phones: stack the button under the input, with a bigger button to tap */
/* Not enough room beside the ghost, so the bubble sits above it, tail pointing down */
@media (max-width: 720px) {
  /* Above its head, centred */
  .speech {
    /* No longer beside it */
    top: auto;
    /* Above the horns */
    bottom: calc(100% + 18px);
    /* Centred */
    left: 50%;
    /* Centred, while following the ghost */
    transform: translate(calc(-50% + var(--ghost-x, 0px)), var(--ghost-y, 0px));
  }

  /* Pointing down from the middle of the bottom edge */
  .speech-tail {
    /* Middle */
    left: calc(50% - 10px);
    /* Its covering column lands on the border once turned */
    bottom: -18px;
    /* Turned to point down */
    transform: rotate(-90deg);
  }

  /* One neat block */
  .speech-words {
    /* Centred lines */
    text-align: center;
  }
}

@media (max-width: 480px) {
  /* Slightly smaller ghost, still a whole-number scale */
  .mascot {
    /* 4 times its width */
    width: 128px;
    /* 4 times its height */
    height: 120px;
    /* Tighter space */
    margin-bottom: 22px;
  }

  /* Box becomes a column */
  .key-box {
    /* Vertical stack */
    flex-direction: column;
    /* Space between input and button */
    gap: 8px;
  }

  /* Input area keeps its height in a column */
  .key-field {
    /* Stops flex from changing the height */
    flex: none;
    /* Full width */
    width: 100%;
  }

  /* Centres the placeholder */
  .key-input {
    /* Centred */
    text-align: center;
  }

  /* Centres the dots to match */
  .key-mask {
    /* Centred */
    text-align: center;
  }

  /* The button keeps its height when the box grows */
  .key-submit {
    /* Stops flex from changing it */
    flex: none;
  }

  /* Big, easy button */
  .key-submit {
    /* Comfortable to tap, well above the 44px minimum */
    height: 56px;
  }
}

/* Phones held sideways, or an open keyboard: little height to spare */
@media (max-height: 480px) {
  /* Much smaller ghost, still a whole-number scale */
  .mascot {
    /* 2 times its width */
    width: 64px;
    /* 2 times its height */
    height: 60px;
    /* Tight space */
    margin-bottom: 14px;
  }

  /* Status line takes less room */
  .login-status {
    /* Tighter space */
    margin-top: 16px;
  }
}

/* Short and narrow, such as a small phone with the keyboard open: the ghost steps aside */
@media (max-height: 360px) and (max-width: 480px) {
  /* Not shown */
  .mascot {
    /* Leaves room for the input and button */
    display: none;
  }
}

/* Respect the system's reduced motion setting */
@media (prefers-reduced-motion: reduce) {
  /* No fade in */
  #background {
    /* Appears at once */
    transition: none;
  }

  /* No blinking cursor, shaking or enter animation */
  .key-field:focus-within .key-mask::after,
  .speech-body,
  .speech.is-error .speech-body,
  .speech-cursor::after,
  .mascot.is-sad,
  .login-form.is-error .key-box,
  .login-form.is-entering .key-box,
  .login-form.is-entering .key-flash,
  .veil.is-active {
    /* Turned off */
    animation: none;
  }
}
