/* ============================================================
   Sleevz — polish layer

   Loads AFTER styles.css and only ever adds to it. Nothing in here
   invents a new visual direction: same graphite surfaces, same single
   gold accent, same "one family of exponential ease-outs, no bounce"
   the tokens already commit to. What it adds is the last ten percent —
   the browser's own furniture made to match the page, hover weight,
   entrances that arrive in an order rather than all at once, and two
   segmented controls that slide instead of blink.

   Every rule here is either a brand-new selector or a deliberate
   override that relies on this file being second in the cascade. Delete
   the <link> and the site is exactly what it was.

   Reduced motion is handled at the bottom, in one block, the same way
   styles.css does it: every effect below has a resting state there.
   ============================================================ */

/* ============================================================
   1 — FOUNDATIONS
   The things a visitor never notices until they are missing.
   ============================================================ */

:root {
  /* The page is dark and never offers a light mode, so say so once. This
     is what makes the browser's own chrome render dark instead of
     dropping a white box on a black page: the <select> menus in the
     console, the scrollbars, the text caret, autofill, and the date and
     password UI on the auth pages. */
  color-scheme: dark;

  /* --- Motion, continued ---------------------------------------
     Two curves added to the existing family. Neither overshoots: this
     is a page about money, and money interfaces that spring read as
     toys. The one exception is --ease-throw, below, which is reserved
     for the offline switch, because a switch is a physical object and
     a knob that lands dead-flat looks broken.                        */

  /* Decisive rather than floaty. --ease-out is a long expo tail, which
     is right for something arriving from off-screen and wrong for a
     control that has to feel pressed. Use this for short travel over a
     known distance: the tab pill, the nav underline, the condition
     pill. */
  --ease-snap: cubic-bezier(0.22, 0.86, 0.24, 1);

  /* Lands about one percent past and settles. Only the toggle knob. */
  --ease-throw: cubic-bezier(0.34, 1.24, 0.5, 1);

  /* Hover is asymmetric on purpose. A surface that arrives fast under
     the pointer and lets go slowly feels weighted; one that uses the
     same duration both ways feels switched. --dur-in goes on the
     :hover rule, --dur-out on the resting one. */
  --dur-in: 0.16s;
  --dur-out: 0.42s;
}

/* Selected text picks up the accent instead of the OS blue, which on
   this canvas is the loudest thing on the page the moment anyone drags
   across a headline. */
::selection {
  background: rgba(var(--accent-rgb), 0.3);
  color: var(--text);
}

body {
  /* iOS paints a grey box over every tapped link and button by default.
     Every control on this page already has its own :active state. */
  -webkit-tap-highlight-color: transparent;
}

/* Thin, dark, and only visible against the accent when you are actually
   dragging it. Applies to the page and to the two inner scrollers (the
   tab strip, the trade menu). */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.16) transparent;
}

*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }

*::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  background-clip: padding-box;
  border-radius: var(--radius-pill);
  background-color: rgba(255, 255, 255, 0.16);
}

*::-webkit-scrollbar-thumb:hover { background-color: rgba(var(--accent-rgb), 0.45); }

/* Chrome's autofill paints a hard blue-white block over a filled email
   field, which is the single most common way a dark site looks
   unfinished. The 9999s transition is the standard trick: the colour
   never gets a chance to animate in. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--bg-3) inset;
  caret-color: var(--accent);
  transition: background-color 9999s ease-in-out 0s;
}

/* The caret is the one bit of the accent a visitor sees while typing. */
input, select, textarea { caret-color: var(--accent); }

/* styles.css sets this on the waitlist field only; the console's inputs
   and the auth pages were relying on the UA default. */
::placeholder { color: var(--text-3); opacity: 1; }

/* Same ring, one step warmer and one step further out, so it clears the
   1px borders the fields and buttons already carry instead of sitting
   on top of them. Deliberately does not touch border-radius — the rule
   in styles.css leaves each component its own corner. */
:focus-visible {
  outline-color: var(--accent-hover);
  outline-offset: 3px;
}

/* Cross-document fades between index, signin, privacy and reset. Chrome
   and Edge only; everywhere else the navigation is exactly what it was.
   Needs the <link> to this file on BOTH pages to fire — see INSTALL. */
@view-transition { navigation: auto; }


