/*
 * dasgeorg.cc landing page.
 * Built on georg-ds tokens only (georg.css + georg-website-tokens.css): no hard-coded colors.
 * Brand rules: purple (--accent) at most once per screen; primary buttons are neutral.
 */

/* ── Layout constants (page-level, not design tokens) ──────── */
:root {
  --page-gutter: clamp(8px, 2vw, 16px);
  --content-width: 1080px;
  --nav-offset: calc(var(--page-gutter) + 12px);
}

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: 96px;
}

body {
  margin: 0;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  font-size: var(--text-md);
  /* A faint accent glow behind the product cards, so the page is not one flat color.
     Kept at 5%: --text-3 on the page background must stay at WCAG AA (4.5:1) in light mode. */
  background:
    radial-gradient(
      60% 40% at 50% 72%,
      color-mix(in srgb, var(--accent) 5%, transparent),
      transparent 70%
    ),
    radial-gradient(
      45% 30% at 100% 45%,
      color-mix(in srgb, var(--accent) 3%, transparent),
      transparent 70%
    ),
    var(--bg);
}

p {
  margin: 0;
}

/* Author display rules (such as .button) would otherwise override the hidden attribute */
[hidden] {
  display: none !important;
}

a {
  color: inherit;
  text-decoration: none;
  transition: color var(--dur-1) var(--ease-out);
}

.skip-link {
  position: absolute;
  top: var(--nav-offset);
  left: var(--nav-offset);
  z-index: 20;
  padding: 8px 14px;
  border-radius: var(--r-pill);
  background: var(--text);
  color: var(--bg);
  font-size: var(--text-sm);
  font-weight: 500;
  transform: translateY(-200%);
}
.skip-link:focus-visible {
  transform: none;
}

/* ── Floating pill nav ─────────────────────────────────────── */
.site-header {
  position: fixed;
  inset: var(--nav-offset) 0 auto;
  z-index: 10;
  display: flex;
  justify-content: center;
  padding-inline: var(--page-gutter);
  pointer-events: none;
}

.nav {
  position: relative;
  display: flex;
  align-items: center;
  gap: 28px;
  padding: 8px 8px 8px 20px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  /* Opaque enough that --text-2 keeps AA contrast over the dark hero */
  background: color-mix(in srgb, var(--bg-elev) 70%, transparent);
  backdrop-filter: blur(12px) saturate(1.4);
  box-shadow: var(--shadow-2);
  pointer-events: auto;
}

.wordmark {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav-links a {
  display: block;
  padding: 8px 12px;
  border-radius: var(--r-pill);
  /* --text, not --text-2: at 70% opacity over the dark hero, --text-2 drops to about 3.6:1 in light mode */
  color: var(--text);
  font-size: var(--text-base);
  font-weight: 500;
}
.nav-links a:hover {
  color: var(--text);
  background: var(--bg-hover);
}
.nav-links .mono {
  font-size: var(--text-sm);
}

/* With site.js, one pill slides between links instead of each link lighting up on its own */
.nav.has-indicator .nav-links a:hover {
  background: transparent;
}
.nav-links a {
  position: relative;
}

.nav-indicator {
  position: absolute;
  top: 8px;
  bottom: 8px;
  left: 0;
  width: var(--indicator-width, 0);
  border-radius: var(--r-pill);
  background: var(--bg-hover);
  opacity: 0;
  transform: translateX(var(--indicator-x, 0));
  transition:
    transform var(--dur-3) var(--ease-out),
    width var(--dur-3) var(--ease-out),
    opacity var(--dur-2) var(--ease-out);
  pointer-events: none;
}
.nav-indicator.is-visible {
  opacity: 1;
}
.nav-indicator.is-placing {
  transition: opacity var(--dur-2) var(--ease-out);
}

.icon {
  width: var(--icon);
  height: var(--icon);
  flex: none;
  stroke-width: var(--icon-stroke);
}

/* Phones: the wordmark and the contact address are enough */
@media (max-width: 560px) {
  .nav {
    gap: 12px;
  }
  .nav-product {
    display: none;
  }
}

/* ── Dark hero ─────────────────────────────────────────────── */
main {
  flex: 1;
}

.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  place-items: center;
  min-height: min(88svh, 860px);
  /* Aligned with the products on wide screens; never touching the screen edges on phones */
  width: min(100% - 2 * var(--page-gutter), var(--content-width) + 2 * 24px);
  margin: 0 auto;
  padding: 128px 24px 96px;
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  background: var(--bg);
  color: var(--text);
  text-align: center;
}

