/* Verginals design system.
 *
 * One stylesheet every page inherits, so a component looks the same wherever it lands. The palette
 * is unchanged from what the site already shipped: the work here is typography, density and
 * structure, which is where the site was weakest.
 *
 * Two typefaces do all of it. Bricolage Grotesque carries every headline, tight and heavy, because
 * the site's job is to make permanent things feel considered rather than casual. JetBrains Mono
 * carries every label and every figure, because almost everything on this site is countable and a
 * column of numbers that does not line up reads as an estimate.
 *
 * Both are SELF-HOSTED. The CSP is default-src 'self' and would block a font CDN anyway, but the
 * real reason is that a page explaining that nothing about you leaves your device should not open a
 * connection to a third party to draw its own headline.
 */

/* --- the faces ------------------------------------------------------------------------------- */

@font-face {
  font-family: 'Bricolage Grotesque';
  src: url('/fonts/bricolage-grotesque-latin.woff2') format('woff2');
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/fonts/jetbrains-mono-latin.woff2') format('woff2');
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Unchanged. The design pass kept every one of these. */
  --bg: #0b1017;
  --bg2: #121a24;
  --card: #16202c;
  --line: #26323f;
  --line2: #2f3d4c;
  --hair: rgba(255, 255, 255, .08);
  --txt: #e8eff6;
  --muted: #8ea0b2;
  --dim: rgba(232, 239, 246, .5);
  --accent: #4cc2f1;
  --accent2: #1aa3e0;
  --ok: #38d39f;
  --danger: #ff6b6b;
  --warn: #e0906f;

  --display: 'Bricolage Grotesque', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

  --r-pill: 999px;
  --r-card: 12px;
  --r-ctl: 8px;
  --r-chip: 5px;

  --nav-h: 60px;
  --page: 1180px;
}

/* --- the ground ------------------------------------------------------------------------------ */

.vg body, body.vg {
  margin: 0;
  background: var(--bg);
  color: var(--txt);
  font: 400 14px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
}

.vg-wrap { max-width: var(--page); margin: 0 auto; padding: 0 22px; }

/* --- type ------------------------------------------------------------------------------------ */

/* The display scale. Tight leading and negative tracking are the whole character of it: at these
   weights and sizes, default spacing reads as a template. */
.vg-d1, .vg-d2, .vg-d3, .vg-d4 {
  font-family: var(--display);
  font-weight: 700;
  margin: 0;
  text-wrap: balance;
}
.vg-d1 { font-size: clamp(34px, 5.2vw, 62px); line-height: .94; letter-spacing: -.035em; }
.vg-d2 { font-size: clamp(27px, 3.4vw, 38px); line-height: 1.02; letter-spacing: -.03em; }
.vg-d3 { font-size: clamp(21px, 2.2vw, 27px); line-height: 1.15; letter-spacing: -.02em; }
.vg-d4 { font-size: 17px; line-height: 1.25; letter-spacing: -.01em; }

/* The micro-label. Used above almost every figure on the site, and it is what makes a number read
   as a measurement rather than as decoration. */
.vg-label {
  font: 700 10px/1 var(--mono);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}
.vg-label.dim { color: var(--dim); }

.vg-say { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.65; max-width: 62ch; }
.vg-say b, .vg-say strong { color: var(--txt); font-weight: 600; }
.vg-small { font-size: 12.5px; line-height: 1.6; color: var(--muted); }

/* Every figure on the site. Tabular by default: these sit in columns more often than not. */
.vg-num {
  font-family: var(--mono);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.vg-n-xl { font-size: 34px; }
.vg-n-lg { font-size: 26px; }
.vg-n-md { font-size: 20px; }
.vg-n-sm { font-size: 14px; }
.vg-mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

.vg-accent { color: var(--accent); }
.vg-ok { color: var(--ok); }
.vg-bad { color: var(--danger); }
.vg-warnc { color: var(--warn); }
.vg-muted { color: var(--muted); }

/* --- a stat: label above figure, the site's most repeated unit ------------------------------- */

.vg-stat { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.vg-stat > .vg-label { order: -1; }
.vg-stat-sub { font: 400 11.5px/1.4 var(--mono); color: var(--dim); }

/* A row of stats divided by hairlines rather than boxed. Reads as one instrument panel. */
.vg-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
}
.vg-stats > * { background: var(--card); padding: 16px 18px; }

/* --- surfaces -------------------------------------------------------------------------------- */

.vg-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 18px 20px;
}
.vg-card.flush { padding: 0; overflow: hidden; }
.vg-panel {
  background: var(--bg2);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 16px 18px;
}
.vg-hr { height: 1px; background: var(--hair); border: 0; margin: 0; }

