/* ==========================================================================
   Packages - design system
   Layout / type / motion study rebuilt from scratch.
   ========================================================================== */

:root {
  --radius: 1rem;

  /* The brand accent, sampled from the reference artwork (#FDFB04). Every
     yellow on the site resolves to this one value. */
  --brand: oklch(95.81% 0.2077 109.2);
  --brand-dim: oklch(86% 0.18 109.2);
  --brand-ink: oklch(14.5% 0.002 286);   /* text that sits on the yellow */
  --brand-tint: oklch(26% 0.07 109.2);   /* yellow-tinted dark surface */
  --brand-line: oklch(95.81% 0.2077 109.2 / 0.38);

  --background: oklch(14.5% 0.002 286);
  --foreground: oklch(94.7% 0.003 286);
  --ink: oklch(94.7% 0.003 286);
  --card: oklch(19.2% 0.004 286);
  --muted: oklch(22.8% 0.006 286);
  --muted-foreground: oklch(68.9% 0.012 286);
  --border: oklch(28.7% 0.007 286);

  /* Ink at the alphas the sheet uses for secondary and tertiary text. */
  --ink-78: oklch(94.7% 0.003 286 / 0.78);
  --ink-65: oklch(94.7% 0.003 286 / 0.65);
  --ink-62: oklch(94.7% 0.003 286 / 0.62);
  --ink-60: oklch(94.7% 0.003 286 / 0.6);
  --ink-35: oklch(94.7% 0.003 286 / 0.35);
  --ink-30: oklch(94.7% 0.003 286 / 0.3);
  --ink-22: oklch(94.7% 0.003 286 / 0.22);

  /* The old palette kept its names so nothing downstream had to be renamed.
     The brand hue is now the sampled yellow; the status hues keep their
     meaning and move to lightnesses that read on a dark ground. */
  --violet: var(--brand);
  --violet-soft: var(--brand-tint);
  --coral: oklch(80% 0.006 286);
  --sky: oklch(76% 0.008 286);

  --glass: oklch(19.2% 0.004 286 / 0.62);
  --glass-strong: oklch(19.2% 0.004 286 / 0.84);
  --hairline: oklch(100% 0 0 / 0.1);

  /* Shadows on a dark ground come from deeper black, not from tinted grey. */
  --shadow-soft: 0 1px 2px oklch(0% 0 0 / 0.5), 0 12px 32px oklch(0% 0 0 / 0.45);
  --shadow-lift: 0 2px 6px oklch(0% 0 0 / 0.55), 0 24px 60px oklch(0% 0 0 / 0.6);

  --atmosphere: linear-gradient(180deg, oklch(13.5% 0.003 286), oklch(16% 0.004 286) 58%, oklch(14% 0.003 286));

  /* decorative "developer surface" panels behind the hero */
  --dev-line: oklch(100% 0 0 / 0.1);
  --dev-text: oklch(88% 0.004 286);
  --dev-dim: oklch(62% 0.01 286);
  --dev-blue: oklch(72% 0.008 286);
  --dev-panel: oklch(20.5% 0.005 286 / 0.9);
  --dev-panel-shadow: 0 20px 70px oklch(0% 0 0 / 0.55), 0 2px 12px oklch(0% 0 0 / 0.4);

  --sans: "Geist", "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --shell: 1152px;   /* max-w-6xl */
  --rail: 768px;     /* max-w-3xl */
  /* Kept at its original height although the bar it was named for is gone:
     the nav, the hero and every page head are positioned from it, and zeroing
     it would move the whole composition up by 32px. */
  --ca-bar: 32px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* --------------------------------------------------------------- reset -- */

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

* { margin: 0; padding: 0; }

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

body {
  min-height: 100vh;
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  letter-spacing: -0.011em;
  color: var(--ink);
  background: var(--background);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

img, svg { display: block; max-width: 100%; }
button, input, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4 { font-weight: 500; letter-spacing: -0.03em; line-height: 1.05; }
ul { list-style: none; }

:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 3px;
  border-radius: 6px;
}

/* ------------------------------------------------------- page scaffold -- */

.atmosphere {
  position: relative;
  min-height: 100vh;
  background: var(--atmosphere);
}

/* soft violet bloom that sits behind the hero */
.atmosphere::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 900px;
  pointer-events: none;
  /* Deliberately empty. Three coloured washes used to sit here and they read
     as stage lighting rather than as a dark surface. Tinting them yellow
     would be the same effect in the brand colour, so they are gone instead.
     The element stays so the hero's stacking and positioning are unchanged. */
  background: none;
}

.shell {
  position: relative;
  z-index: 10;
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: 20px;
}

.rail { max-width: var(--rail); margin-inline: auto; }

main { position: relative; z-index: 10; }
.ca-bar__copy.is-done { color: var(--brand); }

/* ----------------------------------------------------------- nav pill -- */

.nav {
  position: fixed;
  inset-inline: 0;
  top: calc(var(--ca-bar) + 12px);
  z-index: 60;
  display: flex;
  justify-content: center;
  padding-inline: 16px;
  pointer-events: none;
}

.nav__inner {
  pointer-events: auto;
  display: flex;
  width: 100%;
  max-width: var(--rail);
  align-items: center;
  gap: 4px;
  padding: 9px 10px;
  border-radius: 9999px;
  background: var(--glass-strong);
  backdrop-filter: blur(28px) saturate(1.8);
  -webkit-backdrop-filter: blur(28px) saturate(1.8);
  box-shadow: var(--shadow-soft);
  transition: box-shadow 0.5s var(--ease), background-color 0.5s var(--ease);
}

.nav.is-stuck .nav__inner { box-shadow: var(--shadow-lift); }

.nav__brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding-inline: 8px 12px;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.02em;
}

.nav__mark { width: 20px; height: 20px; border-radius: 6px; }

.nav__links {
  display: none;
  align-items: center;
  gap: 2px;
}

.nav__link {
  padding: 7px 12px;
  border-radius: 9999px;
  font-size: 13.5px;
  white-space: nowrap;
  color: var(--ink-62);
  transition: color 0.18s, background-color 0.18s;
}

.nav__link:hover { color: var(--ink); background: oklch(0% 0 0 / 0.5); }
.nav__link[aria-current="page"] { color: var(--ink); background: oklch(100% 0 0 / 0.08); }

.nav__spacer { flex: 1; }

/* ----------------------------------------------------------- buttons --- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-radius: 9999px;
  font-weight: 500;
  white-space: nowrap;
  transition: transform 0.18s var(--ease), box-shadow 0.25s var(--ease),
              background-color 0.2s, color 0.2s;
}

.btn:active { transform: scale(0.97); }

/* A control that cannot act yet says so by looking inert, rather than looking
   ready and doing nothing when pressed. */
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.38;
  cursor: not-allowed;
  box-shadow: none;
}