/* Hero glow: accent light from the top edge and a softer one in the lower corner, drifting slowly.
   The top glow fades out above the headline (about 30% of the hero's height, drift included), so text
   never sits on it; the corner glow ends before the buttons. Contrast holds without relying on it. */
.hero::before {
  content: "";
  position: absolute;
  inset: -10%;
  z-index: -2;
  background:
    radial-gradient(
      55% 40% at 50% 0%,
      color-mix(in srgb, var(--accent) 26%, transparent),
      transparent 70%
    ),
    radial-gradient(
      35% 30% at 88% 100%,
      color-mix(in srgb, var(--accent) 14%, transparent),
      transparent 70%
    ),
    radial-gradient(
      30% 25% at 8% 85%,
      color-mix(in srgb, var(--text) 4%, transparent),
      transparent 70%
    );
  animation: georg-website-glow-drift 22s ease-in-out infinite alternate;
  pointer-events: none;
}

/* Sparkles canvas (scripts/sparkles.js): concentrated around the headline, fading toward the edges */
.sparkles {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  color: var(--text);
  opacity: 0;
  transition: opacity 1.2s var(--ease-out);
  pointer-events: none;
}
/* Masks are alpha only: just the opacity of each stop matters, not the color */
.hero .sparkles {
  mask-image: radial-gradient(ellipse 65% 60% at 50% 45%, black 25%, transparent 100%);
}
.sparkles.is-ready {
  opacity: 1;
}

.hero-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
  max-width: 920px;
}

.hero-title {
  margin: 0;
  font-size: var(--text-display);
  line-height: 1.04;
  letter-spacing: -0.025em;
}
.hero-line {
  display: block;
}
.hero-title .word {
  display: inline-block;
}
.hero-title-soft {
  font-weight: 300;
  color: var(--text-3);
}

/* Generated-text cursor (georg-ds pattern): blinks after the headline lands, then goes away */
.cursor {
  display: inline-block;
  width: 0.07em;
  height: 0.78em;
  margin-left: 0.08em;
  border-radius: 1px;
  background: var(--accent);
  opacity: 0;
  vertical-align: -0.04em;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}

.hero-subhead {
  max-width: 36ch;
  color: var(--text-2);
  font-size: var(--text-lg);
  line-height: 1.5;
  text-wrap: balance;
}

/* 404: the hero fills the window */
.not-found {
  min-height: calc(100svh - 2 * var(--page-gutter));
  padding-top: 96px;
}
.not-found .hero-content {
  gap: 24px;
}
.not-found .wordmark {
  font-size: var(--text-xl);
}

/* ── Buttons ───────────────────────────────────────────────── */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  border: 1px solid transparent;
  font-family: inherit;
  cursor: pointer;
  padding: 14px 32px;
  border-radius: var(--r-pill);
  font-size: var(--text-md);
  font-weight: 500;
  line-height: 1;
  transition:
    background-color var(--dur-1) var(--ease-out),
    transform var(--dur-1) var(--ease-out);
}
.button:active {
  transform: scale(0.98);
}
.button:focus-visible {
  border-radius: var(--r-pill);
}

/* Primary buttons stay neutral, never purple */
.button-primary {
  background: var(--text);
  color: var(--bg);
}
.button-primary:hover {
  background: var(--text-2);
}

.button-arrow {
  transition: transform var(--dur-3) var(--ease-out);
}
.button:hover .button-arrow,
.button:focus-visible .button-arrow {
  transform: translateX(3px);
}

.button-secondary {
  border-color: var(--border-strong);
  background: transparent;
  color: var(--text);
  transition:
    background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-2) var(--ease-out),
    transform var(--dur-1) var(--ease-out);
}
.button-secondary:hover {
  background: var(--bg-hover);
  border-color: var(--text-3);
}

/* ── Copy (georg-ds pattern): icon morphs into a check, the label slides to "Copied" ── */
.copy-icons,
.copy-label {
  display: grid;
}
.copy-icons > *,
.copy-label > * {
  grid-area: 1 / 1;
}

.copy-icons .icon {
  transition:
    transform var(--dur-3) var(--ease-spring),
    opacity var(--dur-2) var(--ease-out);
}
.icon-check {
  opacity: 0;
  transform: scale(0.4) rotate(-45deg);
}

.copy-label > * {
  justify-self: start;
  transition:
    transform var(--dur-3) var(--ease-out),
    opacity var(--dur-2) var(--ease-out);
}
.copy-idle {
  font-size: var(--text-base);
}
.copy-done {
  opacity: 0;
  transform: translateY(6px);
}

