/* ═══════════════════════════════════════════════════════════════════════
   CoinPool Pro — marketing homepage
   ═══════════════════════════════════════════════════════════════════════

   Design source: Figma "Marketing" → Homepage COINPOOL (node 1470:37296).
   Frame is 1440 wide on an 1100px content column (170px gutters).

   Every colour and type size below comes from the file's own variables --
   none of it is eyedropped off a screenshot.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ── colour, straight from the Figma variables ── */
  --bg:        #0C0C0F;   /* (D) BG UI        */
  --brand:     #00FFAD;   /* Brand / green    */
  --green-bg:  #69DAB6;   /* (D) Green BG     */
  --red:       #FF4548;   /* (D) Red          */

  --text-high: #FFFFFF;   /* (D) Text _High   */
  --text-mid:  #94969F;   /* Text _Medium, taken down a step from the frame's
                             #BBBDC5 so the copy sits behind the headline */
  --text-low:  #737580;   /* (D) Text _Low    */

  --stroke:    #232324;   /* (D) BG_box_small stroke   */
  --stroke-2:  #2E2E2E;   /* (D) BG_box_2_small stroke */

  --card:      #111114;
  --radius:    16px;
  --content:   1100px;

  --font: 'Poppins', system-ui, -apple-system, 'Segoe UI', sans-serif;
}

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

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-high);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }

.wrap { width: var(--content); margin-inline: auto; }

/* The file's four text styles: 18/600, 14/600, 14/400, 12/400. */
.t-bold { font-size: 14px; font-weight: 600; }
.t-body { font-size: 14px; color: var(--text-mid); }
.t-sub  { font-size: 12px; color: var(--text-low); }


/* ═══ header ════════════════════════════════════════════════════════════ */
/*
 * Fixed and full-bleed, 30px gutters as in the frame -- not the 1100px
 * content column. Transparent over the hero so the mesh runs underneath it
 * unbroken; the frosted bar fades in only after the page has scrolled.
 */
.site-header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  height: 58px; display: flex; align-items: center;
  /* isolation so the ::before sheen can sit at z-index -1 and stay behind the
     logo and button without falling behind the page itself.
     overflow:hidden so the blurred sheen stops at the bar's own edges. */
  isolation: isolate; overflow: hidden;
  background: transparent;
  border-bottom: 1px solid transparent;
  /* backdrop-filter is deliberately NOT in this list. Transitioning it from
     `none` is unreliable -- some Chromium builds skip applying the filter
     altogether until the next unrelated repaint, which is exactly how a
     frosted bar ends up looking like plain transparency. */
  transition: background .3s ease, border-color .3s ease, box-shadow .3s ease;
}
.header-bar {
  width: 100%; padding-inline: 30px;
  display: flex; align-items: center; justify-content: space-between;
}

.site-header.is-stuck {
  /*
   * The bacoor.io navbar recipe: a 3%-white wash, a wide blur and a hairline.
   * On its own that is almost exactly the page colour and reads as nothing --
   * what makes the bar visible over flat stretches is the sheen below, not
   * the tint. Both halves are needed.
   */
  background: rgba(255, 255, 255, .03);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  border-bottom-color: rgba(255, 255, 255, .09);
}

/*
 * THE SHEEN -- the half of bacoor.io's bar that actually makes it look like
 * glass: a few soft light pools drifting inside it.
 *
 * The blur is STATIC. It rasterises once; only transform and opacity animate,
 * so this is compositor work and the main thread never sees it. Animating the
 * filter itself would repaint a 1440px blurred surface every single frame,
 * which is the exact mistake that cost weeks on the other site.
 */
.site-header::before {
  content: ''; position: absolute; inset: -40%; z-index: -1;
  /* visibility, not opacity: the keyframes below animate opacity, and an
     animated property beats a plain declaration -- gating on opacity here
     would simply be ignored and the sheen would show over the hero too. */
  pointer-events: none; visibility: hidden;
  background:
    radial-gradient(190px 90px at 14% 45%, rgba(255,255,255,.20), transparent 68%),
    radial-gradient(240px 100px at 46% 60%, rgba(255,255,255,.15), transparent 68%),
    radial-gradient(170px 80px at 78% 40%, rgba(255,255,255,.18), transparent 68%),
    radial-gradient(120px 60px at 62% 30%, rgba(0,255,173,.12), transparent 70%);
  filter: blur(30px);
  animation: navglow 16s ease-in-out infinite alternate;
}
.site-header.is-stuck::before { visibility: visible; }

@keyframes navglow {
  0%   { transform: translateX(-6%) scale(1);    opacity: .75; }
  50%  { transform: translateX(4%)  scale(1.12); opacity: 1; }
  100% { transform: translateX(-2%) scale(.95);  opacity: .6; }
}

/* A 1px target at the very top of the document. An IntersectionObserver
   watches it instead of a scroll listener, so scrolling costs the main
   thread nothing at all. */
#top-sentinel { position: absolute; top: 0; left: 0; width: 1px; height: 40px; }

.logo img { height: 24px; width: auto; }

.header-right { display: flex; align-items: center; gap: 14px; }

/* flag.svg is a 24×40 canvas with the flag drawn as a 24×16 band at y=12.
   Boxing it to 24×24 sliced the bottom 4px off; the window is the band's own
   size and the image is pulled up by exactly its offset. */
.flag { width: 20px; height: 14px; overflow: hidden; border-radius: 4px; }
/* the whole 24×40 canvas scaled by 20/24, so the -12px offset scales with it */
.flag img { width: 20px; height: 33.33px; max-width: none; margin-top: -10px; }

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font); font-size: 13px; font-weight: 600;
  border-radius: 10px; cursor: pointer; white-space: nowrap;
  border: 1px solid transparent;
  transition: background .2s, border-color .2s, color .2s;
}
/*
 * Connect Wallet. Was the frame's one solid brand button; now the same glass
 * as Explore App, at header scale. It carries .glass in the markup, so only
 * the size lives here -- the fill, blur, hairline and glow all come from the
 * one place, and changing .glass changes both buttons together.
 */
.btn-wallet {
  height: 26px; padding: 0 12px; border-radius: 7px;
  font-size: 11px; font-weight: 600;
  color: var(--brand);
}
/* Two classes, because .glass sets `border: .5px solid rgba(221,207,209,.22)`
   and sits later in the file -- a single .btn-wallet would lose the hairline
   back to grey. */
