/* Green Development Index — shared design system.
   Tokens are lifted from gri-cop17-deck.html so the site and the deck read as
   one artifact. Dark only, by design.

   Inter is served from /assets/fonts rather than a CDN: the CSP is
   `font-src 'self' data:`, and a self-hosted font is one request against a
   cache that never expires instead of a DNS lookup, a TLS handshake and a
   third party who can see every reader. */

/* One file covers the whole 100–900 range because the deck embeds a variable
   Inter. Declaring the range rather than a single weight is what lets the
   browser instance 600 and 800 from it instead of faux-bolding 400. */
@font-face {
  font-family: 'Inter';
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  src: url('/assets/fonts/inter.woff2') format('woff2');
}

:root {
  color-scheme: dark;

  --bg:        #070c09;
  --bg-raise:  #101813;
  --panel:     #16211a;
  --ink:       #f4f3ec;
  --ink-2:     #c2c9bd;
  --ink-mute:  #8b9585;
  --hairline:  #223028;
  --hairline-2:#314337;
  --green:     #008300;
  --rust:      #d4663f;
  --sage:      #9dc08b;
  --lime:      #c9ef7d;
  --sand:      #e0b357;

  /* #008300 on #070c09 fails AA, so green *text* uses --sage; --green is for
     solid fills only. */
  --rise: var(--sage);
  --fall: var(--rust);

  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  --pad: clamp(16px, 4vw, 32px);
  --measure: 68ch;
  --wide: 1120px;
  --radius: 10px;
  --radius-lg: 18px;

  /* The sticky search bar parks directly under the sticky header, so both read
     the same number. Change it once. */
  --header-h: 64px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body.page {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  font-synthesis-weight: none;
  /* Rank, score and change columns are numbers stacked in a list; without
     tabular figures the digits shimmy as you scroll. */
  font-variant-numeric: tabular-nums;
}

/* ── ambient backdrop ────────────────────────────────────
   Two soft greens off the top corners and a film of grain, fixed behind
   everything. The gradients keep a long dark page from reading as a black
   rectangle; the grain is what stops those gradients from banding into visible
   steps on an 8-bit display, which is the same reason film stocks were never
   clean. Both are painted on ::before so no element needs a stacking context
   for them. */
body.page::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(90ch 60ch at 12% -10%, rgba(0, 131, 0, 0.16), transparent 65%),
    radial-gradient(70ch 50ch at 92% 4%,  rgba(157, 192, 139, 0.09), transparent 60%),
    linear-gradient(180deg, #0b120e 0%, var(--bg) 46%);
}

body.page::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

a { color: var(--sage); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--lime); }

:focus-visible { outline: 2px solid var(--lime); outline-offset: 3px; border-radius: 4px; }

::selection { background: rgba(201, 239, 125, 0.25); color: var(--ink); }

.container { max-width: var(--wide); margin: 0 auto; padding-inline: var(--pad); }

/* ── header ─────────────────────────────────────────────
   Transparent over the hero photograph, glass once you scroll past it. The
   swap is driven by body.is-scrolled from ui.js rather than by a scroll
   listener here, so the paint happens on one class change. */

