/* ===========================================================================
   OnSolana — generated page structure
   ---------------------------------------------------------------------------
   The only stylesheet a customer page loads. It is static: shipped once, cached
   forever, identical for every site. Everything that varies per site arrives as
   custom properties in the page's own <style> block (services/render/css.js).

   That split is the whole design. A palette change or a preset change rewrites
   five lines of custom properties, not a stylesheet — so switching a page from
   Clean/Solana to Brutalist/Matrix costs one round trip and repaints instantly,
   and a new palette never means touching this file.

   Naming:
     --p-*   palette roles          (bg, surface, text, primary, accent)
     --c-*   colours derived from those (lines, muted text, hover fills)
     --t-*   style-preset tokens    (radius, shadows, fonts, spacing)
     --bd-*  backdrop (the blurred cover behind the page)
   =========================================================================== */

@font-face {
  font-family: "Geist";
  src: url("/fonts/Geist-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("/fonts/GeistMono-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-display: swap;
}

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

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
  /* Both, deliberately. overflow-x on body alone propagates to the viewport in
     some browsers and clips nothing in others; setting it here as well is what
     actually guarantees no horizontal scrollbar. */
  overflow-x: hidden;
}

body.page {
  margin: 0;
  min-height: 100dvh;
  background: var(--p-bg);
  color: var(--p-text);
  font-family: var(--t-font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img,
video {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
}

/* ---------------------------------------------------------------------------
   Backdrop — the cover painted again behind the whole page, blurred.
   Fixed rather than absolute so it stays put while the column scrolls over it;
   that is what makes the page read as a window onto the image rather than a
   header that scrolls away.
   --------------------------------------------------------------------------- */

.backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Scrolls away with the page instead of staying put. */
.backdrop--scroll { position: absolute; height: 100vh; }

.bd-media {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Scaled up before blurring: a blur samples past the element's edges, and
     without the overscan those edges show as pale bands down the sides. */
  transform: scale(var(--bd-scale));
  filter: blur(var(--bd-blur)) saturate(var(--bd-saturate, 100%));
}

/* A flat cover's backdrop is its own fill — blurring or overscanning a solid
   colour/gradient only costs compositing, so both are dropped. */
.bd-media--flat {
  transform: none;
  filter: saturate(var(--bd-saturate, 100%));
}

/* `transparent` is a FALLBACK, not decoration.
   ---------------------------------------------------------------------------
   `--bd-tint` is only emitted when an owner sets a tint, which nobody does. An
   unset var() inside a shorthand does not fall back to nothing — it makes the
   whole declaration invalid at computed-value time, so `background` resolved to
   `none` and THE SCRIM NEVER PAINTED ON ANY SITE.

   The consequence was invisible and everywhere: the blurred cover has been
   showing through behind every page at full strength, with no dimming at all.
   That is why the contrast solver kept disagreeing with the browser — it modelled
   a 45% scrim that was not there, so it thought every column was safer than it
   was, and the fleet needed far more opacity than it should have.

   Found by measuring `.bd-scrim` on a live page: `--bd-dim: 0.45` set correctly on
   the parent, and `background-image: none` on the element. */
.bd-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
      to bottom,
      color-mix(in srgb, var(--p-bg) calc(var(--bd-dim) * 70%), transparent),
      color-mix(in srgb, var(--p-bg) calc(var(--bd-dim) * 100%), transparent)
    ),
    var(--bd-tint, transparent);
}

/* ---------------------------------------------------------------------------
   Shell — one centred column at every width.
   --------------------------------------------------------------------------- */

/* One contained column, centred at every width — the reference sites' whole
   shape. Nothing inside it bleeds to the viewport edge: a full-bleed child
   (width:100vw + negative margin) keeps a layout box wider than the column even
   though a transform moves it back visually, and that overflow pushes the
   entire page sideways. Containment is both the correct look and the only way
   the column stays honest about its own width. */
.shell {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--shell-width, 600px);
  margin: 0 auto;
  padding: 0 var(--t-page-pad) var(--shell-bottom, 72px);

  /* The column is opaque at EVERY width.
     ---------------------------------------------------------------------
     This used to be set only inside the 640px media query, so on a phone the
     column had no background at all and every glyph composited straight onto
     the blurred cover. That single gap owned 726 of 842 high-severity contrast
     failures in the fleet audit — on `catjak` the coin name, the social icons,
     the contract address and the whole disclaimer measured 1:1 and were
     literally invisible.

     The value is not a guess. Measured across 145 live link.me profiles —
     the same format we are, 200M+ users — body text sits on a FULLY OPAQUE
     surface on 145 of 145, with backdrop-filter behind text essentially
     absent. The cover reaches their page through the hero, never through the
     copy.

     --shell-opacity is the knob the adaptive pass turns: a cover measured to
     be compatible with the palette can relax it and get the glass look back.
     The default is safe, and safety is what an arbitrary upload needs. */
  background: color-mix(in srgb, var(--p-bg) var(--shell-opacity, 100%), transparent);
}

/* "Card off" means no floating card — no radius, no shadow, no detached
   margins. It does NOT mean transparent: that is a look, and an unreadable
   page is not a look. The column keeps its backing and the content simply
   runs full-bleed down the page. */
.shell[data-card="off"] {
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  margin-top: 0;
  margin-bottom: 0;
}

@media (min-width: 640px) {
  .shell,
  .shell[data-card="on"] {
    margin-top: 28px;
    margin-bottom: 28px;
    padding-bottom: 40px;
    border-radius: calc(var(--t-radius) + 8px);
    /* Wider screens have margins either side, which is where the blurred cover
       now does its atmospheric work — around the column instead of through it. */
    background: color-mix(in srgb, var(--p-bg) var(--shell-opacity, 100%), transparent);
    backdrop-filter: blur(18px);
    box-shadow: 0 24px 70px var(--c-shadow-strong);
    overflow: hidden;
  }
}

/* ---------------------------------------------------------------------------
   Header shapes (doc.profile.headStyle)
   ---------------------------------------------------------------------------
   One tree, four shapes. The head was structurally identical on every site
   ever produced — cover, avatar, name, centred — and no amount of palette
   variety fixes a page whose opening screen never changes.

   `classic` is the original and needs no rules here.
   --------------------------------------------------------------------------- */

/* --- hero: the artwork IS the header ------------------------------------- */
.head--s-hero .cover {
  height: clamp(340px, 58vh, 560px);
  border-radius: 0;
}
.head--s-hero {
  /* The text sits on the cover, so the head is as tall as the cover and the
     text is positioned within it rather than after it. */
  display: grid;
}
.head--s-hero .head-text {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: 0 var(--t-page-pad) 18px;
  margin: 0;
}
/* A far stronger scrim than the floor, because here the text is ON the image
   rather than below it. Tinted to --p-bg like the cover scrim, so it works for
   a light palette and a dark one, and reaching full strength at the bottom so
   the cover dissolves into the page instead of ending at a seam. */
.head--s-hero .cover::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    to top,
    var(--p-bg) 0%,
    color-mix(in srgb, var(--p-bg) 82%, transparent) 22%,
    color-mix(in srgb, var(--p-bg) 30%, transparent) 52%,
    transparent 78%
  );
}
/* The avatar becomes a small inline mark rather than the focal point — the
   artwork already is. */
.head--s-hero .head-avatar-slot {
  position: absolute;
  top: 16px;
  left: var(--t-page-pad);
  z-index: 3;
}
.head--s-hero .avatar {
  --avatar-size: 52px;
  margin-bottom: 0;
}
.head--s-hero .head-name { font-size: calc(var(--head-name-size, 30px) * 1.15); }

/* --- bare: no cover, the avatar leads ------------------------------------ */
.head--s-bare {
  padding-top: 34px;
}
.head--s-bare .head-avatar-slot { margin-bottom: 14px; }

/* The avatar's negative pull exists to straddle a cover's lower edge. With no
   cover to straddle — or with the avatar repositioned as a corner mark — it just
   drags the avatar off the top of the page, which is exactly what it did in
   `hero` before this: clipped at -10px. */
.head--s-bare .avatar,
.head--s-hero .avatar,
.head--s-split .avatar {
  margin-top: 0;
  margin-bottom: 0;
}

/* --- split: avatar beside the name, not above it -------------------------
   Applies at every width, including a phone. Gated behind a 460px media query
   it did nothing on mobile, which is where the traffic is — so the one
   asymmetric shape on offer was invisible to most visitors.

   The avatar shrinks to make room instead. A 124px avatar plus an 18px gap
   leaves 248px for a name on a 390px screen, which "BCGame Coin" does not fit
   in; at 76px there is 296px, which it does. */
.head--s-split {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: 16px;
  text-align: left;
}
.head--s-split .cover { grid-column: 1 / -1; }
.head--s-split .head-avatar-slot { justify-content: flex-start; margin-bottom: 0; }
.head--s-split .head-text { text-align: left; min-width: 0; }
.head--s-split .head-bio { margin-left: 0; margin-right: 0; }
/* !important because style.js writes --avatar-size as an INLINE property, and
   inline beats a stylesheet rule. That value is the owner's choice for the
   classic shape — a 124px avatar straddling a cover — and it is simply the
   wrong number once the avatar is sitting in a row beside the name. The shape
   has to win here, or `split` renders as a squashed `classic`. */
.head--s-split .avatar { --avatar-size: 76px !important; }
.head--s-split .head-name { font-size: clamp(20px, calc(var(--head-name-size, 30px) * 0.8), 30px); }

@media (min-width: 560px) {
  .head--s-split { column-gap: 20px; }
  .head--s-split .avatar { --avatar-size: 104px !important; }
  .head--s-split .head-name { font-size: var(--head-name-size, 30px); }
}

/* ---------------------------------------------------------------------------
   Sticky bar — appears once the head has scrolled away.
   ---------------------------------------------------------------------------
   Opaque, not translucent. It floats over page content by definition, and the
   whole lesson of the fleet audit is that text over a moving background is text
   nobody can read. The one place a blur is safe is behind a solid fill, where
   it only softens the edges of what is already hidden.

   Hidden by default and revealed by the runtime, so a page with scripting
   blocked never shows a bar it cannot dismiss.
   --------------------------------------------------------------------------- */
/* The bar tracks the COLUMN, not the viewport.
   ---------------------------------------------------------------------------
   The outer element is a full-width positioning layer with nothing painted on
   it; everything visible lives on `.topbar-in`, which is capped at the column
   width. A full-bleed black band across a 1280px screen reads as browser
   chrome that arrived from somewhere else — this reads as the top of the page's
   own column, which is what it is.

   Transparent outer also means the bar only intercepts pointer events over the
   column itself, leaving the margins clickable. */
.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  transform: translateY(-100%);
  transition: transform 0.24s ease, opacity 0.24s ease;
  opacity: 0;
  pointer-events: none;
  padding-top: env(safe-area-inset-top, 0px);
}

.topbar[data-show] {
  transform: translateY(0);
  opacity: 1;
}

.topbar-in {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-width: var(--shell-width, 600px);
  margin: 0 auto;
  padding: 8px var(--t-page-pad);
  min-height: 54px;
  pointer-events: auto;
  /* Opaque, always — this floats over page content, and the whole finding of
     the fleet audit is that text over a moving background is text nobody can
     read. Never wired to --shell-opacity for that reason. */
  background: var(--p-bg);
  border-bottom: 1px solid var(--c-line);
}

/* Once there are margins either side, the bar becomes a card with the column's
   own corner radius rather than a strip with cut-off ends. */
@media (min-width: 640px) {
  .topbar-in {
    border: 1px solid var(--c-line);
    border-top: none;
    border-radius: 0 0 calc(var(--t-radius) + 8px) calc(var(--t-radius) + 8px);
    box-shadow: 0 12px 30px var(--c-shadow);
  }
}

.topbar-av {
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 999px;
  object-fit: cover;
}