.btn:disabled:hover,
.btn[aria-disabled="true"]:hover { box-shadow: none; }
.btn:disabled:active,
.btn[aria-disabled="true"]:active { transform: none; }

.btn--sm { height: 36px; padding-inline: 16px; font-size: 13px; }
.btn--lg { height: 56px; padding-inline: 28px; font-size: 15px; }

.btn--ink { background: var(--brand); color: var(--brand-ink); }
.btn--ink:hover { background: oklch(80% 0.01 286); box-shadow: var(--shadow-lift); }

.btn--glass {
  background: var(--glass);
  color: var(--ink);
  box-shadow: var(--shadow-soft);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}

.btn--glass:hover { background: oklch(24% 0.005 286 / 0.92); box-shadow: var(--shadow-lift); }

/* ------------------------------------------------------------ forms ---- */

/* The one text input style. Shares the card's hairline and radius language so
   it sits with .kv and .split rather than against them. */
.field {
  width: 100%;
  min-width: 0;
  height: 56px;
  padding: 0 18px;
  border: 1px solid var(--hairline);
  border-radius: 16px;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink);
  background: oklch(21% 0.005 286 / 0.8);
  transition: border-color 0.2s, box-shadow 0.25s var(--ease), background-color 0.2s;
}

.field::placeholder { color: var(--ink-60); }

.field:hover { background: oklch(22% 0.005 286 / 0.88); }

.field:focus {
  outline: 0;
  border-color: var(--violet-soft);
  background: var(--card);
  box-shadow: var(--shadow-soft);
}

.field:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }

.form-row .field { flex: 1; min-width: 260px; }

/* ------------------------------------------------------------- hero ---- */

.hero {
  position: relative;
  padding-top: 190px;
  padding-bottom: 72px;
  /* Bounds the floating panel layer. clip, not hidden, so this never becomes a
     scroll container. */
  overflow: clip;
}

.hero__inner {
  position: relative;
  z-index: 10;
  /* Wider than the 768px rail so the headline holds two lines. The sub-head
     and graph set their own narrower measures, so only the title uses it. */
  max-width: 980px;
  margin-inline: auto;
  text-align: center;
}

.hero__title {
  font-size: clamp(3.1rem, 6.4vw, 6.4rem);
  line-height: 0.92;
  letter-spacing: -0.045em;
  font-weight: 500;
}

.hero__title span { display: block; }

.hero__sub {
  max-width: 576px;
  margin: 24px auto 0;
  font-size: 15px;
  line-height: 1.625;
  color: var(--ink-60);
}

.hero__cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 36px;
}

.hero__status {
  display: inline-flex;
  max-width: 100%;
  align-items: center;
  gap: 8px;
  margin-top: 28px;
  padding: 8px 16px;
  border: 1px solid var(--hairline);
  background: oklch(14.5% 0.002 286 / 0.72);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink-65);
}

.pulse-dot {
  position: relative;
  width: 6px; height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--violet);
}

.pulse-dot::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  background: var(--violet);
  opacity: 0.35;
  animation: pulse 2.4s var(--ease) infinite;
}

@keyframes pulse {
  0%   { transform: scale(0.6); opacity: 0.5; }
  70%  { transform: scale(1.9); opacity: 0; }
  100% { transform: scale(1.9); opacity: 0; }
}

/* the small branch diagram + caption under the hero */
.hero__graph {
  position: relative;
  max-width: var(--rail);
  margin-top: 26px;
  margin-inline: auto;
  height: 92px;
}

.hero__graph svg {
  position: absolute;
  inset: 0;
  margin-inline: auto;
  width: min(453px, 100%);
  height: 92px;
  overflow: visible;
}

.hero__graph path,
.hero__graph line { stroke: var(--dev-line); stroke-width: 1; fill: none; }
.hero__graph circle { fill: var(--background); stroke: var(--dev-line); stroke-width: 1; }

.hero__graph-caption {
  position: absolute;
  inset-inline: 0;
  top: 32px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink-60);
}

.hero__graph-caption span { padding-inline: 12px; }

/* -------------------------------------------------- floating dev world -- */

.world {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  /* The panels are anchored at calc(50% - 782px), so the composition needs
     about 1600px to sit on canvas. Rather than crop it against the viewport
     edge or drop it, scale the whole layer down below that: every panel keeps
     its exact position relative to the hero column, just smaller.
     The clip lives on .hero, not here: scaling this element would shrink its
     own overflow box and cut the very panels the scale just brought inside. */
  transform: scale(var(--world-scale, 1));
  transform-origin: 50% 0;
}

.world__panel {
  position: absolute;
  border: 1px solid var(--dev-line);
  border-radius: 10px;
  background: var(--dev-panel);
  box-shadow: var(--dev-panel-shadow);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dev-text);
  opacity: 0.55;
  will-change: transform;
}
.world__panel--faint { opacity: 0.3; }

.world__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 7px 10px;
  border-bottom: 1px solid var(--dev-line);
  font-size: 10px;
  color: var(--dev-dim);
}

.world__dots { letter-spacing: 2px; color: var(--dev-line); }
.world__body { padding: 8px 10px; display: grid; gap: 5px; }

.world__row {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 2px 0;
  color: var(--dev-text);
}
.world__row--top { align-items: flex-start; }
.world__push { margin-left: auto; }
.world__body--roomy { padding: 12px 10px; }
.world__body--gap { gap: 12px; }
.world__body--pad { padding: 10px; }
.world__bar--foot { border-bottom: 0; border-top: 1px solid var(--dev-line); }
.world__status { margin-left: auto; width: 5px; height: 5px; border-radius: 50%; background: var(--brand); }
.world__row svg { flex: none; color: var(--dev-dim); }
.world__dim { color: var(--dev-dim); }

.world__code { display: grid; gap: 3px; padding: 10px; }
.world__code-line { display: flex; gap: 10px; white-space: pre; }
.world__ln { color: var(--dev-line); width: 14px; flex: none; }

.world__kw { color: oklch(76% 0.008 286); }
.world__fn { color: var(--dev-blue); }

.world__caret {
  display: inline-block;
  animation: blink 1.1s steps(1) infinite;
}

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

@keyframes drift {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(0, -14px, 0); }
}

/* Positions are anchored to the centre line so the cluster keeps its
   relationship to the hero column as the viewport widens. */
.world__panel[data-slot="profile"]    { top: 73px;  left: calc(50% - 782px); width: 250px; }
.world__panel[data-slot="tiers"]    { top: 67px;  right: calc(50% - 772px); width: 310px; }
.world__panel[data-slot="posts"] { top: 431px; left: calc(50% - 774px); width: 285px; }
.world__panel[data-slot="community"]  { top: 438px; right: calc(50% - 760px); width: 260px; }
.world__panel[data-slot="events"]  { top: 37px;  left: calc(50% - 446px); width: 180px; }
.world__panel[data-slot="wallet"]    { top: 618px; right: calc(50% - 436px); width: 225px; }

