/* The landing page's theme: its own copy of `lucy`, the panel's first.
 *
 * Copied rather than linked, on purpose. The landing page is lucy.fyi from
 * outside and will grow things the panel does not have and should never
 * see, and the panel's stylesheets change for the panel's reasons - so
 * neither may lean on the other. Nothing here is loaded by the panel, and
 * the landing page loads nothing from /static/css. The theme chosen in the
 * panel's settings does not reach it either: this is the only look it has.
 *
 * Taken from the panel at 1.76.1, in three parts, and free to drift apart
 * from them from here on:
 *   the ground     tokens, the background, type    (static/css/base.css)
 *   motion         keyframes and the entrances     (static/css/motion.css)
 *   the sigil      her portrait in its rings       (static/css/components.css)
 *
 * No web fonts, for the panel's reason: a request to somebody else's
 * server to draw four letters is not a trade worth making.
 */

/* ====================================================================== *
 * The ground
 * ====================================================================== */

@property --holo-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

:root {
  --dark:  #0B1248;   /* the ground, never text */
  --mid:   #2D3FAB;   /* frames and secondary detail */
  --light: #7596F3;   /* every accent that has to be read */

  --holo-cyan:   #8FE3FF;
  --holo-violet: #B39CFF;

  --ink-bright: #E9EEFC;
  --ink:        #C8D3F2;
  --ink-soft:   #7E8AB4;
  --ink-faint:  #4A5378;

  --warn:      #F08BA8;
  --warn-ink:  #F6C3D2;

  --surface:      rgba(10, 15, 48, .55);
  --surface-deep: rgba(3, 5, 20, .80);
  --well:         rgba(3, 6, 24, .55);
  --line:         rgba(117, 150, 243, .18);
  --line-strong:  rgba(117, 150, 243, .48);

  /* The box: one look for every container a section is made of - a
   * status card, a message, a row in the workspace, a file being read.
   * Glass with a light blur behind it, a thin line and nothing else: no
   * stripe, no glow. Where there are dozens of them on screen at once (the
   * messages, the rows) they take the still background instead of the
   * blur, because the field behind them changes on every frame and each
   * blurred pane would be blurred again on each one. */
  --box-bg:       rgba(10, 15, 48, .55);
  --box-bg-still: rgba(9, 13, 42, .80);
  --box-line:     var(--line);
  --box-blur:     blur(12px) saturate(120%);

  --radius-sm: 10px;
  --radius:    14px;
  --radius-lg: 22px;

  /* Motion. Almost everything leaves quickly and arrives slowly: the
   * expo-out curve is what makes a thing feel like it settled rather
   * than stopped. kit.js carries the same numbers for the animations
   * that are started from script. */
  --ease-out:    cubic-bezier(.16, 1, .3, 1);
  --ease-in:     cubic-bezier(.7, 0, .84, 0);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
  --ease-glide:  cubic-bezier(.22, 1.2, .36, 1);   /* arrives, just past, and back */

  --t-fast:   160ms;
  --t:        320ms;
  --t-slow:   720ms;
  --t-reveal: 1100ms;
  --stagger:  70ms;

  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
  --serif: ui-serif, Georgia, "Iowan Old Style", "Times New Roman", serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  color-scheme: dark;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100dvh;
  /* Clear of the notch and the rounded corners on a phone that has them
   * (viewport-fit=cover); everywhere else the insets are zero. */
  padding-block:
    calc(clamp(1.5rem, 5vh, 4rem) + env(safe-area-inset-top, 0px))
    calc(clamp(1.5rem, 5vh, 4rem) + env(safe-area-inset-bottom, 0px));
  padding-inline:
    max(20px, env(safe-area-inset-left, 0px))
    max(20px, env(safe-area-inset-right, 0px));
  overscroll-behavior: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  background: #000;
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* Centred by auto margins rather than `justify-content: center`. Once the
 * page is taller than the window, centring pushes the top of it above the
 * edge where no scrolling reaches; auto margins just stop being centred. */
body > main { margin-block: auto; }

::selection { background: rgba(117, 150, 243, .35); color: #fff; }

/* The ground: two blooms of her dark blue, low and wide, and a vignette
 * over them so the edges of the screen stay properly black. Fixed rather
 * than scrolled - on a short phone the page moves and the light should
 * not move with it. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  background:
    radial-gradient(120vw 90vh at 50% 22%, rgba(45, 63, 171, .22), transparent 62%),
    radial-gradient(80vw 60vh at 50% 50%,  rgba(117, 150, 243, .07), transparent 68%),
    radial-gradient(100vw 80vh at 50% 100%, rgba(11, 18, 72, .85), transparent 70%),
    #000;
}

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(70vw 70vh at 50% 40%, transparent 30%, rgba(0,0,0,.78) 100%);
}

#field {
  position: fixed;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* Black over everything, lifting as the page arrives and falling again
 * as it leaves. A CSS animation rather than a class set from script, so
 * a page whose script never runs is still a page and not a black screen. */
.veil {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #000;
  pointer-events: none;
  animation: veil-lift 900ms var(--ease-out) both;
}

.veil.is-falling {
  pointer-events: all;
  animation: veil-fall 560ms var(--ease-in-out) both;
}

/* ---------------------------------------------------------------------- *
 * Type
 * ---------------------------------------------------------------------- */

.wordmark {
  margin: 0;
  font-size: clamp(2.3rem, 9vw, 4rem);
  font-weight: 400;
  /* Tracking this wide leaves a gap after the last letter that the eye
   * reads as the whole line being off-centre. Half of it back as padding
   * puts the letters where they look centred. */
  letter-spacing: .36em;
  padding-left: .36em;
  line-height: 1;
  background: linear-gradient(170deg, #FFFFFF 0%, #C8D3F2 38%, var(--light) 82%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 18px rgba(117, 150, 243, .14));
}

.wordmark--sm { font-size: clamp(1.7rem, 7vw, 2.4rem); }

.expansion {
  margin: 1rem 0 0;
  font-size: clamp(.56rem, 2vw, .68rem);
  font-weight: 400;
  letter-spacing: .42em;
  padding-left: .42em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.expansion i { font-style: normal; color: var(--mid); }

@media (max-width: 470px) {
  .expansion { letter-spacing: .24em; padding-left: .24em; }
}

.eyebrow {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 0 0 .75rem;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--light);
}

.dot {
  flex: none;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--light);
  box-shadow: 0 0 5px rgba(117, 150, 243, .45);
  animation: pulse 3.4s ease-in-out infinite;
}

.rule {
  display: flex;
  align-items: center;
  gap: 1rem;
  width: min(20rem, 70%);
  margin: clamp(1.2rem, 3vh, 2.1rem) auto;
}

.rule span {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(117, 150, 243, .38), transparent);
}

.rule b {
  font-weight: 400;
  font-size: .6rem;
  color: var(--light);
  opacity: .75;
}

.build {
  margin: 1.4rem 0 0;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .12em;
  color: #2A3154;
  text-align: center;
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ====================================================================== *
 * Motion
 * ====================================================================== */

@keyframes turn    { to { transform: rotate(360deg); } }
@keyframes holo-turn { to { --holo-angle: 360deg; } }

@keyframes breathe {
  0%, 100% { opacity: .92; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.012); }
}

@keyframes pulse {
  0%, 100% { opacity: .35; }
  50%      { opacity: 1; }
}

@keyframes blink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

@keyframes veil-lift { from { opacity: 1; } to { opacity: 0; } }
@keyframes veil-fall { from { opacity: 0; } to { opacity: 1; } }

/* Arrivals */
@keyframes rise-in {
  from { opacity: 0; transform: translate3d(0, 16px, 0); }
  to   { opacity: 1; transform: none; }
}

@keyframes blur-in {
  from { opacity: 0; transform: translate3d(0, 8px, 0); filter: blur(8px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

@keyframes scale-in {
  from { opacity: 0; transform: scale(.86); filter: blur(12px) brightness(1.6); }
  to   { opacity: 1; transform: none; filter: blur(0) brightness(1); }
}

/* Letters drawn together out of a wide, unfocused line. */
@keyframes focus-in {
  from { opacity: 0; letter-spacing: 1em; padding-left: 1em; filter: blur(14px); }
  to   { opacity: 1; filter: blur(0); }
}

/* Built from the top down, bright while it forms. */
@keyframes materialize {
  0%   { opacity: 0; clip-path: inset(0 0 100% 0); filter: brightness(2.2) saturate(0); }
  35%  { opacity: 1; }
  100% { opacity: 1; clip-path: inset(0 0 0 0); filter: brightness(1) saturate(1); }
}

@keyframes digit-in {
  from { opacity: 0; transform: translateY(9px) scale(.7); filter: blur(5px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

/* Light */
@keyframes sheen {
  0%, 100% { background-position: 0 0, 160% 0; }
  50%      { background-position: 0 0, -60% 0; }
}

@keyframes shimmer {
  from { background-position: 180% 0; }
  to   { background-position: -80% 0; }
}

@keyframes ripple {
  from { opacity: .8; transform: scale(1); }
  to   { opacity: 0;  transform: scale(1.9); }
}

@keyframes flare {
  0%   { opacity: .92; transform: scale(1);    filter: blur(14px) brightness(1); }
  30%  { opacity: 1;   transform: scale(1.25); filter: blur(18px) brightness(2); }
  100% { opacity: 1;   transform: scale(1.1);  filter: blur(16px) brightness(1.4); }
}

@keyframes dots {
  0%, 80%, 100% { opacity: .25; transform: scale(.65); }
  40%           { opacity: 1;   transform: scale(1); }
}

/* A stroked icon drawing itself (shapes carry pathLength="1"). */
@keyframes draw {
  from { stroke-dasharray: 1; stroke-dashoffset: 1; }
  to   { stroke-dasharray: 1; stroke-dashoffset: 0; }
}

/* A streak of light running down an edge, then a pause. */
@keyframes edge-run {
  0%        { top: -40%; }
  60%, 100% { top: 100%; }
}

@keyframes scan-down {
  0%   { top: 0;    opacity: 0; }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { top: 100%; opacity: 0; }
}

/* ---------------------------------------------------------------------- *
 * Entrance classes
 * ---------------------------------------------------------------------- */

.enter {
  animation: rise-in var(--t-reveal) var(--ease-out) both;
  animation-delay: calc(var(--delay, 120ms) + var(--i, 0) * var(--stagger));
}

.enter--blur  { animation-name: blur-in; }
.enter--fade  { animation-name: fade-in; }
.enter--scale { animation-name: scale-in; animation-duration: 1500ms; }
.enter--focus { animation-name: focus-in; animation-duration: 1600ms; }

/* Text that is waiting on something. */
.shimmer-text {
  background: linear-gradient(90deg, var(--ink-soft) 0%, var(--ink-bright) 50%, var(--ink-soft) 100%) 0 0 / 220% 100%;
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  animation: shimmer 2.6s linear infinite;
}

/* On a phone, arrivals without the blur. A blur is drawn again on every
 * frame it changes, and a page arriving is the worst moment to stutter. */
@media (pointer: coarse), (max-width: 560px) {
  .enter--blur { animation-name: fade-in; }
  .enter--scale { animation-name: fade-in; animation-duration: 900ms; }
}

/* Somebody who has asked their system to stop things moving has asked
 * for this too. Everything lands in its final state at once; nothing in
 * the layout depends on having watched it get there. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
  #field { display: none; }
}

/* ====================================================================== *
 * The sigil
 * ====================================================================== */

/* ---------------------------------------------------------------------- *
 * Sigil
 *
 * Masked into the dark instead of cropped to a circle. A hard edge reads
 * as an avatar; a fade reads as somebody standing just far enough into
 * the light to be seen.
 * ---------------------------------------------------------------------- */

.sigil {
  --size: 150px;
  position: relative;
  width: var(--size);
  height: var(--size);
  display: grid;
  place-items: center;
}

.sigil picture, .sigil img {
  display: block;
  width: 100%;
  height: 100%;
}

.sigil img {
  position: relative;
  object-fit: cover;
  border-radius: 50%;
  -webkit-mask-image: radial-gradient(circle at 50% 46%, #000 52%, transparent 76%);
          mask-image: radial-gradient(circle at 50% 46%, #000 52%, transparent 76%);
  animation: breathe 9s ease-in-out infinite;
}

.sigil__bloom {
  position: absolute;
  inset: -19%;
  border-radius: 50%;
  z-index: -1;
  background: radial-gradient(circle, rgba(117, 150, 243, .18) 0%, rgba(45, 63, 171, .10) 38%, transparent 68%);
  filter: blur(14px);
  animation: breathe 9s ease-in-out infinite;
}

/* Two rings, turning at different speeds in opposite directions: a conic
 * gradient punched out in the middle, so what is left is a thread of
 * light going round rather than a border. */
.sigil__halo {
  position: absolute;
  inset: -8%;
  border-radius: 50%;
  pointer-events: none;
  background: conic-gradient(from 0deg,
    transparent 0deg,
    rgba(117, 150, 243, .00) 40deg,
    rgba(117, 150, 243, .45) 92deg,
    rgba(255, 255, 255, .55) 104deg,
    rgba(143, 227, 255, .35) 118deg,
    rgba(45, 63, 171, .00) 190deg,
    transparent 360deg);
  -webkit-mask: radial-gradient(closest-side, transparent 97.2%, #000 97.6%);
          mask: radial-gradient(closest-side, transparent 97.2%, #000 97.6%);
  animation: turn 26s linear infinite;
}

.sigil__halo--outer {
  inset: -17%;
  opacity: .45;
  background: conic-gradient(from 180deg,
    transparent 0deg,
    rgba(45, 63, 171, .00) 30deg,
    rgba(179, 156, 255, .45) 70deg,
    rgba(200, 211, 242, .60) 78deg,
    rgba(45, 63, 171, .00) 150deg,
    transparent 360deg);
  -webkit-mask: radial-gradient(closest-side, transparent 98.4%, #000 98.7%);
          mask: radial-gradient(closest-side, transparent 98.4%, #000 98.7%);
  animation: turn 64s linear infinite reverse;
}

.sigil__pulse {
  position: absolute;
  inset: -8%;
  border-radius: 50%;
  border: 1px solid rgba(143, 227, 255, .7);
  box-shadow: 0 0 24px rgba(143, 227, 255, .35), inset 0 0 24px rgba(143, 227, 255, .2);
  opacity: 0;
  pointer-events: none;
}

.sigil__pulse + .sigil__pulse { inset: -8%; }

/* Recognised: the light flares and two rings go out from her. */
.sigil.is-awake .sigil__bloom { animation: flare 1400ms var(--ease-out) forwards; }
.sigil.is-awake .sigil__pulse { animation: ripple 1300ms var(--ease-out) both; }
.sigil.is-awake .sigil__pulse + .sigil__pulse { animation-delay: 260ms; }

.sigil--sm { --size: 34px; }
.sigil--sm .sigil__halo--outer, .sigil--sm .sigil__bloom { display: none; }