/* ============================================================
   2 — HOVER WEIGHT
   Same movements, retimed. Fast in, slow out.
   ============================================================ */

.cell,
.position,
.pricer,
.offline,
.step,
.footer-col a,
.btn { transition-duration: var(--dur-out); }

.cell:hover,
.cell:focus-within,
.position:hover,
.pricer:hover,
.pricer:focus-within,
.offline:hover,
.offline:focus-within,
.step:hover,
.footer-col a:hover,
.btn:hover { transition-duration: var(--dur-in); }

/* The headings that warm to gold when their card or step does. Without
   these the surface lifts in 0.16s and the title catches up in 0.34s,
   which is the difference between one gesture and two. */
.cell-h,
.step-h,
.clip-stage { transition-duration: var(--dur-out); }

.cell:hover .cell-h,
.step:hover .step-h,
.cell:hover .clip-stage { transition-duration: var(--dur-in); }

/* The press stays the fastest thing on the page — it has to land inside
   the same frame as the click or it reads as lag. Restated here because
   the :hover rule above would otherwise win on source order. */
.btn:active { transition-duration: 0.07s; }

/* A specular highlight that follows the pointer across the primary CTA,
   under the light sweep that is already there. Same idea as the
   .spotlight cells, so it is a pattern the page has rather than a new
   one; --bx / --by are written by polish.js. Kept at 0.28 white so it
   reads as the gold catching a light, not as a second button. */
.btn-primary::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background: radial-gradient(
    150px circle at var(--bx, 50%) var(--by, 50%),
    rgba(255, 255, 255, 0.28),
    transparent 68%
  );
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) ease;
}

.btn-primary:hover::before { opacity: 1; }
.btn:disabled::before { display: none; }


/* ============================================================
   3 — NAV
   ============================================================ */

/* One bar that travels between the links, instead of four bars that
   each grow and shrink in place. The travel is the point: it says the
   sections are a sequence you are moving through. It follows the
   pointer on hover and returns to whichever section you are actually
   in when the pointer leaves.

   The per-link ::after stays in the stylesheet as the no-JS hover and
   is switched off only once polish.js has built the bar. */
.nav-ink {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 1.5px;
  border-radius: 2px;
  background: var(--accent);
  opacity: 0;
  pointer-events: none;
  will-change: transform, width;
  transition:
    transform 0.4s var(--ease-snap),
    width 0.4s var(--ease-snap),
    opacity var(--dur-fast) ease;
}

.nav-ink.is-on { opacity: 1; }
.nav-links.has-ink .nav-link::after { display: none; }

/* The scrolled bar currently ends in a hard hairline. A short gradient
   under it lets the blur meet the page instead of stopping dead. */
.nav::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  /* top:100% lands on the INNER edge of the 1px bottom border — an
     absolute box is placed against its container's padding box — so the
     wash would tint the hairline it is meant to sit under. */
  top: 100%;
  margin-top: 1px;
  height: 20px;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(180deg, rgba(6, 8, 12, 0.5), transparent);
  transition: opacity var(--dur) ease;
}

.nav.scrolled::after { opacity: 1; }


/* ============================================================
   4 — HERO
   The panel arrives as a panel, then fills in: heading, figure,
   chart, hand, grid. Nothing here changes what the panel says or
   where anything sits — only the order it turns up in.
   ============================================================ */

/* .polish is added by polish.js in its first statement. Every rule below
   that starts something at opacity 0 hangs off it, so if this file ever
   ships without its script — a blocked asset, a JS error above it — the
   panel is simply the panel, fully drawn, rather than an empty box. */
.polish .position > * {
  opacity: 0;
  transform: translateY(9px);
  transition:
    opacity 0.45s ease,
    transform 0.55s var(--ease-out);
}

.polish .position.visible > * {
  opacity: 1;
  transform: none;
}

.polish .position.visible > *:nth-child(1) { transition-delay: 0.05s; }
.polish .position.visible > *:nth-child(2) { transition-delay: 0.10s; }
.polish .position.visible > *:nth-child(3) { transition-delay: 0.15s; }
.polish .position.visible > *:nth-child(4) { transition-delay: 0.20s; }
.polish .position.visible > *:nth-child(5) { transition-delay: 0.25s; }
.polish .position.visible > *:nth-child(6) { transition-delay: 0.30s; }