.site-header {
  position: sticky; top: 0; z-index: 20;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background 220ms var(--ease), border-color 220ms var(--ease),
              backdrop-filter 220ms var(--ease);
}
.is-scrolled .site-header {
  background: rgba(7, 12, 9, 0.78);
  backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: var(--hairline);
}
.site-header > .container {
  display: flex; align-items: center; gap: 16px;
  min-height: var(--header-h); flex-wrap: wrap;
}
.brand { display: flex; align-items: center; gap: 11px; min-height: 44px; text-decoration: none; color: var(--ink); }
.brand img {
  width: 32px; height: 32px; object-fit: contain;
  border-radius: 8px; padding: 2px;
  background: rgba(244, 243, 236, 0.06);
  box-shadow: inset 0 0 0 1px var(--hairline);
}
.brand b { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.brand span { display: block; font-size: 11px; color: var(--ink-mute); letter-spacing: 0.06em; text-transform: uppercase; }

.site-nav { margin-left: auto; display: flex; gap: 2px; }
.site-nav a {
  position: relative;
  display: flex; align-items: center;
  min-height: 44px; padding: 0 14px;
  border-radius: var(--radius);
  color: var(--ink-2); text-decoration: none; font-size: 15px;
  transition: color 160ms var(--ease), background 160ms var(--ease);
}
.site-nav a:hover { background: rgba(244, 243, 236, 0.05); color: var(--ink); }
.site-nav a[aria-current="page"] { color: var(--ink); }
/* The current page is marked with a rule under the label, not a filled pill:
   over a photograph a filled pill needs an opaque background to stay legible,
   and an opaque rectangle in the middle of the hero is a hole. */
.site-nav a[aria-current="page"]::after {
  content: ""; position: absolute; left: 14px; right: 14px; bottom: 9px;
  height: 2px; border-radius: 2px; background: var(--lime);
}

/* ── hero ───────────────────────────────────────────────
   A graded photograph, a scrim, and the words. The scrim is a two-stop
   gradient rather than a flat overlay: flat enough at the top to keep the
   header legible, opaque at the bottom so the image dissolves into the page
   instead of ending on a visible seam. */

.hero { position: relative; isolation: isolate; margin-top: calc(var(--header-h) * -1); }
.hero-media { position: absolute; inset: 0; z-index: -1; overflow: hidden; }
.hero-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hero-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(7, 12, 9, 0.72) 0%, rgba(7, 12, 9, 0.34) 34%,
                            rgba(7, 12, 9, 0.80) 78%, var(--bg) 100%),
    linear-gradient(90deg, rgba(7, 12, 9, 0.66) 0%, transparent 62%);
}

/* Widths are in px, not ch. `ch` resolves against the element's own font-size
   — 17px here — so a 40ch hero would be a 340px column with a 74px headline
   trying to live inside it. */
.hero-body {
  padding: calc(var(--header-h) + clamp(56px, 13vh, 128px)) 0 clamp(40px, 9vh, 84px);
  max-width: 640px;
}
.hero-body .lede { margin-bottom: 0; }

/* The band hero carries a heading and nothing else, so the heading is set
   smaller than the ranking page's statement headline and given a wider column
   to run in. */
.hero-band .hero-body {
  padding-block: calc(var(--header-h) + clamp(38px, 8vh, 72px)) clamp(30px, 6vh, 56px);
  max-width: 780px;
}
.hero-band h1 { font-size: clamp(30px, 4.8vw, 48px); margin-bottom: 0; }

.hero-cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }

/* The hero is the one block above the fold, so it animates on load rather
   than waiting for an observer. `both` holds the from-state before the delay
   elapses, which is what stops the three lines flashing in unstyled first. */
@keyframes rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}
.hero-body > * { animation: rise 700ms var(--ease) both; }
.hero-body > *:nth-child(2) { animation-delay: 80ms; }
.hero-body > *:nth-child(3) { animation-delay: 160ms; }
.hero-body > *:nth-child(4) { animation-delay: 240ms; }

/* ── skip link ──────────────────────────────────────────
   Off-screen until focused. Without it the first thing a keyboard reader
   reaches on the ranking page is the top of a 193-row table. */
.skip {
  position: absolute; left: 12px; top: 12px; z-index: 40;
  transform: translateY(-200%);
  padding: 13px 18px; border-radius: var(--radius);
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--hairline-2); text-decoration: none;
  transition: transform 160ms var(--ease);
}
.skip:focus { transform: none; }

/* ── type ───────────────────────────────────────────────── */

.eyebrow {
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-mute); margin: 0 0 14px;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.eyebrow::before {
  content: ""; width: 22px; height: 1px; background: var(--sage); opacity: 0.7; flex: none;
}
h1 {
  font-size: clamp(38px, 7.4vw, 74px); line-height: 1.02;
  letter-spacing: -0.035em; margin: 0 0 20px; font-weight: 600;
  text-wrap: balance;
}
h2 {
  font-size: clamp(22px, 3vw, 29px); line-height: 1.25; letter-spacing: -0.02em;
  margin: 52px 0 16px; font-weight: 600; text-wrap: balance;
}
.lede {
  font-size: clamp(18px, 2.4vw, 22px); line-height: 1.45; color: var(--ink-2);
  margin: 0 0 26px; letter-spacing: -0.01em; text-wrap: pretty;
}