.btn-wallet.glass { border-color: rgba(0, 255, 173, .6); }

.btn-ghost {
  height: 50px; min-width: 230px; padding: 0 34px;
  font-size: 14px; color: var(--text-high);
}

/* Filled on hover, not just outlined. The background here beats .glass on
   specificity (0,2,0 against 0,1,0) even though .glass sits later in the
   file -- that is the only reason it takes effect. */
.btn-ghost:hover,
.btn-wallet:hover {
  background: var(--brand); border-color: var(--brand); color: #04120D;
}


/* ═══ the sheen ═════════════════════════════════════════════════════════
 *
 * Ported from bacoor.io's send button. A single gradient band swept across
 * with transform ONLY -- compositor work, no layout and no repaint of the
 * button. Two dials, and they should stay in step with that site if either
 * is ever retuned:
 *
 *     4s   x 87.5%   = 3.5s sweep, 0.5s pause   (resting)
 *     3.1s x 90.32%  = 2.8s sweep, 0.3s pause   (hover)
 *
 * overflow clips the band to the radius; isolation keeps it out of the
 * page's root stacking context.
 */
.btn-ghost, .btn-wallet {
  position: relative; overflow: hidden; isolation: isolate;
}

.btn-ghost::after, .btn-wallet::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg,
    transparent 38%, rgba(255, 255, 255, .16) 50%, transparent 62%);
  transform: translateX(-130%);
}

/* .shine-on is added by an observer in app.js and only while the button is
   on screen -- the support button is two thirds of the way down the page,
   so ungated it would animate for the whole scroll. */
.btn-ghost.shine-on::after,
.btn-wallet.shine-on::after { animation: btn-shine 4s ease-in-out infinite; }

@keyframes btn-shine {
  0%          { transform: translateX(-130%); }
  87.5%, 100% { transform: translateX(130%); }
}

.btn-ghost:hover::after, .btn-wallet:hover::after {
  background: linear-gradient(105deg,
    transparent 32%, rgba(255, 255, 255, .42) 50%, transparent 68%);
  animation-name: btn-shine-hover;
  animation-duration: 3.1s;
}

@keyframes btn-shine-hover {
  0%           { transform: translateX(-130%); }
  90.32%, 100% { transform: translateX(130%); }
}


/* ═══ hero ══════════════════════════════════════════════════════════════ */
/* The header is fixed now, so it takes no space in the flow and the hero
   starts at the top of the document. 180px puts the badge exactly where the
   frame has it. */
.hero { position: relative; padding-top: 150px; text-align: center; }

/*
 * THE WIREFRAME MESH.
 *
 * In Figma this group is 1716 × 1163.67 starting at y = -10.5 -- wider than
 * the 1440 frame on both sides and running down to y ≈ 1153, which is 496px
 * INTO the dashboard. The export came out with a viewBox cropped to the
 * 1440 artboard, so the outer third of every line was being clipped away;
 * assets/mesh.svg now carries viewBox="-146 -10.508 1716 1163.669", the
 * group's real bounds.
 *
 * Opacity, not full strength: the strokes export as #F0F1F1, which on this
 * background is a bright wire cage straight across the headline. In the
 * frame it is a texture you notice second, after the words.
 */
.hero-mesh {
  width: 1716px; max-width: none; height: 1163.669px;
  opacity: .085; transition: opacity .7s ease;
}

/*
 * THE LIVE MESH.
 *
 * Occupies exactly the box the SVG export did (1716 x 1163.67 starting at
 * y -10.5 in the frame), except full-bleed rather than a fixed 1716 -- the
 * shader takes the viewport aspect, so it fills any width without stretching.
 *
 * Both layers sit at z-index 0, under the side overlay and the copy. The
 * bottom mask is what lets it end rather than being cut off at the canvas
 * edge; the shader's own horizon fade handles the far distance.
 */