/* ---- The chart draws itself ----
   A sparkline that fades in as a finished shape is a picture of a
   chart. One that draws left to right is the same chart arriving the
   way it was measured. polish.js sets pathLength="1" on the stroked
   path, so the dash lengths below are in units of "the whole line" and
   nothing has to measure it — including after the console redraws it.
   The fill and the head follow the line rather than racing it.

   The `d` transitions from styles.css are restated because these
   selectors outrank the ones that carry them — the console below still
   redraws this chart live, and that must keep working. */
.polish .position .spark-line {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition:
    d 0.6s var(--ease-out),
    stroke-dashoffset 0.85s var(--ease-out-quart) 0.3s;
}

.polish .position .spark-area {
  opacity: 0;
  transition:
    d 0.6s var(--ease-out),
    opacity 0.5s ease 0.75s;
}

.polish .position .spark-dot {
  opacity: 0;
  transition:
    cx 0.6s var(--ease-out),
    cy 0.6s var(--ease-out),
    opacity 0.3s ease 1.05s;
}

.polish .position.visible .spark-line { stroke-dashoffset: 0; }
.polish .position.visible .spark-area,
.polish .position.visible .spark-dot { opacity: 1; }

/* ---- The hand deals itself ----
   The fan is described in styles.css as cards "dealt as a hand", and
   dealIn already exists for stock arriving from the console. This runs
   the same animation, once, seat by seat, when the panel first shows —
   so the opening state and a card bought later arrive identically.

   .is-dealing is added and then removed by polish.js, which is what
   keeps this rule from outranking .pos-card.is-new afterwards. */
.polish .pos-fan.is-pending .pos-card { opacity: 0; }

.polish .pos-fan.is-dealing .pos-card {
  animation: dealIn 0.62s var(--ease-out) both;
  animation-delay: calc(0.42s + var(--i) * 0.075s);
}


/* ============================================================
   5 — THE CONSOLE
   ============================================================ */

/* The pill was travelling on a 0.45s expo, which is the curve for
   something entering the page, not for a 90px slide between two tabs a
   thumb's width apart. Same distance, decisive. */
.tab-pill.is-ready {
  transition:
    transform 0.4s var(--ease-snap),
    width 0.4s var(--ease-snap),
    height 0.4s var(--ease-snap),
    opacity 0.2s ease;
}

/* Switching tabs used to lift and fade the whole panel as one block,
   which on the wide layout is a lot of surface for a small action. The
   two panes come in instead, form first and result a beat behind — the
   order you read them in, and the order the product works in. */
.polish .panel { animation: none; }

.polish .panel .pane-form { animation: paneIn 0.42s var(--ease-out) 0.02s both; }
.polish .panel .pane-out { animation: paneIn 0.46s var(--ease-out) 0.1s both; }

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

/* Committing a deal changes numbers in four places, one of them a
   screen away in the hero. A single ring off the button is the receipt
   for the press itself, so the button is not the only thing on screen
   that stayed still. The resting shadow is restated in both stops so
   the gold does not flatten while it runs. */
.commit.is-done {
  animation: commitPulse 0.62s var(--ease-out) both;
}

@keyframes commitPulse {
  from {
    box-shadow:
      0 1px 0 rgba(255, 255, 255, 0.25) inset,
      0 8px 24px rgba(var(--accent-rgb), 0.24),
      0 0 0 0 rgba(var(--accent-rgb), 0.45);
  }
  to {
    box-shadow:
      0 1px 0 rgba(255, 255, 255, 0.25) inset,
      0 8px 24px rgba(var(--accent-rgb), 0.24),
      0 0 0 13px rgba(var(--accent-rgb), 0);
  }
}

/* ---- The deal desk stays visibly alive -----------------------
   The console is the only part of the page where a visitor can change
   the product's numbers, so it earns the page's one perpetual accent.
   A narrow beam orbits the existing border while the deck is in view.
   The large conic layer rotates with transform; the mask only exposes
   the one-pixel perimeter, keeping the moving layer out of the form. */
.deck {
  position: relative;
  isolation: isolate;
  --deck-x: 50%;
  --deck-y: 20%;
}

.deck-tabs,
.deck-body,
.ledger {
  position: relative;
  z-index: 1;
}