.world__ghost {
  position: absolute;
  font-family: var(--mono);
  font-size: 11px;
  /* Deliberately below the contrast floor the rest of the page holds to.
     The whole .world layer is aria-hidden decoration that no reader is meant
     to read, and darkening it would turn a background texture into content
     competing with the headline. */
  color: var(--ink-22);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.world__ghost[data-slot="verified"] { top: 382px; left: calc(50% - 568px); }
.world__ghost[data-slot="live"] { top: 343px; right: calc(50% - 558px); }

/* Each step is the largest scale that still keeps the outermost panel, anchored
   782px from centre, clear of the viewport edge at the narrow end of its band:
   scale <= (50vw - 16px) / 782px. */
@media (max-width: 1599px) { .world { --world-scale: 0.97; } }@media (max-width: 1560px) { .world { --world-scale: 0.91; } }@media (max-width: 1460px) { .world { --world-scale: 0.84; } }@media (max-width: 1360px) { .world { --world-scale: 0.78; } }@media (max-width: 1260px) { .world { --world-scale: 0.73; } }@media (max-width: 1180px) { .world { --world-scale: 0.68; } }/* Below this the scaled panels would reach the hero column and the panel text
   stops being legible, so the hero stands on its own. */
@media (max-width: 1100px) { .world { display: none; } }

/* ----------------------------------------------------------- ticker ---- */

.ticker {
  position: relative;
  z-index: 10;
  overflow: hidden;
  padding-block: 8px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.ticker__track {
  display: flex;
  width: max-content;
  gap: 12px;
  animation: marquee 46s linear infinite;
}

.ticker:hover .ticker__track { animation-play-state: paused; }

.ticker__item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border: 1px solid var(--hairline);
  border-radius: 9999px;
  background: var(--glass);
  box-shadow: var(--shadow-soft);
  font-size: 13px;
  white-space: nowrap;
}

.ticker__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--coral); flex: none; }

@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* --------------------------------------------------------- section ----- */

.section { padding-block: 96px; }
.section--tight { padding-block: 64px; }

.section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 40px;
}

.section__title {
  font-size: clamp(1.9rem, 4vw, 2.8rem);
  line-height: 1.03;
  letter-spacing: -0.03em;
}

/* Two measures, replacing the four one-off max-widths that were set inline. */
.section__title--measure { max-width: 520px; }
.section__title--wide { max-width: 620px; }
.section__title--sm { font-size: clamp(1.6rem, 3vw, 2.2rem); }

.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--violet);
  margin-bottom: 14px;
}

.lede {
  max-width: 560px;
  margin-top: 18px;
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink-60);
}

.page-head { padding-top: 190px; padding-bottom: 8px; }
.page-head__title { font-size: clamp(2.4rem, 5.4vw, 4rem); letter-spacing: -0.04em; }

/* ------------------------------------------------------ package cards -- */

.pkgs {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}

.pkg {
  position: relative;
  min-width: 0; /* grid items default to min-width:auto and would overflow */
  /* A column, so the four bands (preview, name, stats, actions) can be held
     level with the card beside them. The sections were stacked by margin
     alone, so a description wrapping to a different number of lines pushed
     everything below it out of line with its neighbour. */
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 16px;
  border: 1px solid var(--hairline);
  border-radius: 24px;
  background: oklch(19.2% 0.004 286 / 0.5);
  box-shadow: var(--shadow-soft);
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
}

.pkg:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); }

.repo {
  display: block;
  padding: 16px;
  border: 1px solid var(--hairline);
  border-radius: 16px;
  background: var(--card);
}

.repo__top { display: flex; align-items: flex-start; gap: 12px; }

/* Letter avatar. The only thing the markup still sets is --h, the hue, because
   it is per-item data; everything else used to be repeated inline. */
.repo__avatar {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  flex: none;
  border-radius: 10px;
  object-fit: cover;
  font-size: 14px;
  font-weight: 500;
  background: var(--muted);
}

.repo__avatar[style*="--h"] {
  background: var(--muted);
  color: var(--ink-78);
}

.repo__id { min-width: 0; flex: 1; }

.repo__name { font-size: 14px; color: var(--ink-62); }
.repo__name b { font-weight: 500; color: var(--ink); }

.repo__url {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  margin-top: 2px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink-60);
}

/* One line, ellipsised. Wrapping split the url mid-token and left the icon
   stranded on the first line, which made neighbouring cards uneven. */
.repo__url span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chip-tag {
  flex: none;
  padding: 4px 9px;
  border-radius: 9999px;
  background: var(--muted);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.02em;
  color: var(--ink-62);
}

.repo__desc {
  margin-top: 14px;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink-78);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Three lines whether the registry gives one or five, so the block below
     starts at the same height on every card. The clamp alone only caps the
     tall ones; this holds the short ones open. 1.5 line-height x 3. */
  min-height: calc(14.5px * 1.5 * 3);
}

.repo__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin-top: 14px;
  font-size: 12px;
  color: var(--ink-60);
}

.repo__meta span { display: inline-flex; align-items: center; gap: 5px; }
.lang-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.tag-archived { color: var(--coral); }

.repo__topics {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 14px;
  /* One row. A second row of chips made one card taller than the card beside
     it, which is the misalignment this pass is removing. */
  max-height: 26px;
  overflow: hidden;
}

.topic {
  padding: 4px 10px;
  border-radius: 9999px;
  background: oklch(24% 0.03 109.2);
  font-size: 11px;
  color: oklch(72% 0.1 109.2);
}

/* package body */
.pkg__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 18px;
  padding-inline: 2px;
}

.pkg__name { font-size: 26px; letter-spacing: -0.03em; }

.stats {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: end;
  gap: 16px;
  margin-top: 14px;
  padding-inline: 2px;
}

.stat__label {
  font-size: 11px;
  color: var(--ink-60);
  margin-bottom: 2px;
}

.stat__value {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 21px;
  letter-spacing: -0.02em;
}

.stat--right { text-align: right; justify-items: end; }
.stat--right .stat__value { font-variant-numeric: tabular-nums; }

.pkg__foot {
  display: flex;
  align-items: center;
  gap: 14px;
  /* auto, not a fixed margin: this is what puts every card's actions on
     the same baseline as its neighbour. The 16px minimum is preserved so
     spacing is unchanged on a card that already filled its height. */
  margin-top: auto;
  padding-top: 16px;
  padding-inline: 2px;
}

.pkg__ago { font-size: 12px; color: var(--ink-60); }

.kv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 20px; }
.kv-grid > * { min-width: 0; }