/* min-width:0 so a long coin name truncates instead of pushing the CTA out. */
.topbar-id { display: flex; align-items: baseline; gap: 6px; min-width: 0; flex: 1; }

.topbar-name {
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  letter-spacing: var(--t-display-tracking);
  font-size: 15px;
  color: var(--p-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar-ticker {
  font-size: var(--t-label-size, 12px);
  color: var(--c-accent-text);
  flex: none;
}

/* The same badge as the head, smaller.
   ---------------------------------------------------------------------------
   It was a bare glyph tinted with --c-accent-text, which reads as a stray tick
   rather than a verification mark — and on a deliberately monochrome palette
   like Wojak (primary #4a4a4a, accent #7a7a7a) it looked like a rendering
   accident rather than a decision.

   Filled, it is unambiguous on all fifty palettes, and the glyph colour comes
   from --c-on-primary, which readableOn() already guarantees is legible on
   whatever the primary happens to be. */
.topbar-verified {
  display: inline-grid;
  place-items: center;
  width: 16px;
  height: 16px;
  margin-left: 5px;
  border-radius: 999px;
  background: var(--p-primary);
  color: var(--c-on-primary);
  flex: none;
  vertical-align: middle;
}

/* Live figures.
   ---------------------------------------------------------------------------
   Tabular numerals so a price ticking from $0.0₅2913 to $0.0₅2914 does not
   shuffle everything beside it on every poll — the bar is pinned to the top of
   the screen, so any width jitter is permanently in view.

   Ordered by what a visitor came for: price first, then the direction of the
   move, then market cap. That is also the order they drop out in as the screen
   narrows. */
.topbar-live {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex: none;
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.topbar-price {
  font-size: 14px;
  font-weight: 700;
  color: var(--p-text);
}

.topbar-change { font-size: var(--t-label-size, 12px); font-weight: 600; }

.topbar-mcap {
  font-size: var(--t-label-size, 12px);
  color: var(--c-muted);
}
.topbar-mcap-k { opacity: 0.7; }

/* A phone has room for identity, price and the button — and nothing else.
   Market cap and the ticker are the first to go; the change badge follows on
   the narrowest screens, because a price with no context still beats a row
   that has been squeezed into ellipses. */
@media (max-width: 719px) {
  .topbar-mcap { display: none; }
}
@media (max-width: 539px) {
  .topbar-ticker { display: none; }
}
@media (max-width: 419px) {
  .topbar-change { display: none; }
}

.topbar-cta {
  flex: none;
  min-height: var(--t-tap-min, 44px);
  padding: 0 16px;
  border: none;
  border-radius: var(--t-btn-radius);
  background: var(--p-primary);
  color: var(--c-on-primary);
  font-family: inherit;
  font-size: 14px;
  font-weight: var(--t-btn-weight);
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .topbar { transition: none; }
}

/* ---------------------------------------------------------------------------
   Page head — cover, avatar, name, bio.
   --------------------------------------------------------------------------- */

.head {
  position: relative;
  text-align: center;
}

.head--left {
  text-align: left;
}

/* The cover spans the column and only the column. It cancels the shell's side
   padding with equal negative margins — which, unlike a 100vw child, keeps the
   layout box exactly as wide as its parent. */
.cover {
  position: relative;
  width: auto;
  margin: 0 calc(var(--t-page-pad) * -1);
  height: var(--cover-h);
  overflow: hidden;
  border-radius: var(--cover-radius, 0px);
  background: var(--p-surface);
}

/* A measured wide banner takes its own shape, so `object-fit: cover` has nothing
   left to crop. `--cover-aspect` is only emitted for banners at 2:1 or wider that
   have actually been measured; everything else keeps the fixed height. The height
   still floors at a readable minimum so a very wide banner cannot become a
   letterbox slit on a narrow phone. */
.cover[style*="--cover-aspect"] {
  height: auto;
  aspect-ratio: var(--cover-aspect);
  min-height: 120px;
}

/* Inset keeps the shell's padding instead of cancelling it, so the cover reads
   as a card rather than a banner. */
.cover--inset { margin: var(--t-page-pad) 0 0; }

.cover--color { background: var(--cover-color, var(--p-primary)); }
.cover--gradient { background: var(--cover-gradient, linear-gradient(160deg, var(--p-primary), var(--p-accent))); }

/* A scrim over the cover itself, so a bright banner never swallows the name.
   ---------------------------------------------------------------------------
   The default tint is `--p-bg`, not black. That one change is what makes this
   work in both directions. Black darkens, which rescues light text on a dark
   palette and actively harms dark text on a light one — and the fleet audit
   found both failure modes in equal measure (a dark palette under a bright
   cover, and a light palette under a dark one). Tinting toward the page's own
   background always moves the banner AWAY from whatever the text colour is,
   whichever way round the page happens to be.

   It also produces the right look: the cover fades into the page instead of
   ending at a hard seam, which is how every link-in-bio page in the reference
   corpus handles a full-bleed hero. */
.cover-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(var(--cover-overlay-dir, to top),
      color-mix(in srgb, var(--cover-overlay-tint, var(--p-bg)) calc(var(--cover-overlay, 0) * 100%), transparent),
      transparent 68%);
}
.cover-scrim--both {
  background:
    linear-gradient(to top,
      color-mix(in srgb, var(--cover-overlay-tint, var(--p-bg)) calc(var(--cover-overlay, 0) * 100%), transparent),
      transparent 45%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--cover-overlay-tint, var(--p-bg)) calc(var(--cover-overlay, 0) * 100%), transparent),
      transparent 45%);
}
.cover-scrim--even {
  background: color-mix(in srgb, var(--cover-overlay-tint, var(--p-bg)) calc(var(--cover-overlay, 0) * 100%), transparent);
}

@media (min-width: 640px) {
  /* Inside the card the cover is flush with its top corners. */
  .shell > .head > .cover {
    margin-top: 0;
  }
}

.cover-media {
  width: 100%;
  height: 100%;
  object-fit: var(--cover-fit);
  object-position: var(--cover-pos);
}

.head-avatar-slot {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: center;
}

.head--left .head-avatar-slot {
  justify-content: flex-start;
}

/* How far the avatar hangs below the cover — the whole range, from fully inside
   it to well clear of it. --avatar-pull is a unitless ratio of the avatar's own
   size (see style.js):
     -1     fully inside the cover
     -0.5   half in, half out — the familiar link-in-bio look
      0     fully below it
     +0.6   detached, floating under the cover

   A margin rather than a transform, so everything below moves with it and the
   page has no gap to explain. Multiplied against --avatar-size, which is in px,
   because a percentage margin would resolve against the column width. */
.head--cover .head-avatar-slot {
  margin-top: calc(var(--avatar-size, 116px) * var(--avatar-pull, -0.5));
}

/* No straddle over a placeholder cover: the avatar (or its own placeholder)
   would sit on the "click to upload" caption. They stack instead. */
.cover--ph + .head-avatar-slot {
  margin-top: 12px;
}

.head--bare .head-avatar-slot {
  margin-top: 40px;
}

.head--right { text-align: right; }
.head--right .head-avatar-slot { justify-content: flex-end; }
.head--right .head-bio { margin-right: 0; }

.avatar {
  --avatar-size: 116px;
  width: var(--avatar-size);
  height: var(--avatar-size);
  position: relative;
  overflow: hidden;
  background: var(--p-surface);
  flex: none;
}

.avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar--circle {
  border-radius: 999px;
}
.avatar--rounded {
  border-radius: calc(var(--avatar-size) * 0.22);
}
.avatar--squircle {
  border-radius: calc(var(--avatar-size) * 0.32);
}
.avatar--hex {
  clip-path: polygon(50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%);
}
.avatar--none {
  border-radius: 0;
}

.avatar--diamond { border-radius: 12%; transform: rotate(45deg); }
.avatar--diamond .avatar-img { transform: rotate(-45deg) scale(1.42); }
.avatar--leaf { border-radius: 50% 6% 50% 6%; }
.avatar--blob { border-radius: 62% 38% 46% 54% / 54% 46% 54% 46%; }

.avatar--ring {
  outline: var(--avatar-ring, 4px) solid var(--avatar-ring-color, var(--p-bg));
  outline-offset: 0;
}
/* clip-path crops an outline, so shapes that use one get a border instead. */
.avatar--hex.avatar--ring,
.avatar--diamond.avatar--ring {
  outline: none;
  border: var(--avatar-ring, 4px) solid var(--avatar-ring-color, var(--p-bg));
}

.avatar { box-shadow: var(--avatar-shadow, none); }
.avatar--gray .avatar-img { filter: grayscale(1); }
.avatar--tilt { rotate: var(--avatar-rotate, 0deg); }

/* --- upload placeholders ---------------------------------------------------
   Missing cover or avatar renders as a visible slot — dashed outline, icon,
   label — until something is uploaded. Shown on the live page too, so absent
   art reads as "awaiting upload" rather than as a finished design. The editor
   adds .ph-click and wires the box to open the matching settings panel. */
.cover--ph {
  height: auto;
  min-height: 136px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--p-surface) 60%, transparent);
}
.cover--ph::before {
  content: "";
  position: absolute;
  inset: 10px;
  border: 1.5px dashed color-mix(in srgb, var(--p-text) 28%, transparent);
  border-radius: 12px;
  pointer-events: none;
}
.ph-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 16px;
  text-align: center;
  color: color-mix(in srgb, var(--p-text) 55%, transparent);
}
.ph-t { font-size: 13px; font-weight: 600; line-height: 1.2; }
.ph-s { font-size: 11px; opacity: 0.75; }
.avatar--ph {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px dashed color-mix(in srgb, var(--p-text) 28%, transparent);
  background: color-mix(in srgb, var(--p-surface) 60%, transparent);
}
.avatar--ph .ph-inner { gap: 2px; padding: 6px; }
.avatar--ph .ph-t { font-size: 10px; }

.avatar--glow {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--p-primary) 60%, transparent),
    0 0 34px color-mix(in srgb, var(--p-primary) 55%, transparent);
}

.head-text {
  padding: var(--head-space, 14px) 0 4px;
}

.head--bare:not(.head--avatar) .head-text {
  padding-top: 48px;
}

.head-name {
  margin: 0;
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  letter-spacing: var(--t-display-tracking);
  text-transform: var(--t-display-transform);
  font-size: var(--head-name-size, 30px);
  line-height: 1.15;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* A coin name is whatever the founder typed at 3am, and
     "SuperMegaUltraHyperDeflationaryRewardTokenOnSolanaBlockchainCoin" has no
     space to break at. Without this it renders straight out of the column and
     takes the page's horizontal scroll with it — the one layout failure the
     text-length stress matrix could still produce. */
  max-width: 100%;
  overflow-wrap: anywhere;
}

.head-verified {
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: var(--p-primary);
  color: var(--c-on-primary);
  flex: none;
}

.head-ticker {
  margin: 4px 0 0;
  font-family: var(--t-font-display);
  font-weight: 600;
  font-size: var(--head-ticker-size, 15px);
  letter-spacing: 0.04em;
  color: var(--c-accent-text);
}

.head-bio {
  margin: 10px auto 0;
  max-width: var(--head-bio-width, 46ch);
  color: var(--c-muted);
  font-size: var(--head-bio-size, 15px);
}

.head--left .head-bio {
  margin-left: 0;
}

/* ---------------------------------------------------------------------------
   Widget stack
   --------------------------------------------------------------------------- */

.widgets {
  display: flex;
  flex-direction: column;
  gap: calc(var(--t-gap) + var(--shell-gap-extra, 0px));
  margin-top: var(--head-gap, 26px);
}

.w {
  position: relative;
}

.w-head {
  margin-bottom: 12px;
  text-align: center;
}

.w-head--left {
  text-align: left;
}