.prose { max-width: var(--measure); margin: 0 auto; padding: clamp(28px, 6vh, 56px) var(--pad) 90px; }
.prose p { margin: 0 0 22px; color: var(--ink-2); text-wrap: pretty; }
.prose strong { color: var(--ink); font-weight: 600; }
/* Section headings in a long read get a hairline above them, so the eye can
   find the joints while scrolling without the headings having to grow. */
.prose h2 { padding-top: 26px; border-top: 1px solid var(--hairline); }

/* The opening paragraph of a written page is set larger and led by a raised
   cap — the one piece of ornament on the site, and the cheapest way to say
   "this is an essay, not a table". */
.prose .opener { font-size: clamp(18px, 2.2vw, 21px); color: var(--ink-2); }
.prose .opener::first-letter {
  float: left; font-size: 3.5em; line-height: 0.82; font-weight: 600;
  margin: 0.06em 0.09em 0 0; color: var(--lime);
}

.pull {
  margin: 40px 0; padding: 28px 30px;
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--green);
  background: linear-gradient(140deg, var(--panel), var(--bg-raise));
  border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
  font-size: clamp(19px, 2.6vw, 25px); line-height: 1.35;
  color: var(--ink); letter-spacing: -0.02em; text-wrap: balance;
}
.pull b { color: var(--lime); font-weight: 600; }

/* ── footer ─────────────────────────────────────────────── */

.site-footer {
  border-top: 1px solid var(--hairline);
  padding: 40px 0 56px; margin-top: 70px;
  color: var(--ink-mute); font-size: 14px;
}
.site-footer .container {
  display: flex; flex-wrap: wrap; gap: 10px 26px; align-items: baseline;
}
.site-footer .org { color: var(--ink-2); }
.site-footer nav { margin-left: auto; display: flex; gap: 18px; flex-wrap: wrap; }
.site-footer a { color: var(--ink-mute); text-decoration: none; }
.site-footer a:hover { color: var(--lime); text-decoration: underline; }
.credit { flex-basis: 100%; margin: 0; font-size: 12.5px; color: #5f6b5b; }

/* ── reveal on scroll ───────────────────────────────────
   The hidden state is scoped to .js so that a reader without JavaScript — or
   one whose network dropped ui.js — gets the content rather than a page of
   invisible elements. */
.js [data-reveal] {
  opacity: 0; transform: translateY(14px);
  transition: opacity 620ms var(--ease), transform 620ms var(--ease);
  /* Siblings that come into view together stagger by their index rather than
     arriving as one block. */
  transition-delay: calc(var(--i, 0) * 70ms);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  .js [data-reveal] { opacity: 1; transform: none; }
}

/* ── ranking page ───────────────────────────────────────── */

.controls {
  position: sticky; top: var(--header-h); z-index: 10;
  background: rgba(7, 12, 9, 0.9);
  backdrop-filter: blur(14px);
  padding: 12px 0 12px;
  border-bottom: 1px solid var(--hairline);
  /* The skip link targets this block, and it is sticky under a sticky header
     — without the margin the jump parks it behind the header. */
  scroll-margin-top: calc(var(--header-h) + 8px);
}
.search-wrap { position: relative; }
.search-wrap svg { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); width: 18px; height: 18px; fill: none; stroke: var(--ink-mute); stroke-width: 2; pointer-events: none; transition: stroke 160ms var(--ease); }
.search-wrap:focus-within svg { stroke: var(--sage); }
#q {
  width: 100%; min-height: 54px;
  padding: 0 48px 0 46px;
  background: var(--bg-raise); color: var(--ink);
  border: 1px solid var(--hairline); border-radius: 999px;
  font-family: var(--sans); font-size: 17px; /* 16px+ stops iOS zooming on focus */
  appearance: none;
  transition: border-color 160ms var(--ease), box-shadow 160ms var(--ease), background 160ms var(--ease);
}
#q::placeholder { color: var(--ink-mute); }
#q::-webkit-search-cancel-button { appearance: none; }
#q:focus {
  border-color: var(--sage); outline: none; background: var(--panel);
  box-shadow: 0 0 0 4px rgba(157, 192, 139, 0.12);
}
#clear {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 42px; height: 42px; display: none; place-items: center;
  background: none; border: 0; color: var(--ink-mute); font-size: 20px; cursor: pointer; border-radius: 50%;
}
#clear:hover { color: var(--ink); background: rgba(244, 243, 236, 0.07); }
#q:not(:placeholder-shown) ~ #clear { display: grid; }