.kv {
  padding: 14px 16px;
  border: 1px solid var(--hairline);
  border-radius: 16px;
  background: oklch(19.2% 0.004 286 / 0.6);
}

.kv__k { font-size: 11px; color: var(--ink-60); }
.kv__v { margin-top: 3px; font-size: 15px; letter-spacing: -0.01em; }
.kv__v--mono {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--violet);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------------------------------------------------- lifecycle strip -- */

.lifecycle { position: relative; margin-top: 56px; }

.lifecycle__rail {
  position: relative;
  height: 1px;
  margin-bottom: 36px;
  background: var(--hairline);
}

.lifecycle__fill {
  position: absolute;
  inset-block: 0;
  left: 0;
  width: 0;
  background: linear-gradient(90deg, var(--brand), var(--violet));
}

.lifecycle__bead {
  position: absolute;
  top: 50%;
  left: 0;
  width: 18px; height: 18px;
  margin: -9px 0 0 -9px;
  border-radius: 50%;
  /* Flat accent, and a ring in the page colour rather than a halo: the glow
     was lighting, which this palette does not use. Size and position are
     unchanged, so the pinned sequence tracks exactly as before. */
  background: var(--brand);
  box-shadow: 0 0 0 5px var(--background);
}

.lifecycle__steps {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
}

.lifecycle__step {
  opacity: 0.25;
  transform: translateY(10px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}

.lifecycle__step.is-active { opacity: 1; transform: none; }

.lifecycle__key {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--violet);
}

.lifecycle__name { margin-top: 10px; font-size: 16px; font-weight: 500; letter-spacing: -0.02em; }

.lifecycle__copy {
  margin-top: 10px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-60);
}

/* ------------------------------------------------------------- fees ---- */

.cards-3 { display: grid; grid-template-columns: 1fr; gap: 24px; margin-top: 44px; }

.card {
  padding: 26px;
  border: 1px solid var(--hairline);
  border-radius: 24px;
  background: oklch(19.2% 0.004 286 / 0.62);
  box-shadow: var(--shadow-soft);
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
}

.card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lift); }

.card__icon {
  display: grid;
  place-items: center;
  width: 36px; height: 36px;
  border-radius: 12px;
  background: var(--brand-tint);
  color: var(--violet);
  margin-bottom: 26px;
}

.card__title { font-size: 16px; font-weight: 500; letter-spacing: -0.02em; }

.card__copy {
  margin-top: 10px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-60);
}

.split {
  margin-top: 24px;
  padding: 24px 26px;
  border: 1px solid var(--hairline);
  border-radius: 24px;
  background: oklch(19.2% 0.004 286 / 0.62);
  box-shadow: var(--shadow-soft);
}

.split--loose { margin-top: 40px; }

.split__label { font-size: 12.5px; color: var(--ink-60); }

/* ------------------------------------------------------------ steps ---- */

.steps { display: grid; gap: 20px; margin-top: 44px; }

.step {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  padding: 28px;
  border: 1px solid var(--hairline);
  border-radius: 24px;
  background: oklch(19.2% 0.004 286 / 0.6);
  box-shadow: var(--shadow-soft);
}

.step__n {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--violet);
}

.step__title { font-size: 19px; font-weight: 500; letter-spacing: -0.025em; }

.step__copy {
  margin-top: 10px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-60);
}

.step__list { display: grid; gap: 8px; margin-top: 16px; }

.step__list li {
  display: flex;
  gap: 9px;
  font-size: 13px;
  color: var(--ink-60);
}

.step__list li::before { content: "—"; color: var(--violet-soft); flex: none; }

/* --------------------------------------------------------- treasury ---- */

.stat-grid { display: grid; grid-template-columns: 1fr; gap: 20px; margin-top: 40px; }

.stat-card {
  padding: 24px;
  border: 1px solid var(--hairline);
  border-radius: 24px;
  background: oklch(19.2% 0.004 286 / 0.62);
  box-shadow: var(--shadow-soft);
}

.stat-card__k { font-size: 12.5px; color: var(--ink-60); }

.stat-card__v {
  margin-top: 12px;
  font-size: 30px;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
}

.stat-card__n { margin-top: 10px; font-size: 12px; color: var(--ink-60); }

.empty {
  display: grid;
  place-items: center;
  gap: 10px;
  padding: 72px 24px;
  border: 1px dashed var(--border);
  border-radius: 24px;
  text-align: center;
  background: oklch(100% 0 0 / 0.07);
}

.empty__title { font-size: 17px; font-weight: 500; letter-spacing: -0.02em; }
.empty__copy { max-width: 400px; font-size: 13.5px; line-height: 1.6; color: var(--ink-60); }
.empty .btn { margin-top: 8px; }

/* ----------------------------------------------------------- footer ---- */

.footer { position: relative; z-index: 10; padding-block: 36px 110px; }

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-top: 28px;
  border-top: 1px solid var(--hairline);
}

.footer__brand { display: inline-flex; align-items: center; gap: 10px; font-size: 14px; }
.footer__note { font-size: 13px; color: var(--ink-60); }
.footer__links { display: flex; flex-wrap: wrap; gap: 20px; font-size: 13px; color: var(--ink-60); }
.footer__links a:hover { color: var(--ink); }

/* ------------------------------------------------------ mobile tabbar -- */

.tabbar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 60;
  display: flex;
  padding: 8px 10px calc(8px + env(safe-area-inset-bottom));
  background: var(--glass-strong);
  backdrop-filter: blur(24px) saturate(1.8);
  -webkit-backdrop-filter: blur(24px) saturate(1.8);
  border-top: 1px solid var(--hairline);
}

.tabbar__link {
  flex: 1;
  display: grid;
  justify-items: center;
  gap: 4px;
  padding: 6px 2px;
  border-radius: 12px;
  font-size: 10.5px;
  color: var(--ink-60);
}

.tabbar__link[aria-current="page"] { color: var(--ink); }

/* ----------------------------------------------------------- reveal ---- */

[data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.75s var(--ease), transform 0.75s var(--ease);
}

[data-reveal].is-in { opacity: 1; transform: none; }/* ------------------------------------------------------- breakpoints --- */

@media (min-width: 640px) {
  .hero__cta { flex-direction: row; }
  /* stretch, so paired cards are the same height and their bands line up */
  .pkgs { grid-template-columns: 1fr 1fr; align-items: stretch; }
  .hero__status { font-size: 12px; }
  .hero__sub { font-size: 16px; }
  .stat-grid { grid-template-columns: 1fr 1fr; }
}@media (min-width: 768px) {
  .nav__links { display: flex; }
  .tabbar { display: none; }
  .footer { padding-bottom: 36px; }
  .cards-3 { grid-template-columns: repeat(3, 1fr); }
  .lifecycle__steps { grid-template-columns: repeat(4, 1fr); }
  .step { grid-template-columns: 96px 1fr; gap: 28px; }
  .stat-grid { grid-template-columns: repeat(3, 1fr); }
}@media (min-width: 1024px) {
  .stat-grid { grid-template-columns: repeat(4, 1fr); }
}@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  [data-reveal] { opacity: 1; transform: none; }
  /* The lifecycle strip is scrubbed by scroll, and that scrubbing is what
     normally raises these from 0.25. With motion off the script never runs,
     so without this the six steps stay permanently faint. */
  .lifecycle__step { opacity: 1; transform: none; }
}

