/*
 * TinyOrbit3D — the front door.
 *
 * The palette, the spacing rhythm, the radii and the type scale are lifted
 * straight from the generator's own stylesheet (src/style.css), because the two
 * pages have to read as one product: a paper-and-ink workshop surface, a dot
 * grid like a cutting mat, mono for anything that is a label or a number, and a
 * single saturated orange used only where something is live.
 *
 * The one idea this page adds is that the links are keys. Each one sits on a
 * hard bottom edge and travels down when you press it, the way the switch the
 * clicker is built around does. Nothing else moves.
 *
 * No webfonts, no scripts, no third parties — one HTML file, this file, and the
 * images in ./assets. The brand artwork under ./assets/brand is generated from
 * ./logo by scripts/build-logo-assets.mjs; nothing here redraws it by hand.
 */

/* --------------------------------------------------------------------------
   Tokens — light is the default, dark follows prefers-color-scheme
   -------------------------------------------------------------------------- */

:root {
  --c-bg: #f1f1ee;
  --c-surface: #ffffff;
  --c-surface-2: #f7f7f4;
  --c-line: #dedcd5;
  --c-line-strong: #c6c3b9;
  --c-ink: #18191c;
  --c-ink-2: #55585f;
  --c-accent: #cf4318;
  --c-accent-ink: #ffffff;
  --c-accent-edge: #96300f;
  --c-accent-hairline: rgba(255, 255, 255, 0.5);
  --c-accent-soft: rgba(207, 67, 24, 0.1);
  --c-accent-line: rgba(207, 67, 24, 0.35);
  --c-grid: rgba(24, 25, 28, 0.055);
  --c-glow: rgba(207, 67, 24, 0.1);
  --c-shadow: 0 1px 2px rgba(20, 20, 18, 0.05), 0 10px 28px rgba(20, 20, 18, 0.08);
  --grain-ink: #18191c;
  --grain-opacity: 0.035;

  /* Type scale, extended upward from the app's 13 px base for a page that is
     read at arm's length rather than worked in. */
  --t-micro: 0.6875rem;
  --t-small: 0.8125rem;
  --t-base: 0.875rem;
  --t-body: 1.0625rem;
  --t-lede: 1.0625rem;

  --f-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
    sans-serif;
  --f-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;

  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;

  --r-sm: 4px;
  --r-md: 6px;
  --r-lg: 10px;
  --r-xl: 14px;

  /* Key travel: how far a link drops when it is pressed. */
  --travel: 3px;

  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root {
    --c-bg: #0d0e10;
    --c-surface: #17181b;
    --c-surface-2: #1d1f23;
    --c-line: #2c2f35;
    --c-line-strong: #3d4148;
    --c-ink: #eceef1;
    --c-ink-2: #a6acb5;
    --c-accent: #ff6a33;
    --c-accent-ink: #1a0d06;
    --c-accent-edge: #b8481f;
    --c-accent-hairline: rgba(26, 13, 6, 0.42);
    --c-accent-soft: rgba(255, 106, 51, 0.14);
    --c-accent-line: rgba(255, 106, 51, 0.4);
    --c-grid: rgba(236, 238, 241, 0.05);
    --c-glow: rgba(255, 106, 51, 0.14);
    --c-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 14px 34px rgba(0, 0, 0, 0.5);
    --grain-ink: #ffffff;
    --grain-opacity: 0.03;

    color-scheme: dark;
  }
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100dvh;
  margin: 0;
  color: var(--c-ink);
  font-family: var(--f-sans);
  font-size: var(--t-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;

  /* Three layers, cheapest last: a warm bloom behind the masthead, the 24 px dot
     grid that the app uses on its stage, then the paper itself. */
  background-color: var(--c-bg);
  background-image: radial-gradient(120% 62% at 50% -8%, var(--c-glow) 0%, transparent 68%),
    radial-gradient(var(--c-grid) 1px, transparent 1px);
  background-size: 100% 100%, 24px 24px;
  background-position: center top, center top;
  background-repeat: no-repeat, repeat;
}

h1,
h2,
p {
  margin: 0;
}

/* A single tile of value noise, inlined so the page stays self-contained. It
   takes the flatness off the paper without ever becoming visible as texture. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: var(--grain-opacity);
  background-color: var(--grain-ink);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  -webkit-mask-size: 160px 160px;
  mask-size: 160px 160px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   Frame — a phone-width column, centred and unchanged on desktop
   -------------------------------------------------------------------------- */

.page {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
  width: 100%;
  max-width: 30rem;
  min-height: 100dvh;
  margin-inline: auto;
  padding: max(var(--s-6), env(safe-area-inset-top)) var(--s-5)
    max(var(--s-5), env(safe-area-inset-bottom));
}

/* --------------------------------------------------------------------------
   Masthead
   -------------------------------------------------------------------------- */

.masthead {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-4);
  padding-top: var(--s-4);
}

