/*
 * base.css - reset, shell, header, product nav, footer, the "How it works"
 * modal, and every component class shared across the three surfaces.
 *
 * Rules this file lives by:
 *   1. Colour comes from tokens.css. The only literal colours below are the two
 *      the token system genuinely cannot carry (see the body glow and
 *      ::backdrop, each explained where it happens).
 *   2. Both themes must survive. Nothing here defines a colour that exists only
 *      inside a media query - tokens.css already flips the roles.
 *   3. Layout is mobile first. Every breakpoint below only widens; nothing is
 *      corrected downwards.
 *
 * Load order matters: this sheet ships before portfolios.css / token.css /
 * indexes.css, so a surface can override a shared component at equal
 * specificity without resorting to !important.
 */

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

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

html {
  /* `clip`, not `hidden`. `hidden` on the root turns the viewport into a
   * scroll container on BOTH axes, which silently kills position:sticky on the
   * header. `clip` is compatible with `overflow-y: visible`, so the sticky
   * header keeps working while a stray wide child still cannot pan the page. */
  overflow-x: clip;
  /* Reserve the scrollbar track so opening the modal (which locks scrolling)
   * does not shift the whole layout sideways by ~15px. */
  scrollbar-gutter: stable;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  /* One gutter for the header, the shell and the footer, so the brand, the
   * product card and the footer links all sit on the same left edge. Widened
   * at the breakpoints below rather than being restated per component. */
  --gutter: calc(var(--step) * 4);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.55;
  color: var(--text-primary);
  /* The ponsfamily glow: one soft lime bloom off the top right corner, sitting
   * on an explicit page colour so no host background can show through.
   *
   * The faded lime is --accent-glow, a token: a gradient cannot fade a custom
   * property without color-mix(), so tokens.css pre-bakes the alpha and each
   * theme picks its own strength. Keeping it there means no hex escapes the
   * palette layer, which is the rule the selftest enforces.
   *
   * Painted on <body>, never on a fixed child: a full-viewport fixed layer
   * would sit above the page and eat pointer events on iOS. */
  background-color: var(--bg-page);
  background-image: radial-gradient(circle at 84% -8%, var(--accent-glow), transparent 44%);
  background-repeat: no-repeat;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, p, figure, ul, ol, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }

h1, h2, h3, h4 {
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.015em;
}

h1 { font-size: clamp(26px, 5.4vw, 34px); }
h2 { font-size: clamp(19px, 3.2vw, 22px); }
h3 { font-size: 16px; }

a { color: inherit; text-decoration: none; }
a:hover { color: var(--text-primary); }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

button:disabled { cursor: not-allowed; }

input, select, textarea { font: inherit; color: inherit; }

img, canvas, video { max-width: 100%; height: auto; }

code, kbd, samp, .mono { font-family: var(--font-mono); font-size: 0.92em; }

hr { border: 0; border-top: 1px solid var(--divider); margin: 0; }

/* dom.js builds icons with svg(viewBox, paths) and leaves styling to CSS, so
 * the stroke defaults live here rather than being repeated per call site. */
svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: middle;
  max-width: 100%;
}

/* An SVG with a viewBox and no width lays out at the SVG default 300x150. Every
 * icon is unclassed; charts always pass a class and size themselves, so this
 * gives icons a text-relative size without touching a single chart. */
svg:not([class]), .icon { width: 1em; height: 1em; flex: none; }

/* Focus. One ring, on everything, and only for keyboard traversal. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* The tab panel is focused programmatically after a tab switch. That move is
 * for screen readers, not a visible focus event, so it draws no ring. */
.surface:focus { outline: none; }

::selection { background: var(--accent); color: var(--accent-contrast); }

/* ------------------------------------------------------------ skip link -- */

.skip-link {
  position: fixed;
  top: calc(var(--step) * 2);
  left: calc(var(--step) * 2);
  z-index: 200;
  padding: calc(var(--step) * 2) calc(var(--step) * 4);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 14px;
  font-weight: 600;
  /* Off screen rather than display:none so it stays in the tab order. */
  transform: translateY(-200%);
  transition: transform 0.16s var(--ease);
}