.w-title {
  margin: 0;
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  letter-spacing: var(--t-display-tracking);
  text-transform: var(--t-display-transform);
  font-size: 19px;
  line-height: 1.25;
  /* "NotInEmploymentEducationTraining" needs 389px in a 358px column and
     was hard-clipped. Same fix as .head-name. */
  overflow-wrap: anywhere;
}

.w-sub {
  margin: 4px 0 0;
  color: var(--c-muted);
  font-size: 14px;
}

.w-empty {
  margin: 0;
  padding: 18px;
  border: 1px dashed var(--c-line-strong);
  border-radius: var(--t-radius);
  color: var(--c-muted);
  font-size: 14px;
  text-align: center;
}

/* Hidden widgets are dropped at render time on a live page; this only ever
   applies inside the editor preview, where they stay visible but recede. */
.w--hidden {
  opacity: 0.4;
}

/* ---------------------------------------------------------------------------
   Card + button primitives
   --------------------------------------------------------------------------- */

.card {
  background: var(--t-card-fill);
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  box-shadow: var(--t-card-shadow);
  padding: 16px;
}

/* Glass only means anything with something behind it to frost. */
.card {
  backdrop-filter: blur(10px);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: 48px;
  padding: 12px 20px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-btn-radius);
  background: var(--t-card-fill);
  color: var(--p-text);
  font-family: var(--t-font-body);
  font-size: 15px;
  font-weight: var(--t-btn-weight);
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.15s ease, background 0.15s ease;
}

.btn:hover {
  background: color-mix(in srgb, var(--p-text) 6%, var(--t-card-flat));
}

.btn:active {
  transform: translateY(1px);
}

/* `--c-btn-bg` is only emitted by presets that paint a texture film over the
   page: the primary nudged just far enough that its label still clears AA under
   5% of the text colour. Every other preset falls through to the palette's own
   primary, unchanged. */
.btn-primary {
  background: var(--c-btn-bg, var(--p-primary));
  border-color: transparent;
  color: var(--c-on-btn, var(--c-on-primary));
  box-shadow: var(--t-btn-shadow);
}

.btn-primary:hover {
  background: var(--c-btn-bg, var(--p-primary));
  filter: brightness(1.08);
}

.btn-ghost {
  background: transparent;
  border-color: var(--c-line-strong);
}

.btn-ghost:hover {
  background: var(--c-hover);
}

/* ---------------------------------------------------------------------------
   Stat — the shape behind every number on the page
   --------------------------------------------------------------------------- */

.stat {
  text-align: center;
}

.stat-value {
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  letter-spacing: var(--t-display-tracking);
  font-size: 21px;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}

.stat-label {
  margin-top: 2px;
  color: var(--c-muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
}

.stat-sub {
  margin-top: 2px;
  color: var(--c-muted);
  font-size: 13px;
}

/* ---------------------------------------------------------------------------
   Widget: socials
   --------------------------------------------------------------------------- */

.socials {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}

.social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  text-decoration: none;
  color: var(--p-text);
  transition: transform 0.12s ease, background 0.15s ease, color 0.15s ease;
}

.social:active {
  transform: translateY(1px);
}

.social-icon {
  display: inline-grid;
  place-items: center;
}

/* icon-row — bare icons in a line, the lightest treatment */
.socials--icon-row .social {
  width: 46px;
  height: 46px;
  border-radius: var(--t-radius-pill);
  border: var(--t-card-border) solid var(--c-line);
  background: var(--t-card-fill);
}
.socials--icon-row .social:hover {
  color: var(--c-on-primary);
  background: var(--p-primary);
  border-color: transparent;
}

/* icon-grid — same, but laid out as an even grid */
.socials--icon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
}
.socials--icon-grid .social {
  height: 62px;
  border-radius: var(--t-radius);
  border: var(--t-card-border) solid var(--c-line);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
}
.socials--icon-grid .social:hover {
  color: var(--c-on-primary);
  background: var(--p-primary);
  border-color: transparent;
}

/* pill-row — icon plus label, wrapping */
.socials--pill-row .social {
  padding: 10px 16px;
  border-radius: var(--t-radius-pill);
  border: var(--t-card-border) solid var(--c-line);
  background: var(--t-card-fill);
  font-size: 14px;
  font-weight: 600;
}
.socials--pill-row .social:hover {
  background: var(--c-hover);
}

/* big-cards — one per row on small screens, two up when there is room */
.socials--big-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.socials--big-cards .social {
  flex-direction: column;
  gap: 8px;
  padding: 20px 12px;
  border-radius: var(--t-radius);
  border: var(--t-card-border) solid var(--c-line);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
  font-size: 14px;
  font-weight: 600;
}
.socials--big-cards .social:hover {
  border-color: var(--p-primary);
}

/* list — full-width rows, the Linktree shape */
.socials--list {
  flex-direction: column;
  gap: 10px;
}
.socials--list .social {
  justify-content: flex-start;
  width: 100%;
  min-height: 52px;
  padding: 12px 18px;
  border-radius: var(--t-btn-radius);
  border: var(--t-card-border) solid var(--c-line);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
  font-size: 15px;
  font-weight: var(--t-btn-weight);
}
.socials--list .social:hover {
  background: color-mix(in srgb, var(--p-text) 6%, var(--t-card-flat));
}

/* ---------------------------------------------------------------------------
   Widget: contract address
   --------------------------------------------------------------------------- */

.ca {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
}

.ca-label {
  font-size: var(--t-label-size, 12px);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--c-muted);
  flex: none;
}

.ca-value {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--t-font-body);
  font-size: 14px;
  letter-spacing: 0.01em;
}

/* The copy and explorer controls, side by side, wherever the variant puts them. */
.ca-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

/* The column variants exist to show the FULL address rather than a truncated
   one — that is the whole reason to give it its own line. So the address gets
   the width, and the actions sit under it as a row rather than continuing the
   stack. Before this they were a vertical tower of two buttons down the left
   edge of a centred card, which is what the visual review kept catching.

   Conditional on `data-long`, which the widget sets only when the address is
   actually shown in full. With `truncate` on — the default — the value is
   thirteen characters, and stacking a label, thirteen characters and two buttons
   onto three rows of a 970px page is the layout reading as broken. One row is
   correct for a short value at every width. */