.copy-button.is-copied {
  color: var(--success);
}
.copy-button.is-copied .icon-copy {
  opacity: 0;
  transform: scale(0.4) rotate(45deg);
}
.copy-button.is-copied .icon-check {
  opacity: 1;
  transform: none;
}
.copy-button.is-copied .copy-idle {
  opacity: 0;
  transform: translateY(-6px);
}
.copy-button.is-copied .copy-done {
  opacity: 1;
  transform: none;
}

/* ── Products ──────────────────────────────────────────────── */
.products {
  width: min(100% - 2 * var(--page-gutter), var(--content-width) + 2 * 24px);
  margin: 0 auto;
  padding: 80px 0;
}

/* A label, not a display heading: Stack Sans Notch is never used below 15px */
.products > .label {
  margin: 0 0 20px;
  font-family: var(--font);
  font-weight: 500;
  letter-spacing: 0.06em;
  line-height: 1.45;
}

.product-grid {
  display: grid;
  gap: 16px;
}
@media (min-width: 900px) {
  .product-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Each card spans three rows of the grid (header, tagline, descriptor) through subgrid, so the
   taglines and descriptors of every card line up even when a tagline wraps to more lines. */
.product {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  row-gap: 12px;
  align-content: start;
  padding: 28px;
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  background: var(--bg-elev);
  box-shadow: var(--shadow-1);
  /* `translate`, not `transform`: the scroll reveal animates `transform`, and the two compose */
  transition:
    translate var(--dur-3) var(--ease-out),
    box-shadow var(--dur-3) var(--ease-out),
    border-color var(--dur-2) var(--ease-out);
}
.product:hover {
  translate: 0 -3px;
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border-strong));
  box-shadow: var(--shadow-2);
}

/* Border beam: a short streak of accent light travelling around each card, just inside its border.
   A conic gradient turns with a registered --beam-angle; the mask keeps only a 1.5px ring.
   The overflow: hidden of the card clips at the padding edge, so the ring sits inside the border. */
@property --beam-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.product::after {
  --beam-width: 1.5px;
  content: "";
  position: absolute;
  inset: 0;
  padding: var(--beam-width);
  border-radius: calc(var(--r-3) - 1px);
  background: conic-gradient(
    from var(--beam-angle),
    transparent 0 72%,
    color-mix(in srgb, var(--accent) 35%, transparent) 82%,
    var(--accent) 92%,
    var(--accent-2) 96%,
    transparent 100%
  );
  /* Alpha masks: the content box is cut out of the full box, leaving the ring */
  mask:
    linear-gradient(black 0 0) content-box exclude,
    linear-gradient(black 0 0);
  animation: georg-website-beam 9s linear infinite;
  pointer-events: none;
}
/* Out of step between cards, so the three beams never move together */
.product:nth-child(2)::after {
  animation-delay: -3s;
}
.product:nth-child(3)::after {
  animation-delay: -6s;
}

/* Card sparkles: purple, gathered along the borders and corners so the text in the middle stays clear */
.product .sparkles {
  color: var(--accent);
  mask-image: radial-gradient(ellipse 72% 68% at 50% 50%, transparent 40%, black 100%);
}

.product-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 40px;
}

.product-name {
  margin: 0;
  font-size: var(--text-lg);
}

.product-tagline {
  font-size: var(--text-2xl);
  line-height: 1.12;
}

.product-descriptor {
  color: var(--text-2);
}

.status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  color: var(--text-2);
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
}

/* Every status dot ripples (georg-ds "Live waiting"): neutral and slower while a product is in
   development, purple for the one that is live. The cards ripple out of step, never in unison. */
.status-dot {
  --dot-color: var(--text-3);
  --ping-duration: 2.4s;
  position: relative;
  width: 6px;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--dot-color);
}
.status-dot::after {
  content: "";
  position: absolute;
  inset: -4px;
  border: 1.5px solid var(--dot-color);
  border-radius: inherit;
  animation: georg-website-ping var(--ping-duration) var(--ease-out) infinite;
}
.product:nth-child(2) .status-dot::after {
  animation-delay: -0.8s;
}
.product:nth-child(3) .status-dot::after {
  animation-delay: -1.6s;
}

/* The one purple detail on the page: Georg mail is live */
.status-live .status-dot {
  --dot-color: var(--accent);
  --ping-duration: 1.6s;
  box-shadow: var(--accent-glow);
}