/* :focus, not :focus-visible. The link exists only for keyboard traversal, and
 * :focus-visible depends on heuristics that can leave it hidden while focused. */
.skip-link:focus { transform: translateY(0); }

/* --------------------------------------------------------------- header -- */

.app-header {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 3);
  /* Full bleed bar, shell-aligned contents. The markup has no inner wrapper, so
   * the alignment is done with padding: the percentage resolves against the
   * header's own width, and max() clamps it to a plain gutter once the viewport
   * is narrower than the shell. The gutter is added on top of the centring
   * offset so the brand lands on the same edge as the shell's content, not on
   * the shell's outer box. Deliberately not 100vw, which would include the
   * scrollbar and push the page sideways. */
  padding-block: calc(var(--step) * 3);
  padding-inline: max(var(--gutter), calc((100% - var(--shell-max)) / 2 + var(--gutter)));
  background: var(--glass-bg);
  border-bottom: 1px solid var(--divider);
  /* The blur belongs on the sticky element itself. A backdrop-filter on any
   * ANCESTOR would create a containing block and pin this header in place,
   * which is how sticky headers have died on past builds. <body> stays clean. */
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.02em;
  flex: none;
}

/* The mark is a raster app icon, not a line glyph, so `color` and `stroke-width`
 * no longer apply. The artwork carries its own light ground right out to the
 * edge, so the radius here is what stops it reading as a white square sitting on
 * the dark header. `object-fit: contain` guards against a future replacement
 * that is not perfectly square. */
.brand-mark {
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: 5px;
  object-fit: contain;
  transition: transform 140ms var(--ease);
}

.brand:hover .brand-mark { transform: scale(1.06); }

.chain-status {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  min-width: 0;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 2.5);
  border: 1px solid var(--divider);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}

#chain-status-text {
  overflow: hidden;
  text-overflow: ellipsis;
}

.chain-status .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-subtle);
  flex: none;
}

.chain-status.loading .dot { background: var(--warning); animation: dot-breathe 1.4s ease-in-out infinite; }
.chain-status.ok .dot { background: var(--live-dot); animation: dot-pulse 2.6s ease-out infinite; }
.chain-status.error { color: var(--danger); border-color: var(--danger); }
.chain-status.error .dot { background: var(--danger); animation: none; }

@keyframes dot-breathe {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}