/* ==========================================================================
   Conversion components
   Added in the conversion. Everything here reuses the existing tokens, radii
   and shadows rather than introducing a second visual language.
   ========================================================================== */

/* ------------------------------------------------ hero panel additions -- */

.world__avatar {
  display: grid;
  place-items: center;
  width: 20px; height: 20px;
  flex: none;
  border-radius: 6px;
  font-size: 10px;
  background: oklch(24% 0.004 286);
  color: oklch(74% 0.008 286);
}

.world__pip {
  width: 6px; height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--brand-dim);
}

.world__pip--two   { background: oklch(76% 0.008 286); }
.world__pip--three { background: var(--brand); }

/* ------------------------------------------------------------- notice -- */

.notice {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
  max-width: 720px;
  margin-bottom: 28px;
  padding: 14px 18px;
  border: 1px dashed var(--border);
  border-radius: 16px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-60);
  background: oklch(100% 0 0 / 0.07);
}

.notice__tag {
  flex: none;
  padding: 2px 8px;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--violet);
  background: var(--brand-tint);
}

.creator .stats { margin-top: 20px; }
.creator .pkg__foot { margin-top: auto; padding-top: 18px; }

/* ----------------------------------------------------- verified badge -- */

.verified {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--brand-dim);
  background: oklch(22% 0.04 109.2);
}

/* -------------------------------------------------------------- panel -- */

.panel {
  margin-top: 20px;
  padding: 26px;
  border: 1px solid var(--hairline);
  border-radius: 24px;
  background: oklch(19.2% 0.004 286 / 0.62);
  box-shadow: var(--shadow-soft);
}

.panel--quiet { background: oklch(100% 0 0 / 0.06); box-shadow: none; }

/* A step that cannot be started yet. Reads as inactive, not as broken. */
.panel--locked { background: oklch(100% 0 0 / 0.05); box-shadow: none; }
.panel--locked .panel__title { color: var(--ink-60); }

.panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.panel__title { font-size: 19px; font-weight: 500; letter-spacing: -0.025em; }

/* When the title is not inside a .panel__head it has to carry its own gap. */
.panel > .panel__title { margin-bottom: 10px; }

.panel__copy {
  max-width: 62ch;
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-60);
}

.panel__note {
  max-width: 62ch;
  margin-top: 14px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-60);
}

.panel__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 20px;
}

.panel__hint { font-size: 12.5px; color: var(--ink-60); }

.status-chip {
  flex: none;
  padding: 4px 10px;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink-60);
  background: var(--muted);
}

/* ------------------------------------------------------ creator page --- */

.page-head--profile { padding-bottom: 0; }

.profile {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 18px;
  margin-top: 8px;
}

.profile__id { min-width: 0; flex: 1; }

.profile__name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  letter-spacing: -0.035em;
}

.profile__handle {
  margin-top: 6px;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink-60);
}

.profile__line { margin-top: 10px; font-size: 15px; color: var(--ink-60); }

.profile__actions { display: flex; flex-wrap: wrap; gap: 10px; }

.profile-grid { display: grid; grid-template-columns: 1fr; gap: 20px; }

/* A grid track's automatic minimum is the min-content of its widest item, so a
   single unbreakable string (an sha512 integrity hash) pushes the column
   wider than the viewport even though the string itself is clipped. The same
   fix the .latest and .kv-grid rules already carry. */
.profile-grid > * { min-width: 0; }

/* ---------------------------------------------------------------- tiers -- */

.tiers { display: grid; gap: 8px; margin-top: 16px; }

.tier {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  background: oklch(19.2% 0.004 286 / 0.5);
}

.tier__dot { width: 7px; height: 7px; flex: none; border-radius: 50%; background: var(--brand-dim); }
.tier__name { font-size: 14px; }

/* ------------------------------------------------------------ helpers -- */

.kv-grid--stack { grid-template-columns: 1fr; }
.stat-card__v--sm { font-size: 20px; letter-spacing: -0.02em; }
.stat__value--sm { font-size: 15px; }
.nav__link--quiet { display: none; }

.footer__legal {
  max-width: 62ch;
  margin-top: 22px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-60);
}/* -------------------------------------------- conversion breakpoints --- */

@media (min-width: 640px) {
  .kv-grid--stack { grid-template-columns: 1fr 1fr; }
}@media (min-width: 768px) {
  .nav__link--quiet { display: inline-flex; }
  .profile-grid { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 28px; align-items: start; }
  .kv-grid--stack { grid-template-columns: 1fr 1fr; }
}

/* ==========================================================================
   Packages components
   The package-specific pieces: search, version history, contributors, the
   verification evidence list, copyable proof strings and transient feedback.
   Every one reuses the tokens, radii and shadows above rather than
   introducing a second visual language.
   ========================================================================== */

/* ------------------------------------------------------------- search --- */

.searchbar { position: relative; margin-top: 28px; }

.searchbar__row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.searchbar__row .field { flex: 1; min-width: 220px; }

.searchbar__hint {
  flex-basis: 100%;
  margin-top: 2px;
  font-size: 12.5px;
  color: var(--ink-60);
}

.searchbar__state {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ink-60);
}

/* --------------------------------------------------- avatars that load --- */

/* The letter avatar in .repo__avatar covers the no-image case. This is the
   same box holding a real image, so a card does not change size when a
   GitHub avatar arrives or fails to. */
.avatar-img {
  width: 34px; height: 34px;
  flex: none;
  border-radius: 10px;
  object-fit: cover;
  background: var(--muted);
}

.avatar-img--sm { width: 24px; height: 24px; border-radius: 7px; }
.avatar-img--lg { width: 72px; height: 72px; border-radius: 20px; }

/* ---------------------------------------------------- version history --- */

.vlist {
  margin-top: 18px;
  border: 1px solid var(--hairline);
  border-radius: 20px;
  overflow: hidden;
  background: oklch(19.2% 0.004 286 / 0.55);
}

.vrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px 14px;
  align-items: baseline;
  padding: 13px 18px;
  border-top: 1px solid var(--hairline);
}

.vrow:first-child { border-top: 0; }

.vrow--head {
  background: oklch(19.2% 0.004 286 / 0.5);
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-60);
}

.vrow__v {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: -0.01em;
}

.vrow__v b { font-weight: 500; }