#count {
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--ink-mute); padding: 18px 0 6px; margin: 0;
}

/* One grid definition drives BOTH the header row and every body row, so the
   column headings stay aligned with the numbers underneath them. A body row is
   a single <td> holding a grid button, so the two can only line up by sharing
   this template. */
table.rank {
  width: 100%; border-collapse: collapse;
  /* `fixed` is load-bearing: an auto-layout table sizes to its content's
     min-content width, and the nowrap country names push that past the
     viewport on a phone. */
  table-layout: fixed;
  /* minmax(0, 1fr) for the same reason — a bare 1fr floors at min-content, so
     the name column would refuse to shrink and ellipsize. */
  --cols: 78px 30px minmax(0, 1fr) 62px 62px 78px;
  --colgap: 12px;
}
table.rank caption { text-align: left; clip-path: inset(50%); position: absolute; }

thead tr, tr.row td button.rowbtn {
  display: grid; grid-template-columns: var(--cols);
  gap: var(--colgap); align-items: center;
}

/* ── sortable header ─────────────────────────────────────
   Clicking a heading sorts by that column; clicking the active one reverses it.
   "Change" is how you get biggest-fallers and biggest-risers. */

/* The rule goes on the row, not each cell — per-cell borders break into
   segments wherever the grid gap falls. */
thead tr { border-bottom: 1px solid var(--hairline); }
thead th { padding: 0; }
thead th button {
  display: flex; align-items: center; gap: 5px;
  width: 100%; min-height: 44px; padding: 0;
  background: none; border: 0; color: var(--ink-mute);
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.09em;
  text-transform: uppercase; cursor: pointer; white-space: nowrap;
  transition: color 140ms var(--ease);
}
thead th button:hover { color: var(--ink); }
thead th[aria-sort] button { color: var(--ink); }
.h-rank button, .h-hdi button, .h-gdi button, .h-chg button { justify-content: flex-end; }

/* The triangle shows which column is active and which way it points; inactive
   columns keep a dimmed one so the affordance is visible before the first click. */
.tri::after { content: "\25B2"; font-size: 8px; opacity: 0.2; }
th[aria-sort="ascending"] .tri::after { content: "\25B2"; opacity: 1; color: var(--sage); }
th[aria-sort="descending"] .tri::after { content: "\25BC"; opacity: 1; color: var(--sage); }

tr.row > td { border-top: 1px solid var(--hairline); padding: 0; }
tr.row td button.rowbtn {
  position: relative;
  width: 100%; min-height: 56px; padding: 8px 0;
  background: none; border: 0; color: inherit;
  font-family: var(--sans); font-size: 16px; text-align: left; cursor: pointer;
  border-radius: var(--radius);
  transition: background 140ms var(--ease), box-shadow 140ms var(--ease);
}
/* The highlight has to reach past the text on both sides or it looks like a
   selected word rather than a selected row. A spread box-shadow in the same
   colour paints that margin without adding a negative margin, which on a
   table cell would push the row wider than its container and produce a
   horizontal scrollbar. */
tr.row td button.rowbtn:hover {
  background: rgba(244, 243, 236, 0.045);
  box-shadow: 0 0 0 10px rgba(244, 243, 236, 0.045);
}
tr.row td button.rowbtn[aria-expanded="true"] {
  background: rgba(157, 192, 139, 0.07);
  box-shadow: 0 0 0 10px rgba(157, 192, 139, 0.07);
}