/* A low-energy inspection light follows the pointer through the demo.
   It sits under the real panes, so it catches their translucent surfaces
   instead of tinting labels or reducing contrast. */
.deck::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: radial-gradient(
    440px circle at var(--deck-x) var(--deck-y),
    rgba(var(--accent-rgb), 0.09),
    rgba(var(--accent-rgb), 0.025) 38%,
    transparent 72%
  );
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s var(--ease-out);
}

.deck:hover::after,
.deck:focus-within::after { opacity: 1; }

.deal-laser {
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  width: 100%;
  height: 100%;
  overflow: visible;
  opacity: 0.86;
  pointer-events: none;
  filter:
    drop-shadow(0 0 2px rgba(255, 255, 255, 0.9))
    drop-shadow(0 0 7px rgba(255, 255, 255, 0.34));
  transition: opacity 0.35s var(--ease-out);
}

.deal-laser-segment {
  fill: none;
  stroke: #fff;
  stroke-linecap: butt;
  vector-effect: non-scaling-stroke;
}

.deal-laser-motion {
  stroke-dashoffset: 0;
  animation: dealLaserOrbit 7.6s linear infinite paused;
}

/* polish.js builds the entire beam from many near-transparent white strokes.
   They share a leading edge, then peel away one at a time from almost zero
   length through the full tail. There is intentionally no separate head:
   accumulated light creates the peak and lets it decay without a hard step. */

.deck.is-energized .deal-laser-motion { animation-play-state: running; }

.deck:hover .deal-laser,
.deck:focus-within .deal-laser {
  opacity: 0.94;
}

/* A completed deal briefly puts more current through the same perimeter,
   extending the button's existing confirmation pulse to the whole tool. */
.deck:has(.commit.is-done) .deal-laser {
  opacity: 1;
}

@keyframes dealLaserOrbit {
  to { stroke-dashoffset: 1; }
}


/* ============================================================
   6 — SEGMENTED CONTROLS
   The four condition buttons and the two scanner buttons both worked
   by moving a gold block from one button to another instantly. Same
   two controls, with the block sliding. polish.js builds the pill and
   adds .has-pill; without JS both fall straight back to what they are
   in styles.css.
   ============================================================ */

.conds,
.clip-modes { position: relative; }

.cond,
.clip-mode { position: relative; z-index: 1; }

.seg-pill {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  background: var(--accent);
  opacity: 0;
  pointer-events: none;
  will-change: transform, width;
  transition:
    transform 0.36s var(--ease-snap),
    width 0.36s var(--ease-snap),
    height 0.36s var(--ease-snap),
    opacity var(--dur-fast) ease;
}

.seg-pill.is-ready { opacity: 1; }
.conds .seg-pill { border-radius: var(--radius-sm); }
.clip-modes .seg-pill { border-radius: var(--radius-pill); }

/* The one thing a sliding segmented control gets wrong if you let it.
   These labels invert — muted grey when off, near-black on gold when on
   — so a colour change that runs in step with the pill spends its
   middle putting dark text on the dark canvas the pill has not reached
   yet, and the label appears to blink out.

   The delay is the fix, and it works in both directions because both
   directions have the same problem mirrored. Arriving: the pill covers
   the button before the text darkens. Leaving: the text stays dark
   until the pill is off it, then goes grey. 0.16s in front of a 0.36s
   travel is roughly the halfway point either way. */
/* This is the LEAVING direction: the rule a button falls back to the
   moment it stops being the active one. Its gold is dropped at once —
   the pill is still sitting on top of it and slides off to reveal the
   dark underneath, which is what a sliding pill is supposed to look
   like. Only the label waits. */
.conds.has-pill .cond,
.clip-modes.has-pill .clip-mode {
  transition:
    color 0.2s var(--ease-snap) 0.16s,
    background-color 0.01s linear,
    border-color 0.01s linear,
    transform var(--dur-fast) var(--ease-out);
}

/* Hover has no pill to wait for, so it does not wait — a 160ms pause
   before a plain grey-to-white nudge just reads as a slow page. The
   :not() is what keeps that from undoing the rule above: the moment a
   button becomes the active one it stops matching here, so clicking the
   button you were already hovering still gets the delay. */