/* Ring expands out of the dot; box-shadow costs no layout and no extra node. */
@keyframes dot-pulse {
  0% { box-shadow: 0 0 0 0 var(--accent); }
  70% { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

.header-actions {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  margin-left: auto;
}

.ghost-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: calc(var(--step) * 1.5);
  padding: calc(var(--step) * 2) calc(var(--step) * 3);
  border: 1px solid var(--divider-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.ghost-button:hover {
  color: var(--text-primary);
  border-color: var(--text-subtle);
  background: var(--bg-elevated);
}

.ghost-button:disabled { opacity: 0.45; }
.ghost-button:disabled:hover { color: var(--text-muted); background: transparent; border-color: var(--divider-strong); }

/* wallet.js owns everything inside this node. Base only guarantees a sane
 * trigger button and a glass panel for whatever it anchors underneath. */
.wallet-menu { position: relative; display: inline-flex; }

.wallet-menu button {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
  padding: calc(var(--step) * 2) calc(var(--step) * 3.5);
  border: 1px solid var(--accent);
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  transition: background 0.15s var(--ease), box-shadow 0.15s var(--ease);
}

.wallet-menu button:hover { background: var(--accent-hover); box-shadow: var(--btn-glow); }

/* Once connected the trigger drops to a quiet pill: the address is a status,
 * not the primary action on the page. */
.wallet-menu button[data-connected],
.wallet-menu button.connected {
  background: var(--bg-sunken);
  border-color: var(--divider-strong);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-weight: 500;
}

.wallet-menu button[data-connected]:hover,
.wallet-menu button.connected:hover {
  background: var(--bg-elevated);
  border-color: var(--text-subtle);
  box-shadow: none;
}

/* wallet.js may label its trigger a ghost button in some state. Honour that
 * rather than letting the lime default win on specificity alone. */
.wallet-menu button.ghost-button {
  background: transparent;
  border-color: var(--divider-strong);
  color: var(--text-muted);
  font-weight: 500;
}

.wallet-menu button.ghost-button:hover {
  background: var(--bg-elevated);
  color: var(--text-primary);
  box-shadow: none;
}

.wallet-menu [role="menu"],
.wallet-menu .wallet-panel,
.wallet-menu .wallet-dropdown {
  position: absolute;
  top: calc(100% + var(--step) * 2);
  right: 0;
  z-index: 70;
  min-width: 232px;
  padding: calc(var(--step) * 2);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-raised);
}

.wallet-menu [role="menu"] button,
.wallet-menu .wallet-panel button,
.wallet-menu .wallet-dropdown button {
  width: 100%;
  justify-content: flex-start;
  padding: calc(var(--step) * 2) calc(var(--step) * 2.5);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-primary);
  font-weight: 500;
}

.wallet-menu [role="menu"] button:hover,
.wallet-menu .wallet-panel button:hover,
.wallet-menu .wallet-dropdown button:hover {
  background: var(--bg-sunken);
  box-shadow: none;
}

/* ---------------------------------------------------------------- shell -- */

.app-shell {
  width: 100%;
  max-width: var(--shell-max);
  margin-inline: auto;
  /* min-width:0 so a wide grandchild inside a grid or flex track cannot force
   * the shell wider than the viewport. */
  min-width: 0;
  padding: calc(var(--step) * 6) var(--gutter) calc(var(--step) * 12);
  display: flex;
  flex-direction: column;
  gap: calc(var(--step) * 6);
}

.product-nav {
  display: flex;
  gap: var(--step);
  padding: var(--step);
  border: 1px solid var(--divider);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  /* width:fit-content + auto margins centres the group when it fits; the
   * overflow keeps it usable at 360px instead of clipping the first tab, which
   * is what justify-content:center would do once it overflows. */
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  overflow-x: auto;
  scrollbar-width: none;
}

.product-nav::-webkit-scrollbar { display: none; }

.product-nav button {
  padding: calc(var(--step) * 2) calc(var(--step) * 4);
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.product-nav button:hover { color: var(--text-primary); }

.product-nav button[aria-selected="true"] {
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}

.surface {
  width: 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--step) * 6);
}

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

.legal-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: calc(var(--step) * 2) calc(var(--step) * 5);
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: calc(var(--step) * 6) var(--gutter) calc(var(--step) * 10);
  border-top: 1px solid var(--divider);
  color: var(--text-subtle);
  font-size: 13px;
}

.legal-footer a { color: var(--text-muted); }

/* Hover is an underline, not lime: --accent measures 1.07:1 on the light page
 * background, so a lime hover state would read as the link disappearing. */
.legal-footer a:hover {
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------------------------------------------------- shared components -- */

.card {
  /* The glass panel every surface builds on. backdrop-filter here means a
   * position:fixed descendant would anchor to the card, so keep overlays out
   * of cards and put them in the dialog instead. */
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: calc(var(--step) * 5);
  min-width: 0;
}

@media (min-width: 560px) {
  .card { padding: calc(var(--step) * 6); }
}

.card > * + * { margin-top: calc(var(--step) * 4); }

/* The single product column. Surfaces opt in; the shell stays full width. */
.card.card-column {
  width: 100%;
  max-width: var(--card-max);
  margin-inline: auto;
}

.primary-action {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(var(--step) * 2);
  width: 100%;
  padding: calc(var(--step) * 3.5) calc(var(--step) * 4);
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  transition: background 0.15s var(--ease), box-shadow 0.2s var(--ease), transform 0.1s var(--ease);
}

.primary-action:hover:not(:disabled) {
  background: var(--accent-hover);
  box-shadow: var(--btn-glow);
}

.primary-action:active:not(:disabled) { transform: translateY(1px); }

.primary-action:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
}