.vrow__tag {
  margin-left: 8px;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--brand-tint);
  font-family: var(--sans);
  font-size: 10.5px;
  color: oklch(72% 0.1 109.2);
}

.vrow__tag--muted { background: var(--muted); color: var(--ink-60); }

.vrow__when {
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-60);
  text-align: right;
}

.vrow__hash {
  grid-column: 1 / -1;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--ink-60);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ------------------------------------------------------- contributors --- */

.contribs {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
}

.contrib {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 6px 6px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: oklch(19.2% 0.004 286 / 0.6);
  font-size: 13px;
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

.contrib:hover { transform: translateY(-2px); box-shadow: var(--shadow-soft); }

.contrib__n {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink-60);
}

/* --------------------------------------------------------- the evidence -- */

/* The four proofs. Each row states the fact, whether it passed, and whether
   passing it is sufficient on its own, because the whole point is that most
   of them are not. */
.evidence { display: grid; gap: 10px; margin-top: 18px; }

.ev {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--hairline);
  border-radius: 16px;
  background: oklch(19.2% 0.004 286 / 0.5);
}

.ev__mark {
  display: grid;
  place-items: center;
  width: 20px; height: 20px;
  margin-top: 1px;
  border-radius: 50%;
  background: var(--muted);
  color: var(--ink-60);
}

.ev--pass .ev__mark { background: oklch(24% 0.05 109.2); color: var(--brand-dim); }

.ev__label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.015em;
}

.ev__flag {
  padding: 2px 8px;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  font-weight: 400;
  background: var(--muted);
  color: var(--ink-60);
}

.ev__flag--enough { background: oklch(24% 0.05 109.2); color: var(--brand-dim); }
.ev__flag--signal { background: oklch(23% 0.004 286); color: oklch(74% 0.008 286); }

.ev__detail {
  margin-top: 5px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-60);
}

/* ------------------------------------------------- status chip variants -- */

.status-chip--ok { background: oklch(24% 0.05 109.2); color: var(--brand-dim); }
.status-chip--part { background: oklch(23% 0.004 286); color: oklch(74% 0.008 286); }
.status-chip--off { background: var(--muted); color: var(--ink-60); }

/* ------------------------------------------------------- copyable code -- */

.codeblock {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-top: 16px;
  padding: 14px 16px;
  border: 1px solid var(--hairline);
  border-radius: 16px;
  background: oklch(0% 0 0 / 0.4);
}

.codeblock__code {
  flex: 1;
  min-width: 0;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-all;
}

.codeblock__copy {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: 9px;
  color: var(--ink-60);
  background: oklch(21% 0.005 286 / 0.8);
  transition: color 0.18s, background-color 0.18s;
}

.codeblock__copy:hover { color: var(--brand-ink); background: var(--brand); }
.codeblock__copy.is-done { color: var(--brand); }

/* ------------------------------------------------------- the sparkline -- */

/* Drawn from real daily download counts. When npm reports none, the element
   is not rendered at all rather than drawn flat. */
.spark { display: block; width: 100%; height: 58px; overflow: visible; }
.spark__area { fill: oklch(95.81% 0.2077 109.2 / 0.12); }
.spark__line { fill: none; stroke: var(--violet); stroke-width: 1.5; stroke-linejoin: round; }

/* ------------------------------------------------------------ loading --- */

.skeleton {
  border-radius: 14px;
  background: linear-gradient(
    90deg,
    oklch(0% 0 0 / 0.45),
    oklch(100% 0 0 / 0.1),
    oklch(0% 0 0 / 0.45)
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s linear infinite;
}

/* Sized to the real thing it stands in for. A placeholder shorter than its
   content makes everything below it jump when the content arrives, which is
   measured as layout shift and felt as the page moving under the cursor.
   A rendered .pkg card is about 337px at every width, because its content is
   fixed-height apart from the clamped description. */
.skeleton--card { height: 337px; }
.skeleton--row { height: 44px; margin-top: 10px; }

/* The async regions reserve roughly what they will occupy. Released by
   .is-loaded once the real content is in, so a short page does not keep a
   tall empty box below it. */
[data-package]:not(.is-loaded) { min-height: 1400px; }
[data-results]:not(.is-loaded),
[data-featured]:not(.is-loaded) { min-height: 740px; }
[data-feed]:not(.is-loaded) { min-height: 420px; }
[data-dashboard]:not(.is-loaded) { min-height: 600px; }

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

.spinner {
  width: 13px; height: 13px;
  flex: none;
  border-radius: 50%;
  border: 1.5px solid oklch(100% 0 0 / 0.16);
  border-top-color: var(--violet);
  animation: spin 0.7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* -------------------------------------------------------------- toast --- */

.toast {
  position: fixed;
  left: 50%;
  bottom: 86px;
  z-index: 80;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(440px, calc(100vw - 32px));
  padding: 13px 18px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--glass-strong);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  box-shadow: var(--shadow-lift);
  font-size: 13.5px;
  /* Starts below its resting place, so the entrance is one transform rather
     than a layout change. */
  transform: translate(-50%, 16px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}

.toast.is-in { opacity: 1; transform: translate(-50%, 0); }
.toast--bad { border-color: oklch(80% 0.006 286 / 0.5); }
.toast__dot { width: 6px; height: 6px; flex: none; border-radius: 50%; background: var(--brand); }
.toast--bad .toast__dot { background: var(--coral); }

/* ------------------------------------------------------- package meta --- */

.pkg__title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.pkg__name-lg {
  font-family: var(--mono);
  font-size: clamp(1.6rem, 4vw, 2.4rem);
  letter-spacing: -0.035em;
  word-break: break-all;
}

.meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  margin-top: 16px;
  font-size: 13px;
  color: var(--ink-60);
}

.meta-row span { display: inline-flex; align-items: center; gap: 6px; }
.meta-row a:hover { color: var(--ink); }

/* ------------------------------------------------------ activity rows --- */

.feed {
  margin-top: 24px;
  border: 1px solid var(--hairline);
  border-radius: 24px;
  overflow: hidden;
  background: oklch(19.2% 0.004 286 / 0.55);
}

.feed__row {
  display: grid;
  grid-template-columns: 76px minmax(0, 1fr) auto;
  gap: 14px;
  align-items: baseline;
  padding: 16px 20px;
  border-top: 1px solid var(--hairline);
}

.feed__row:first-child { border-top: 0; }

.feed__src {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-60);
}

.feed__what { font-size: 13.5px; line-height: 1.5; min-width: 0; }
.feed__what b { font-weight: 500; }

.feed__when {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-60);
  white-space: nowrap;
}

/* ----------------------------------------------------- the step ladder -- */

/* The six launch steps on the connect page. A step that cannot be started yet
   is dimmed by .panel--locked above; this adds the number. */