.conds.has-pill .cond:hover:not(.is-active),
.clip-modes.has-pill .clip-mode:hover:not(.is-active) { transition-delay: 0s; }

/* And this is the ARRIVING direction.
   The obvious way to build a sliding pill is to take the background off
   the active button and let the travelling <span> be the only gold on
   screen. Do not: it breaks scripts/audit.mjs. That script measures
   every label against the first PAINTED background up its own ancestry,
   and with the gold living on a sibling the four condition buttons have
   none — the accent-ink label falls through to the page canvas and
   reports 1.08:1 against a 4.5 bar. Two rows, both widths, gate fails.
   It is the same false failure the gradient handling in that file was
   written to fix, arriving by a different route.

   So the button keeps the real background styles.css already gives it,
   and only switches it on once the pill has finished travelling. There
   is never a second gold block, because the gold only appears where the
   pill already is — and the button still owns a background for anything
   reading the DOM rather than the screen.

   0.36s is the pill's own travel time. The two are one number; if the
   .seg-pill transition changes, this changes with it. */
.conds.has-pill .cond.is-active,
.clip-modes.has-pill .clip-mode.is-active {
  transition:
    color 0.2s var(--ease-snap) 0.16s,
    background-color 0.01s linear 0.36s,
    border-color 0.01s linear 0.36s;
}

/* The conditions were the one row of buttons on the page with no press
   state; .clip-mode already had this. */
.conds.has-pill .cond:active { transform: scale(0.97); }


/* ============================================================
   7 — FAQ
   ============================================================ */

/* The answer used to unroll at full opacity, which means the first
   line is legible before the row has finished opening and the eye
   catches it mid-move. Now it follows the row open by a beat. */
.faq-body > p {
  opacity: 0;
  transform: translateY(-5px);
  transition:
    opacity 0.28s ease,
    transform 0.34s var(--ease-out);
}

.faq-item[open] .faq-body > p {
  opacity: 1;
  transform: none;
  transition-delay: 0.12s;
}

/* The plus mark leans into the press.

   Only the horizontal bar. The vertical one is the half that rotates
   away and fades to 0 when the row opens, and dimming it here would
   land on exactly the same specificity as that rule — so pressing an
   already-open question would flash the minus back into a faint plus.
   The bar that is present in both states is the one to press. */
.faq-item summary::before,
.faq-item summary::after { transition-duration: 0.28s; }
.faq-item:active summary::before { opacity: 0.6; }


/* ============================================================
   8 — READING PROGRESS
   main.js drives this bar from a scroll listener. Where the browser
   can drive it off the scroller directly it should: the work moves to
   the compositor and the bar stops being one more thing competing for
   the main thread on a page with six videos on it. A CSS animation
   outranks the inline transform main.js writes, so the handover needs
   no change on the JS side and reverts by itself elsewhere.
   ============================================================ */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .progress span {
      animation: progressGrow linear both;
      animation-timeline: scroll(root block);
    }
  }
}

@keyframes progressGrow {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}


/* ============================================================
   9 — REDUCED MOTION
   Same contract as styles.css: everything above has a resting state
   here, and nothing is only legible once it has moved.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .btn-primary::before { display: none; }

  .nav-ink,
  .seg-pill,
  .tab-pill.is-ready { transition: none; }

  .nav::after { transition: none; }

  /* The panel and everything in it is simply there. */
  .polish .position > * { opacity: 1; transform: none; transition: none; }
  .polish .position .spark-line { stroke-dasharray: none; stroke-dashoffset: 0; transition: none; }
  .polish .position .spark-area,
  .polish .position .spark-dot { opacity: 1; transition: none; }

  /* Not `transform: none` — the seat angle is layout, not motion. */
  .polish .pos-fan.is-pending .pos-card,
  .polish .pos-fan.is-dealing .pos-card { opacity: 1; animation: none; }

  .polish .panel .pane-form,
  .polish .panel .pane-out { animation: none; }

  .commit.is-done { animation: none; }

  .deal-laser {
    opacity: 0.36;
    transition: none;
  }

  .deal-laser-motion {
    animation: none;
    stroke-dashoffset: 0.08;
  }

  .deck::after { display: none; }

  .faq-body > p { opacity: 1; transform: none; transition: none; }

  .progress span { animation: none; }

  /* A cross-fade between pages is still motion. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