/* A hairline rail along the foot of each row, filled to that country's GDI
   score. It is deliberately near-invisible: at 193 rows anything stronger
   reads as stripes, but at this weight the eye still picks up the slope of the
   column as it scrolls. */
tr.row td button.rowbtn::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 4px; height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgba(157, 192, 139, 0.28) var(--v, 0%), transparent 0);
}

.r-rank { font-family: var(--mono); font-size: 15px; color: var(--ink-mute); text-align: right; }
.r-flag { font-size: 21px; line-height: 1; text-align: center; }
.r-flag.iso { font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em; color: var(--ink-mute); border: 1px solid var(--hairline); border-radius: 4px; padding: 3px 0; }
.r-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; letter-spacing: -0.008em; }
.r-name mark { background: rgba(201, 239, 125, 0.22); color: var(--lime); border-radius: 3px; }

/* `display: contents` lets the two scores sit in their own grid columns on a
   wide screen while staying one element for the phone layout below — no
   duplicated markup. */
.r-scores { display: contents; }
.r-hdi, .r-gdi { font-family: var(--mono); font-size: 14px; text-align: right; }
.r-hdi { color: var(--ink-mute); }
.r-gdi { color: var(--ink-2); }
.r-hdi .k, .r-gdi .k { display: none; }

/* The change column is the finding, so it is the one cell that carries a
   filled shape rather than bare text. */
.r-chg {
  font-family: var(--mono); font-size: 13px; text-align: right;
  justify-self: end; padding: 4px 9px; border-radius: 999px; white-space: nowrap;
}
.r-chg.up { color: var(--rise); background: rgba(157, 192, 139, 0.12); }
.r-chg.down { color: var(--fall); background: rgba(212, 102, 63, 0.14); }
.r-chg.flat, .r-chg.none { color: var(--ink-mute); background: rgba(244, 243, 236, 0.06); }

/* ── the phone layout ────────────────────────────────────
   At 390px there is no room for rank, flag, name, two scores and a chip on one
   line without crushing the country name, so the scores drop to a second line
   and carry their own labels. The header becomes a scrollable strip of sort
   buttons, since it can no longer align to columns. */
@media (max-width: 620px) {
  table.rank { --cols: 32px 24px minmax(0, 1fr) auto; --colgap: 8px; }

  thead tr {
    display: flex; gap: 6px; overflow-x: auto;
    padding-bottom: 10px; scrollbar-width: none;
  }
  thead tr::-webkit-scrollbar { display: none; }
  thead tr { border-bottom: 0; }
  thead th.h-flag { display: none; }
  thead th { flex: 0 0 auto; }
  thead th button {
    min-height: 40px; padding: 0 14px;
    border: 1px solid var(--hairline); border-radius: 999px;
  }
  thead th[aria-sort] button { border-color: var(--sage); background: var(--bg-raise); }

  tr.row td button.rowbtn {
    grid-template-areas:
      "rank flag name chg"
      ".    .    nums nums";
    row-gap: 4px; font-size: 15px;
  }
  .r-rank { grid-area: rank; }
  .r-flag { grid-area: flag; }
  .r-name { grid-area: name; }
  .r-chg  { grid-area: chg; align-self: start; }
  .r-scores { display: flex; gap: 14px; grid-area: nums; }
  .r-hdi, .r-gdi { font-size: 13px; text-align: left; }
  .r-hdi .k, .r-gdi .k {
    display: inline; margin-right: 4px;
    font-size: 10px; letter-spacing: 0.08em; color: var(--ink-mute);
  }
}

/* ── expanded detail ────────────────────────────────────── */

tr.detail > td { padding: 0 0 20px; border: 0; }
tr.detail[hidden] { display: none; }
.detail-inner {
  background: linear-gradient(150deg, var(--panel), var(--bg-raise));
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); padding: 20px 22px;
}
.bars { display: grid; gap: 12px; }
.bar { display: grid; grid-template-columns: 34px 52px 1fr 54px; gap: 10px; align-items: center; font-family: var(--mono); font-size: 13px; }
.bar .lbl { color: var(--ink-mute); }
.bar .pos { color: var(--ink-2); }
.bar .val { color: var(--ink); text-align: right; }
.bar .track { height: 9px; background: rgba(255, 255, 255, 0.06); border-radius: 5px; overflow: hidden; }
/* `display:block` is load-bearing, not decoration. .track is a grid item so it
   gets blockified automatically, but .fill is a plain <span> inside it — left
   inline, both `height` and the inline `width:NN%` are silently ignored and the
   bar renders as an empty track. */