/* The sealed texture. Used for anything that exists but has not been revealed, and for the unminted
   remainder of a progress bar, so "not yet" reads the same way everywhere. */
.vg-hatch { background: repeating-linear-gradient(45deg, var(--bg) 0 5px, var(--card) 5px 10px); }
.vg-hatch-lg { background: repeating-linear-gradient(45deg, var(--bg) 0 7px, var(--card) 7px 14px); }
.vg-grid-motif {
  background-image: linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 26px 26px;
}

/* --- controls -------------------------------------------------------------------------------- */

.vg-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 11px 20px; border-radius: var(--r-pill); cursor: pointer;
  border: 1px solid var(--line2); background: transparent; color: var(--txt);
  font: 600 13.5px/1 var(--sans); text-decoration: none; white-space: nowrap;
  transition: background .13s ease, border-color .13s ease, color .13s ease;
}
.vg-btn:hover { border-color: var(--accent); color: var(--accent); }
.vg-btn.primary { background: var(--accent); border-color: var(--accent); color: #06121b; }
.vg-btn.primary:hover { background: var(--accent2); border-color: var(--accent2); color: #fff; }
.vg-btn.block { display: flex; width: 100%; padding: 13px 20px; font-size: 14.5px; }
.vg-btn[disabled], .vg-btn.is-off { opacity: .4; cursor: not-allowed; pointer-events: none; }
.vg-btn:focus-visible, .vg-pill:focus-visible, .vg-input:focus-visible,
.vg-side a:focus-visible, .vg-side button:focus-visible,
.vg-top a:focus-visible, .vg-top button:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

.vg-pill {
  padding: 7px 13px; border-radius: var(--r-pill); cursor: pointer;
  border: 1px solid var(--line); background: transparent; color: var(--muted);
  font: 500 12.5px/1 var(--sans); white-space: nowrap;
}
.vg-pill:hover { color: var(--txt); border-color: var(--line2); }
.vg-pill.is-on { background: var(--accent); border-color: var(--accent); color: #06121b; font-weight: 600; }

.vg-input {
  width: 100%; padding: 12px 14px; border-radius: var(--r-ctl);
  border: 1px solid var(--line); background: var(--bg); color: var(--txt);
  font: 400 15px/1.3 var(--mono);
}
.vg-input::placeholder { color: var(--dim); }
.vg-field { display: flex; flex-direction: column; gap: 7px; }

/* A chip carrying a fact, not a decoration: "permanent", "verified", "sealed". */
.vg-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 9px; border-radius: var(--r-chip);
  font: 700 10px/1.4 var(--mono); letter-spacing: .14em; text-transform: uppercase;
  border: 1px solid var(--line2); color: var(--muted);
}
.vg-chip.ok { border-color: rgba(56, 211, 159, .4); color: var(--ok); }
.vg-chip.bad { border-color: rgba(255, 107, 107, .45); color: var(--danger); }
.vg-chip.warn { border-color: rgba(224, 144, 111, .45); color: var(--warn); }
.vg-chip.accent { background: var(--accent); border-color: var(--accent); color: #06121b; }

/* --- progress -------------------------------------------------------------------------------- */

/* The remainder is hatched rather than empty, because on this site the unfilled part is not nothing:
   it is the art that has not been revealed, or the coins nobody has claimed. */
.vg-bar { height: 8px; border-radius: 2px; overflow: hidden; background: repeating-linear-gradient(45deg, var(--card) 0 6px, var(--bg) 6px 12px); }
/* Width 0 by default. A bar whose fill has no width renders full, so an unloaded progress bar was
   claiming a finished mint until the first fetch came back. */
.vg-bar > span { display: block; width: 0; height: 100%; background: var(--accent); border-radius: 2px 0 0 2px; }
.vg-bar.thin { height: 5px; }
.vg-bar > span.done { background: var(--ok); }
.vg-bar > span.fire { background: var(--danger); }
.vg-bar > span.water { background: var(--accent); }
.vg-bar > span.earth { background: var(--ok); }



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

.vg-foot { margin-top: 72px; border-top: 1px solid var(--line); background: var(--bg); }
.vg-foot-in {
  max-width: var(--page); margin: 0 auto; padding: 22px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px;
  font-size: 12.5px; color: var(--muted);
}
.vg-foot-in a { color: var(--muted); text-decoration: none; }
.vg-foot-in a:hover { color: var(--accent); }
.vg-foot-claim {
  margin-left: auto; font: 700 10px/1 var(--mono); letter-spacing: .16em;
  text-transform: uppercase; color: var(--dim);
}

/* --- states ---------------------------------------------------------------------------------- */

/* An empty list is the screen most visitors actually see here, so it is a component rather than an
   afterthought, and it is built to make an offer rather than an apology. */
.vg-empty {
  padding: 40px 26px; border: 1px dashed var(--line); border-radius: var(--r-card);
  display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center;
}
.vg-empty p { margin: 0; max-width: 46ch; color: var(--muted); font-size: 14px; line-height: 1.65; }

.vg-note {
  padding: 12px 15px; border-radius: var(--r-ctl);
  border-left: 3px solid var(--accent); background: var(--bg2);
  font-size: 13px; line-height: 1.6; color: var(--muted);
}
.vg-note b { color: var(--txt); }
.vg-note.bad { border-left-color: var(--danger); }
.vg-note.warn { border-left-color: var(--warn); }
.vg-note.ok { border-left-color: var(--ok); }

/* A blocking guard. Not a toast: a toast is dismissed by reflex, and the things this stops are
   permanent. It states the consequence in the units the person is thinking in and offers the fix. */
.vg-guard {
  border: 1px solid rgba(255, 107, 107, .45); border-radius: var(--r-card);
  background: linear-gradient(rgba(255, 107, 107, .07), rgba(255, 107, 107, .02));
  padding: 18px 20px; display: flex; flex-direction: column; gap: 14px;
}
.vg-guard-say { margin: 0; font: 700 21px/1.22 var(--display); letter-spacing: -.02em; color: var(--danger); }
.vg-guard-why { margin: 0; font-size: 13px; line-height: 1.65; color: var(--muted); }
.vg-guard-why b { color: var(--txt); }
.vg-ack {
  display: flex; align-items: flex-start; gap: 10px; padding: 11px 13px;
  border: 1px solid var(--line2); border-radius: var(--r-ctl); background: var(--bg);
  font-size: 13px; line-height: 1.5; color: var(--txt); cursor: pointer;
}
.vg-ack input { margin: 2px 0 0; accent-color: var(--accent); width: 15px; height: 15px; }

/* Wide content scrolls inside its own box; the page never scrolls sideways. */
.vg-scroll { overflow-x: auto; }

/* --- phone ----------------------------------------------------------------------------------- */

@media (max-width: 860px) {
  .vg-foot-claim { margin-left: 0; width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}

/* The app's twelve-item tab strip is the routing mechanism, not a menu: every hash link and every
   in-page shortcut works by clicking one of those buttons. So it stays in the document and stops
   being drawn. Deleting it would break every deep link on the site. */
.vg-hosted .tabs { display: none; }
.vg-hosted .topbar { display: none; }

/* --- the sidebar ------------------------------------------------------------------------------
   Everything the site can do, on screen at once, at every width.

   This replaces a horizontal strip whose three hover panels were the most reported thing here. A
   hover menu has to survive the pointer crossing a seam, a click landing on the button underneath
   it, and a touch screen with no hover at all, and it kept failing at least one. Nothing here is
   hidden on a desktop, so none of those failures have anywhere to happen. */

/* Collapsed to its icons, opened by pointing at it.
   The page is padded by the COLLAPSED width and never by the open one, so the rail expands OVER the
   content instead of shoving it sideways. A sidebar that reflows the whole page every time the
   pointer crosses it is worse than no sidebar. */
:root { --side-w: 64px; --side-open: 236px; --top-h: 56px; }

.vg-sided { padding-left: var(--side-w); }

.vg-side {
  position: fixed; inset: 0 auto 0 0; z-index: 70; width: var(--side-w);
  display: flex; flex-direction: column;
  background: var(--bg2); border-right: 1px solid var(--line);
  overflow: hidden; transition: width .16s ease;
}
/* focus-within as well as hover, or the rail is unusable from a keyboard. */
.vg-side:hover, .vg-side:focus-within { width: var(--side-open); box-shadow: 12px 0 40px rgba(0, 0, 0, .45); }

/* THE WORDMARK IS NOT A LINK, whatever element it is made of.
   Its base rule lived in the horizontal bar that the sidebar replaced, and went with it. What was
   left styled the box and nothing else, so the wordmark fell back to the browser's own link
   colours: blue, underlined, and purple once anybody had clicked it. Every state is named here so
   there is no state left for a default to win. */
.vg-brand {
  display: flex; align-items: center; gap: 12px;
  color: var(--txt); text-decoration: none;
  font: 700 15px/1 var(--display); letter-spacing: .04em;
}
.vg-brand:link, .vg-brand:visited, .vg-brand:hover, .vg-brand:active, .vg-brand:focus {
  color: var(--txt); text-decoration: none;
}
.vg-side .vg-brand { flex: none; height: var(--top-h); padding: 0 18px; border-bottom: 1px solid var(--line); }
.vg-logo { flex: none; width: 28px; height: 28px; display: block; border-radius: 7px; }
.vg-side-scroll { flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; padding: 14px 12px 20px; }

/* Everything that is words rather than a symbol. Held at the open width so the labels do not
   reflow while the rail animates, and clipped by the rail's own overflow. */
.vg-side-text {
  white-space: nowrap; opacity: 0; transition: opacity .12s ease;
}
.vg-side:hover .vg-side-text, .vg-side:focus-within .vg-side-text { opacity: 1; }

.vg-side-icon { flex: none; width: 20px; height: 20px; display: block; }
.vg-side-label {
  margin: 14px 0 6px 12px; height: 12px; overflow: hidden;
  font: 700 10px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--dim);
  white-space: nowrap; opacity: 0; transition: opacity .12s ease;
}
.vg-side:hover .vg-side-label, .vg-side:focus-within .vg-side-label { opacity: 1; }
.vg-side-scroll > .vg-side-label:first-child { margin-top: 0; }
.vg-side-list { list-style: none; margin: 0 0 4px; padding: 0; }
.vg-side-link {
  display: flex; align-items: center; gap: 12px;
  /* Fixed height and a fixed left inset so the icons form a straight column whether the rail is
     open or shut, and nothing shifts vertically when it opens. */
  height: 38px; padding: 0 10px; border-radius: var(--r-ctl);
  color: var(--muted); text-decoration: none; font: 500 13.5px/1 var(--sans);
}
.vg-side-link em { margin-left: auto; }
.vg-side-link:hover { background: var(--card); color: var(--txt); }
/* The page you are on, marked by a filled row rather than a colour change alone, so it survives a
   screenshot and a colour-blind reader. */
.vg-side-link.is-on { background: var(--card); color: var(--txt); box-shadow: inset 2px 0 0 var(--accent); }
.vg-side-link em {
  font: 400 10.5px/1 var(--mono); font-style: normal; color: var(--dim);
  white-space: nowrap; opacity: 0; transition: opacity .12s ease;
}
.vg-side:hover .vg-side-link em, .vg-side:focus-within .vg-side-link em { opacity: 1; }

.vg-side-foot { flex: none; padding: 12px; border-top: 1px solid var(--line); }
.vg-side-install {
  display: flex; align-items: center; gap: 12px; height: 38px; padding: 0 10px;
  border-radius: var(--r-ctl);
  border: 1px solid var(--line2); color: var(--txt); text-decoration: none;
  font: 600 12.5px/1 var(--sans);
}
.vg-side-install:hover { border-color: var(--accent); color: var(--accent); }
/* Words only, so it is nothing at all while the rail is shut and must not hold height there. */
.vg-side-mini {
  display: flex; flex-wrap: wrap; gap: 4px 10px; padding: 0 4px;
  max-height: 0; margin-top: 0; overflow: hidden; opacity: 0;
  transition: max-height .16s ease, opacity .12s ease, margin-top .16s ease;
}
.vg-side:hover .vg-side-mini, .vg-side:focus-within .vg-side-mini { max-height: 120px; margin-top: 12px; opacity: 1; }
.vg-side-mini a { font-size: 11px; color: var(--dim); text-decoration: none; }
.vg-side-mini a:hover { color: var(--muted); }

/* --- the top bar ------------------------------------------------------------------------------
   Present at every width, not only on a phone: the connect button is the primary action on this
   site and burying it in a drawer somebody has to open first would be worse than what it replaced. */

.vg-top {
  position: sticky; top: 0; z-index: 60; height: var(--top-h);
  background: rgba(11, 16, 23, .92); backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--line);
}
.vg-top-in {
  max-width: var(--page); margin: 0 auto; height: 100%; padding: 0 22px;
  display: flex; align-items: center; gap: 14px;
}
.vg-crumb { min-width: 0; font: 400 11px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vg-crumb a { color: var(--muted); text-decoration: none; }
.vg-crumb a:hover { color: var(--accent); }
.vg-top-right { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.vg-top-say { font: 400 11px/1 var(--mono); color: var(--dim); white-space: nowrap; }
.vg-nav-wallet { padding: 8px 15px; font-size: 12.5px; }

/* Three lines drawn in CSS, so the control is the same shape on every platform. */
.vg-burger {
  display: none; flex-direction: column; justify-content: center; gap: 4px;
  width: 36px; height: 36px; padding: 0 8px; border-radius: var(--r-ctl);
  border: 1px solid var(--line); background: transparent; cursor: pointer;
}
.vg-burger i { display: block; height: 2px; background: var(--txt); border-radius: 2px; }

.vg-scrim { display: none; }

/* --- the drawer ---------------------------------------------------------------------------------
   Below this width the sidebar slides off canvas and the burger brings it back. The content column
   reclaims its padding, so nothing is squeezed into a third of the screen. */

@media (max-width: 900px) {
  .vg-sided { padding-left: 0; }
  /* A drawer is never collapsed: it is either shut or it is the whole navigation. */
  .vg-side, .vg-side:hover, .vg-side:focus-within { width: min(86vw, 300px); }
  .vg-side-text, .vg-side-label, .vg-side-link em, .vg-side-mini {
    opacity: 1; max-height: none; margin-top: 0;
  }
  .vg-side-mini { margin-top: 12px; }
  .vg-side {
    transform: translateX(-100%); transition: transform .22s ease;
    box-shadow: 0 0 44px rgba(0, 0, 0, .6);
  }
  .vg-side-open .vg-side { transform: translateX(0); }
  .vg-burger { display: flex; }
  .vg-top-say { display: none; }
  .vg-scrim {
    display: block; position: fixed; inset: 0; z-index: 65;
    background: rgba(4, 8, 12, .6); opacity: 0; pointer-events: none; transition: opacity .22s ease;
  }
  .vg-side-open .vg-scrim { opacity: 1; pointer-events: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .vg-side, .vg-scrim { transition: none; }
}

@media (max-width: 560px) {
  .vg-top-in { padding: 0 14px; gap: 10px; }
  .vg-tip { display: none; }
}

/* --- the figure strip ------------------------------------------------------------------------- */

/* One row of numbers, hairline-separated, with a tiny label over each. It lives here rather than on
   a page because both markets are read the same way and a second copy is how two pages start
   disagreeing about what a floor looks like.

   No boxes around the figures on purpose: boxes make six readings of one market look like six
   separate decisions. */
.vg-strip {
  display: flex; flex-wrap: wrap; gap: 0; margin: 0; flex: 0 1 auto;
  border: 1px solid var(--line); border-radius: var(--r-card); overflow: hidden;
}
.vg-strip-stat { padding: 9px 16px; min-width: 84px; border-left: 1px solid var(--line); }
.vg-strip-stat:first-child { border-left: 0; }
.vg-strip-stat dt {
  font: 500 10px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); margin: 0 0 5px;
}
.vg-strip-stat dd { margin: 0; font: 600 15px/1.1 var(--mono); color: var(--txt); white-space: nowrap; }
.vg-strip-stat dd .sub { font-size: 11px; font-weight: 400; color: var(--muted); margin-left: 5px; }
.vg-strip-stat.is-floor dd { color: var(--accent); }
.vg-strip-stat.is-free dd { color: var(--ok); }

/* A change is the most trusted number on a marketplace, so it is the one place a colour carries
   meaning rather than decoration. Nothing is drawn at all when the server declines to state one:
   see the null contract in src/pricelog.js. */
.vg-chg { margin-left: 7px; font: 600 11.5px/1 var(--mono); color: var(--muted); }
.vg-chg.up { color: var(--ok); }
.vg-chg.down { color: var(--danger); }

@media (max-width: 860px) {
  .vg-strip { flex: 1 1 100%; }
  .vg-strip-stat { flex: 1 1 auto; }
}