/*
 * The mark — the real TinyOrbit3D logo, not a drawing of one.
 *
 * The artwork is /assets/brand/mark.svg, cut from logo/Vertical Logo.png by
 * scripts/build-logo-assets.mjs and referenced rather than pasted in, so it
 * cannot go stale behind a change to logo/.
 *
 * It is painted as a MASK over a background colour, which is the whole trick
 * here: the ink is a token, not a pixel. --c-ink resolves to near-black on
 * paper and near-white on the dark theme, so ONE file serves both — 5.7 kB on
 * disk, 2.0 kB on the wire — the swap is instant when the OS theme changes
 * with no second request and no reload, and the white variant is a true white
 * rather than an inverted black PNG with grey antialiased edges.
 *
 * A <picture> with a prefers-color-scheme source was the alternative. It would
 * have cost two rasters per density for line art this fine, and its source is
 * chosen once by the image-selection algorithm — engines have historically not
 * re-run it when the OS theme flips, which would leave a black mark sitting on
 * the dark page while every other token on it had already swapped.
 *
 * The box is declared here instead of being inherited from the image, so it is
 * reserved at first paint and the wordmark under it never moves. Width is the
 * ring's diameter — the aspect ratio adds the bead on top.
 */
.mark {
  display: block;
  width: 68px;
  aspect-ratio: 536 / 612;
}

/*
 * A child rather than .mark itself, because .mark is a .reveal and .reveal
 * animates transform. Two transforms on one element is one transform; this way
 * the box rises with the rest of the masthead and the glyph presses inside it.
 */
.mark::before {
  content: '';
  display: block;
  width: 100%;
  height: 100%;
  background-color: var(--c-ink);
  -webkit-mask-image: url('/assets/brand/mark.svg');
  mask-image: url('/assets/brand/mark.svg');
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;

  /* The ring's centre, not the box's: the bead occupies the top 12%, so
     scaling about 50% 50% would pivot above the circle and look like a nod
     rather than a press. */
  transform-origin: 50% 56%;
}

.wordmark {
  font-size: clamp(2.125rem, 10.5vw, 2.75rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.02;
}

/*
 * Large text (well over 24 px at every viewport), so the accent is held to the
 * 3:1 bar rather than 4.5:1 — it measures 4.15:1 on paper, 6.77:1 on ink.
 *
 * This span and the primary key are now the only accent above the fold. The
 * mark used to be orange too, which was the placeholder taking a liberty the
 * real logo does not offer: the artwork is monochrome, and the brand's own
 * share card uses orange as a rule under the lockup, never as the lockup. Ink
 * for the mark also restores this stylesheet's own rule — the accent marks
 * what is live, and the brand is not a control.
 */
.wordmark-3d {
  color: var(--c-accent);
}

.lede {
  max-width: 30ch;
  color: var(--c-ink-2);
  font-size: var(--t-lede);
  line-height: 1.5;
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   Links
   -------------------------------------------------------------------------- */

.main {
  flex: 1;
}

.links {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

/* The one section label on the page, in the app's vocabulary: mono, uppercase,
   widely tracked, with a hairline running out to the margin. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-1);
  color: var(--c-ink-2);
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.eyebrow::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--c-line-strong);
}

/*
 * A key. It rests on a hard bottom edge, lifts a little under the cursor, and
 * travels the full 3 px down when pressed — the same gesture as the switch it
 * is advertising. Everything else about it is flat.
 */
.link {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  min-height: 5.5rem;
  padding: var(--s-4);
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-xl);
  box-shadow: 0 var(--travel) 0 var(--c-line-strong);
  color: inherit;
  text-decoration: none;
  transition: transform 110ms cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 110ms cubic-bezier(0.4, 0, 0.2, 1), border-color 110ms ease;
}

@media (hover: hover) {
  .link:hover {
    transform: translateY(-1px);
    border-color: var(--c-accent-line);
    box-shadow: 0 calc(var(--travel) + 1px) 0 var(--c-line-strong), var(--c-shadow);
  }
}

.link:active {
  transform: translateY(var(--travel));
  box-shadow: 0 0 0 var(--c-line-strong);
}

.link:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 3px;
}

/* The generator is the reason the page exists, so it is the only filled key. */
.link-primary {
  background: var(--c-accent);
  border-color: var(--c-accent);
  color: var(--c-accent-ink);
  box-shadow: 0 var(--travel) 0 var(--c-accent-edge);
}

@media (hover: hover) {
  .link-primary:hover {
    border-color: var(--c-accent);
    box-shadow: 0 calc(var(--travel) + 1px) 0 var(--c-accent-edge), var(--c-shadow);
  }
}

.link-primary:active {
  box-shadow: 0 0 0 var(--c-accent-edge);
}

/* The accent ring would vanish against the accent key, so this one focuses in
   ink — 15.5:1 against the paper it sits on, 16.6:1 against the dark. */
.link-primary:focus-visible {
  outline-color: var(--c-ink);
}

/* Top-aligned rather than centred, so the number sits on the same baseline band
   as the kicker and the card reads down the left edge like a numbered list. */
.link-index {
  display: grid;
  place-items: center;
  align-self: start;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-md);
  color: var(--c-ink-2);
  font-family: var(--f-mono);
  font-size: var(--t-small);
  letter-spacing: -0.02em;
}