.bar .fill { display: block; height: 100%; border-radius: 5px; transition: width 260ms ease; }
.bar .fill.hdi { background: var(--sand); }
.bar .fill.gdi { background: linear-gradient(90deg, var(--green), var(--sage)); }
.verdict { margin: 16px 0 0; font-size: 15px; color: var(--ink-2); }
.verdict b.up { color: var(--rise); font-weight: 600; }
.verdict b.down { color: var(--fall); font-weight: 600; }
.tier { font-size: 13px; color: var(--ink-mute); margin: 8px 0 0; }

#empty { padding: 40px 6px 60px; color: var(--ink-mute); }
#empty[hidden] { display: none; }
#empty b { color: var(--ink); font-weight: 600; }

.note { margin: 36px 0 0; padding-top: 20px; border-top: 1px solid var(--hairline); font-size: 14px; color: var(--ink-mute); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ── buttons ────────────────────────────────────────────── */

.launch { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin: 30px 0 8px; }
.btn {
  display: inline-flex; align-items: center; gap: 10px;
  min-height: 52px; padding: 0 26px;
  background: var(--green); color: #fff;
  border: 0; border-radius: 999px;
  font-family: var(--sans); font-size: 17px; font-weight: 600;
  text-decoration: none; cursor: pointer;
  box-shadow: 0 10px 30px -12px rgba(0, 131, 0, 0.9);
  transition: background 160ms var(--ease), transform 160ms var(--ease), box-shadow 160ms var(--ease);
}
.btn:hover { background: #009b00; color: #fff; transform: translateY(-1px); box-shadow: 0 14px 34px -12px rgba(0, 131, 0, 1); }
.btn:active { transform: none; }
.btn svg { width: 15px; height: 15px; fill: currentColor; }
.btn.ghost {
  background: rgba(244, 243, 236, 0.04); color: var(--ink-2);
  border: 1px solid var(--hairline-2); font-weight: 500; box-shadow: none;
}
.btn.ghost:hover { background: rgba(244, 243, 236, 0.09); color: var(--ink); box-shadow: none; }

.meta {
  display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center;
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-mute); margin: 0 0 22px;
}
.meta span { padding: 5px 11px; border: 1px solid var(--hairline); border-radius: 999px; }

/* ── cards ──────────────────────────────────────────────
   Used for the deck launcher and the end-of-page next step. */

.card {
  display: block; text-decoration: none; color: inherit;
  border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  background: linear-gradient(150deg, var(--panel), var(--bg-raise));
  padding: 24px 26px; margin: 34px 0 0;
  transition: border-color 160ms var(--ease), transform 160ms var(--ease), background 160ms var(--ease);
}
a.card:hover { border-color: var(--hairline-2); transform: translateY(-2px); }
.card .eyebrow { margin-bottom: 8px; }
.card h3 { margin: 0 0 6px; font-size: 20px; font-weight: 600; letter-spacing: -0.02em; color: var(--ink); }
.card p { margin: 0; color: var(--ink-mute); font-size: 15px; }
.card .go { color: var(--sage); }
a.card:hover .go { color: var(--lime); }

/* ── deck launcher ──────────────────────────────────────
   The framed strip on the presentation page: a 16:9 well with the graded
   assembly photograph behind it, so the launch button sits on something that
   looks like a slide rather than on empty page. */

.deck-frame {
  position: relative; margin: 30px 0 0;
  border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  overflow: hidden; background: var(--bg-raise);
  aspect-ratio: 16 / 7;
  display: grid; place-items: center;
}
.deck-frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.55; }
.deck-frame .launch { position: relative; margin: 0; justify-content: center; }

@media (max-width: 560px) {
  .deck-frame { aspect-ratio: 3 / 2; }
}