.ca--stacked[data-long],
.ca--card[data-long] {
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.ca--stacked[data-long] .ca-value,
.ca--card[data-long] .ca-value {
  flex: none;
  width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
  font-size: 13px;
}
.ca--stacked { gap: 8px; }
.ca--card { padding: 18px; gap: 10px; }

/* Minimal drops the card, not the layout: it stays a single row and inherits
   the page's alignment instead of hugging the left edge. */
.ca--minimal {
  border: none;
  background: none;
  box-shadow: none;
  padding: 4px 0;
}

/* Touch targets.
   ---------------------------------------------------------------------------
   44px is the floor (WCAG 2.5.5, Apple HIG). The fleet audit found tap-target
   failures on 120 of 120 sites, and these two buttons alone accounted for 472
   of them at 36×36 — the copy button is the single most-tapped control on a
   memecoin page, because copying the contract address is the whole point.

   Worth recording that link.me is NOT the reference here: 74% of their targets
   measure under 44px too. Where a competitor is also wrong, the standard wins. */
.ca-copy,
.ca-explorer {
  display: inline-grid;
  place-items: center;
  position: relative;
  width: var(--t-tap-min, 44px);
  height: var(--t-tap-min, 44px);
  flex: none;
  border: none;
  border-radius: var(--t-radius-sm);
  background: var(--c-faint);
  color: var(--p-text);
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
}

.ca-copy:hover,
.ca-explorer:hover {
  background: var(--p-primary);
  color: var(--c-on-primary);
}

/* The confirmation is CSS-only past the runtime setting one attribute — no
   layout shift, and nothing to clean up if the page is left open. */
.ca-copied {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: inherit;
  background: var(--p-primary);
  color: var(--c-on-primary);
  font-size: var(--t-label-size, 12px);
  font-weight: 700;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}

.ca-copy[data-copied] .ca-copied {
  opacity: 1;
}

.ca-note {
  margin: 8px 0 0;
  color: var(--c-muted);
  font-size: 13px;
  text-align: center;
}

/* ---------------------------------------------------------------------------
   Widget: buy
   --------------------------------------------------------------------------- */

.buy {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.buy-btn {
  width: 100%;
}

.buy--row {
  flex-direction: row;
  flex-wrap: wrap;
}
.buy--row .buy-btn {
  flex: 1 1 140px;
  width: auto;
}

.buy--split {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
/* A third venue would sit alone in the left column with a hole beside it —
   `cupsey` shows Jupiter and Raydium paired, then Pump.fun stranded at half
   width. An odd tail spans the row instead. `.buy` is flex/grid rather than one
   of the widget grids, so the general orphan rule does not reach it. */
.buy--split > :last-child:nth-child(odd) {
  grid-column: 1 / -1;
}
.buy--split .buy-btn {
  width: 100%;
}

.buy--primary .buy-btn {
  min-height: 56px;
  font-size: 17px;
  box-shadow: var(--t-btn-shadow), var(--t-glow);
}

.buy--ghost .buy-btn {
  background: transparent;
  border-color: var(--c-line-strong);
  color: var(--p-text);
  box-shadow: none;
}

/* ---------------------------------------------------------------------------
   Motion
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------------------------------------------------------------------------
   Widget: heading + divider + marquee (structure)
   --------------------------------------------------------------------------- */

.heading { text-align: center; }
.heading--left { text-align: left; }
.heading-eyebrow {
  margin-bottom: 4px;
  color: var(--c-accent-text);
  font-size: var(--t-label-size, 12px);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.heading-title {
  margin: 0;
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  letter-spacing: var(--t-display-tracking);
  text-transform: var(--t-display-transform);
  font-size: 22px;
  line-height: 1.2;
}
.heading-sub { margin: 5px 0 0; color: var(--c-muted); font-size: 14px; }
.heading--rule { display: flex; align-items: center; gap: 12px; text-align: left; }
.heading--rule::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--c-line);
}

/* A separator does not cost more room than the sections it separates.
   ---------------------------------------------------------------------------
   `.widgets` is a flex column with `gap: var(--t-gap)`, and a divider is a widget
   like any other — so a 4px dotted rule was taking a full gap above AND below.
   Measured on `dancing-squirrel`: 32px + 4px + 32px, a 68px void between the buy
   buttons and how-to-buy, with a hairline lost in the middle of it. It reads as a
   rendering hole, which is the opposite of what a divider is for.

   Pulled in by two thirds of a gap on each side, so a divider still breathes more
   than adjacent text but no longer out-spaces the content. The model reaches for
   dividers constantly now that it has been told to give the page rhythm, so this
   is on 169 of 302 pages. */
.w-divider {
  /* Through the spacing VARIABLES, not `margin-block`. A later rule sets
     `.w { margin-top: var(--w-mt, 0px) }` at equal specificity and would win on
     source order — and going through the variables means an owner who sets a
     widget's spacing by hand still overrides this, which a flat margin would not
     let them do. */
  /* Against the STACK gap, which is `--t-gap` plus the layout's own extra — on a
     `degen` page `--t-gap` is 12px while the widgets actually sit 32px apart, so
     measuring against the preset token alone pulled back a quarter of what it
     looked like it would. */
  --w-mt: calc((var(--t-gap) + var(--shell-gap-extra, 0px)) * -0.66);
  --w-mb: calc((var(--t-gap) + var(--shell-gap-extra, 0px)) * -0.66);
}

.divider { height: var(--divider-space, 16px); }
.divider--line { height: 1px; margin: var(--divider-space, 16px) 0; background: var(--c-line); }
.divider--glow {
  height: 2px;
  margin: var(--divider-space, 16px) 0;
  background: linear-gradient(90deg, transparent, var(--p-primary), transparent);
  opacity: 0.8;
}
.divider--dots {
  margin: var(--divider-space, 16px) 0;
  height: 4px;
  background-image: radial-gradient(var(--c-line-strong) 1.5px, transparent 1.5px);
  background-size: 12px 4px;
  background-position: center;
  background-repeat: repeat-x;
}

.marquee {
  overflow: hidden;
  margin: 0 calc(var(--t-page-pad) * -1);
  padding: 8px 0;
  user-select: none;
  /* Fade the ends instead of guillotining them.
     A hard clip cuts mid-word at both edges — `bonk` reads "OON … MOON …
     MOON … MOO" — which looks like broken text rather than a loop. Fading
     lets the eye read it as something passing through. */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
}
/* The bar variant paints its own background, and a mask would fade that too —
   there the band is meant to run edge to edge. */
.marquee--bar {
  -webkit-mask-image: none;
  mask-image: none;
}
.marquee--bar { background: var(--p-primary); color: var(--c-on-primary); }
.marquee--outline { border-block: 1px solid var(--c-line); }
.marquee-track { display: flex; width: max-content; animation: os-marquee var(--marquee-speed, 22s) linear infinite; }
.marquee--reverse .marquee-track { animation-direction: reverse; }
.marquee-run { display: flex; }
.marquee-run span {
  padding: 0 18px;
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  font-size: 17px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
@keyframes os-marquee {
  to { transform: translateX(-50%); }
}

/* ---------------------------------------------------------------------------
   Widget: links
   --------------------------------------------------------------------------- */

.links { display: flex; flex-direction: column; gap: 10px; }
.link-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 52px;
  padding: 12px 18px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-btn-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
  color: var(--p-text);
  font-size: 15px;
  font-weight: var(--t-btn-weight);
  text-decoration: none;
  transition: transform 0.12s ease, background 0.15s ease;
}
.link-btn:hover { background: color-mix(in srgb, var(--p-text) 6%, var(--t-card-flat)); }
.link-btn:active { transform: translateY(1px); }
.link-label { flex: 1; min-width: 0; }
.link-note { color: var(--c-muted); font-size: 12px; font-weight: 500; }
.link-icon { display: inline-grid; place-items: center; flex: none; }

.links--outline .link-btn { background: transparent; border-color: var(--c-line-strong); box-shadow: none; }
.links--compact .link-btn { min-height: 42px; padding: 8px 14px; font-size: 14px; }
.links--cards .link-btn { flex-direction: column; gap: 4px; padding: 18px; text-align: center; }
.links--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.links--grid .link-btn { justify-content: center; }

/* A link can be placed in a half-width composer cell even on a desktop
   viewport. At that allocation the action label remains primary and the
   provenance note moves below it instead of competing for one narrow row. */
@container widget (max-width: 380px) {
  .links:not(.links--cards) .link-btn {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 10px;
    row-gap: 3px;
  }
  .links:not(.links--cards) .link-btn:not(:has(.link-icon)) { grid-template-columns: minmax(0, 1fr); }
  .links:not(.links--cards) .link-icon { grid-row: 1 / span 2; }
  .links:not(.links--cards) .link-label,
  .links:not(.links--cards) .link-note {
    grid-column: 2;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .links:not(.links--cards) .link-btn:not(:has(.link-icon)) :is(.link-label, .link-note) { grid-column: 1; }
}

/* Branded buttons — each destination in its own colours (LittleLink-style).
   Three custom properties per button, set inline by the renderer: fill, a
   measured label colour, and a protective stroke that only exists when the
   brand fill would sink into this page's background. The hover is
   brightness(), not a per-brand shade: one rule that darkens flats, gradients
   and bordered buttons alike. Unbranded destinations keep the house style. */
.link-btn--branded {
  border: 1px solid var(--lb-border, transparent);
  background: var(--lb-bg);
  color: var(--lb-text);
  transition: filter 0.15s ease, transform 0.12s ease;
}
.link-btn--branded:hover { background: var(--lb-bg); filter: brightness(92%); transform: translateY(-1px); }
.link-btn--branded .link-note { color: color-mix(in srgb, var(--lb-text) 72%, transparent); }

/* ---------------------------------------------------------------------------
   Widget: price
   --------------------------------------------------------------------------- */

.price {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 18px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
  text-align: center;
}
.price-main { display: flex; align-items: baseline; justify-content: center; gap: 7px; }
.price-value {
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  letter-spacing: var(--t-display-tracking);
  font-size: 1em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.price-unit { color: var(--c-muted); font-size: 12px; font-weight: 700; letter-spacing: 0.08em; }
.price-stats { display: flex; flex-wrap: wrap; justify-content: center; gap: 18px; }
.price-stat-value { font-weight: 700; font-size: 15px; font-variant-numeric: tabular-nums; }
.price-stat-label { color: var(--c-muted); font-size: var(--t-label-size, 12px); text-transform: uppercase; letter-spacing: 0.07em; }

/* The three live headline components: price, market cap, and both.
   ---------------------------------------------------------------------------
   They share `.price` and its variants deliberately — same object, different
   numbers — so everything above (card fill, variant sizing, the `--wo-value`
   option) applies to all three without being written three times. Only what is
   genuinely new lives here. */

/* One figure: caption, headline, and in `both` mode the other currency beneath. */
.price-figure { display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 0; font-size: 34px; }

.price-caption {
  color: var(--c-muted);
  font-size: var(--t-label-size, 12px);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

/* The second currency: smaller, quieter, and NOT a second headline.
   At 390px a sub-cent price is already fifteen characters — two of them at equal
   weight either wrap or shrink until neither leads. One number leads and the
   other is available underneath, which is what somebody glancing at a phone
   actually reads. */
.price-alt {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 5px;
  color: var(--c-muted);
}
.price-alt .price-value {
  font-size: max(0.42em, 11px);
  font-weight: 600;
  letter-spacing: 0;
}
.price-alt .price-unit { font-size: 11px; }

/* Market cap leads with its caption, because a big number beside a currency
   reads as a price by default — on a coin page it usually is one. */
.mcap .price-caption { order: -1; }

/* Both figures, as equals. Price and market cap answer different questions, so
   neither is the other's subtitle — unlike the two currencies inside one figure. */
/* auto-fit against a real minimum, not a viewport breakpoint.
   What decides whether two figures fit is the COLUMN width — an owner can set a
   420px layout on a 1400px screen — so a media query asks the wrong question. A
   sub-cent price is thirteen characters at 30px, about 190px wide; below two of
   those the grid drops to one column on its own.

   Measured before this: at 390px the two columns collided into
   "$0.0₅3346 US$3.23K" with the unit label overlapping the next figure. */
.pricecap-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px 18px;
  align-items: start;
}
.pricecap--stacked .pricecap-figures { grid-template-columns: 1fr; }
.pricecap--bar .pricecap-figures { gap: 20px; }

.price--hero .price-figure { font-size: 44px; }
.price--bar { flex-direction: row; align-items: center; justify-content: space-between; gap: 16px; text-align: left; padding: 14px 18px; }
.price--bar .price-figure { font-size: 24px; }
.price--inline { border: none; background: none; box-shadow: none; padding: 0; }
.price--tiles .price-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); gap: 12px; }

/* Direction colours are the one place a page does not use its palette: green
   for up and red for down is a convention traders read faster than any brand. */
/* Palette-derived (services/render/css.js). A fixed green and a fixed red
   cannot serve fifty backgrounds: #ea3943 failed WCAG AA on 28 of 33 dark
   palettes and all 17 light ones. Same hues, lightness per page polarity. */
.is-up { color: var(--c-up, #16c784); }
.is-down { color: var(--c-down, #ea3943); }

/* ---------------------------------------------------------------------------
   Widget: market stats
   --------------------------------------------------------------------------- */

.mstats { display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); gap: 10px; }
.mstat {
  padding: 13px 10px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
  text-align: center;
}
.mstat-value { font-weight: 700; font-size: 16px; font-variant-numeric: tabular-nums; }
.mstat-label { margin-top: 2px; color: var(--c-muted); font-size: var(--t-label-size, 12px); text-transform: uppercase; letter-spacing: 0.06em; }

.mstats--rows { grid-template-columns: 1fr; }
.mstats--rows .mstat { display: flex; align-items: center; justify-content: space-between; text-align: left; }
.mstats--rows .mstat-label { margin: 0; }
.mstats--strip { display: flex; flex-wrap: wrap; gap: 18px; justify-content: center; }
.mstats--strip .mstat { border: none; background: none; padding: 0; }
.mstats--cards .mstat { box-shadow: var(--t-card-shadow); padding: 18px 12px; }

/* ---------------------------------------------------------------------------
   Widget: buy / sell pressure
   --------------------------------------------------------------------------- */

.press { display: flex; flex-direction: column; gap: 8px; }
.press-track {
  display: flex;
  height: 12px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--c-faint);
}
.press-buy { width: 50%; background: var(--c-up, #16c784); transition: width 0.5s cubic-bezier(0.32, 0.72, 0.25, 1); }
.press-sell { width: 50%; background: var(--c-down, #ea3943); transition: width 0.5s cubic-bezier(0.32, 0.72, 0.25, 1); }
.press-legend { display: flex; justify-content: space-between; font-size: 12px; color: var(--c-muted); }
.press-buys, .press-sells { display: inline-flex; align-items: baseline; gap: 5px; }
.press-sells { justify-content: flex-end; }
.press-legend small { font: inherit; font-variant-numeric: tabular-nums; }
.press-buys b { color: var(--c-up, #16c784); }
.press-sells b { color: var(--c-down, #ea3943); }
.press--compact .press-track { height: 7px; }
.press--split .press-track { border-radius: var(--t-radius-sm); }

/* ---------------------------------------------------------------------------
   Widget: about
   --------------------------------------------------------------------------- */

.about-body { font-size: 15.5px; line-height: 1.65; color: color-mix(in srgb, var(--p-text) 88%, transparent); }
.about-body p { margin: 0 0 0.8em; }
.about-body p:last-child { margin-bottom: 0; }
.about--card .about-body {
  padding: 18px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
}
.about--centered { text-align: center; }
.about--quote-mark .about-body {
  padding-left: 18px;
  border-left: 3px solid var(--p-primary);
  font-style: italic;
}

/* ---------------------------------------------------------------------------
   Widget: FAQ
   --------------------------------------------------------------------------- */

.faq { display: flex; flex-direction: column; gap: 8px; }
.faq-item {
  overflow: hidden;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
}
.faq-q {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px 16px;
  border: 0;
  background: none;
  color: inherit;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.faq-q > span:first-child { flex: 1; }
.faq-chev { display: inline-grid; place-items: center; color: var(--c-muted); transition: transform 0.2s ease; }
[data-open] > .faq-q .faq-chev { transform: rotate(180deg); }
/* Grid rows animate to auto, so an answer of any length opens smoothly without
   anyone having to measure it in JS. */
.faq-a { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.25s ease; }
[data-open] > .faq-a { grid-template-rows: 1fr; }
.faq-a-inner { overflow: hidden; }
[data-open] > .faq-a .faq-a-inner { padding: 0 16px 14px; }
.faq-a-inner { color: var(--c-muted); font-size: 14.5px; line-height: 1.6; }
.faq--plain .faq-item { border: none; background: none; border-radius: 0; }
.faq--plain .faq-item + .faq-item { border-top: 1px solid var(--c-line); }
.faq--cards .faq-item { box-shadow: var(--t-card-shadow); }

/* ---------------------------------------------------------------------------
   Widget: gallery
   --------------------------------------------------------------------------- */

.gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.gallery--two-up { grid-template-columns: repeat(2, 1fr); }
.gallery-item { margin: 0; overflow: hidden; border-radius: var(--t-radius-sm); background: var(--t-card-flat); }
.gallery-item img { width: 100%; aspect-ratio: 1; object-fit: cover; transition: transform 0.3s ease; }
.gallery-item:hover img { transform: scale(1.05); }
.gallery-item figcaption { padding: 6px 8px; color: var(--c-muted); font-size: var(--t-label-size, 12px); text-align: center; }

.gallery--masonry { display: block; columns: 2; column-gap: 8px; }
.gallery--masonry .gallery-item { margin-bottom: 8px; break-inside: avoid; }
.gallery--masonry .gallery-item img { aspect-ratio: auto; }

.gallery--carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.gallery--carousel::-webkit-scrollbar { display: none; }
.gallery--carousel .gallery-item { flex: 0 0 68%; scroll-snap-align: center; }

.gallery-link { display: block; cursor: zoom-in; }

/* ---------------------------------------------------------------------------
   Lightbox
   ---------------------------------------------------------------------------
   The gallery crops every image to a square, so the grid is never the whole
   picture — opening one has to be possible. Built here rather than borrowed: a
   viewer that arrives with its own stylesheet looks like somebody else's product
   dropped onto the page, and every one of them ships more JavaScript than this
   whole runtime.

   It takes the page's own tokens, so it is themed by construction: `brutalist`
   gets square corners and a hard border, `glass` gets a translucent panel, and a
   new preset needs no work here at all. The scrim is --p-bg rather than black so
   a light page dims to light grey instead of punching a hole in itself.

   `dialog` for the semantics and the top layer: it is the only element that
   escapes every stacking context on the page, including the texture film at
   z-index 3 and the sticky bar at 40, without picking a number and hoping. */
.lightbox {
  width: 100vw;
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: hidden;
}

.lightbox::backdrop {
  background: color-mix(in srgb, var(--lb-scrim, #000) 88%, transparent);
  backdrop-filter: blur(6px);
}

.lightbox-in {
  display: grid;
  grid-template-rows: 1fr auto;
  width: 100%;
  height: 100%;
  padding: max(12px, env(safe-area-inset-top, 0px)) 12px max(12px, env(safe-area-inset-bottom, 0px));
  box-sizing: border-box;
}

.lightbox-stage {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 0; /* or the image forces the row taller than the viewport */
}

.lightbox-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--t-radius-sm);
  /* A dark image on a dark scrim needs an edge to sit against. */
  box-shadow: 0 24px 64px rgb(0 0 0 / 45%);
}

.lightbox-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding-top: 12px;
  min-height: 44px;
}

.lightbox-cap {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--p-text);
  font-family: var(--t-font-body);
  font-size: 13px;
  line-height: 1.4;
  text-align: center;
  /* Two lines, then ellipsis: a long caption must not eat the image. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.lightbox-count {
  flex: 0 0 auto;
  color: var(--c-muted);
  font-family: var(--t-font-body);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* 44px, not the 30px a tidier icon button would want: these are the only
   controls in the viewer and half its use is one-handed on a phone. */
.lightbox-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  padding: 0;
  border: var(--t-card-border) solid var(--c-line-strong);
  border-radius: var(--t-btn-radius);
  background: var(--p-surface);
  color: var(--p-text);
  cursor: pointer;
  transition: background 0.15s ease, opacity 0.15s ease;
}

.lightbox-btn:hover { background: color-mix(in srgb, var(--p-text) 10%, var(--p-surface)); }
.lightbox-btn:disabled { opacity: 0.35; cursor: default; }
.lightbox-btn svg { width: 20px; height: 20px; }

.lightbox-close {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 1;
}

/* Desktop puts prev/next either side of the image, where a cursor already is;
   the bar keeps them on a phone, where the thumb is at the bottom. */
.lightbox-nav { display: none; }

@media (min-width: 700px) {
  .lightbox-nav {
    display: inline-flex;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
  }
  .lightbox-nav--prev { left: 0; }
  .lightbox-nav--next { right: 0; }
  .lightbox-bar .lightbox-nav { display: none; }
}

.lightbox-dots { display: flex; gap: 6px; justify-content: center; padding-top: 10px; }

.lightbox-dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 99px;
  background: color-mix(in srgb, var(--p-text) 30%, transparent);
  cursor: pointer;
}

.lightbox-dot[aria-current="true"] { background: var(--p-primary); }

/* A dot is a 7px target inside a 24px hit area — WCAG 2.2 AA is 24px, and
   shrinking the dot itself would make the strip unreadable. */
.lightbox-dot::before {
  content: "";
  position: absolute;
  inset: -9px;
}
.lightbox-dot { position: relative; }

/* ---------------------------------------------------------------------------
   Widget: tokenomics
   --------------------------------------------------------------------------- */

.tok { display: flex; flex-direction: column; gap: 12px; }
.tok-head { display: flex; gap: 10px; }
.tok-fact {
  flex: 1;
  padding: 14px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
  text-align: center;
}
.tok-fact b { display: block; font-family: var(--t-font-display); font-size: 19px; font-weight: var(--t-display-weight); }
.tok-fact span { color: var(--c-muted); font-size: var(--t-label-size, 12px); text-transform: uppercase; letter-spacing: 0.06em; }
.tok-row-head { display: flex; justify-content: space-between; font-size: 14px; margin-bottom: 5px; }
.tok-label { font-weight: 600; }
.tok-pct { color: var(--c-muted); font-variant-numeric: tabular-nums; }
.tok-bar { height: 8px; overflow: hidden; border-radius: 999px; background: var(--c-faint); }
.tok-bar span { display: block; height: 100%; border-radius: 999px; background: var(--p-primary); }
.tok-note { margin-top: 3px; color: var(--c-muted); font-size: 12px; }
.tok--tiles .tok-bar,
.tok--tiles .tok-note { display: none; }
.tok--tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); }
.tok--tiles .tok-head { grid-column: 1 / -1; }
.tok--cards .tok-row {
  padding: 14px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
}

/* ---------------------------------------------------------------------------
   Widget: roadmap
   --------------------------------------------------------------------------- */

.road { display: flex; flex-direction: column; gap: 4px; }
.road-item { display: flex; gap: 14px; padding-bottom: 18px; }
.road-marker {
  position: relative;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: none;
  margin-top: 2px;
  border: 2px solid var(--c-line-strong);
  border-radius: 999px;
  color: var(--c-on-primary);
}
.road-item.is-done .road-marker { border-color: var(--p-primary); background: var(--p-primary); }
/* The connecting line is drawn from each marker to the next, so it never
   overshoots past the final item. */
.road--timeline .road-item:not(:last-child) .road-marker::after {
  content: "";
  position: absolute;
  top: 22px;
  left: 50%;
  width: 2px;
  height: calc(100% + 18px);
  transform: translateX(-50%);
  background: var(--c-line);
}
.road-phase { color: var(--c-accent-text); font-size: var(--t-label-size, 12px); font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; }
.road-title { font-family: var(--t-font-display); font-weight: var(--t-display-weight); font-size: 16px; }
.road-text { margin: 3px 0 0; color: var(--c-muted); font-size: 14px; line-height: 1.55; }
.road--cards .road-item {
  padding: 14px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
}
.road--checklist .road-marker { border-radius: var(--t-radius-sm); }

/* ---------------------------------------------------------------------------
   Widget: how to buy
   --------------------------------------------------------------------------- */

.steps { display: flex; flex-direction: column; gap: 12px; }
.step { display: flex; gap: 14px; }
.step-num {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 999px;
  /* Same film-safe surface as a primary button: this is a filled badge with a
     label on it, and a preset that paints a texture over the page taxes it
     identically. It was 4.25:1 on terminal/solana across the fleet, which is only
     visible now that `howtobuy` appears on every page instead of two of them. */
  background: var(--c-btn-bg, var(--p-primary));
  color: var(--c-on-btn, var(--c-on-primary));
  font-family: var(--t-font-display);
  font-weight: 700;
  font-size: 14px;
}
.step-title { font-weight: 700; font-size: 15px; }
.step-text { margin: 2px 0 0; color: var(--c-muted); font-size: 14px; line-height: 1.55; }
.steps--cards .step {
  padding: 14px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
}
.steps--compact .step-text { display: none; }

/* ---------------------------------------------------------------------------
   Widget: CTA
   --------------------------------------------------------------------------- */

.cta {
  padding: 26px 20px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
  text-align: center;
}
.cta-title {
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  letter-spacing: var(--t-display-tracking);
  text-transform: var(--t-display-transform);
  font-size: 24px;
  line-height: 1.2;
}
.cta-body { margin: 8px 0 0; color: var(--c-muted); font-size: 15px; }
.cta-btn { margin-top: 16px; }
.cta--glow {
  border-color: color-mix(in srgb, var(--p-primary) 45%, transparent);
  box-shadow: var(--t-glow), var(--t-card-shadow);
}
.cta--outline { background: transparent; border-color: var(--c-line-strong); box-shadow: none; }
.cta--minimal { padding: 8px 0; border: none; background: none; box-shadow: none; }

/* ---------------------------------------------------------------------------
   Widget: waitlist
   --------------------------------------------------------------------------- */

.waitlist { text-align: inherit; }
.wl-row { display: flex; gap: 8px; }
.wl-input {
  flex: 1;
  min-width: 0;
  min-height: 48px;
  padding: 10px 16px;
  border: var(--t-card-border, 1px) solid var(--c-line-strong);
  border-radius: var(--t-btn-radius);
  background: var(--t-card-fill);
  color: var(--p-text);
  font: inherit;
  font-size: 15px;
}
.wl-input:focus { outline: 2px solid var(--p-primary); outline-offset: 1px; }
.wl-input::placeholder { color: var(--c-muted); }
/* The honeypot: off-canvas, not display:none — some bots skip invisible
   fields, none skip ones that merely aren't on screen. */
.wl-hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.wl-btn {
  min-height: 48px;
  padding: 10px 22px;
  border: none;
  border-radius: var(--t-btn-radius);
  background: var(--p-primary);
  color: var(--c-on-primary);
  font: inherit;
  font-size: 15px;
  font-weight: var(--t-btn-weight);
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.12s ease;
}
.wl-btn:hover { filter: brightness(92%); transform: translateY(-1px); }
.wl-btn:disabled { opacity: 0.6; cursor: default; transform: none; }
.wl-done { margin: 4px 0 0; color: var(--p-text); font-weight: 600; }
.wl-err { margin: 6px 0 0; color: var(--c-muted); font-size: 13px; }

.waitlist--stacked .wl-row { flex-direction: column; }
.waitlist--stacked .wl-btn { width: 100%; }

/* ---------------------------------------------------------------------------
   Widget: countdown
   --------------------------------------------------------------------------- */

.cd { display: flex; gap: 8px; justify-content: center; }
.cd-unit {
  flex: 1;
  max-width: 92px;
  padding: 14px 6px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
  text-align: center;
}
.cd-value {
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  font-size: 26px;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.cd-label { margin-top: 2px; color: var(--c-muted); font-size: var(--t-label-size, 12px); text-transform: uppercase; letter-spacing: 0.08em; }
.cd--inline { gap: 16px; }
.cd--inline .cd-unit { border: none; background: none; padding: 0; }
.w[data-finished] .cd-unit { display: none; }
.w[data-finished]::before {
  content: attr(data-done);
  display: block;
  width: 100%;
  padding: 18px;
  border-radius: var(--t-radius);
  background: var(--p-primary);
  color: var(--c-on-primary);
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  font-size: 20px;
  text-align: center;
}

/* ---------------------------------------------------------------------------
   Widget: stat tiles + followers
   --------------------------------------------------------------------------- */

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 10px; }
.tile {
  padding: 16px 10px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
  text-align: center;
}
.tile-value { font-family: var(--t-font-display); font-weight: var(--t-display-weight); font-size: 22px; line-height: 1.15; }
.tile-label { margin-top: 2px; color: var(--c-muted); font-size: var(--t-label-size, 12px); text-transform: uppercase; letter-spacing: 0.06em; }
.tiles--row { display: flex; flex-wrap: wrap; justify-content: center; gap: 22px; }
.tiles--row .tile { border: none; background: none; padding: 0; }
.tiles--big .tile-value { font-size: 30px; }
.tiles--cards .tile { box-shadow: var(--t-card-shadow); padding: 22px 12px; }

.foll {
  padding: 20px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
  text-align: center;
}
.foll-value {
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  letter-spacing: var(--t-display-tracking);
  font-size: 38px;
  line-height: 1.1;
}
.foll-label { color: var(--c-muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; }
.foll-list { display: flex; flex-wrap: wrap; justify-content: center; gap: 18px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--c-line); }
.foll-row { display: flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 600; }
.foll-icon { display: inline-grid; place-items: center; color: var(--c-muted); }
.foll--row { display: flex; align-items: center; justify-content: space-between; gap: 16px; text-align: left; }
.foll--row .foll-list { margin: 0; padding: 0; border: none; }
.foll--row .foll-value { font-size: 26px; }

/* ---------------------------------------------------------------------------
   Widget: disclaimer
   --------------------------------------------------------------------------- */

.disclaimer { margin: 0; color: var(--c-muted); font-size: 12px; line-height: 1.6; }
.disclaimer--centered { text-align: center; }
.disclaimer--boxed {
  padding: 14px;
  border: 1px solid var(--c-line);
  border-radius: var(--t-radius-sm);
}

/* ---------------------------------------------------------------------------
   Widget: team
   --------------------------------------------------------------------------- */

.team { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; }
.team-member {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 16px 12px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
  color: inherit;
  text-align: center;
  text-decoration: none;
}
a.team-member:hover { border-color: var(--p-primary); }
.team-photo { width: 64px; height: 64px; border-radius: 999px; object-fit: cover; }
.team-name { font-weight: 700; font-size: 15px; }
.team-role { color: var(--c-accent-text); font-size: var(--t-label-size, 12px); text-transform: uppercase; letter-spacing: 0.07em; }
.team-bio { margin: 4px 0 0; color: var(--c-muted); font-size: 13px; line-height: 1.5; }
.team-link { color: var(--c-muted); }
.team--rows { grid-template-columns: 1fr; }
.team--rows .team-member { flex-direction: row; text-align: left; }
.team--rows .team-text { flex: 1; }
.team--avatars { display: flex; flex-wrap: wrap; justify-content: center; }
.team--avatars .team-member { border: none; background: none; box-shadow: none; padding: 6px; }
.team--avatars .team-bio { display: none; }

/* ---------------------------------------------------------------------------
   Widget: listings
   --------------------------------------------------------------------------- */

.listings { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; }
.listing {
  display: flex;
  align-items: center;
  width: 100%;
  min-width: 0;
  gap: 9px;
  padding: 12px 14px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
  color: inherit;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.listing:hover { border-color: var(--p-primary); background: var(--c-hover); }
.listing-icon { display: inline-grid; place-items: center; flex: none; color: var(--c-muted); }
.listing-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.listing-note {
  max-width: 46%;
  min-width: 0;
  color: var(--c-muted);
  font-size: var(--t-label-size, 12px);
  font-weight: 500;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.listings--rows { grid-template-columns: 1fr; }
.listings--pills { display: flex; flex-wrap: wrap; }
.listings--pills .listing { border-radius: 999px; padding: 8px 14px; }
.listings--logos .listing-label { display: none; }
.listings--logos { display: flex; flex-wrap: wrap; justify-content: center; }
.listings--logos .listing { padding: 12px; }

/* ---------------------------------------------------------------------------
   Widget: video + embed + image
   --------------------------------------------------------------------------- */

.video, .embed {
  overflow: hidden;
  border-radius: var(--t-radius);
  background: var(--t-card-flat);
}
.video video { width: 100%; height: auto; display: block; }
.video--square video { aspect-ratio: 1; object-fit: cover; }
.video--wide video { aspect-ratio: 16 / 9; object-fit: cover; }
.video--card { border: var(--t-card-border) solid var(--c-line); box-shadow: var(--t-card-shadow); }

.embed { position: relative; display: grid; aspect-ratio: 16 / 9; place-items: center; }
.embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.embed--card { border: var(--t-card-border) solid var(--c-line); box-shadow: var(--t-card-shadow); }
.embed-play {
  position: absolute;
  z-index: 1;
  inset: 0 0 52px;
  display: flex;
  width: 100%;
  padding: clamp(18px, 6cqi, 38px);
  border: 0;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background:
    radial-gradient(circle at 25% 15%, color-mix(in srgb, var(--p-primary) 24%, transparent), transparent 42%),
    var(--t-card-flat);
  color: var(--p-text);
  cursor: pointer;
  text-align: left;
}
.embed-play:hover { background-color: var(--c-hover); }
.embed-play:focus-visible { outline: 3px solid var(--p-primary); outline-offset: -5px; }
.embed-play i { color: var(--p-primary); font-size: clamp(34px, 10cqi, 58px); }
.embed-play span { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
.embed-play strong { font-family: var(--t-font-display); font-size: clamp(18px, 5cqi, 26px); }
.embed-play small { color: var(--c-muted); font-size: 12px; line-height: 1.4; }
.embed-source {
  position: absolute;
  z-index: 2;
  right: 12px;
  bottom: 10px;
  display: inline-flex;
  min-height: 32px;
  padding: 6px 9px;
  align-items: center;
  border-radius: 999px;
  background: color-mix(in srgb, var(--t-card-flat) 92%, transparent);
  color: var(--p-text);
  font-size: 11px;
  font-weight: 700;
  text-decoration: none;
}
.embed-source:hover { color: var(--c-primary-text); }
.embed-unavailable { max-width: 34ch; margin: 0; padding: 20px; color: var(--c-muted); text-align: center; }
.embed.is-loading .embed-play { cursor: progress; opacity: .65; }
.embed.has-frame :is(.embed-play, .embed-source, .embed-unavailable) { display: none; }
.embed.has-error .embed-play { display: none; }

.figure { margin: 0; }
.figure img { width: 100%; height: auto; border-radius: var(--t-radius); }
.figure figcaption { margin-top: 6px; color: var(--c-muted); font-size: 12px; text-align: center; }
.figure--circle img { aspect-ratio: 1; object-fit: cover; border-radius: 999px; max-width: 220px; margin: 0 auto; }
.figure--float img { animation: os-float 5s ease-in-out infinite; }
@keyframes os-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

/* ---------------------------------------------------------------------------
   Widget: quote
   --------------------------------------------------------------------------- */

.quote { margin: 0; text-align: center; }
.quote-text {
  margin: 0;
  font-family: var(--t-font-display);
  font-weight: var(--t-display-weight);
  letter-spacing: var(--t-display-tracking);
  font-size: 21px;
  line-height: 1.35;
}
.quote-author { margin-top: 8px; color: var(--c-muted); font-size: 13px; }
.quote-mark { display: inline-grid; place-items: center; color: var(--p-primary); opacity: 0.5; margin-bottom: 4px; }
.quote--card {
  padding: 24px 20px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
}
.quote--big .quote-text { font-size: 27px; }
.quote--minimal .quote-text { font-family: var(--t-font-body); font-weight: 500; font-size: 17px; font-style: italic; }

/* ---------------------------------------------------------------------------
   Widget: market cap goal
   --------------------------------------------------------------------------- */

.goal {
  padding: 16px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
}
.goal-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 8px; }
.goal-now { font-family: var(--t-font-display); font-weight: var(--t-display-weight); font-size: 20px; font-variant-numeric: tabular-nums; }
.goal-target { color: var(--c-muted); font-size: 13px; font-weight: 600; }
.goal-track { height: 10px; overflow: hidden; border-radius: 999px; background: var(--c-faint); }
.goal-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--p-primary), var(--p-accent));
  transition: width 0.6s cubic-bezier(0.32, 0.72, 0.25, 1);
}
.goal-pct { margin-top: 6px; color: var(--c-muted); font-size: 12px; text-align: center; }
.goal--card { box-shadow: var(--t-card-shadow), var(--t-glow); }
.goal--milestones .goal-track { height: 16px; }

/* ---------------------------------------------------------------------------
   Widget: timeline
   --------------------------------------------------------------------------- */

.tl { display: flex; flex-direction: column; }
.tl-item { display: flex; gap: 14px; padding-bottom: 16px; }
.tl-dot {
  position: relative;
  width: 10px;
  height: 10px;
  flex: none;
  margin-top: 6px;
  border-radius: 999px;
  background: var(--p-primary);
}
.tl--timeline .tl-item:not(:last-child) .tl-dot::after {
  content: "";
  position: absolute;
  top: 10px;
  left: 50%;
  width: 2px;
  height: calc(100% + 16px);
  transform: translateX(-50%);
  background: var(--c-line);
}
.tl-date { color: var(--c-accent-text); font-size: var(--t-label-size, 12px); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
.tl-title { font-weight: 700; font-size: 15px; }
.tl-text { margin: 3px 0 0; color: var(--c-muted); font-size: 14px; line-height: 1.55; }
.tl--cards .tl-item {
  padding: 14px;
  margin-bottom: 8px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
}
.tl--list .tl-dot { display: none; }

/* ---------------------------------------------------------------------------
   Widget: partners
   --------------------------------------------------------------------------- */

.partners { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 14px; }
.partner {
  display: grid;
  place-items: center;
  padding: 8px 12px;
  color: inherit;
  text-decoration: none;
}
.partner img { max-height: 34px; width: auto; object-fit: contain; }
.partner-name { font-weight: 700; font-size: 15px; }
.partners--mono .partner img { filter: grayscale(1); opacity: 0.65; transition: filter 0.2s ease, opacity 0.2s ease; }
.partners--mono .partner:hover img { filter: grayscale(0); opacity: 1; }
.partners--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); }
.partners--cards .partner {
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
  padding: 16px;
}

/* ---------------------------------------------------------------------------
   Widget: community wall
   --------------------------------------------------------------------------- */

.wall { display: grid; grid-template-columns: 1fr; gap: 10px; }
.wall-item {
  display: block;
  padding: 14px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
  box-shadow: var(--t-card-shadow);
  color: inherit;
  text-decoration: none;
}
a.wall-item:hover { border-color: var(--p-primary); }
.wall-head { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }
.wall-avatar { width: 34px; height: 34px; border-radius: 999px; object-fit: cover; flex: none; }
.wall-who { flex: 1; min-width: 0; }
.wall-author { font-weight: 700; font-size: 14px; }
.wall-handle { color: var(--c-muted); font-size: 12px; }
.wall-mark { color: var(--c-muted); flex: none; }
.wall-text { margin: 0; font-size: 14.5px; line-height: 1.55; }
.wall--carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}
.wall--carousel .wall-item { flex: 0 0 100%; scroll-snap-align: start; }
.wall-controls { display: flex; margin-top: 10px; align-items: center; justify-content: center; gap: 10px; }
.wall-controls button {
  display: inline-grid;
  width: 40px;
  height: 40px;
  padding: 0;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: 999px;
  place-items: center;
  background: var(--t-card-fill);
  color: var(--p-text);
  cursor: pointer;
}
.wall-controls button:first-child svg { transform: rotate(180deg); }
.wall-controls button:hover { border-color: var(--p-primary); background: var(--c-hover); }
.wall-controls button:focus-visible { outline: 3px solid var(--p-primary); outline-offset: 2px; }
.wall-controls button:disabled { cursor: default; opacity: .45; }
.wall-controls span { min-width: 4ch; color: var(--c-muted); font-size: 12px; text-align: center; }
.wall--list .wall-item { border: none; background: none; box-shadow: none; padding: 10px 0; border-radius: 0; }
.wall--list .wall-item + .wall-item { border-top: 1px solid var(--c-line); }

/* ---------------------------------------------------------------------------
   Widget: feature grid
   --------------------------------------------------------------------------- */

.feats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; }
.feat {
  padding: 16px 14px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius);
  background: var(--t-card-fill);
}
.feat-icon { display: inline-grid; place-items: center; margin-bottom: 8px; color: var(--p-primary); }
.feat-title { font-weight: 700; font-size: 15px; }
.feat-text { margin: 4px 0 0; color: var(--c-muted); font-size: 13.5px; line-height: 1.5; }
.feats--rows, .feats--two-up { grid-template-columns: 1fr; }
.feats--two-up { grid-template-columns: repeat(2, 1fr); }
.feats--rows .feat { display: flex; align-items: flex-start; gap: 12px; }
.feats--rows .feat-icon { margin: 0; }
.feats--cards .feat { box-shadow: var(--t-card-shadow); }

/* ---------------------------------------------------------------------------
   Widget: safety checks
   --------------------------------------------------------------------------- */

.safety { display: flex; flex-direction: column; gap: 8px; }
.safety-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--t-radius-sm);
  background: var(--t-card-fill);
  font-size: 14px;
  font-weight: 600;
}
.safety-tick {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  flex: none;
  border-radius: 999px;
  background: var(--c-up, #16c784);
  color: var(--c-on-up, #fff);
}
.safety-note { margin: 8px 0 0; color: var(--c-muted); font-size: 12px; text-align: center; }
.safety-note a { color: var(--c-accent-text); }
.safety--tiles { flex-direction: row; flex-wrap: wrap; }
.safety--tiles .safety-row { flex: 1 1 140px; flex-direction: column; text-align: center; }
.safety--compact .safety-row { padding: 8px 10px; font-size: 13px; }

/* ===========================================================================
   Shared widget style (doc.WIDGET_STYLE → services/render/style.js)
   ---------------------------------------------------------------------------
   Every widget gets these without knowing they exist. The renderer puts the
   custom properties on the <section> wrapper and adds a couple of structural
   classes; everything below reads them. Adding an option here gives all 34
   widgets a new control at once.

   Fallbacks matter: a widget whose owner has touched nothing must render
   exactly as it did before this layer existed, so every var() falls back to the
   style preset's own token.
   =========================================================================== */

.w {
  margin-top: var(--w-mt, 0px);
  margin-bottom: var(--w-mb, 0px);
  color: var(--w-text, inherit);
}

/* Alignment cascades to the widget's own text and to any flex row inside it. */
.w[style*="--w-align"] { text-align: var(--w-align); }
.w[style*="--w-align"] .w-head { text-align: var(--w-align); }
.w[style*="--w-align:left"] .socials,
.w[style*="--w-align:left"] .partners,
.w[style*="--w-align:left"] .listings--pills { justify-content: flex-start; }
.w[style*="--w-align:right"] .socials,
.w[style*="--w-align:right"] .partners,
.w[style*="--w-align:right"] .listings--pills { justify-content: flex-end; }

/* Per-widget accent override. Anything inside that would use the palette accent
   picks this up instead, because --p-accent is re-pointed for this subtree. */
.w[style*="--w-accent"] {
  --p-accent: var(--w-accent);
  --c-accent-text: var(--w-accent);
}

/* Radius / shadow / border overrides reach the surfaces a widget draws. */
.w[style*="--w-radius"] :is(.card, .ca, .price, .mstat, .tile, .foll, .cta, .link-btn, .btn, .faq-item,
  .team-member, .listing, .goal, .feat, .wall-item, .tok-fact, .cd-unit, .safety-row, .gallery-item, .video, .embed) {
  border-radius: var(--w-radius) !important;
}
.w[style*="--w-shadow"] :is(.card, .ca, .price, .foll, .cta, .link-btn, .goal, .wall-item, .team-member) {
  box-shadow: var(--w-shadow) !important;
}
.w[style*="--w-border"] :is(.card, .ca, .price, .mstat, .tile, .foll, .cta, .link-btn, .faq-item,
  .team-member, .listing, .goal, .feat, .wall-item, .cd-unit, .safety-row) {
  border-width: var(--w-border) !important;
}

/* A background tint sits behind the whole widget rather than each of its parts,
   so a widget with several cards reads as one block. */
.w[style*="--w-bg"] {
  background: var(--w-bg);
  border-radius: var(--w-radius, var(--t-radius));
  padding: var(--w-pad, 16px);
}

/* Force a card on, or take it away. */
.w--carded {
  padding: var(--w-pad, 16px);
  border: var(--t-card-border) solid var(--c-line);
  border-radius: var(--w-radius, var(--t-radius));
  background: var(--w-bg, var(--t-card-fill));
  box-shadow: var(--w-shadow, var(--t-card-shadow));
}
.w--bare :is(.card, .ca, .price, .foll, .cta, .goal, .wall-item, .team-member, .mstat, .tile, .feat, .cd-unit) {
  border: none !important;
  background: none !important;
  box-shadow: none !important;
  padding-inline: 0 !important;
}

/* Width: break out of the column, or fill it edge to edge. */
.w--wide { margin-inline: calc(var(--t-page-pad) * -0.6); }
.w--full { margin-inline: calc(var(--t-page-pad) * -1); }

/* Entrance animations. Opt-in per widget, and disabled wholesale by the
   reduced-motion block at the bottom of this file. */
@keyframes w-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes w-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes w-pop  { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: none; } }
.w--anim-fade { animation: w-fade .5s ease both; }
.w--anim-rise { animation: w-rise .5s cubic-bezier(.32,.72,.25,1) both; }
.w--anim-pop  { animation: w-pop .4s cubic-bezier(.32,.72,.25,1) both; }

/* ===========================================================================
   Per-widget options (services/render/options.js → --wo-*)
   ---------------------------------------------------------------------------
   A widget declares `...group("columns", "gap")` and gets a working control
   with no CSS of its own: the renderer emits --wo-columns / --wo-gap onto the
   section, and the generic rules below apply them wherever that widget type
   lays things out.

   Every var() falls back to the value the widget used before options existed,
   so a document that has never touched an option renders byte-identically.
   =========================================================================== */

/* Columns — every widget that lays out a grid reads the same property. */
.gallery,
.mstats,
.tiles,
.feats,
.team,
.listings,
.wall,
.socials--icon-grid,
.socials--big-cards,
.links--grid,
.tok--tiles {
  grid-template-columns: repeat(var(--wo-columns, 3), minmax(0, 1fr));
}
.gallery--masonry { columns: var(--wo-columns, 2); }

/* ---------------------------------------------------------------------------
   Variant layout beats the columns option.
   ---------------------------------------------------------------------------
   The block above is one class deep, and so is every `--rows` variant that
   declares its own grid. Being later in the file, it silently won — so SEVEN
   variants across six widgets were all rendering as the same three-column grid
   regardless of what they were designed to be:

     mstats--rows  team--rows  listings--rows  feats--rows  feats--two-up

   That is where `$45.4KLIQUIDITY` came from. `mstats--rows` is meant to be one
   full-width row per stat with the value and label pushed apart; forced into a
   109px column, a 52px value and a 53px label had nowhere to go and touched.
   The same override also produced the "3 + 1 orphan" wrapping seen across the
   fleet, because four stats in a three-column grid leave one alone.

   A `columns` option on a variant whose entire identity is "one per row" is a
   contradiction, so the variant wins. Grid-shaped variants are untouched and
   still take the option. */
.mstats.mstats--rows,
.team.team--rows,
.listings.listings--rows,
.feats.feats--rows {
  grid-template-columns: 1fr;
}
.feats.feats--two-up {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Belt and braces for the row variants: even in a cramped column the value and
   the label can never end up touching. */
.mstats--rows .mstat,
.listings--rows .listing {
  gap: 12px;
}

/* A lone item on the last row spans it instead of sitting orphaned.
   ---------------------------------------------------------------------------
   Four stats in a three-column grid leave one on its own, left-aligned, under
   a centred page — seen on `bccoin`, `biollm`, `cate1` and others, and it reads
   as a layout accident rather than a design. Stretching the tail turns the
   remainder into a deliberate full-width row.

   Two rules because the arithmetic differs per column count, and each must be
   confined to the width where that count applies. Unscoped, the three-column
   rule fired on a phone too: with four socials the 4th child matches 3n+1, so
   it stretched and pushed the 3rd onto a row of its own — 2+1+1 instead of
   2+2, one orphan traded for two. */
@media (min-width: 480px) {
  :is(.tiles, .feats, .team, .listings, .wall, .socials--icon-grid, .socials--big-cards, .links--grid, .tok--tiles)
    > :last-child:nth-child(3n + 1) {
    grid-column: 1 / -1;
  }
}

/* Market stats pick a column count that divides evenly instead of stretching.
   ---------------------------------------------------------------------------
   Stretching the tail is the right remedy for a card with content in it. It is
   the WRONG remedy for a metric, which is two short lines: four stats in three
   columns made the fourth span the full width, and the page ended up with a
   full-bleed hero card whose entire content was "5 / 24h txns". The rule meant to
   fix an orphan produced something worse-looking than the orphan.

   Four metrics want 2x2. So the count comes from the number of items rather than
   from whatever `auto-fit` can squeeze in, and there is no tail to rescue.

   `.mstats` only, deliberately: the grids above take a `columns` option an owner
   can set, and overriding a choice somebody made by hand would be a different
   bug. Market stats have no such option — the count was always automatic. */
@media (min-width: 480px) {
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(2):last-child) { grid-template-columns: repeat(2, 1fr); }
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(3):last-child) { grid-template-columns: repeat(3, 1fr); }
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, 1fr); }
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(5):last-child) { grid-template-columns: repeat(3, 1fr); } /* 3 + 2 */
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(6):last-child) { grid-template-columns: repeat(3, 1fr); }
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(7):last-child) { grid-template-columns: repeat(4, 1fr); } /* 4 + 3 */
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(8):last-child) { grid-template-columns: repeat(4, 1fr); }
  /* `rows` and `strip` are not grids of cards and must keep their own layout. */
  .mstats--rows[class],
  .mstats--strip[class] { grid-template-columns: 1fr; }
  .mstats--strip[class] { display: flex; }
}
@media (max-width: 479px) {
  :is(.tiles, .feats, .team, .listings, .wall, .socials--icon-grid, .socials--big-cards, .links--grid, .tok--tiles)
    > :last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
  /* Market stats keep an even grid on a phone too. Three metrics in two columns
     left "41 / 24H TXNS" spanning the full width beneath a pair half its size —
     the same imbalance as the four-metric case, just smaller. A metric is two
     short lines and three of them fit across 390px. */
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(3):last-child) { grid-template-columns: repeat(3, 1fr); }
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, 1fr); }
  .mstats:not(.mstats--rows):not(.mstats--strip):has(> :nth-child(2):last-child) { grid-template-columns: repeat(2, 1fr); }
}
/* Row variants are already full width — the rule above must not fight them. */
:is(.mstats--rows, .team--rows, .listings--rows, .feats--rows) > * {
  grid-column: auto;
}