/* ── Footer ────────────────────────────────────────────────── */
.site-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 24px;
  width: 100%;
  max-width: calc(var(--content-width) + 2 * 24px);
  margin: 0 auto;
  padding: 24px 24px 40px;
  border-top: 1px solid var(--border);
  color: var(--text-3);
  font-size: var(--text-sm);
}
.site-footer a {
  color: var(--text-2);
}
.site-footer a:hover {
  color: var(--text);
}

/* ── Toasts (georg-ds pattern) ─────────────────────────────── */
.toasts {
  position: fixed;
  left: 50%;
  bottom: 20px;
  z-index: 30;
  display: grid;
  justify-items: center;
  transform: translateX(-50%);
}

.toast {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  background: var(--bg-elev);
  box-shadow: var(--shadow-2);
  font-size: var(--text-sm);
  white-space: nowrap;
  animation: georg-website-toast-in 360ms var(--ease-spring);
}
.toast-icon {
  color: var(--success);
}
.toast.is-error .toast-icon {
  display: none;
}
.toast.is-bumped {
  animation: georg-website-bump 260ms var(--ease-spring);
}
/* Leaving is faster than arriving */
.toast.is-leaving {
  animation: georg-website-toast-out 140ms var(--ease-out) forwards;
}

/* ── Motion ────────────────────────────────────────────────── */
@keyframes georg-website-reveal {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

@keyframes georg-website-word-in {
  from {
    opacity: 0;
    transform: translateY(0.35em);
  }
}
@keyframes georg-website-cursor {
  0%,
  50% {
    opacity: 1;
  }
  50.01%,
  100% {
    opacity: 0;
  }
}
@keyframes georg-website-ping {
  from {
    transform: scale(0.4);
    opacity: 0.9;
  }
  to {
    transform: scale(1.4);
    opacity: 0;
  }
}
@keyframes georg-website-toast-in {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.96);
  }
}
@keyframes georg-website-toast-out {
  to {
    opacity: 0;
    transform: translateY(6px) scale(0.98);
  }
}
@keyframes georg-website-bump {
  50% {
    transform: scale(1.03);
  }
}
@keyframes georg-website-beam {
  to {
    --beam-angle: 360deg;
  }
}
@keyframes georg-website-glow-drift {
  to {
    transform: translate3d(3%, 4%, 0) scale(1.08);
  }
}
@keyframes georg-website-hero-exit {
  to {
    opacity: 0.15;
    transform: translateY(72px) scale(0.98);
  }
}

/* Headline: words land in a 45 ms cascade, line by line */
.hero-title .word {
  animation: georg-website-word-in 720ms var(--ease-out) both;
}
.hero-line:first-child .word:nth-child(1) {
  animation-delay: 60ms;
}
.hero-line:first-child .word:nth-child(2) {
  animation-delay: 105ms;
}
.hero-line:first-child .word:nth-child(3) {
  animation-delay: 150ms;
}
.hero-line:first-child .word:nth-child(4) {
  animation-delay: 195ms;
}
.hero-title-soft .word:nth-child(1) {
  animation-delay: 300ms;
}
.hero-title-soft .word:nth-child(2) {
  animation-delay: 345ms;
}
.cursor {
  animation: georg-website-cursor 1s linear 900ms 3;
}

.hero .reveal {
  animation: georg-website-reveal 720ms var(--ease-out) both;
}
.hero-subhead.reveal {
  animation-delay: 480ms;
}
.hero-actions.reveal {
  animation-delay: 580ms;
}

/* While the hero scrolls away, its content drifts down and fades: a light parallax.
   A page-scroll timeline starting at 0, so the hero is untouched until the first scroll. */
@supports (animation-timeline: scroll()) {
  .hero-content {
    animation: georg-website-hero-exit linear both;
    animation-timeline: scroll(root block);
    animation-range: 0 70svh;
  }
}

/* Product cards reveal as they scroll into view, where the browser supports it; otherwise they are simply visible */
@supports (animation-timeline: view()) {
  .product.reveal {
    animation: georg-website-reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 40%;
  }
  .product.reveal:nth-child(2) {
    animation-range: entry 10% entry 50%;
  }
  .product.reveal:nth-child(3) {
    animation-range: entry 20% entry 60%;
  }
}

/* georg.css shortens durations under reduced motion, but delays and scroll timelines would still hide content */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .reveal,
  .hero-title .word,
  .hero-content,
  .hero::before {
    animation: none !important;
  }
  .cursor,
  .status-dot::after,
  .product::after {
    display: none;
  }
}