.link-primary .link-index {
  border-color: var(--c-accent-hairline);
  color: var(--c-accent-ink);
}

.link-body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.link-kicker {
  color: var(--c-ink-2);
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.link-primary .link-kicker {
  color: var(--c-accent-ink);
}

.link-title {
  font-size: var(--t-body);
  font-weight: 650;
  letter-spacing: -0.014em;
  line-height: 1.2;
}

.link-note {
  color: var(--c-ink-2);
  font-size: var(--t-base);
  line-height: 1.45;
  text-wrap: pretty;
}

.link-primary .link-note {
  color: var(--c-accent-ink);
}

.link-glyph {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--c-ink-2);
  transition: transform 140ms cubic-bezier(0.4, 0, 0.2, 1);
}

.link-primary .link-glyph {
  color: var(--c-accent-ink);
}

@media (hover: hover) {
  .link:hover .link-glyph {
    transform: translateX(3px);
  }

  .link:hover .link-glyph-out {
    transform: translate(2px, -2px);
  }
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.footer {
  padding-top: var(--s-5);
  border-top: 1px solid var(--c-line);
}

.footer-line {
  color: var(--c-ink-2);
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

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

/*
 * Only ever runs once, and only when motion is welcome: the page settles from
 * the top down and the mark clicks itself once, which is the whole idea of the
 * product stated in half a second. Declared inside the query so that with
 * reduced motion the elements are simply painted where they belong.
 */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    animation: rise 500ms cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
  }

  .reveal-1 {
    animation-delay: 0ms;
  }
  .reveal-2 {
    animation-delay: 60ms;
  }
  .reveal-3 {
    animation-delay: 110ms;
  }
  .reveal-4 {
    animation-delay: 160ms;
  }
  .reveal-5 {
    animation-delay: 210ms;
  }
  .reveal-6 {
    animation-delay: 265ms;
  }
  .reveal-7 {
    animation-delay: 320ms;
  }
  .reveal-8 {
    animation-delay: 390ms;
  }

  .mark::before {
    animation: keypress 620ms cubic-bezier(0.34, 1.4, 0.5, 1) 620ms both;
  }
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Shallower than it was when it drove a 12 px cap inside a 32 px bezel: the
   same travel read across the whole 68 px mark would be a lurch, not a click. */
@keyframes keypress {
  0% {
    transform: scale(1);
  }
  28% {
    transform: scale(0.88);
  }
  100% {
    transform: scale(1);
  }
}

/* --------------------------------------------------------------------------
   Wider than a phone
   -------------------------------------------------------------------------- */

@media (min-width: 34rem) {
  .page {
    gap: var(--s-7);
    padding-block: var(--s-7) var(--s-6);
  }

  .masthead {
    gap: var(--s-5);
  }

  .mark {
    width: 78px;
  }

  .wordmark {
    font-size: 3rem;
  }

  .lede {
    max-width: 34ch;
    font-size: 1.125rem;
  }

  .link {
    padding: var(--s-4) var(--s-5);
    gap: var(--s-4);
  }
}

/* Taller viewports get the column optically centred rather than pinned to the
   top; on a phone it stays where it is so the keys sit under the thumb. */
@media (min-width: 34rem) and (min-height: 46rem) {
  .page {
    justify-content: center;
  }

  .main {
    flex: 0 0 auto;
  }
}

/* On a wide screen the column would otherwise float in the middle of nothing.
   Two hairlines rule its edges, the way a margin is ruled on a drawing sheet,
   and the dot grid carries on past them. */
@media (min-width: 60rem) {
  .page::before,
  .page::after {
    content: '';
    position: fixed;
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--c-line);
    pointer-events: none;
  }

  .page::before {
    left: calc(50% - 15rem);
  }

  .page::after {
    right: calc(50% - 15rem);
  }
}

@media (pointer: coarse) {
  .link {
    min-height: 6rem;
  }
}

/* The smallest phones still in circulation: give the copy back the 16 px the
   outer gutter was taking. */
@media (max-width: 22.5rem) {
  .page {
    padding-inline: var(--s-4);
  }

  .link {
    padding: var(--s-3);
  }
}