/* Three columns of anything is too many on a phone. The option still applies
   above this width; below it, a grid that would crush its contents steps down
   rather than overlapping them. */
@media (max-width: 479px) {
  .mstats,
  .tiles,
  .feats,
  .team,
  .listings,
  .wall,
  .socials--icon-grid,
  .socials--big-cards,
  .links--grid,
  .tok--tiles {
    grid-template-columns: repeat(min(var(--wo-columns, 3), 2), minmax(0, 1fr));
  }
  .mstats.mstats--rows,
  .team.team--rows,
  .listings.listings--rows,
  .feats.feats--rows {
    grid-template-columns: 1fr;
  }
}

/* ---------------------------------------------------------------------------
   No market yet — say it once.
   ---------------------------------------------------------------------------
   Set by the page runtime when our own proxy reports no tradable pair. Without
   this, every live widget independently rendered an em-dash and a page could
   show seven of them; a visitor reads that as broken rather than as pre-launch.

   The widgets that exist ONLY to show market data are collapsed, and the price
   widget carries a single honest sentence in their place. Deliberately not
   applied on a network error — a blip is not the same claim as "this does not
   trade", and the runtime keeps the last known values in that case.
   --------------------------------------------------------------------------- */
body[data-market="none"] :is(.w-marketstats, .w-pressure, .w-goal) {
  display: none;
}