/* Secondary companion to .primary-action, same metrics, quiet fill. */
.secondary-action {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(var(--step) * 2);
  width: 100%;
  padding: calc(var(--step) * 3.5) calc(var(--step) * 4);
  border: 1px solid var(--divider-strong);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  color: var(--text-primary);
  font-size: 15px;
  font-weight: 600;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.secondary-action:hover:not(:disabled) { border-color: var(--text-subtle); background: var(--bg-sunken); }
.secondary-action:disabled { opacity: 0.45; cursor: not-allowed; }

.segmented {
  display: flex;
  gap: var(--step);
  padding: var(--step);
  border: 1px solid var(--divider);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}

.segmented::-webkit-scrollbar { display: none; }

.segmented button {
  flex: 1 1 auto;
  padding: calc(var(--step) * 2) calc(var(--step) * 3);
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.segmented button:hover { color: var(--text-primary); }

/* aria-pressed is the contract; aria-selected is honoured too so a group built
 * as a radiogroup or tablist does not silently lose its selected pill. */
.segmented button[aria-pressed="true"],
.segmented button[aria-selected="true"] {
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}

.segmented button:disabled { opacity: 0.4; }
.segmented button:disabled:hover { color: var(--text-muted); }

.detail-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(var(--step) * 4);
  padding-block: calc(var(--step) * 2);
  font-size: 14px;
}

.detail-row + .detail-row { border-top: 1px solid var(--divider); }

.detail-row > :first-child {
  color: var(--text-muted);
  min-width: 0;
}

.detail-row > :last-child {
  color: var(--text-primary);
  font-weight: 600;
  text-align: right;
  /* Numbers change on every poll; tabular figures stop the row twitching. */
  font-variant-numeric: tabular-nums;
}

.detail-row.is-strong > :last-child { font-size: 16px; }
.detail-row .positive { color: var(--success); }
.detail-row .negative { color: var(--danger); }

.pending-note {
  display: block;
  color: var(--text-subtle);
  font-size: 12.5px;
  font-style: italic;
  line-height: 1.5;
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: calc(var(--step) * 3);
}

/* Works for <div><span class="stat-label">,  and for a <dl> of dt/dd pairs. */
.stat-grid > div,
.stat-grid > .stat {
  display: flex;
  flex-direction: column;
  gap: calc(var(--step) * 0.5);
  min-width: 0;
}

.stat-label, .stat-grid dt {
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
}

.stat-value, .stat-grid dd {
  color: var(--text-primary);
  font-size: 18px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}

.surface-title {
  display: flex;
  flex-direction: column;
  gap: calc(var(--step) * 3);
  align-items: center;
  text-align: center;
  max-width: 660px;
  margin-inline: auto;
}

.surface-title p {
  color: var(--text-muted);
  font-size: clamp(15px, 2.6vw, 17px);
  line-height: 1.6;
  text-wrap: pretty;
}

details.disclosure {
  border-top: 1px solid var(--divider);
  padding-block: calc(var(--step) * 3);
}

details.disclosure > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--step) * 3);
  cursor: pointer;
  color: var(--text-primary);
  font-size: 14px;
  font-weight: 500;
  list-style: none;
}

details.disclosure > summary::-webkit-details-marker { display: none; }

/* The + is drawn from two gradient bars rather than a glyph: it inherits the
 * text colour, needs no font, and rotates into an x on open. */
details.disclosure > summary::after {
  content: "";
  width: 12px;
  height: 12px;
  flex: none;
  background:
    linear-gradient(currentColor, currentColor) center / 12px 1.5px no-repeat,
    linear-gradient(currentColor, currentColor) center / 1.5px 12px no-repeat;
  color: var(--text-subtle);
  transition: transform 0.2s var(--ease), color 0.15s var(--ease);
}