.panel__step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px; height: 22px;
  margin-right: 10px;
  border-radius: 7px;
  background: var(--brand-tint);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--violet);
}

.panel--done .panel__step { background: oklch(24% 0.05 109.2); color: var(--brand-dim); }/* ------------------------------------------------- packages breakpoints -- */

@media (max-width: 560px) {
  /* The source column is the first thing to go: the row still reads without
     it, and the description is what matters. */
  .feed__row { grid-template-columns: minmax(0, 1fr) auto; }
  .feed__src { display: none; }
}@media (min-width: 768px) {
  .searchbar__row .field { min-width: 320px; }
}/* Six steps rather than four. The rail, the bead and the per-step activation
   are all computed from the step count in app.js, so only the columns change. */
@media (min-width: 768px) {
  .lifecycle--six .lifecycle__steps { grid-template-columns: repeat(3, 1fr); gap: 32px 28px; }
}@media (min-width: 1100px) {
  .lifecycle--six .lifecycle__steps { grid-template-columns: repeat(6, 1fr); gap: 24px; }
  .lifecycle--six .lifecycle__copy { font-size: 12.5px; }
}

/* ==========================================================================
   Entrance motion
   Three additions, all on the first screen, all of which hand back to the
   motion system above once they have played. Nothing here animates font
   size, and nothing loops.
   ========================================================================== */

/* ------------------------------------------------- masked word reveal --- */

/* The hero headline. Each word sits in its own overflow-hidden box and rises
   into it from below, rotated a few degrees and straightening as it lands.
   app.js builds the spans; without it the headline renders as plain text,
   so the title is never dependent on script to be legible. */

.hero__title .word {
  display: inline-block;
  /* The mask. Vertical padding would clip descenders, so the box is grown
     and pulled back by the same amount instead. */
  overflow: hidden;
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
  vertical-align: top;
}

.hero__title .word__in {
  display: inline-block;
  transform: translateY(105%) rotate(4deg);
  transform-origin: 0 100%;
  will-change: transform;
}

.is-ready .hero__title .word__in {
  transform: none;
  transition:
    transform 0.9s cubic-bezier(0.16, 1, 0.3, 1) var(--word-delay, 0s);
}

/* --------------------------------------------- restrained hero pop-in --- */

/* The supporting elements follow the headline rather than arriving with it:
   a short rise, once, and no scaling of type. */
.hero__sub,
.hero__cta,
.hero__status,
.hero__graph {
  opacity: 0;
  transform: translateY(12px);
}

.is-ready .hero__sub,
.is-ready .hero__cta,
.is-ready .hero__status,
.is-ready .hero__graph {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.6s var(--ease) var(--in-delay, 0s),
    transform 0.6s var(--ease) var(--in-delay, 0s);
}

.hero__sub { --in-delay: 0.42s; }
.hero__cta { --in-delay: 0.52s; }
.hero__status { --in-delay: 0.62s; }
.hero__graph { --in-delay: 0.7s; }

/* ------------------------------------------- sequential panel assembly -- */

/* The floating panels build in order rather than being present from the
   first frame. The entrance animates clip-path and filter only: the drift
   loop owns transform, and each panel's class owns its opacity, so the
   three never contend for the same property.

   Both animations are declared together because a second `animation`
   declaration would replace the first. Their delays are per slot, which is
   why each is a custom property. */
.world__panel {
  animation:
    assemble 0.62s var(--ease) var(--in-delay, 0s) both,
    drift 18s ease-in-out var(--drift-delay, 0s) infinite;
}

@keyframes assemble {
  from {
    clip-path: inset(0 0 100% 0);
    filter: blur(3px);
  }
  to {
    clip-path: inset(0 0 0 0);
    filter: none;
  }
}

/* The ghost labels are text, not panels, so they simply arrive last. */
.world__ghost {
  animation:
    ghost-in 0.5s var(--ease) var(--in-delay, 0s) both,
    drift 22s ease-in-out var(--drift-delay, 0s) infinite;
}

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

/* Assembly order: the two panels nearest the headline, then outward. The
   drift delays are the original negative offsets, unchanged, so the loop
   keeps the same desynchronised phase it always had. */
.world__panel[data-slot="profile"]   { --in-delay: 0.18s; --drift-delay: -1s; }
.world__panel[data-slot="tiers"]     { --in-delay: 0.26s; --drift-delay: -5s; }
.world__panel[data-slot="events"]    { --in-delay: 0.34s; --drift-delay: -12s; }
.world__panel[data-slot="posts"]     { --in-delay: 0.42s; --drift-delay: -9s; }
.world__panel[data-slot="community"] { --in-delay: 0.5s;  --drift-delay: -3s; }
.world__panel[data-slot="wallet"]    { --in-delay: 0.58s; --drift-delay: -7s; }
.world__ghost[data-slot="verified"]  { --in-delay: 0.72s; --drift-delay: -4s; }
.world__ghost[data-slot="live"]      { --in-delay: 0.8s;  --drift-delay: -11s; }

/* ----------------------------------------------- restrained pop-ins ----- */

/* Interface feedback: fast, small, and only on things that were just
   created. Used for a result list replacing a skeleton. */