/* A heading whose whole section is live data goes with it. Otherwise the page
   says "Market Live" directly above "Not trading yet", which is the page
   contradicting itself. The renderer marks these — CSS cannot see which widgets
   follow a heading. */
body[data-market="none"] [data-live-heading] {
  display: none;
}

/* And a live widget's OWN title, which is the same contradiction one level down.
   A section titled "Live Market Data" sitting on "Not trading yet — market data
   appears once a pair is live" reads no better for the label being the widget's
   rather than a heading's — and since duplicate headings are now merged onto the
   section they introduce, this is where most of them ended up. */
body[data-market="none"] :is(.w-price, .w-marketstats, .w-pressure, .w-goal) > .w-head {
  display: none;
}

body[data-market="none"] .w-price .price {
  display: none;
}

/* One empty-state message per page, not one per widget.
   `duky` carries two `price` widgets — legitimate when they are showing different
   figures, and with no pair they both collapse to the same sentence: "Not trading
   yet — market data appears once a pair is live." printed twice, forty pixels
   apart. The first one says it; the rest step aside. (The AI path allows one of
   each widget type, so this is a hand-built and edited-page case.) */
body[data-market="none"] .w-price ~ .w-price {
  display: none;
}

body[data-market="none"] .w-price::after {
  content: "Not trading yet — market data appears once a pair is live.";
  display: block;
  padding: 14px 0;
  color: var(--c-muted);
  font-size: 14px;
  text-align: center;
}