.mesh-layer {
  position: absolute; top: -10.5px; left: 50%; transform: translateX(-50%);
  z-index: 0; pointer-events: none;
  -webkit-mask-image: linear-gradient(#000 70%, rgba(0,0,0,.5) 88%, transparent 100%);
  mask-image: linear-gradient(#000 70%, rgba(0,0,0,.5) 88%, transparent 100%);
}

/*
 * Taller than the frame's 1163.669 so the surface actually runs the full
 * depth of the dashboard -- the panel is glass now, and the mesh used to
 * fade out at 70% of its height, which is roughly where the panel starts,
 * so there was nothing left to see through it. The shader compensates for
 * the extra height (see uF in app.js) so the composition is unchanged; the
 * new pixels are foreground, not stretch.
 */
#hero-mesh {
  width: 100vw; height: 1340px;
  opacity: 0; transition: opacity .7s ease;
  -webkit-mask-image: linear-gradient(#000 90%, transparent 100%);
  mask-image: linear-gradient(#000 90%, transparent 100%);
}
#hero-mesh.is-live { opacity: 1; }

/* The SVG fallback. app.js inserts this element only when WebGL is missing
   or the context is lost, so in the normal case it is never in the document
   and never downloaded. */

/*
 * THE SIDE OVERLAY.
 *
 * A horizontal vignette in the frame's own background colour: fully
 * transparent across the middle, up to 85% opaque at both edges. It stops
 * the mesh running off the sides of the viewport and lifts the contrast
 * under the headline.
 *
 * A gradient, not the PNG: the artwork is a pure left-to-right ramp that is
 * identical on every row, so it costs nothing and stretches to any width
 * instead of being locked to 1440.
 */
.hero::before {
  content: ''; position: absolute; z-index: 1; pointer-events: none;
  top: -10.5px; left: 50%; transform: translateX(-50%);
  width: 100vw; height: 1340px;
  background: linear-gradient(90deg,
    rgba(12,12,15,.85) 0%,
    rgba(12,12,15,0)  16.6%,
    rgba(12,12,15,0)  83.5%,
    rgba(12,12,15,.85) 100%);
}

/* The :not() matters, and it has to name the shared class rather than
   either layer. Both selectors are a single class, so a plain `.hero > *`
   wins on source order, turns an absolutely-positioned mesh relative, and
   its full 1164px drops into the flow -- shoving the whole hero a thousand
   pixels down the page. That happened once with the SVG; adding the canvas
   would have done it again. */
.hero > *:not(.mesh-layer) { position: relative; z-index: 2; }

/* 200×30, r20, Poppins Medium 14 -- straight from the frame. No uppercase
   letter-spacing: the text is already set as "COINPOOL PRO VERSION". */
.badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 170px; height: 26px; padding: 0 14px;
  border-radius: 20px;
  font-size: 12px; font-weight: 400; color: var(--text-high);
}
.badge b { color: var(--brand); font-weight: 400; margin-left: 5px; }

.hero h1 {
  margin: 22px auto 0; width: 500px;
  font-size: 58px; font-weight: 700; line-height: 65px; letter-spacing: -.015em;

  /*
   * background-clip: text paints only inside the element's own box, and with
   * line-height 65 under a 58px face the descender of a 'g' hangs below the
   * last line box -- so the tail of "Moving" was simply not being painted.
   * The padding extends the paint box; the negative margin gives the space
   * straight back so nothing below moves.
   */
  padding-bottom: 16px; margin-bottom: -16px;

  /* White at the cap line falling away to grey, on a 150° axis. Clipped to
     the glyphs, so it is one paint -- no duplicated text, no filter. */
  background: linear-gradient(150deg, #FFFFFF 0%, #FFFFFF 32%, #9A9CA4 112%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
}
/* line-height 65 makes the headline 20px shorter than the frame's 150px box.
   The gap below absorbs it so the sub-copy, the button and the dashboard all
   stay on their Figma positions (412 / 519 / 657). */
.hero-sub { margin: 30px auto 0; width: 450px; font-size: 16px; line-height: 26px; color: var(--text-mid); }
.hero .btn-ghost { margin-top: 40px; }


/* ═══ dashboard, with the travelling light on its edge ══════════════════ */
.dash-shell { margin: 88px auto 0; width: var(--content); }

/*
 * BORDER BEAM.
 *
 * One conic gradient on a pseudo-element, masked so only the 1px frame is
 * visible, with a single rotating angle driving it.
 *
 * Deliberately NOT a moving box-shadow and NOT an animated filter. Both of
 * those repaint the whole surface every frame, which is the exact trap that
 * cost weeks of debugging on bacoor.io. @property makes the angle a real
 * animatable type so the browser interpolates it without JavaScript.
 */
@property --beam-angle { syntax: '<angle>'; initial-value: 0deg; inherits: true; }

.dash {
  position: relative; border-radius: var(--radius);
  padding: 1px; isolation: isolate;

  /* The angle lives here and inherits, so the sharp lights and their halo
     are driven by ONE animation and can never drift out of step. */
  animation: beam-spin 16s linear infinite;
}
@keyframes beam-spin { to { --beam-angle: 360deg; } }

/*
 * ⚠ NO BACKGROUND ON .dash. It used to carry background: var(--stroke) to
 * colour the 1px ring, and a background paints across the WHOLE box --
 * directly behind .dash-inner. So the panel's backdrop-filter was blurring
 * a flat opaque grey rectangle rather than the page, which is why it looked
 * like a solid panel while the buttons, with the identical .glass rule,
 * were see-through. Measured: with the background there the panel read a
 * flat 38.9 with a standard deviation of 0.21 and hiding the mesh changed
 * nothing at all; without it, 16.2 with five times the variance, because the
 * mesh is finally reaching the glass.
 *
 * The ring is drawn by ::after instead, which is a border and so paints only
 * where a border paints.
 */
.dash::after {
  content: ''; position: absolute; inset: 0; z-index: 0;
  border: 1px solid var(--stroke); border-radius: inherit;
  pointer-events: none;
}

/*
 * TWO TRAVELLING LIGHTS.
 *
 * One conic gradient carrying two bright points 180 degrees apart, so a
 * single rotating angle moves both. Each has a soft tail either side, and
 * the stops are arranged so 0deg is bright -- a transparent stop at the
 * wrap point would show as a flicker once per lap.
 *
 * Still deliberately NOT a moving box-shadow: @property makes the angle a
 * real animatable type, so the browser interpolates it with no JavaScript
 * and no per-frame work on the main thread.
 */
.dash::before,
.beam-glow {
  content: ''; position: absolute; inset: 0;
  border-radius: inherit; pointer-events: none;
  /* Tails deliberately short. A conic gradient maps its angle onto a
     rectangle's perimeter very unevenly -- a few degrees covers a corner
     while the same few degrees stretches a long way down a side -- so wide
     stops stop reading as travelling points and start reading as two lit
     edges. 16 degrees keeps them points. */
  background: conic-gradient(from var(--beam-angle),
    var(--brand)            0deg,
    rgba(0,255,173,.18)     7deg,
    transparent            16deg,
    transparent           164deg,
    rgba(0,255,173,.18)   173deg,
    var(--brand)          180deg,
    rgba(0,255,173,.18)   187deg,
    transparent           196deg,
    transparent           344deg,
    rgba(0,255,173,.18)   353deg,
    var(--brand)          360deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

/* the sharp core, above the panel so it reads as a lit border. It is
   thicker than .dash's 1px padding on purpose -- the extra width laps onto
   the panel edge, which is what makes it read as a light rather than a
   coloured border. */
.dash::before { z-index: 2; padding: 5px; }

/* the halo: a thicker ring, blurred, underneath. The blur radius never
   changes -- only the gradient's angle does -- so this is one static filter
   over a rotating background rather than an animated filter, which is the
   expensive thing. */
.beam-glow {
  z-index: 0; padding: 13px; opacity: .65;
  filter: blur(10px);
}

/*
 * THE PANEL BACKGROUND IS CSS, NOT PART OF THE IMAGE.
 *
 * assets/dashboard.webp is an alpha export with the panel's own background
 * rectangle hidden -- it carries only the UI sitting on it (the Select-pair
 * row, the tabs, the search field, the pair cards). 87% of it is fully
 * transparent.
 *
 * That separation is the whole point. The previous version faded the entire
 * opaque screenshot to let the mesh through, which dimmed the dashboard's
 * own text by exactly as much and could never make the baked-in dark
 * actually transparent. Now the background is a surface we own: change the
 * fill, the blur, the tint, anything -- the content on top is untouched.
 *
 * The glass itself is the button recipe at panel scale.
 */
.dash-inner {
  position: relative; z-index: 1;
  border-radius: calc(var(--radius) - 1px);
  /* No background of its own. The panel carries the .glass class in the
     markup, exactly like the Explore App button, so the two can never drift
     apart -- change .glass and the panel follows. */
  overflow: hidden;
}

/* The one place the panel departs from .glass: a wider blur, because it is
   a far bigger surface than a button and 1.5px barely registers across it.
   Two classes beats .glass's one, so this wins wherever it sits in the file. */
.dash-inner.glass {
  -webkit-backdrop-filter: blur(5px) saturate(106%);
  backdrop-filter: blur(5px) saturate(106%);
}
/* Full opacity now -- there is nothing to see through any more, because
   what used to be in the way simply is not in the file. */
.dash-inner img { width: 100%; height: auto; }


/* ═══ the live stat cards ═══════════════════════════════════════════════
 *
 * Figma: Frame 1321315765 — the row is 1060×115 at 20,20 inside the
 * 1100×727 panel; two 518px cards, 24px gutter, 12px padding, 8px radius,
 * #232324. Each card is two 241px columns: the figures, then the chart.
 *
 * Laid OVER dashboard.webp rather than the image being cropped. The panel
 * background is a soft horizontal gradient, not a flat colour, so a cropped
 * edge would leave a seam. The ring below hides any sub-pixel rounding at
 * the card edges -- it is the same colour as the fill, so it is invisible
 * except where it is doing its job.
 */
.dash-stats {
  position: absolute; z-index: 1;
  left: 19px; top: 19px; width: 1060px; height: 115px;
  display: flex; gap: 24px;
}

.stat {
  flex: 1 0 0; min-width: 0; display: flex; gap: 12px; align-items: flex-start;
  padding: 12px; border-radius: 8px;
  background: #232324; box-shadow: 0 0 0 1px #232324;
}

/* fixed 91px so the left card's two rows sit exactly where the right card's
   first two do -- the frame reserves a third row on the left that is set to
   zero opacity, and this reproduces its effect without the empty markup */
.stat-info { flex: 1 0 0; min-width: 0; height: 91px; display: flex;
             flex-direction: column; gap: 8px; align-items: flex-start; }

.stat-num {
  font-size: 18px; font-weight: 600; line-height: 1.5; color: var(--text-high);
  white-space: nowrap;
  /* tabular figures so the digits do not jitter while the value is rolling */
  font-variant-numeric: tabular-nums;
  transition: color .45s ease;
}
/* a brief lift on each up-tick, the way a trading terminal marks a change */
.stat-num.up { color: var(--brand); transition: color .08s ease; }

.stat-row { display: flex; gap: 4px; align-items: center; }
.stat-label { font-size: 14px; font-weight: 600; line-height: 1.5; color: var(--text-low); white-space: nowrap; }

.stat-pill, .stat-chip {
  display: inline-flex; align-items: center; justify-content: center;
  height: 24px; padding: 4px 8px; border-radius: 8px;
  font-size: 14px; font-weight: 400; line-height: 1.5; white-space: nowrap;
}
.stat-pill {
  background: rgba(0, 255, 173, .12); color: var(--brand);
  font-variant-numeric: tabular-nums;
  transition: background-color .3s ease, color .3s ease;
}
/* the frame's red variant, for when the figure moves the other way */
.stat-pill.down { background: rgba(255, 69, 72, .12); color: var(--red); }
.stat-chip { background: rgba(255, 255, 255, .06); color: var(--text-mid); }

/* The chart, traced off the export so it matches the artwork it replaces.
   preserveAspectRatio="none" because the box is a fixed 241×91 here, and
   the dev team can swap the points for real series data. */
.spark { flex: 1 0 0; min-width: 0; align-self: stretch; height: 91px; }
.spark polyline {
  fill: none; stroke: #00FFAD; stroke-width: 1.4;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* The group the scroller slides. Transform only, so each step is compositor
   work -- the points attribute is rewritten once per step, not per frame. */
.spark-g { will-change: transform; }

/* The newest reading. It rides inside .spark-g, so it arrives at the right
   edge with the segment it belongs to rather than sitting there waiting. */
.spark-tip { fill: #00FFAD; }
.spark-halo { fill: rgba(0, 255, 173, .22); }


/* ═══ sections ══════════════════════════════════════════════════════════ */
.section { padding-top: 180px; }
/* The cards section sits 100px under the dashboard in the frame, not 180. */
.section-first { padding-top: 100px; }

/*
 * THE HEADLINE SLIDER.
 *
 * One line at a time, stepping up through three and looping.
 *
 * The window is THREE steps tall with the live line in the middle one, and
 * masked to transparent at top and bottom. That is what makes a line fade
 * away into the dark as it leaves and emerge out of the dark as it arrives,
 * instead of being chopped off at a hard edge. A one-step window cannot do
 * it: there is nowhere for a line to be half-gone.
 *
 * STEP is 100px against a 50px line-height, and the generosity is the point.
 * A tight step leaves nowhere for the mask to ramp: at 62px the line above
 * was still sitting at about 40% opacity through the whole hold, because the
 * fade had to start before its text had cleared. With 50px of dark between
 * lines the ramp lives entirely in empty space, so a resting line is fully
 * lit and its neighbours are fully gone.
 *
 * line-height equal to the step centres each line in its own slot, which is
 * also what gives the descenders their room.
 *
 * The negative block margin hands the two spare slots back to the layout:
 * the heading paints 300px tall but occupies 100px of flow, so the section
 * spacing is that of the single visible line. The overflow is invisible
 * because the mask has already faded it out.
 */
.rotator {
  display: block; overflow: hidden;
  height: 300px;             /* 3 x 100 */
  margin-block: -100px;      /* ...but only the middle one costs layout */

  /* opaque only across the middle slot's text; both ramps fall in the dead
     space between lines */
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 28%, #000 41%, #000 59%, transparent 72%);
  mask-image: linear-gradient(to bottom,
    transparent 28%, #000 41%, #000 59%, transparent 72%);
}

.rotator-track {
  display: block;
  animation: headline-roll 9s cubic-bezier(.65, 0, .35, 1) infinite;
  animation-play-state: paused;
  will-change: transform;
}
.section-head.in .rotator-track { animation-play-state: running; }
.rotator-track > span { display: block; height: 100px; line-height: 100px; }

/* Three holds of 2.16s with a 0.84s slide between. Offsets are one step
   higher than you would expect throughout, because the live line sits in the
   middle slot rather than the top one. The last step lands on the duplicate
   and the loop restarts on the original. */
@keyframes headline-roll {
  0%,     24%    { transform: translateY(100px); }
  33.33%, 57.33% { transform: translateY(0); }
  66.66%, 90.66% { transform: translateY(-100px); }
  100%           { transform: translateY(-200px); }
}

/*
 * Reduced motion gets the whole headline at once rather than a frozen first
 * line -- stopping the slider where it stands would hide two thirds of the
 * sentence from exactly the people least able to wait for it to come round.
 */
@media (prefers-reduced-motion: reduce) {
  .rotator {
    height: auto; margin-block: 0;
    -webkit-mask-image: none; mask-image: none;
  }
  .rotator-track { animation: none; }
  .rotator-track > span { height: auto; line-height: 50px; }
  .rotator-track > span[aria-hidden="true"] { display: none; }
}

/* Explore App jumps here, and the id is on the HEADING rather than the
   section: the section carries 100px of padding, so targeting it left the
   words 184px down the viewport with a large empty gap above them.
   scroll-margin-top clears the 58px fixed header. The smooth scroll comes
   from html { scroll-behavior }, which the reduced-motion block turns off. */
#features { scroll-margin-top: 90px; }

.section-head {
  width: var(--content); margin: 0 auto; text-align: center;
  font-size: 43px; font-weight: 700; line-height: 50px;
  letter-spacing: -.01em;
  color: #FFFFFF;
}

/* Two 535px cards with a 30px gutter make the 1100 column exactly. */
.cards {
  width: var(--content); margin: 107px auto 0;
  display: grid; grid-template-columns: repeat(2, 535px); gap: 31px 30px;
}

/*
 * The four cards share one light source: the middle of the 2x2 block. Each
 * runs white .10 at the corner facing that centre out to white .03 at its
 * far outer corner, so the group reads as lit from within rather than as
 * four identical tiles.
 *
 * The angles are the diagonal each card faces, and CSS gradient angles point
 * in the direction the ramp TRAVELS -- 135deg runs toward the bottom-right,
 * which is the centre for the top-left card. Get one wrong and that card
 * lights from the outside and the whole block stops holding together.
 *
 *     1 Multisig   top-left      135deg -> centre is bottom-right
 *     2 Uniswap    top-right     225deg -> centre is bottom-left
 *     3 Smart LP   bottom-left    45deg -> centre is top-right
 *     4 Revenue    bottom-right  315deg -> centre is top-left
 */
.card {
  position: relative; height: 400px;
  padding: 35px 42px 0 43px;
  border: 1px solid var(--stroke); border-radius: var(--radius);
  overflow: hidden;
}

/* One gradient, four angles. The ramp holds flat at .03 for the first 75%
   and only lifts to .10 across the last quarter, so the bright area is
   confined to the corner facing the centre rather than washing over the
   whole card. Retune the stop once here and all four follow. */
.cards .card {
  background: linear-gradient(var(--lit),
    rgba(255,255,255,.03)  0%,
    rgba(255,255,255,.03) 75%,
    rgba(255,255,255,.10) 100%);
}
.cards .card:nth-child(1) { --lit: 135deg; }   /* centre is bottom-right */
.cards .card:nth-child(2) { --lit: 225deg; }   /* centre is bottom-left  */
.cards .card:nth-child(3) { --lit:  45deg; }   /* centre is top-right    */
.cards .card:nth-child(4) { --lit: 315deg; }   /* centre is top-left     */
.card h3 { margin: 0 0 30px; font-size: 20px; line-height: 30px; font-weight: 600; }
.card p  { margin: 0; color: var(--text-mid); max-width: 449px; }

.tick-list { margin: 48px 0 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.tick-list li { display: flex; align-items: center; gap: 17px; font-size: 14px; font-weight: 600; }
/*
 * The bullets read as depth, not as a lit ball. A linear ramp from the brand
 * green down into a much darker one, with no white highlight anywhere: the
 * earlier version put a specular dot at the top-left, which made them look
 * lit from the front and sat them ON the card rather than IN it.
 *
 * The inset shadow deepens the lower edge; the outer glow is deliberately
 * faint, enough to separate them from the card without lighting them.
 */
.tick-list li::before {
  content: ''; width: 12px; height: 12px; border-radius: 50%; flex: none;
  background: linear-gradient(145deg,
    #00FFAD   0%,
    #00C489  42%,
    #007A55  74%,
    #00432F 100%);
  box-shadow: 0 0 5px rgba(0, 255, 173, .22),
              inset 0 -2px 3px rgba(0, 26, 18, .55);
}

/* ═══ the range gauge ════════════════════════════════════════════════════
 *
 * Figma Group 48097517: an 87x289 column at 405,56 inside the card, with 11
 * ticks 24px apart from y=26 to y=266 and the rail at x=44.
 *
 * Everything that moves -- the bump, the bubble and the highlighted tick --
 * is driven from ONE custom property, --gy, the active row's y in px. Two
 * elements read it and both resolve to a transform, so a frame is one style
 * write and two composited moves. The SVG is sized 87x289 exactly so a user
 * unit and a CSS pixel are the same thing and --gy means the same in both.
 */
.card-scale .gauge {
  position: absolute; left: 405px; top: 56px;
  width: 87px; height: 289px;
  --gy: 146px;                       /* 5%, the middle tick */
}
/* the copy has to stop short of the gauge; the frame sets it at 340 */
.card-scale p { max-width: 340px; }

.gauge svg { overflow: visible; }

/* .28 keeps the brightest point of the ramp near the flow wires' weight --
   the gradient's own stop is opacity 1, this scales the whole thing. */
.gauge-rail { stroke: url(#rail-fade); stroke-width: 1; opacity: .28; }

/* the hole travels on the same property as the bend, so they cannot separate */
.rail-hole { transform: translateY(var(--gy)); }

.gauge-ticks .tk {
  stroke: rgba(255, 255, 255, .16); stroke-width: 1;
  transition: stroke .35s ease;
}
/* the two rows either side of the bubble, and the row itself. The x values
   are the frame's: the closer a tick is, the further left it starts AND the
   further short of the rail it stops, because the curve is in the way. */
.gauge-ticks .tk.near   { stroke: rgba(255, 255, 255, .38); }
.gauge-ticks .tk.active { stroke: var(--brand); stroke-width: 1.6; }

/*
 * The glow under the lit tick: one line per row, cross-fading. A single glow
 * transformed between rows looked like a light being dragged down the ruler;
 * fading in place reads as the row itself lighting up. Same .32s as the
 * tick's own colour change, so the pair move together.
 */
.tick-glows { filter: blur(3.5px); }
.tick-glows line {
  stroke: var(--brand); stroke-width: 6; stroke-linecap: round;
  opacity: 0; transition: opacity .32s ease;
}
.tick-glows line.on { opacity: .8; }

.gauge-bump {
  transform: translateY(var(--gy));
  fill: none; stroke-linecap: round;
}
/* the same two-pass build as the flow lights: thick and blurred underneath,
   thin and sharp on top */
.bump-glow { stroke: url(#bump-fade); stroke-width: 5; filter: blur(3px); opacity: .85; }
.bump-core { stroke: url(#bump-fade); stroke-width: 1.5; }

.gauge-bubble {
  position: absolute; left: 44px; top: 0;
  width: 43px; height: 43px; border-radius: 50%;
  display: inline-flex; align-items: baseline; justify-content: center;
  transform: translateY(var(--gy)) translateY(-50%);

  background: radial-gradient(circle at 34% 26%,
    rgba(255,255,255,.14), rgba(255,255,255,.05) 60%, rgba(255,255,255,.03));
  border: .5px solid rgba(255, 255, 255, .18);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .45);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.gauge-bubble b { font-size: 17px; font-weight: 600; line-height: 43px; color: var(--text-high);
                  font-variant-numeric: tabular-nums; }
.gauge-bubble i { font-size: 11px; font-weight: 500; font-style: normal; color: var(--text-mid);
                  margin-left: 1px; }

/* the unicorn sits bottom-right, bleeding off the card's corner */
/* 193×204, flush into the card's bottom-right corner, as in the frame */
.card-unicorn { position: absolute; right: 0; bottom: 0; width: 193px; height: 204px; opacity: .92; }


/* ── the wallet / revenue flow diagrams ──────────────────────────────────
 *
 * 449 × 150 in the frame. The box is that exact size and everything in it
 * is positioned on that grid, so the pills, the COINPOOL node and the line
 * endpoints cannot drift apart from one another.
 *
 *   wallet pills   130 × 40, at y 0 / 55 / 110
 *   COINPOOL node  207 × 60, at y 45
 *   lines          x 130 → 242 (mirrored for Revenue)
 */
.card-flow h3 { margin-bottom: 39px; }
.diagram { position: relative; width: 449px; height: 150px; margin: 0; }
.flow-svg { position: absolute; inset: 0; width: 449px; height: 150px; }

.card .diagram + p { margin-top: 38px; }

.wallets {
  position: absolute; top: 0; display: grid; gap: 15px;
}
.wallets-left  { left: 0; }
.wallets-right { right: 0; }

.pill {
  display: inline-flex; align-items: center; gap: 9px;
  width: 130px; height: 40px; padding: 0 13px;
  border-radius: 10px;
  font-size: 14px; font-weight: 600; color: var(--text-high);
  white-space: nowrap;
}
.pill-ic { width: 30px; height: 30px; flex: none; }

.cp-node {
  position: absolute; top: 45px;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  width: 207px; height: 60px; border-radius: 10px;
}
.cp-node img { width: 167px; height: auto; }
.node-ic { width: 30px; height: 30px; flex: none; }
.node-label { font-size: 14px; font-weight: 600; color: var(--text-high); white-space: nowrap; }
.cp-right { left: 242px; }
.cp-left  { left: 0; }

/*
 * Only stroke-dashoffset animates: the compositor handles it without
 * repainting the card. A moving gradient or a glow filter here would cost
 * a full repaint per frame for the same visual result.
 */
/*
 * The paths carry pathLength="100", so the dash below is in percent of each
 * path rather than in user units -- which is what fixes the stall. The dash
 * pattern used to total 266 units against paths only ~140 long, so the light
 * crossed the card in the first half of every cycle and then sat off the end
 * of the path, invisible, for the rest of it. Normalised, one cycle is
 * exactly one crossing and the motion never stops.
 *
 * The second and third lines carry NEGATIVE delays (-.5s, -1s) rather
 * than positive ones. A positive delay is dead time before the first run;
 * negative starts them already part-way along, so all three are moving on
 * the first frame and stay evenly spaced from then on.
 */
/*
 * The glow around the moving lights. drop-shadow rather than a duplicated,
 * thicker copy of the group: one filter on the parent covers all three
 * lights, and it follows the stroke's actual shape instead of approximating
 * it. Two shadows -- a tight bright one and a wide soft one -- read as a lit
 * filament rather than a blurred line.
 *
 * It repaints the diagram each frame, which is affordable here only because
 * the box is 449x150. Do not lift this onto anything page-sized.
 */
/*
 * The light is two passes over the same three paths.
 *
 *   .flow-glow   7px wide, blurred 5px, sitting underneath
 *   .flow-core   1.6px, perfectly sharp, on top
 *
 * Neither works alone: the blurred pass by itself is a smudge with no
 * definition, and the sharp line by itself is a wire with no light in it.
 * Together the core reads as the filament and the glow as what it is giving
 * off. It is the same two-layer build as the dashboard beam.
 *
 * Both groups carry .flow, so the animation rule below drives them from one
 * place and the two passes cannot drift apart.
 */
.flow-glow { filter: blur(5px); opacity: .9; }
.flow-core { filter: drop-shadow(0 0 2px rgba(0, 255, 173, .7)); }

.flow use { animation: flow 1.5s linear infinite; }
@keyframes flow { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }


/* ═══ the light-beam band ═══════════════════════════════════════════════
 *
 * Was one 131KB flat export. Now three layers: Figma's Light.svg inlined
 * (4.5KB), ten 60px logos (14KB the lot) and a CSS glass node -- about 19KB
 * against 131, and every piece can move on its own.
 *
 * Everything animates transform or opacity only. The six light bands carry
 * static SVG blur filters; a CSS transform on a filtered element applies to
 * the FILTERED RESULT, so the blur rasterises once and the pulse is a
 * composited scale of that. Animating the filter itself would re-blur a
 * 1440px surface every frame.
 */
.beam-band {
  position: relative; margin-top: 180px;
  height: 614px;
}
.beam-stage {
  position: absolute; left: 50%; top: 0; transform: translateX(-50%);
  width: 1440px; height: 614px;
}

.beam-light { position: absolute; inset: 0; width: 100%; height: 100%; }

/*
 * THE LIVE BEAM.
 *
 * One full-screen triangle, one fragment shader. The hourglass is analytic --
 * half-height = pinch + (flare - pinch) * s^1.8, fitted to the cubic Figma
 * drew -- so the six SVG bands collapse into three gaussians across it and
 * the light itself is a travelling wave in s, running toward the pinch.
 *
 * It sits ON TOP of the SVG and fades in, so the SVG is the fallback rather
 * than a second thing to keep in sync: no WebGL, a lost context, or a slow
 * first paint all just leave the static beam showing. Once the canvas is up
 * the SVG is taken out of the tree entirely -- six blurred 1440px bands with
 * live keyframes are not something to leave animating underneath.
 */
.beam-gl {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 1; opacity: 0; transition: opacity .8s ease;
  pointer-events: none;
}
.beam-gl.is-live { opacity: 1; }
.beam-stage.gl-live .beam-light { display: none; }

/* Each band breathes on its own period, so the beam never pulses as one
   slab. Prime-ish durations keep them from lining up again. */
.beam-light .bl { transform-origin: 50% 50%; }
.bl0 { animation: beam-breathe 11s ease-in-out infinite; }
.bl1 { animation: beam-breathe 14s ease-in-out infinite -3s; }
.bl2 { animation: beam-core    9s ease-in-out infinite -1s; }
.bl3 { animation: beam-breathe 17s ease-in-out infinite -7s; }
.bl4 { animation: beam-breathe 13s ease-in-out infinite -5s; }
.bl5 { animation: beam-core    12s ease-in-out infinite -4s; }

@keyframes beam-breathe {
  0%, 100% { transform: scaleY(1);    opacity: .85; }
  50%      { transform: scaleY(1.07); opacity: 1; }
}
@keyframes beam-core {
  0%, 100% { transform: scaleY(.96); opacity: .7; }
  50%      { transform: scaleY(1.1); opacity: 1; }
}

/* ── the asset chips ── */
.beam-chips { position: absolute; inset: 0; }

.chip {
  position: absolute; left: var(--x); top: var(--y);
  transform: translate(-50%, -50%);
}

/*
 * Three nested elements because each carries its own transform and they must
 * compose: the outer one positions, .chip-drift pulls toward the middle, and
 * .chip-bob floats. Put two of these on one element and the later animation
 * simply replaces the earlier.
 */
.chip-drift {
  display: block;
  animation: chip-drift var(--drift) ease-in-out infinite var(--dd);
}
.chip-bob {
  display: block;
  animation: chip-bob var(--bob) ease-in-out infinite var(--bd);
}

/* toward the pinch and back -- --dir is +1 on the left of centre, -1 on the
   right, so one keyframe serves both sides */
@keyframes chip-drift {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(calc(var(--dir) * 22px)); }
}
@keyframes chip-bob {
  0%, 100% { transform: translateY(-5px); }
  50%      { transform: translateY(5px); }
}

/* Round by default; .chip-sq opts a logo into a squircle. Named for the
   shape rather than for "networks", because the set is not only networks --
   Robinhood is square too. Carried in the markup, so changing one is a
   class, not a rule. */
.chip img {
  display: block; width: 34px; height: 34px;
  padding: 6px; box-sizing: content-box;
  border-radius: 50%;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .10);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .45);
}
.chip-sq img { border-radius: 13px; }

/* ── CoinPool at the pinch ──
 *
 * Its own element on top of the light, not part of it: the site's own
 * logo.svg on the dashboard's glass, 5px blur and all, so the two read as
 * the same material.
 */
.beam-node {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 226px; height: 78px; border-radius: 14px;
  z-index: 2;
}
/* Exactly the dashboard panel's setting: .glass for the fill and hairline,
   with the same 5px blur. Nothing else. */
.beam-node.glass {
  -webkit-backdrop-filter: blur(5px) saturate(106%);
  backdrop-filter: blur(5px) saturate(106%);
}
.beam-node img { width: 167px; height: auto; position: relative; z-index: 1; }

/*
 * SCROLL REACTION.
 *
 * A scroll-driven animation, so the browser runs it off the main thread --
 * no scroll listener, no rAF, no layout reads mid-gesture. The node grows
 * and lights up as the band crosses the viewport and settles back as it
 * leaves.
 *
 * Behind @supports because animation-timeline is Chromium-only for now.
 * Where it is missing the node simply sits still at full size, which is the
 * design; nothing looks broken and no JavaScript is loaded to paper over it.
 *
 * The keyframes repeat translate(-50%, -50%) because a transform declaration
 * replaces rather than extends -- leave it out and the node jumps to the
 * corner the moment the animation starts.
 */
@supports (animation-timeline: view()) {
  .beam-node {
    animation: node-scroll linear both;
    animation-timeline: view();
    animation-range: entry 5% exit 95%;
  }
  @keyframes node-scroll {
    0%   { transform: translate(-50%, -50%) translateY(34px) scale(.8); }
    50%  { transform: translate(-50%, -50%) translateY(0)    scale(1); }
    100% { transform: translate(-50%, -50%) translateY(-34px) scale(.8); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .beam-light .bl, .chip-drift, .chip-bob { animation: none !important; }
}


/* ═══ support card ══════════════════════════════════════════════════════ */
.support {
  position: relative; min-height: 300px;
  padding: 44px 48px;
  border: 1px solid var(--stroke); border-radius: var(--radius);
  background: var(--card);
  /* NOT overflow:hidden -- the robot has to break the top edge, and clipping
     it is exactly what flattens the effect. */
}
.support h3 { margin: 0 0 18px; font-size: 22px; font-weight: 600; }
.support p { margin: 0; max-width: 708px; color: var(--text-mid); }

/* the three-up feature line: smaller than body copy so it reads as a
   caption under the paragraph rather than competing with it */
.support-meta {
  margin-top: 24px !important;
  font-size: 12px; font-weight: 600; letter-spacing: .01em;
  color: var(--text-high) !important;
}
/* A plain link, not a button: no fill, no hairline, no sheen. It sits in a
   card that already has a surface of its own, and a second surface inside
   the first was reading as a box in a box. */
.support-cta {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 26px;
  font-size: 14px; font-weight: 600; color: var(--brand);

  /* grows from its left edge, so it expands into the empty space to its
     right instead of drifting sideways under the cursor */
  transform-origin: left center;
  transition: transform .25s cubic-bezier(.2, .7, .2, 1), filter .25s ease;
}
.support-cta:hover {
  transform: scale(1.08);
  filter: drop-shadow(0 0 10px rgba(0, 255, 173, .35));
}

/* a drawn arrow rather than the → character: it takes currentColor, its
   weight matches the text at 1.7 instead of whatever the font decides, and
   it can move independently of the label */
.cta-arrow {
  width: 16px; height: 16px; flex: none;
  transition: transform .25s cubic-bezier(.2, .7, .2, 1);
}
.support-cta:hover .cta-arrow { transform: translateX(3px); }

@media (prefers-reduced-motion: reduce) {
  .support-cta, .cta-arrow { transition: none; }
  .support-cta:hover { transform: none; }
}

/* the robot overlaps the card's right edge, as in the frame */
/*
 * The robot STANDS IN the card rather than sitting inside it: its feet are
 * on the bottom edge and its head clears the top by 20px, which is what
 * gives the depth.
 *
 * assets/robot.webp is CROPPED TO THE ARTWORK for this to work. The export
 * carried 54px of empty space above the head and 63px below the feet out of
 * 600 -- so the element cleared the card by 20px while the robot inside it
 * still started 9px below the edge, and no amount of resizing fixed that
 * because the gap scaled with everything else. Re-export it padded and this
 * breaks again; crop it first.
 */
.support-robot {
  position: absolute; right: 26px;
  top: -40px;                       /* head clears the card by 40 */
  height: calc(100% + 20px);        /* ...so the feet sit 20 above the floor */
  width: auto;
  pointer-events: none;
}


/* ═══ wallet / phone ════════════════════════════════════════════════════ */
.wallet-grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 60px; align-items: center;
}
.wallet-grid h2 {
  margin: 26px 0 0;
  font-size: 40px; font-weight: 600; line-height: 1.25;
}

/* the three.js canvas mounts here; sized so the layout does not shift
   when the model finishes loading */
#phone-3d { aspect-ratio: 1 / 1; position: relative; }
#phone-3d canvas { display: block; width: 100%; height: 100%; }

.phone-placeholder {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  border: 1px dashed var(--stroke-2); border-radius: var(--radius);
  color: var(--text-low); font-size: 13px;
}

.store-buttons { position: relative; display: inline-block; margin-top: 34px; }
.store-buttons img { width: 356px; height: auto; }
/* stores.svg holds both badges in one 472×90 export, so each half gets its
   own transparent hit area rather than being cut into two files. */
.store-buttons a { position: absolute; top: 0; bottom: 0; width: 50%; }
.store-buttons a:first-of-type { left: 0; }
.store-buttons a:last-of-type  { right: 0; }


/* ═══ footer ════════════════════════════════════════════════════════════ */
.footer-note { width: var(--content); margin: 0 auto; text-align: center; }
.footer-note h4 { margin: 0 0 22px; font-size: 18px; font-weight: 600; }
.footer-note p { margin: 0; font-size: 12px; line-height: 1.8; color: var(--text-low); }
.footer-note a { color: var(--text-mid); }
.footer-note a:hover { color: var(--brand); }

.site-footer {
  margin-top: 74px; height: 58px;
  border-top: 1px solid var(--stroke);
  display: flex; align-items: center;
}
.site-footer .wrap { display: flex; align-items: center; justify-content: space-between; }
.site-footer nav { display: flex; align-items: center; gap: 28px; font-size: 12px; color: var(--text-mid); }
.site-footer nav a:hover { color: var(--brand); }
.site-footer .cyberscope { height: 22px; width: auto; }


/* ═══ glass ═════════════════════════════════════════════════════════════ */
/* Deliberately last in the file. `.btn` sets `border: 1px solid transparent`
   and both selectors are a single class, so whichever comes later wins --
   with the glass block near the top the hairline on every ghost button was
   silently being overwritten. */
/*
 * The frame's own recipe, lifted off the PRO VERSION badge and the Explore
 * App button: a near-transparent white fill, a half-pixel warm-grey hairline
 * and a wide soft glow. The blur is what makes it read as glass, and it only
 * has something to do because the wireframe mesh is sitting behind it.
 *
 * backdrop-filter is GPU work on a small surface, which is fine. Do not be
 * tempted to add a plain `filter: blur()` anywhere near this -- that blurs
 * the element itself and forces a full repaint every frame it changes.
 */
.glass {
  background: rgba(255, 255, 255, .018);
  -webkit-backdrop-filter: blur(1.5px) saturate(106%);
  backdrop-filter: blur(1.5px) saturate(106%);
  /* With the fill this light the hairline and the top highlight are what
     make it read as a pane rather than a hole -- keep them if the blur is
     reduced any further. */
  border: .5px solid rgba(221, 207, 209, .22);
  box-shadow: 0 0 20px rgba(221, 207, 209, .08),
              inset 0 1px 0 rgba(255, 255, 255, .07);
}

/* Safari on an older macOS, and Firefox with the pref off, ignore
   backdrop-filter entirely -- without a floor these panels vanish. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass { background: rgba(255, 255, 255, .05); }
}



/* ═══ desktop only ══════════════════════════════════════════════════════ */
/*
 * By decision: there is no mobile design yet. When one exists, delete this
 * block and .mobile-block from the markup rather than trying to make the
 * 1100px layout reflow.
 */
.mobile-block { display: none; }

@media (max-width: 900px) {
  body > *:not(.mobile-block) { display: none !important; }
  .mobile-block {
    display: grid; place-items: center;
    min-height: 100svh; padding: 40px 28px; text-align: center;
  }
  .mobile-block img { height: 24px; width: auto; margin: 0 auto; }
  .mobile-block p {
    margin: 22px auto 0; max-width: 340px;
    font-size: 15px; line-height: 1.6; color: var(--text-mid);
  }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .dash, .flow use, .beam-band::after,
  .site-header::before,
  .btn-ghost::after, .btn-wallet::after { animation: none !important; }
}