details.disclosure > summary:hover::after { color: var(--text-primary); }
details.disclosure[open] > summary::after { transform: rotate(45deg); }

details.disclosure > summary + * { margin-top: calc(var(--step) * 3); }

details.disclosure p {
  color: var(--text-muted);
  font-size: 13.5px;
  line-height: 1.6;
}

.copy-button {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
  padding: calc(var(--step) * 0.75) calc(var(--step) * 2);
  border: 1px solid var(--divider);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}

.copy-button:hover { color: var(--text-primary); border-color: var(--text-subtle); }

/* Set the class (or data-copied) after a successful clipboard write. The
 * confirmation is carried by the lime border and the tinted fill; the text
 * itself stays ink, because lime type on --accent-soft is 1.12:1 in light. */
.copy-button.copied,
.copy-button[data-copied] {
  color: var(--text-primary);
  border-color: var(--accent);
  background: var(--accent-soft);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
  padding: calc(var(--step) * 0.75) calc(var(--step) * 2.5);
  border: 1px solid var(--divider);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Lime is the border and the dot. The label is ink: --accent on --accent-soft
 * measures 1.12:1 in the light theme, which is not a colour, it is a blank. */
.tag.live {
  border-color: var(--accent-mid);
  background: var(--accent-soft);
  color: var(--text-primary);
}

.tag.live::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--live-dot);
  flex: none;
  animation: dot-pulse 2.6s ease-out infinite;
}

.tag.pending { color: var(--text-subtle); }
.tag.warning { border-color: var(--warning); color: var(--warning); }

/* Any table or wide grid goes inside one of these. The page itself must never
 * gain a horizontal scrollbar. */
.scroll-x {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

.surface table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
}

.surface th, .surface td {
  padding: calc(var(--step) * 2.5) calc(var(--step) * 3);
  text-align: right;
  white-space: nowrap;
  border-bottom: 1px solid var(--divider);
}

.surface th:first-child, .surface td:first-child { text-align: left; }

.surface th {
  color: var(--text-subtle);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.surface tbody tr:last-child td { border-bottom: 0; }

/* Screen-reader-only text, for labels that would otherwise be a bare icon. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------- "How it works" modal -- */

/* A <dialog> is display:none until open. Never give the base rule a display
 * value or the modal paints on top of the page while closed. */
.how-modal {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(560px, calc(100% - var(--step) * 8));
  max-width: 560px;
  max-height: min(84vh, 720px);
  padding: 0;
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  background: var(--bg-elevated);
  color: var(--text-primary);
  box-shadow: var(--shadow-raised);
  overflow: hidden;
}

.how-modal[open] {
  display: flex;
  flex-direction: column;
}

.how-modal::backdrop {
  /* Literal colour on purpose: ::backdrop did not inherit custom properties
   * from :root in shipped engines until recently, so a var() here renders as
   * nothing in older browsers and the page behind stays fully legible. */
  background: rgb(0 0 0 / 0.62);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.how-modal[open] { animation: modal-in 0.2s var(--ease); }

@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

/* Header, body and footer are optional: js/how.js may render one flat block.
 * min-height:0 is what lets whichever child is the body actually scroll
 * instead of stretching the dialog past its max-height. */
.how-modal > * { min-height: 0; }

.how-modal header,
.how-modal .how-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 5) calc(var(--step) * 5) calc(var(--step) * 3);
  border-bottom: 1px solid var(--divider);
  flex: none;
}

.how-modal h2 { font-size: 19px; }

.how-modal .how-body {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: calc(var(--step) * 5);
  display: flex;
  flex-direction: column;
  gap: calc(var(--step) * 4);
}

/* Fallback for a flat dialog with no .how-body: the panel scrolls as a whole. */
.how-modal:not(:has(.how-body)) {
  overflow-y: auto;
  padding: calc(var(--step) * 5);
  gap: calc(var(--step) * 4);
}

.how-modal p {
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1.65;
}

.how-modal ol, .how-modal ul {
  display: flex;
  flex-direction: column;
  gap: calc(var(--step) * 3);
  counter-reset: how-step;
}