/* Item spacing. */
.socials, .links, .gallery, .mstats, .tiles, .feats, .team, .listings, .wall,
.faq, .steps, .tl, .partners, .cd, .safety, .buy, .tok {
  gap: var(--wo-gap, 10px);
}

/* Icon and type scale. */
.social-icon svg, .listing-icon svg, .feat-icon svg, .link-icon svg { width: var(--wo-icon, 21px); height: var(--wo-icon, 21px); }
.w-title, .heading-title { font-size: var(--wo-title, 19px); }
.w-sub, .heading-sub { font-size: max(11px, calc(var(--wo-text, 15px) * .93)); }
.about-body, .faq-a-inner, .wall-text, .feat-text, .step-text, .road-text, .tl-text, .disclaimer {
  font-size: max(11px, var(--wo-text, 15px));
}
.about-body { line-height: var(--wo-line, 1.65); max-width: var(--wo-maxw, 60ch); }
.quote-text, .heading-title { font-weight: var(--wo-weight, var(--t-display-weight)); }
.heading-title, .w-title { text-transform: var(--wo-transform, var(--t-display-transform)); }

/* Numeric sizes that only one or two widgets use, but follow the convention. */
/* The size option drives the FIGURE, and the headline inherits it as 1em, so a
   secondary currency line stays proportional at every variant and every setting. */