@keyframes pop-in {
  from { opacity: 0; transform: translateY(6px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

.pop-in { animation: pop-in 0.26s var(--ease) both; }

/* --------------------------------------------------- reduced motion ----- */

/* The blanket rule above already collapses every duration. These restate the
   end state directly, so nothing depends on a 0.001ms animation having run. */
@media (prefers-reduced-motion: reduce) {
  .hero__title .word__in { transform: none; }
  .hero__title .word { overflow: visible; }
  .hero__sub,
  .hero__cta,
  .hero__status,
  .hero__graph {
    opacity: 1;
    transform: none;
  }
  .world__panel,
  .world__ghost {
    animation: none;
    clip-path: none;
    filter: none;
  }
}

/* ==========================================================================
   Accessibility
   ========================================================================== */

/* Visually hidden, still read aloud and still focusable. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The skip link is the first thing in the tab order and the only element that
   un-hides itself on focus, so a keyboard user is not walked through the whole
   nav on every page. */
.skip {
  position: fixed;
  top: 10px;
  left: 50%;
  z-index: 100;
  padding: 11px 20px;
  border-radius: 9999px;
  /* The accent, not the ink: --ink is off-white on the dark theme, so white
     text on it would be invisible the moment the link took focus. */
  background: var(--brand);
  color: var(--brand-ink);
  font-size: 13px;
  font-weight: 500;
  box-shadow: var(--shadow-lift);
  transform: translate(-50%, -150%);
  transition: transform 0.2s var(--ease);
}

.skip:focus { transform: translate(-50%, 0); }

/* One visible focus ring, on everything interactive. The default ring is
   invisible against the dark pill buttons, so it is drawn as an offset
   outline in the accent instead. */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 3px;
  border-radius: 6px;
}

.btn:focus-visible { outline-offset: 3px; }

/* A target that is only reachable by keyboard should not jump under the
   fixed nav when it receives focus. */
main:focus { outline: none; }
main { scroll-margin-top: 120px; }

/* A fact the registry reports, not a judgement made here: amber rather than
   red, because a deprecated package is still a real package. */
.notice--warn {
  border-style: solid;
  border-color: oklch(78% 0.008 286);
  background: oklch(22% 0.004 286);
}

.notice__tag--warn { color: oklch(74% 0.008 286); background: oklch(24% 0.004 286); }
.vrow__tag--warn { background: oklch(24% 0.004 286); color: oklch(74% 0.008 286); }

/* ---------------------------------------------------------- badge set --- */

/* One badge per established fact. Deliberately plural: there is no single
   "verified" badge, because the facts are not interchangeable. */
.badges {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 13px 6px 11px;
  border: 1px solid var(--hairline);
  border-radius: 9999px;
  background: oklch(19.2% 0.004 286 / 0.6);
  font-size: 12.5px;
  letter-spacing: -0.005em;
  /* The title attribute carries the explanation, so the cursor says so. */
  cursor: help;
}

.badge__dot { width: 6px; height: 6px; flex: none; border-radius: 50%; }

.badge--ok { background: oklch(22% 0.04 109.2); border-color: var(--brand-tint); color: var(--brand-dim); }
.badge--ok .badge__dot { background: var(--brand); }

.badge--info { background: oklch(22% 0.004 286); border-color: oklch(25% 0.004 286); color: var(--brand-dim); }
.badge--info .badge__dot { background: var(--violet); }

.badge--muted { color: var(--ink-60); }
.badge--muted .badge__dot { background: var(--ink-35); }

.badge--off { background: var(--muted); color: var(--ink-60); }
.badge--off .badge__dot { background: var(--ink-30); }

/* ==========================================================================
   Motion ownership
   When motion.js is running it owns opacity and transform on the elements
   below, so the CSS transitions that exist as a no-script fallback stand
   down rather than fighting GSAP for the same properties.
   ========================================================================== */

html.motion-js [data-reveal] {
  /* GSAP sets the start state; without this the CSS transition would run
     alongside it and the two would disagree about the easing. */
  opacity: 1;
  transform: none;
  transition: none;
}

/* The hero entrance is a GSAP timeline when script is present. These are the
   no-script resting states, so nothing is ever left invisible. */
html.motion-js .hero__sub,
html.motion-js .hero__cta,
html.motion-js .hero__status,
html.motion-js .hero__graph {
  opacity: 1;
  transform: none;
  transition: none;
}

html.motion-js .hero__title .word__in { transform: none; }

/* The panels' clip-path entrance is GSAP's now; the drift loop below stays
   exactly as it was, and the two never touch the same property. */
html.motion-js .world__panel {
  animation: drift 18s ease-in-out var(--drift-delay, 0s) infinite;
  clip-path: none;
  filter: none;
}

html.motion-js .world__ghost {
  animation: drift 22s ease-in-out var(--drift-delay, 0s) infinite;
}

/* Lenis drives the wheel when it is active, so the browser's own smooth
   scrolling must get out of the way or the two compete.

   Two selectors, because they are set by different things. `lenis-on` is
   ours, added when the instance is created. `lenis` and `lenis-smooth` are
   Lenis's own, and it assigns the whole className when it sets them.

   The !important is the part that matters. Without it the browser animates
   every scroll position Lenis sets, Lenis reads the lagging position back on
   the next frame, and the two never converge: the page creeps a few pixels
   per wheel event and then lurches once the deltas accumulate. */
html.lenis-on,
html.lenis.lenis-smooth { scroll-behavior: auto !important; }

/* The rest of what Lenis's own stylesheet requires. A fixed height on either
   element would stop it scrolling the document at all. */
html.lenis,
html.lenis body { height: auto; }
html.lenis.lenis-stopped { overflow: hidden; }
html.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }

/* ------------------------------------------------------- curved seam --- */

/* A real path whose geometry is animated, not an oval sliding past. It sits
   between two sections and carries the background colour across the join. */
.seam {
  position: relative;
  z-index: 10;
  display: block;
  width: 100%;
  height: 56px;
  margin-bottom: -1px;
  pointer-events: none;
}

.seam svg { display: block; width: 100%; height: 100%; }
.seam path { fill: oklch(19.2% 0.004 286 / 0.55); }

/* ---------------------------------------------- reduced motion, again -- */

/* The blanket rule earlier collapses durations. These restate the end state
   directly, so nothing depends on a 0.001ms animation having run, and the
   page is correct even if script never executes. */
@media (prefers-reduced-motion: reduce) {
  html.motion-js [data-reveal],
  html.motion-js .hero__sub,
  html.motion-js .hero__cta,
  html.motion-js .hero__status,
  html.motion-js .hero__graph {
    opacity: 1;
    transform: none;
  }
  html.motion-js .world__panel,
  html.motion-js .world__ghost { animation: none; }
  .hero__title .word { overflow: visible; }
  .hero__title .word__in { transform: none; }
}

/* A grid whose cards are mid-animation can briefly measure wider than its
   container. clip, not hidden, so this never becomes a scroll container and
   never breaks a sticky descendant. */
.pkgs { overflow-x: clip; }

/* ------------------------------------------------------------- grain --- */

/* A single fixed overlay, borrowed from the reference artwork's halftone.
   Deliberately restrained: it reads as texture on the dark surfaces and
   disappears entirely behind text.

   On html rather than body, because body carries overflow-x: clip and a
   clipped box clips its fixed descendants. z-index sits below the nav (100)
   and the toasts (80), so nothing interactive is ever covered, and
   pointer-events: none means it cannot take a click in any case.

   It is one element with no scroll listener and no animation, so it adds
   nothing to the scroll path. */
html::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0.055;
  background-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.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* The texture is decoration. Anyone who has asked for less motion and less
   visual noise should not be given it. */
@media (prefers-reduced-motion: reduce) {
  html::after { display: none; }
}

/* The X link sits with the nav's quiet controls: an icon the colour of
   secondary text, lifting to the accent on hover like every other link. */
.nav__x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 9999px;
  color: var(--ink-60);
  /* A little air before the filled call to action, which otherwise sits
     almost flush against the icon. */
  margin-right: 6px;
  transition: color 0.18s var(--ease), background 0.18s var(--ease);
}

.nav__x:hover { color: var(--brand); background: oklch(100% 0 0 / 0.06); }
.nav__x svg { display: block; }

/* The narrow nav already hides its secondary links; the icon goes with them
   rather than crowding the one call to action. */
@media (max-width: 639px) {
  .nav__x { display: none; }
}