.how-modal li {
  display: flex;
  gap: calc(var(--step) * 3);
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1.6;
}

/* Numbered steps get a lime chip; the counter means the markup carries no
 * hardcoded digits and reordering the list cannot desync them. */
.how-modal ol > li::before {
  counter-increment: how-step;
  content: counter(how-step);
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 12px;
  font-weight: 700;
}

.how-modal li strong { color: var(--text-primary); font-weight: 600; }

.how-modal footer,
.how-modal .how-foot {
  display: flex;
  justify-content: flex-end;
  gap: calc(var(--step) * 2);
  padding: calc(var(--step) * 3) calc(var(--step) * 5) calc(var(--step) * 5);
  border-top: 1px solid var(--divider);
  flex: none;
}

.how-modal .how-close {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--divider);
  color: var(--text-muted);
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}

.how-modal .how-close:hover { color: var(--text-primary); border-color: var(--text-subtle); }

/* Lock the page while the modal owns the screen. Without this the body scrolls
 * behind the dialog on wheel and touch. */
html:has(.how-modal[open]) { overflow: hidden; }

/* ---------------------------------------------------------- breakpoints -- */

/* Narrowest supported width. The header keeps one row by shedding the Journal
 * link, which the footer still carries, and by tightening the pills. */
@media (max-width: 599px) {
  .header-actions a.ghost-button { display: none; }

  .ghost-button,
  .wallet-menu button {
    padding: calc(var(--step) * 1.75) calc(var(--step) * 2.5);
    font-size: 12.5px;
  }
}

/* Below this the four header items cannot share a line at any honest size, so
 * the chain status drops to its own row rather than being truncated away. */
@media (max-width: 439px) {
  /* Two rows, so trim the block padding: a sticky bar that eats an eighth of a
   * 760px phone viewport is a bar in the way of the product. */
  .app-header {
    flex-wrap: wrap;
    row-gap: calc(var(--step) * 1.5);
    padding-block: calc(var(--step) * 2);
  }
  .chain-status { order: 3; width: 100%; justify-content: flex-start; padding-block: var(--step); }
  body { --gutter: calc(var(--step) * 3); }
  .card { padding: calc(var(--step) * 4); }
  .product-nav button { padding-inline: calc(var(--step) * 3); font-size: 13px; }
}

@media (min-width: 768px) {
  body { font-size: 16px; --gutter: calc(var(--step) * 6); }

  .app-shell {
    padding-block: calc(var(--step) * 10) calc(var(--step) * 16);
    gap: calc(var(--step) * 8);
  }

  .surface { gap: calc(var(--step) * 8); }
}

@media (min-width: 1080px) {
  .app-shell { padding-bottom: calc(var(--step) * 20); }
  .surface-title { max-width: 720px; }
}

/* --------------------------------------------- light theme corrections -- */

/*
 * --accent is deliberately the same lime in both themes, which is the whole
 * point of the brand. On a near-white page that lime measures 1.07:1, so the
 * three places where it carries meaning rather than decoration hand the job to
 * ink or to the deeper --chart-line olive. Every one of these has a base value
 * defined above, outside any media query, so neither theme is left undefined.
 *
 * Both triggers are covered: the OS preference (guarded so an explicit dark
 * choice still wins) and an explicit data-theme="light".
 */
@media (prefers-color-scheme: light) {
  /* The mark is an image now and carries its own colour in both themes, so there
   * is nothing here to re-tint. */
  :root:not([data-theme='dark']) .chain-status.ok .dot,
  :root:not([data-theme='dark']) .tag.live::before { background: var(--chart-line); }
}

:root[data-theme='light'] .chain-status.ok .dot,
:root[data-theme='light'] .tag.live::before { background: var(--chart-line); }

/* Pointer-coarse devices get no hover lift; it only ever sticks after a tap. */
@media (hover: none) {
  .primary-action:hover:not(:disabled) { box-shadow: none; }
  .wallet-menu button:hover { box-shadow: none; }
}