.price-figure { font-size: var(--wo-value, 34px); }
.foll-value { font-size: var(--wo-value, 38px); }
.tile-value { font-size: var(--wo-value, 22px); }
.cd-value { font-size: var(--wo-value, 26px); }
.team-photo { width: var(--wo-photo, 64px); height: var(--wo-photo, 64px); }
.partner img { max-height: var(--wo-logo, 34px); }
.tl-dot { width: var(--wo-dot, 10px); height: var(--wo-dot, 10px); }
.tok-bar { height: var(--wo-bar, 8px); }
.press-track { height: var(--wo-height, 12px); }
.goal-track { height: var(--wo-height, 10px); }
.press-buy { background: var(--wo-buy, var(--c-up, #16c784)); }
.press-sell { background: var(--wo-sell, var(--c-down, #ea3943)); }
.divider--line { height: var(--wo-thickness, 1px); background: var(--wo-color, var(--c-line)); margin-inline: var(--wo-inset, 0px); }
.divider--glow { height: var(--wo-thickness, 2px); }
.figure img { max-width: var(--wo-maxw, 100%); margin-inline: auto; }

/* Per-widget accent override, from the shared option rather than the style
   block — same effect, reachable from a widget's own Design tab. */
.w[style*="--wo-accent"] { --p-accent: var(--wo-accent); --c-accent-text: var(--wo-accent); }
.w[style*="--wo-text-color"] { color: var(--wo-text-color); }

/* Alignment from a widget's own option. */
.w[style*="--wo-align"] { text-align: var(--wo-align); }
.w[style*="--wo-align:left"] :is(.socials, .partners, .buy, .listings--pills) { justify-content: flex-start; }
.w[style*="--wo-align:right"] :is(.socials, .partners, .buy, .listings--pills) { justify-content: flex-end; }

/* ---- enum options that need a class rather than a property ---------------- */

/* Social / link button fills. */
.w[data-fill="outline"] .social,
.w[data-fill="outline"] .link-btn { background: transparent; border-color: var(--c-line-strong); box-shadow: none; }
.w[data-fill="solid"] .social,
.w[data-fill="solid"] .link-btn { background: var(--p-primary); color: var(--c-on-primary); border-color: transparent; }
.w[data-fill="solid"] .link-btn .link-note { color: var(--c-on-primary); }
.w[data-fill="ghost"] .social,
.w[data-fill="ghost"] .link-btn { background: none; border-color: transparent; box-shadow: none; }

/* Fill overrides are declared after the variant hover rules, so they must own
   the matching hover colour too. Otherwise an outline/ghost social inherited
   the filled button's label colour while its fill stayed transparent. */
.w[data-fill="outline"] .social:hover,
.w[data-fill="ghost"] .social:hover {
  border-color: var(--c-line-strong);
  background: var(--c-hover);
  color: var(--p-text);
}

/* A branded button's identity outranks the fill option: a Telegram button
   repainted in the palette's primary is nobody's Telegram button. */
.w[data-fill] .link-btn--branded {
  background: var(--lb-bg);
  color: var(--lb-text);
  border-color: var(--lb-border, transparent);
}

.w[data-shape="rounded"] .social { border-radius: var(--t-radius-sm); }
.w[data-shape="square"] .social { border-radius: 0; }

/* Buy button sizes. */
.w[data-size="large"] .buy-btn { min-height: 56px; font-size: 17px; }
.w[data-size="huge"] .buy-btn { min-height: 68px; font-size: 20px; }
.w[data-fill="outline"] .buy-btn { background: transparent; border-color: var(--c-line-strong); color: var(--p-text); box-shadow: none; }
/* The second stop is pulled toward the primary and the label is picked against
   THAT, not against the primary — a gradient's label has to clear both ends.
   See --c-on-gradient in services/render/css.js. */
.w[data-fill="gradient"] .buy-btn {
  background: linear-gradient(120deg, var(--p-primary), var(--c-grad-end, var(--p-accent)));
  color: var(--c-on-gradient, var(--c-on-primary));
  border-color: transparent;
}
@keyframes buy-pulse { 0%,100% { transform: none; } 50% { transform: scale(1.02); } }
.w[data-pulse] .buy-btn { animation: buy-pulse 2.4s ease-in-out infinite; }

/* Step / tick / marker styles. */
.w[data-marker="square"] .step-num { border-radius: var(--t-radius-sm); }
.w[data-marker="plain"] .step-num { background: none; color: var(--c-accent-text); }
.w[data-marker="none"] .step-num { display: none; }
.w[data-tick="square"] .safety-tick { border-radius: 4px; }
.w[data-icon="none"] .faq-chev { display: none; }
.w[data-icon="circle"] .feat-icon,
.w[data-icon="square"] .feat-icon {
  width: 38px; height: 38px;
  background: color-mix(in srgb, var(--p-primary) 14%, transparent);
}
.w[data-icon="circle"] .feat-icon { border-radius: 999px; }
.w[data-icon="square"] .feat-icon { border-radius: var(--t-radius-sm); }

/* Image shapes and aspect for gallery/team. */
.w[data-shape="circle"] .gallery-item img { border-radius: 999px; }
.w[data-shape="square"] .gallery-item img { border-radius: 0; }
.w[data-aspect="4:3"] .gallery-item img { aspect-ratio: 4/3; }
.w[data-aspect="3:4"] .gallery-item img { aspect-ratio: 3/4; }
.w[data-aspect="16:9"] .gallery-item img { aspect-ratio: 16/9; }
.w[data-aspect="auto"] .gallery-item img { aspect-ratio: auto; }
.w[data-zoom="off"] .gallery-item:hover img { transform: none; }
.w[data-shape="square"] .team-photo { border-radius: 0; }
.w[data-shape="rounded"] .team-photo { border-radius: var(--t-radius-sm); }
.w[data-gray] .partner img { filter: grayscale(1); }

/* Misc toggles. */
.w[data-dropcap] .about-body > :first-child::first-letter {
  float: left; font-size: 3.1em; line-height: .84; padding-right: .08em;
  font-family: var(--t-font-display); font-weight: var(--t-display-weight);
  color: var(--c-primary-text);
}
.w[data-italic] .quote-text { font-style: italic; }
.w[data-bars="off"] .tok-bar { display: none; }
.w[data-markers="off"] .road-marker { display: none; }
.w[data-dim] .road-item.is-done { opacity: .55; }
.w[data-newest] { flex-direction: column-reverse; }
.w[data-sep="off"] .foll-list { border-top: none; padding-top: 0; }
.w[data-avatars="off"] .wall-avatar { display: none; }
.w[data-labels="off"] .listing-label { display: none; }
.w[data-pause]:hover .marquee-track { animation-play-state: paused; }
.w[data-days="off"] .cd-unit:nth-child(1),
.w[data-seconds="off"] .cd-unit:last-child { display: none; }
.w[data-mono] .ca-value { font-family: var(--t-font-body); }
.w[data-size="large"] { padding: 40px 24px; }
.w[data-size="large"] .cta-title { font-size: 30px; }
.w[data-gradient] { background: linear-gradient(140deg, color-mix(in srgb, var(--p-primary) 22%, var(--t-card-fill)), var(--t-card-fill)); }
/* Media aspect belongs to the media stage. Applying it to `.w` also sized the
   heading and owner background, producing a portrait wrapper with a small
   landscape player stranded at the top. */
.w-embed[data-aspect="4:3"] .embed { aspect-ratio: 4/3; }
.w-embed[data-aspect="1:1"] .embed { aspect-ratio: 1/1; }
.w-embed[data-aspect="9:16"] .embed { aspect-ratio: 9/16; }

/* --- contract: full address on wide screens ---------------------------------
   Both forms are in the markup and CSS picks one, because the server renders
   once for every viewport. A 44-character base58 address does not fit on a
   phone; a shortened one on a desktop makes people copy it just to read it. */
.ca-full { display: none; }
@media (min-width: 640px) {
  .ca-short { display: none; }
  .ca-full { display: inline; }
}

/* --- goal: milestone ticks --------------------------------------------------
   Marks that divide the track, so a bar at 40% reads as "past the second of
   five" instead of as an unplaceable fraction. */
.goal-track { position: relative; }
.goal-tick {
  position: absolute;
  top: 50%;
  width: 2px;
  height: 100%;
  transform: translate(-1px, -50%);
  background: var(--p-bg);
  opacity: 0.55;
  pointer-events: none;
}

/* --- video: mute toggle ------------------------------------------------------
   Sits on the clip rather than beside it: it is about that video, and a page
   with two of them needs no explanation of which button belongs to which. */
.video { position: relative; }
.video-mute {
  position: absolute;
  right: 10px;
  bottom: 10px;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 999px;
  background: rgb(0 0 0 / 0.5);
  color: #fff;
  cursor: pointer;
  backdrop-filter: blur(4px);
  transition: background 0.15s ease;
}
.video-mute:hover { background: rgb(0 0 0 / 0.7); }
/* Two glyphs are rendered; the state decides which is visible, so there is no
   script rewriting markup. */
.video-mute svg:last-child { display: none; }
.video-mute.is-unmuted svg:first-child { display: none; }
.video-mute.is-unmuted svg:last-child { display: block; }
