/* Last Call styles: a black-and-white dashboard with soft grey light (docs/UI_DESIGN.md section 8).
   Phone first: the normal rules are for a phone. The one media query near the bottom
   adjusts things for laptops, from 1024px wide. */


/* ---------- Colors, type and spacing, in one place ---------- */

/* Every value on the site comes from these variables, so changing one line here
   changes it everywhere. Contrast ratios are in UI_DESIGN.md section 8. */
:root {
  --page: #F3F3F1;         /* page background */
  --card: #FFFFFF;         /* cards */
  --ink: #0B0B0B;          /* main text, primary buttons, featured cards, counted */
  --ink-2: #5C5C5C;        /* secondary text */
  --ink-3: #8A8A8A;        /* 3.45:1 on white: too faint for small text, so it is for large text only */
  --line: #E5E5E2;         /* borders and dividers */
  --held: #9A9A9A;         /* held but not counted, in the gauge */
  --on-ink: #FFFFFF;       /* text on black */
  --on-ink-2: #BDBDBD;     /* quieter text on black */
  --danger: #B42318;       /* only Declined, Charge failed, Needs attention, always with a word */
  --danger-bg: #FDECEA;

  /* The greys that gradients are made of. No other hue appears on the page. */
  --grey-50: #FAFAF9;
  --grey-100: #F3F3F1;
  --grey-200: #E8E8E5;
  --grey-300: #D9D9D5;
  --grey-800: #2A2A2A;
  --grey-900: #161616;

  /* Fine noise laid over every gradient, so it never shows bands. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.035'/%3E%3C/svg%3E");

  --font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --text-display: 2.5rem;  /* 40px: the big numbers */
  --text-title: 1.75rem;   /* 28px: the page title */
  --text-card: 1rem;       /* 16px: card titles */
  --text-body: 0.9375rem;  /* 15px */
  --text-caption: 0.8125rem;  /* 13px: captions and chips */

  --gutter: 16px;
  --card-padding: 20px;
  --card-gap: 12px;
  --banner-height: 32px;
  --button-height: 48px;

  --radius-card: 24px;
  --radius-field: 14px;
  /* The nested radius rule: an inner box's corner follows the card's corner around it,
     with a floor so the box never turns square on a laptop. */
  --radius-inner: max(calc(var(--radius-card) - var(--card-padding)), 8px);

  /* Soft black shadows in layers, never a colored glow. */
  --shadow-rest: 0 1px 2px rgba(0, 0, 0, 0.04), 0 2px 8px rgba(0, 0, 0, 0.04);
  --shadow-raised: 0 1px 2px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.08), 0 24px 48px rgba(0, 0, 0, 0.06);
  --shadow-floating: 0 2px 4px rgba(0, 0, 0, 0.05), 0 12px 32px rgba(0, 0, 0, 0.10);
  --shadow-hover: 0 2px 4px rgba(0, 0, 0, 0.06), 0 16px 40px rgba(0, 0, 0, 0.12);
  --button-lit: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 8px 20px rgba(0, 0, 0, 0.18);
  --button-pressed: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 2px 6px rgba(0, 0, 0, 0.18);

  --landing-header-height: 64px;

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
}


/* ---------- Page basics ---------- */

/* Make width include padding and border, so a 100%-wide input
   with padding doesn't stick out past the edge of the screen. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* No gray flash when a phone user taps something. Each control has its own
   pressed look instead. text-size-adjust stops phones inflating text sideways. */
html {
  -webkit-tap-highlight-color: transparent;
  -webkit-text-size-adjust: 100%;
}

/* Menu links and the stat cards' arrows glide to their card. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

/* tabular-nums gives every digit the same width, so money and times never jump. */
body {
  margin: 0;
  background-color: var(--grey-100);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--text-body);
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* The page's light: two soft blooms, the dot grid and the grain, on one layer fixed to the screen.
   A fixed layer works on iPhones, which ignore background-attachment: fixed, and it only has to
   be drawn once, while a fixed background is repainted on every scroll. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image: var(--grain),
    radial-gradient(rgba(0, 0, 0, 0.04) 1px, transparent 1px),
    radial-gradient(70% 70% at 8% 0%, rgba(255, 255, 255, 0.9), transparent),
    radial-gradient(70% 70% at 95% 100%, rgba(217, 217, 213, 0.5), transparent);
  background-size: 200px 200px, 20px 20px, auto, auto;
  pointer-events: none;
}

/* Some rules below set display, which would beat the browser's own [hidden] rule. */
[hidden] {
  display: none !important;
}

h1 {
  margin: 0;
  font-size: var(--text-title);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  overflow-wrap: anywhere;
}

p {
  margin: 0 0 12px;
}

a {
  color: inherit;
  text-underline-offset: 0.15em;
}

.text-soft {
  color: var(--ink-2);
}

.caption {
  color: var(--ink-2);
  font-size: var(--text-caption);
  font-weight: 500;
}

/* The big numbers on the board. */
.display-number {
  font-size: var(--text-display);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1;
}

/* Text for screen readers only: it takes up no space on the page. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}


/* ---------- Icons ---------- */

/* The sheet of drawings stays hidden. Each icon on a page borrows one with <use>.
   The drawings are 24 units wide and shown at 20px, so 1.8 units is a 1.5px line. */
.icon-sprite {
  display: none;
}

.icon {
  flex: none;
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}


/* ---------- Sandbox banner (top of every page) ---------- */

/* It stays on screen while the page scrolls, so the video always shows it. */
.sandbox-banner {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--banner-height);
  margin: 0;
  padding: env(safe-area-inset-top, 0px) var(--gutter) 0;
  background: var(--ink);
  color: var(--on-ink);
  font-size: var(--text-caption);
  text-align: center;
}


/* ---------- The app shell: top bar and tabs on a phone, sidebar on a laptop ---------- */

.shell {
  display: flex;
  flex-direction: column;
  min-height: calc(100dvh - var(--banner-height));
}

.shell-side {
  display: flex;
  flex-direction: column;
}

.wordmark {
  display: flex;
  align-items: center;
  min-height: 56px;
  margin: 0;
  padding-left: var(--gutter);
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* A row of pills that scrolls sideways if it doesn't fit. */
.shell-nav {
  display: flex;
  gap: 8px;
  padding: 0 var(--gutter) 8px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

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

.nav-item {
  position: relative;
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  transition: transform 120ms var(--ease-out), color 200ms ease;
}

.nav-item:active {
  transform: scale(0.97);
}

/* The pills stay short on a phone, so all three fit without scrolling. */
.nav-item .icon {
  display: none;
}

/* The sidebar's black card is for laptops. Phones have the banner. */
.sandbox-card {
  display: none;
}

.sandbox-card-title {
  margin: 0 0 4px;
  font-size: var(--text-card);
  font-weight: 500;
}

.sandbox-card-text {
  margin: 0;
  color: var(--on-ink-2);
  font-size: var(--text-caption);
}

.shell-content {
  flex: 1;
  min-width: 0;
}

/* The bottom padding keeps the last card clear of the sticky action bar on a phone. */
.page {
  max-width: 1280px;
  margin: 0 auto;
  padding: 16px var(--gutter) 96px;
}


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

.page-header {
  margin-bottom: 20px;
}

.page-goal {
  margin: 4px 0 0;
  color: var(--ink-2);
}

/* On a phone the main actions sit in a bar at the bottom, in reach of a thumb.
   The bar pads itself away from the home indicator. */
.page-actions {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 15;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px var(--gutter) calc(12px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid rgba(0, 0, 0, 0.06);
  background: rgba(255, 255, 255, 0.72);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
}

.page-actions > *,
.page-actions form button {
  flex: 1;
  width: 100%;
}

.page-actions form {
  display: flex;
}

/* With nothing left to do (pledging closed), the bar goes away. */
.page-actions:not(:has(> :not([hidden]))) {
  display: none;
}


/* ---------- Cards ---------- */

/* White on the grey page, lifted a little, with a thin highlight along the top edge. */
.card {
  padding: var(--card-padding);
  border-radius: var(--radius-card);
  background: linear-gradient(180deg, var(--card), var(--grey-50));
  box-shadow: var(--shadow-rest), inset 0 1px 0 rgba(255, 255, 255, 0.8);
  border: 1px solid var(--line);
}

.card-title {
  margin: 0 0 12px;
  font-size: var(--text-card);
  font-weight: 500;
}

/* A black card with soft light falling from the top left. .card-bloom is an empty element inside
   the card that holds the light and the grain, and keeps them inside the rounded corners. */
.light-card {
  position: relative;
  isolation: isolate;
  border-color: rgba(255, 255, 255, 0.08);
  background: radial-gradient(circle farthest-corner at 0% 0%, var(--grey-800), #0B0B0B 70%);
  box-shadow: var(--shadow-raised), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  color: var(--on-ink);
}

.card-bloom {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}

.card-bloom::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--grain), radial-gradient(circle farthest-corner at 0% 0%, rgba(255, 255, 255, 0.07), transparent 60%);
}

/* ---------- Avatars ---------- */

.avatar {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--line);
  color: var(--ink);
  font-size: var(--text-caption);
  font-weight: 600;
}

.avatar-counted {
  background: var(--ink);
  color: var(--on-ink);
}


/* ---------- Buttons ---------- */

/* The primary button: a black pill with light along its top edge. .button-link is a real link
   that goes to another page and looks the same. It presses in as soon as a finger lands. */
button,
.button-link {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--button-height);
  padding: 0 20px;
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: 999px;
  background: linear-gradient(180deg, #3A3A3A, #0B0B0B);
  box-shadow: var(--button-lit);
  color: var(--on-ink);
  font: inherit;
  font-size: var(--text-body);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  transition: transform 120ms var(--ease-out);
}

/* A layer waits on each button and fades in on hover, because fading a layer is cheap and
   animating a gradient is not. On a primary button it brightens the pill one step. */
button::before,
.button-link::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  border-radius: inherit;
  background: rgba(255, 255, 255, 0.10);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease;
}

/* A button that can't be pressed yet looks clearly switched off. */
button:disabled {
  border-color: var(--line);
  background: var(--line);
  box-shadow: none;
  color: var(--ink-2);
  cursor: not-allowed;
}

button:disabled::before {
  display: none;
}

/* Frosted glass: the quiet buttons, the current menu item and the small round buttons. */
.button-secondary,
.icon-button,
.nav-item[aria-current="page"] {
  border: 1px solid rgba(0, 0, 0, 0.08);
  background: rgba(255, 255, 255, 0.65);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--ink);
}

.button-secondary {
  font-weight: 500;
}

/* Hover takes the glass from 65% to 85% white. */
.button-secondary::before {
  background: rgba(255, 255, 255, 0.57);
}

.button-full {
  width: 100%;
}

/* Leaving is the only red button, and its word says what it does. */
.button-danger {
  color: var(--danger);
}

/* A 40px circle. Its ::after is an invisible ring that makes the tap target 46px. */
.icon-button {
  position: relative;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  min-height: 0;
  padding: 0;
  border-radius: 50%;
  touch-action: manipulation;
  transition: transform 120ms var(--ease-out);
}

.icon-button::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
}

.icon-button:active {
  transform: scale(0.97);
}

/* On a black card the small round button is dark glass. */
.light-card .icon-button {
  border-color: rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
  color: var(--on-ink);
}

/* Only a real mouse gets hover looks, because a tap would leave them stuck on a phone. */
@media (hover: hover) and (pointer: fine) {
  button:hover:not(:disabled)::before,
  .button-link:hover::before {
    opacity: 1;
  }

  .icon-button:hover {
    background: rgba(255, 255, 255, 0.85);
  }

  .light-card .icon-button:hover {
    background: rgba(255, 255, 255, 0.2);
  }

  .nav-item:hover {
    color: var(--ink);
  }
}

/* Where glass isn't possible, a stronger white does the same job. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .button-secondary,
  .icon-button,
  .nav-item[aria-current="page"] {
    background: rgba(255, 255, 255, 0.92);
  }
}


/* ---------- Forms ---------- */

label {
  display: block;
  margin-bottom: 8px;
  font-size: 0.875rem;
  font-weight: 500;
}

.field {
  margin: 0 0 20px;
}

/* Fields are 48px tall. Their text is always 16px, because iPhones zoom the
   whole page in when you tap anything smaller. */
input,
select,
textarea {
  display: block;
  width: 100%;
  min-height: 48px;
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-field);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 1rem;
  transition: border-color 200ms ease, box-shadow 200ms ease;
}

textarea {
  padding: 12px 16px;
  resize: vertical;
}

/* The black 2px edge on focus: the border turns black and the shadow adds the
   second pixel, so the field doesn't change size. */
input:focus,
select:focus,
textarea:focus {
  border-color: var(--ink);
  box-shadow: 0 0 0 1px var(--ink);
  outline: none;
}

input:disabled,
textarea:disabled {
  background: var(--grey-100);
  color: var(--ink-2);
}

::placeholder {
  color: var(--ink-2);
  opacity: 1;
}

.field-hint {
  display: block;
  margin-top: 4px;
  color: var(--ink-2);
  font-size: var(--text-caption);
}

.form-error {
  margin-top: 4px;
  color: var(--danger);
  font-size: var(--text-caption);
}

/* A checkbox keeps its real size, on one line with its words. The whole row is the
   tap target, because the label belongs to the checkbox. */
.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  margin-bottom: 0;
  font-size: var(--text-body);
  font-weight: 400;
  cursor: pointer;
}

input[type="checkbox"] {
  flex: none;
  width: 20px;
  height: 20px;
  min-height: 0;
  margin: 0;
  padding: 0;
  accent-color: var(--ink);
}


/* ---------- Form pages: create, pledge, leave ---------- */

/* One centered white card. */
.form-page {
  max-width: 560px;
  margin: 0 auto;
}

.form-card {
  padding: 24px;
}

.form-card h1 {
  margin-bottom: 12px;
}

.form-card form {
  margin: 0;
}

.form-promise {
  margin: 8px 0 20px;
  color: var(--ink-2);
}

.back-link {
  margin-bottom: 4px;
}

/* A neutral box for a message, like "You're counted, so you can't leave during last call." */
.info-box {
  margin: 0 0 20px;
  padding: 12px 16px;
  border-radius: var(--radius-inner);
  background: var(--grey-100);
}

/* The pledge page's three steps are sections of one card, split by a line. */
.form-step + .form-step,
.form-step + .result-line + .form-step {
  margin-top: 24px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
}

.ai-line {
  margin: 0 0 4px;
  font-weight: 500;
}

.privacy-note {
  margin: 0 0 12px;
  color: var(--ink-2);
  font-size: var(--text-caption);
}

.check-message {
  margin: 12px 0 0;
  padding: 12px 16px;
  border-radius: var(--radius-inner);
  background: var(--grey-100);
}

.check-message:empty,
.result-line:empty,
.hold-text:empty {
  display: none;
}

/* A box the AI filled has a black edge until the friend edits it. */
input.ai-filled {
  border-color: var(--ink);
}

.hold-text {
  font-weight: 500;
}

/* The card fields are PayPal's own frames: only the space around them is ours. */
.card-field-label {
  margin: 0 0 8px;
  font-size: 0.875rem;
  font-weight: 500;
}

.card-field {
  width: 100%;
  height: 56px;
  margin-bottom: 12px;
}

/* One result card, in place of the step (S5). */
.result-line {
  margin: 24px 0 0;
  padding: 16px;
  border: 1px solid var(--ink);
  border-radius: var(--radius-inner);
  font-weight: 500;
}

.leave-line {
  margin: 12px 0 0;
  overflow-wrap: anywhere;
}


/* ---------- Treasurer page (S2) ---------- */

.share-card {
  margin-bottom: var(--card-gap);
}

/* The link and its Copy button share a line, and wrap on a very narrow phone.
   text-overflow cuts a long link with dots, and it scrolls normally when focused. */
.share-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.share-row input {
  flex: 1 1 12rem;
  width: auto;
  min-width: 0;
  text-overflow: ellipsis;
}

.share-row button {
  flex: none;
}

.share-open {
  display: flex;
  align-items: center;
  min-height: 44px;
  margin: 4px 0;
}

/* The last resort when a browser won't copy: the link, ready to press and hold. It takes a whole
   line under whichever button asked for it. */
.manual-copy {
  flex: 1 0 100%;
  margin-top: 8px;
}

.manual-copy p {
  margin: 0 0 4px;
  color: var(--ink-2);
  font-size: var(--text-caption);
}

.manual-copy input {
  min-height: 44px;
  -webkit-user-select: text;
  user-select: text;
}

/* The hidden box the older way of copying needs. It has to exist on the page, so it sits off
   screen. Its 16px text stops an iPhone zooming in for a moment. */
.copy-helper {
  position: fixed;
  top: 0;
  left: -9999px;
}

.private-note {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  color: var(--ink-2);
  font-size: var(--text-caption);
}


/* ---------- The board (S2, S3) ---------- */

/* On a phone the cards stack in the order of the HTML: status, countdown, totals,
   progress, who's in, how it works. Laptops rearrange them further down. */
.board {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--card-gap);
}

/* Anchored cards stop clear of the sticky banner. */
.board [id] {
  scroll-margin-top: calc(var(--banner-height) + 12px);
}

.card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.card-head .card-title {
  margin-bottom: 0;
}

.invite-hint {
  margin: 0;
  color: var(--ink-2);
  font-size: var(--text-caption);
}

/* The status card */
.board-banner {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

/* Phones have Pledge in the bottom bar, so it isn't shown twice on one screen. */
.status-button {
  display: none;
  margin-top: 20px;
}

.board[data-status="NEEDS_ATTENTION"] .board-banner {
  color: var(--danger);
}

/* The countdown card. container-type lets the number size itself to the card, because
   "47:59:12" has to fit a narrow column on a laptop. */
.countdown-card {
  display: flex;
  flex-direction: column;
  container-type: inline-size;
}

/* Ribbed light, as if it comes through fluted glass. The layer is bigger than the card, so its
   light can drift during last call without showing an edge. */
.countdown-card .card-bloom::before {
  inset: -15%;
  background-image: var(--grain),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.05) 0 1px, transparent 1px 6px),
    radial-gradient(circle farthest-corner at 15% 10%, rgba(255, 255, 255, 0.08), transparent 60%);
}

/* The auto margins center the time in whatever height the card has. */
.board-time {
  margin: auto 0;
  font-size: min(var(--text-display), 24cqi);
}

/* The totals: a swipeable row on a phone. Each card is 78% wide so the next one peeks in.
   The negative margin lets the row run to the screen edges. */
.board-stats {
  display: flex;
  gap: 12px;
  margin: 0 calc(var(--gutter) * -1);
  padding: 0 var(--gutter);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-padding: 0 var(--gutter);
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.board-stats::-webkit-scrollbar {
  display: none;
}

.stat-card {
  display: flex;
  flex: 0 0 min(78%, 320px);
  flex-direction: column;
  min-height: 184px;
  container-type: inline-size;
  scroll-snap-align: start;
}

.stat-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 12px;
}

.stat-top .card-title {
  margin: 0;
  padding-top: 8px;
}

/* 17cqi keeps even "$50,000.00" inside the card at every width. */
.stat-number {
  margin: 0 0 12px;
  font-size: min(var(--text-display), 17cqi);
}

.stat-caption {
  margin: auto 0 0;
  color: var(--ink-2);
  font-size: var(--text-caption);
  font-weight: 500;
}

.light-card .stat-caption {
  color: var(--on-ink-2);
}

/* The half ring */
.ring {
  position: relative;
  width: 100%;
  max-width: 280px;
  margin: 8px auto 16px;
}

.ring-svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.ring-svg path {
  fill: none;
  stroke-linecap: round;
  stroke-width: 32;   /* 28px when the ring is drawn 280px wide */
}

.ring-track {
  stroke: url(#ring-stripes);
}

.ring-counted {
  stroke: var(--ink);
  stroke-dasharray: 100 100;
  transition: stroke-dashoffset 600ms var(--ease-out);
}

/* A zero-length arc with round caps would still draw a dot. */
.ring-empty {
  opacity: 0;
}

.board[data-status="NEEDS_ATTENTION"] .ring-counted {
  stroke: var(--danger);
}

.board[data-status="EXPIRED"] .ring-counted {
  stroke: var(--held);
}

.ring-center {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  text-align: center;
}

.ring-percent {
  margin: 0 0 4px;
}

.ring-center .caption {
  margin: 0;
}

.ring-legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px 16px;
  margin: 0 0 12px;
  padding: 0;
  font-size: var(--text-caption);
  font-weight: 500;
  list-style: none;
}

.ring-legend li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.legend-dot {
  flex: none;
  width: 12px;
  height: 12px;
}

.board-note {
  margin: 0;
  color: var(--ink-2);
  font-size: var(--text-caption);
  text-align: center;
  text-wrap: balance;
}

/* Who's in */
.button-small {
  min-height: 44px;
  padding: 0 16px;
}

.board-rows {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A phone row: the avatar, then the name and chip on one line, then the details across
   the rest of the width. display: contents lets the name and details join this grid. */
.board-rows li {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0 12px;
  min-height: 64px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}

.row-avatar {
  grid-row: 1 / span 2;
  align-self: start;
}

.row-text {
  display: contents;
}

.row-name {
  grid-column: 2;
  grid-row: 1;
}

.row-chip {
  grid-column: 3;
  grid-row: 1;
}

.row-details {
  grid-column: 2 / -1;
  grid-row: 2;
}

.board-rows li:first-child {
  border-top: 0;
}

.board-rows li[data-counted="true"] .row-avatar {
  background: var(--ink);
  color: var(--on-ink);
}

/* overflow-wrap breaks a long name with no spaces, so it can't push the chip off the screen. */
.row-name {
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.row-details {
  display: flex;
  flex-wrap: wrap;
  gap: 0 6px;
  color: var(--ink-2);
  font-size: var(--text-caption);
  overflow-wrap: anywhere;
}

.row-share-part::before,
.row-waiting::before {
  content: "· ";
}

/* Chips stay on one line. */
.status {
  flex: none;
  min-height: 28px;
  padding: 0 12px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font-size: var(--text-caption);
  font-weight: 500;
  line-height: 26px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.board-rows li[data-status="HOLD_PENDING"] .row-chip {
  border-style: dashed;
  border-color: var(--line);
}

.board-rows li[data-status="HELD"] .row-chip {
  border-color: var(--line);
  background: var(--line);
}

/* Only this chip may be cut short with dots, because the line under the name shows the
   same words in full. A "Charged $x" chip is money, so it is never cut. */
.board-rows li[data-waiting="true"] .row-chip {
  max-width: 8rem;
  border-color: var(--ink);
  background: var(--card);
}

.board-rows li[data-counted="true"] .row-chip {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--on-ink);
}

/* Last, because a failed charge also has data-counted="true". */
.board-rows li[data-status="DECLINED"] .row-chip,
.board-rows li[data-status="FAILED"] .row-chip {
  border-color: var(--danger-bg);
  background: var(--danger-bg);
  color: var(--danger);
}

.board-empty {
  padding: 8px 0;
  color: var(--ink-2);
}

/* A missed goal drops its pledges from the list, so "Nobody has pledged yet" would be untrue. */
.board[data-status="EXPIRED"] .board-empty {
  display: none;
}

/* How it works */
.how-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 0 0 12px;
  padding: 0;
  list-style: none;
}

.how-list li {
  display: flex;
  gap: 12px;
}

.how-promise {
  margin: 0;
}

/* ---------- Motion on the board ---------- */

/* Last call: the status card turns black. It is the same element all along, so a
   transition does it, and the 10-second refresh can't restart it. */
.status-card {
  position: relative;
  isolation: isolate;
}

.status-card .card-bloom {
  opacity: 0;
  transition: opacity 300ms var(--ease-out);
}

.status-card .card-bloom {
  background: radial-gradient(circle farthest-corner at 0% 0%, var(--grey-800), #0B0B0B 70%);
}

.status-card .card-bloom::before {
  background-image: var(--grain), radial-gradient(circle farthest-corner at 0% 0%, rgba(255, 255, 255, 0.07), transparent 60%);
}

.board[data-status="LAST_CALL"] .status-card .card-bloom {
  opacity: 1;
}

.board[data-status="LAST_CALL"] .status-card {
  color: var(--on-ink);
}

.board[data-status="LAST_CALL"] .status-button {
  border-color: var(--on-ink);
  background: var(--on-ink);
  box-shadow: var(--shadow-floating);
  color: var(--ink);
}

/* The countdown card's light drifts slowly during last call. Only a layer moves. */
.board[data-status="LAST_CALL"] .countdown-card .card-bloom::before {
  animation: bloom-drift 4s ease-in-out infinite alternate;
}

/* First load: the cards rise and fade in one after another. These elements are never rebuilt,
   so this runs once. "backwards" lets go of the card afterwards, so hover can move it. */
.board .card,
.share-card {
  animation: card-enter 300ms var(--ease-out) backwards;
  animation-delay: calc(var(--order, 0) * 40ms);
}

.countdown-card { --order: 1; }
.stat-card:nth-child(1) { --order: 2; }
.stat-card:nth-child(2) { --order: 3; }
.stat-card:nth-child(3) { --order: 4; }
.stat-card:nth-child(4) { --order: 5; }
.progress-card { --order: 6; }
.who-card { --order: 7; }
.how-card { --order: 8; }

/* The countdown card breathes during last call, after it has finished coming in. */
.board[data-status="LAST_CALL"] .countdown-card {
  animation: card-enter 300ms var(--ease-out) backwards, countdown-pulse 2s ease-in-out 340ms infinite;
  animation-delay: calc(var(--order, 0) * 40ms), 340ms;
}

/* The moments board.js marks. Each element's normal rule is its final look, so reduced
   motion loses only the movement. */
.row-new {
  animation: card-enter 300ms var(--ease-out);
}

/* The circle turns black together: no delay, so they change at the same moment. */
.row-just-counted .row-chip,
.row-just-counted .row-avatar {
  animation: turn-counted 200ms var(--ease-out);
}

.just-pledged {
  display: inline-block;
  padding: 0 8px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--on-ink);
  font-weight: 500;
  animation: wiggle 450ms ease-in-out, fade-away 300ms ease 3700ms forwards;
}

/* A changing number: the old text leaves upward as the new text comes in from below. */
.text-swap {
  position: relative;
  display: inline-block;
}

.text-new {
  display: inline-block;
  animation: text-in 300ms var(--ease-out);
}

.text-old {
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  white-space: nowrap;
  animation: text-out 300ms var(--ease-out);
}

/* The percent in the ring crossfades instead, without moving. */
.ring-percent.text-swap {
  display: block;
}

.ring-percent .text-old {
  right: 0;
}

.ring-percent .text-new {
  animation-name: fade-in;
}

.ring-percent .text-old {
  animation-name: fade-away-now;
}

@keyframes card-enter {
  from { opacity: 0; transform: translateY(8px); }
}

@keyframes turn-counted {
  from { background-color: var(--line); color: var(--ink); }
}

@keyframes bloom-drift {
  from { transform: translate(-5%, 4%); }
  to { transform: translate(5%, -4%); }
}

@keyframes fade-in {
  from { opacity: 0; }
}

@keyframes fade-away {
  to { opacity: 0; }
}

@keyframes fade-away-now {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes text-in {
  from { opacity: 0; transform: translateY(8px); }
}

@keyframes text-out {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-8px); }
}

@keyframes countdown-pulse {
  50% { transform: scale(1.02); }
}

/* The Copy button's words crossfade. */
.button-label {
  display: inline-block;
}

.label-copied {
  animation: fade-in 150ms ease, wiggle 450ms ease-in-out;
}

.label-back {
  animation: fade-in 150ms ease;
}


/* ---------- Landing page (top of S1) ---------- */

/* Anchored sections stop clear of the sticky banner and header. */
.landing section[id] {
  scroll-margin-top: calc(var(--banner-height) + var(--landing-header-height) + 16px);
}

/* Glass: the bar stays readable because 72% white sits behind its text. Where a browser
   has no backdrop-filter, the @supports rule further down makes it solid white. */
.landing-header {
  position: sticky;
  top: var(--banner-height);
  z-index: 18;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  height: var(--landing-header-height);
  padding: 0 var(--gutter);
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  background: rgba(255, 255, 255, 0.72);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
}

.landing-brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  font-size: 1.125rem;
  font-weight: 600;
  text-decoration: none;
}

.landing-logo-wrap {
  display: inline-flex;
}

.landing-logo {
  width: auto;
  height: 28px;
}

.landing-name {
  display: inline-block;
}

.landing-nav {
  display: none;
  gap: 24px;
}

.landing-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-weight: 500;
}

.landing-start {
  min-height: 44px;
  padding: 0 16px;
}

/* The canvas: a big rounded panel on the page's own light, a little brighter behind the logo, with
   faint grey ribs. Its ::before holds that behind everything. overflow keeps the pieces inside it. */
.hero {
  padding: 16px 16px 0;
}

.hero-canvas {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: calc(100dvh - var(--banner-height) - var(--landing-header-height) - 16px);
  padding: 24px 16px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 32px;
  background-color: transparent;
  text-align: center;
}

.hero-canvas::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--grain),
    repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.025) 0 1px, transparent 1px 6px),
    radial-gradient(45% 55% at 50% 35%, #FFFFFF, transparent);
}

/* The logo as a raised white tile. Scrolling moves .hero-tile; hover wiggles the face inside it. */
.hero-tile {
  flex: none;
  width: 96px;
  height: 96px;
  margin-bottom: 24px;
}

.hero-tile-face {
  display: grid;
  width: 100%;
  height: 100%;
  border-radius: 24px;
  background: var(--card);
  box-shadow: var(--shadow-raised), inset 0 1px 0 rgba(255, 255, 255, 0.8);
  place-items: center;
  border: 1px solid var(--line);
}

.hero-tile-face img {
  width: 64%;
  height: auto;
}

.hero-copy {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Two lines at every width: below 580px the size follows the screen, because 44px
   "unless everyone's in." would need a third line on a phone. */
.hero-title {
  font-size: min(2.75rem, 7.6vw);
  letter-spacing: -0.04em;
  line-height: 1;
}

.hero-line {
  display: block;
  white-space: nowrap;
}

.hero-line-soft {
  color: var(--ink-3);
}

.hero-lead {
  max-width: 46ch;
  margin: 16px 0 20px;
  color: var(--ink-2);
  font-size: 1rem;
}

.hero-note {
  margin: 8px 0 0;
  color: var(--ink-2);
  font-size: var(--text-caption);
}

/* The floating pieces. .piece is where it sits and what scrolling moves. .piece-body tilts
   and floats, so the two movements never fight over one transform. */
.piece {
  flex: none;
  display: none;
}

.piece-body {
  width: max-content;
  max-width: 240px;
  padding: 14px;
  border-radius: 18px;
  background: var(--card);
  box-shadow: var(--shadow-floating), inset 0 1px 0 rgba(255, 255, 255, 0.8);
  text-align: left;
  transform: rotate(var(--tilt, 0deg)) scale(var(--piece-scale, 1));
  border: 1px solid var(--line);
}

.piece-body p {
  margin: 0;
}

.piece-line {
  display: flex;
  align-items: center;
  gap: 8px;
}

.piece-name {
  font-weight: 500;
}

.piece-caption {
  color: var(--ink-2);
  font-size: var(--text-caption);
}

.piece-pledge .piece-caption {
  margin-top: 6px;
  padding-left: 48px;
}

.piece-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px !important;
  font-weight: 500;
}

.piece-chip-black {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--on-ink);
}

.piece-chip-outline {
  display: inline-block;
  border-color: var(--ink);
  color: var(--ink);
}

.piece-avatars {
  display: flex;
  margin-bottom: 8px;
}

.piece-avatars .avatar + .avatar {
  margin-left: -8px;
  box-shadow: 0 0 0 2px var(--card);
}

.piece-black {
  min-width: 140px;
  background: radial-gradient(circle farthest-corner at 0% 0%, var(--grey-800), #0B0B0B 70%);
  box-shadow: var(--shadow-floating), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  color: var(--on-ink);
  border-color: rgba(255, 255, 255, 0.08);
}

.piece-black .piece-caption {
  color: var(--on-ink-2);
}

.piece-time {
  font-size: var(--text-display);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.1;
}

.mini-ring {
  display: block;
  width: 136px;
  margin-top: 4px;
}

.mini-ring path {
  fill: none;
  stroke-linecap: round;
  stroke-width: 18;
}

.mini-ring-track {
  stroke: url(#piece-stripes);
}

.mini-ring-arc {
  stroke: var(--ink);
  stroke-dasharray: 70 100;
}

.piece-paper {
  position: relative;
  max-width: 200px;
  padding-top: 20px;
  background: #FFFDF5;
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--ink);
}

.piece-pin {
  position: absolute;
  top: 8px;
  left: 50%;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink);
}

/* Phones get two pieces, in the flow of the page, so they can never cover text. */
.piece-pledge,
.piece-countdown {
  display: block;
  --piece-scale: 0.8;
}

.piece-pledge {
  align-self: flex-start;
  margin: -8px 0 -4px -16px;
  --tilt: -4deg;
  --drift-x: -40px;
  --drift-y: -30px;
}

.piece-countdown {
  align-self: flex-end;
  margin: 8px -8px -12px 0;
  --tilt: 4deg;
  --drift-x: 40px;
  --drift-y: 30px;
}

.piece-ring { --tilt: -5deg; --drift-x: -120px; --drift-y: 0px; }
.piece-waiting { --tilt: 3deg; --drift-x: 120px; --drift-y: 0px; }
.piece-note { --tilt: -3deg; --drift-x: -80px; --drift-y: 60px; }
.piece-hold { --tilt: 5deg; --drift-x: 80px; --drift-y: 60px; }

/* The sections under the hero */
.landing-section {
  max-width: 1120px;
  margin: 0 auto;
  padding: 48px var(--gutter) 0;
}

.landing-title {
  margin: 0 0 20px;
  font-size: var(--text-title);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.how-cards {
  display: grid;
  gap: var(--card-gap);
}

.how-step .icon {
  width: 24px;
  height: 24px;
  margin-bottom: 12px;
}

.how-step .card-title {
  margin-bottom: 4px;
}

.how-step p {
  margin: 0;
  color: var(--ink-2);
}

/* Cards that rise in when they reach the screen. landing.js adds these classes, so with no
   JavaScript, or with reduced motion, the cards just stay in place. */
.will-rise {
  opacity: 0;
  transform: translateY(8px);
}

.will-rise.risen {
  opacity: 1;
  transform: none;
  transition: opacity 300ms var(--ease-out), transform 300ms var(--ease-out);
  transition-delay: calc(var(--rise-order, 0) * 60ms);
}

.front-headline {
  font-size: var(--text-card);
  font-weight: 500;
}

.landing-footer {
  max-width: 1120px;
  margin: 0 auto;
  padding: 48px var(--gutter);
  color: var(--ink-2);
  font-size: var(--text-caption);
}

.landing-footer p {
  margin: 0 0 4px;
}

.landing-footer-name {
  color: var(--ink);
  font-size: var(--text-body);
  font-weight: 600;
}

/* A link whose line slides in from the left under a mouse. Touch screens keep the plain underline.
   inline-flex and the minimum height give a short link a 44px tap target. */
.text-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

@media (hover: hover) and (pointer: fine) {
  .text-link {
    text-decoration: none;
  }

  .text-link::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: 10px;
    left: 0;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 200ms var(--ease-out);
  }

  .text-link:hover::after {
    transform: scaleX(1);
  }
}

@media (min-width: 768px) {
  /* Four pieces, in the corners. The canvas keeps enough height for them to clear the words. */
  .hero-canvas {
    min-height: max(calc(100dvh - var(--banner-height) - var(--landing-header-height) - 24px), 860px);
  }

  .hero-title {
    font-size: 2.75rem;
  }

  .piece-pledge,
  .piece-countdown,
  .piece-note,
  .piece-hold {
    position: absolute;
    display: block;
    margin: 0;
    --piece-scale: 1;
  }

  .piece-pledge { top: 6%; left: 5%; }
  .piece-countdown { top: 5%; right: 5%; --drift-x: 100px; --drift-y: -60px; }
  .piece-note { bottom: 6%; left: 6%; }
  .piece-hold { bottom: 7%; right: 5%; }
  .piece-pledge { --drift-x: -100px; --drift-y: -60px; }

  .how-cards {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Solid where glass isn't possible. Text on it passes either way. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .landing-header,
  .page-actions {
    background: var(--card);
  }
}

/* Solid surfaces too for anyone who asked their device for less transparency. */
@media (prefers-reduced-transparency: reduce) {
  .landing-header,
  .page-actions {
    background: var(--card);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .button-secondary,
  .icon-button,
  .nav-item[aria-current="page"] {
    background: var(--card);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* All six pieces. Below 1200px the headline steps down so they clear it. */
@media (min-width: 1024px) {
  .hero {
    padding: 24px 24px 0;
  }

  .hero-tile {
    width: 120px;
    height: 120px;
    margin-bottom: 32px;
  }

  .hero-canvas {
    min-height: max(calc(100dvh - var(--banner-height) - var(--landing-header-height) - 24px), 720px);
  }

  .hero-tile-face {
    border-radius: 28px;
  }

  .hero-title {
    font-size: 3.5rem;
  }

  .hero-lead {
    font-size: 1.125rem;
  }

  .landing-nav {
    display: flex;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
  }

  .piece-ring,
  .piece-waiting {
    position: absolute;
    display: block;
  }

  .piece-ring { top: 44%; left: 1%; }
  .piece-waiting { top: 44%; right: 3%; }
}

@media (min-width: 1200px) {
  .hero-title {
    font-size: 4.5rem;
  }

  .piece-ring { left: 5%; }
  .piece-waiting { right: 6%; }
}

/* Idle float, and the scroll zoom. .piece-body floats; .piece, .hero-tile and .hero-copy follow
   the scroll, and scrolling back up plays it in reverse by itself. */
@media (prefers-reduced-motion: no-preference) {
  .piece-body {
    animation: piece-float 3s ease-in-out infinite alternate;
  }

  .piece-countdown .piece-body { animation-duration: 3.5s; animation-delay: -1s; }
  .piece-ring .piece-body { animation-duration: 2.8s; animation-delay: -2s; }
  .piece-waiting .piece-body { animation-duration: 3.2s; animation-delay: -0.6s; }
  .piece-note .piece-body { animation-duration: 2.6s; animation-delay: -1.6s; }
  .piece-hold .piece-body { animation-duration: 3.4s; animation-delay: -2.4s; }

  @supports (animation-timeline: scroll()) {
    .hero-tile,
    .hero-copy,
    .piece,
    .landing-logo-wrap,
    .landing-name {
      animation-duration: 1ms;   /* the timeline sets the real length */
      animation-fill-mode: both;
      animation-timeline: scroll(root);
      animation-timing-function: linear;
    }

    /* The tile only needs 280px: by then the page has carried it up to the header. */
    .hero-tile {
      animation-name: tile-to-header;
      animation-range: 0 280px;
    }

    .hero-copy {
      animation-name: copy-recede;
      animation-range: 0 600px;
    }

    .piece {
      animation-name: piece-drift;
      animation-range: 0 600px;
    }

    /* The header logo arrives as the tile fades, and the name slides over to make room. */
    .landing-logo-wrap {
      animation-name: fade-in;
      animation-range: 200px 300px;
    }

    .landing-name {
      animation-name: name-slide;
      animation-range: 200px 300px;
    }
  }
}

/* The fallback does the same thing with one number, --progress, from 0 to 1. */
.scroll-fallback .piece {
  opacity: calc(1 - var(--progress, 0));
  transform: translate(calc(var(--drift-x, 0px) * var(--progress, 0)), calc(var(--drift-y, 0px) * var(--progress, 0)))
    scale(calc(1 + 0.08 * var(--progress, 0)));
}

.scroll-fallback .hero-tile {
  --tile-progress: min(1, calc(var(--progress, 0) * 2.14));   /* done at 280px of the 600px */
  opacity: calc(1 - var(--tile-progress));
  transform: translateX(calc(var(--tile-x) * var(--tile-progress))) scale(calc(1 - 0.4 * var(--tile-progress)));
}

.scroll-fallback .landing-logo-wrap {
  opacity: calc((var(--progress, 0) - 0.33) * 6);
}

.scroll-fallback .hero-copy {
  opacity: calc(1 - 0.4 * var(--progress, 0));
  transform: scale(calc(1 - 0.08 * var(--progress, 0)));
}

/* The tile slides toward the header's logo while the page carries it upward. It stays level with
   the words below it, so it never crosses them. */
.hero-tile {
  --tile-x: calc(-50vw + 40px);
}

@keyframes piece-float {
  from { transform: rotate(var(--tilt, 0deg)) scale(var(--piece-scale, 1)) translateY(0); }
  to { transform: rotate(var(--tilt, 0deg)) scale(var(--piece-scale, 1)) translateY(-6px); }
}

@keyframes tile-to-header {
  to { opacity: 0; transform: translateX(var(--tile-x)) scale(0.6); }
}

@keyframes name-slide {
  from { transform: translateX(-53px); }
}

@keyframes copy-recede {
  to { opacity: 0.6; transform: scale(0.92); }
}

@keyframes piece-drift {
  to { opacity: 0; transform: translate(var(--drift-x, 0px), var(--drift-y, 0px)) scale(1.08); }
}


/* ---------- The share panel (S2, S3) ---------- */

/* A native dialog: it traps focus, Esc closes it, and focus goes back to the button that opened it.
   On a phone it is a sheet along the bottom of the screen. */
.share-panel {
  width: 100%;
  max-width: 100%;
  max-height: calc(100dvh - 24px);
  margin: auto 0 0;
  padding: 0;
  overflow: visible;
  border: 0;
  background: transparent;
  color: var(--ink);
}

/* The page behind the panel stays still. share.js measures the scrollbar that goes away, and the
   padding fills its place, so the page doesn't jump sideways. */
html:has(.share-panel[open]) {
  padding-right: var(--scrollbar-width, 0);
  overflow: hidden;
}

.share-panel::backdrop {
  background: rgba(0, 0, 0, 0.35);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  animation: fade-in 300ms ease;
}

.share-sheet {
  position: relative;
  max-height: calc(100dvh - 24px);
  padding: 0 20px calc(20px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  border-radius: 24px 24px 0 0;
  background: linear-gradient(180deg, var(--card), var(--grey-50));
  box-shadow: var(--shadow-raised), inset 0 1px 0 rgba(255, 255, 255, 0.8);
  transition: transform 200ms var(--ease-out);
}

.share-sheet.is-dragging {
  transition: none;
}

.share-panel[open] .share-sheet {
  animation: sheet-up 300ms var(--ease-out);
}

.share-panel.is-closing .share-sheet {
  animation: sheet-down 200ms ease-in forwards;
}

.share-panel.is-closing::backdrop {
  animation: fade-away-now 200ms ease forwards;
}

/* The grab handle's area is 44px tall, and touch-action hands its drags to share.js. */
.share-grab {
  display: flex;
  justify-content: center;
  height: 44px;
  margin: 0 -20px;
  padding-top: 10px;
  cursor: grab;
  touch-action: none;
}

.share-grab::before {
  content: "";
  flex: none;
  width: 40px;
  height: 4px;
  border-radius: 999px;
  background: var(--grey-300);
}

.share-close {
  position: absolute;
  top: 12px;
  right: 12px;
}

.share-title {
  margin: 0 48px 4px 0;
  font-size: 1.125rem;
  font-weight: 600;
}

.share-note {
  margin: 0 0 16px;
  color: var(--ink-2);
  font-size: var(--text-caption);
}

.share-link-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

/* The link with its dots in the middle: the start gives way, and the end always shows. */
.share-link-field {
  display: flex;
  flex: 1 1 8rem;
  align-items: center;
  min-width: 0;
  min-height: 48px;
  margin: 0;
  padding: 0 14px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-field);
  background: var(--grey-100);
  font-size: 0.875rem;
  white-space: nowrap;
}

.share-link-start {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.share-link-end {
  flex: none;
}

.share-link-row button {
  flex: none;
}

.share-with {
  margin: 0 0 8px;
  font-size: 0.875rem;
  font-weight: 500;
}

/* The row scrolls sideways if it doesn't fit, with no visible scrollbar. */
.share-tiles {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin: 0 -20px 16px;
  padding: 4px 20px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-padding: 0 20px;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}

.share-tiles::-webkit-scrollbar {
  display: none;
}

.share-tile {
  display: flex;
  flex: none;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 64px;
  border-radius: var(--radius-field);
  font-size: var(--text-caption);
  font-weight: 500;
  text-decoration: none;
  scroll-snap-align: start;
  touch-action: manipulation;
  animation: card-enter 300ms var(--ease-out) backwards;
  animation-delay: calc(var(--tile, 0) * 30ms);
}

.share-tile:nth-child(2) { --tile: 1; }
.share-tile:nth-child(3) { --tile: 2; }
.share-tile:nth-child(4) { --tile: 3; }

.share-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 50%;
  background: var(--grey-100);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
  transition: transform 120ms var(--ease-out);
}

.share-tile:active .share-tile-icon {
  transform: scale(0.95);
}

@keyframes sheet-up {
  from { transform: translateY(100%); }
}

@keyframes sheet-down {
  to { transform: translateY(100%); }
}

@keyframes dialog-in {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.97);
  }
}

@keyframes dialog-out {
  to {
    opacity: 0;
    transform: translateY(8px) scale(0.97);
  }
}

@media (hover: hover) and (pointer: fine) {
  .share-tile:hover .share-tile-icon {
    transform: translateY(-2px);
  }
}


/* ---------- Depth, hover and wiggle, site-wide ---------- */

/* A card's bigger shadow lives on a layer that only fades. Animating box-shadow itself
   would repaint on every frame, and opacity doesn't. */
.board .card,
.how-step {
  position: relative;
  transition: transform 250ms var(--ease-out);
}

.board .card::after,
.how-step::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: var(--shadow-hover);
  opacity: 0;
  pointer-events: none;
  transition: opacity 250ms var(--ease-out);
}

.board .status-card {
  transition: transform 250ms var(--ease-out), color 300ms var(--ease-out);
}

.row-avatar {
  transition: transform 200ms var(--ease-out);
}

.icon-button .icon {
  transition: transform 200ms var(--ease-out);
}

.board-rows li {
  margin: 0 -12px;
  padding: 12px;
  transition: background-color 200ms ease;
}

/* Only a real mouse gets these. A tap would leave them stuck on a phone. */
@media (hover: hover) and (pointer: fine) {
  .board .card:hover,
  .how-step:hover {
    transform: translateY(-2px);
    transition-duration: 200ms;
  }

  .board .card:hover::after,
  .how-step:hover::after {
    opacity: 1;
    transition-duration: 200ms;
  }

  .board-rows li:hover {
    border-radius: 12px;
    background: var(--grey-100);
  }

  .board-rows li:hover .row-avatar {
    transform: scale(1.05);
  }

  /* The arrow leans the way it points. */
  .icon-button:hover .icon {
    transform: translate(2px, -2px);
  }

  button:hover:not(:disabled):not(.icon-button),
  .button-link:hover {
    transform: translateY(-1px);
  }

  /* The logo wiggles when pointed at. Never money, the countdown or a status chip. */
  .hero-tile-face:hover,
  .landing-brand:hover .landing-logo {
    animation: wiggle 450ms ease-in-out;
  }
}

/* A press comes last, so it beats hover. The shadow shrinks as the button goes in. */
button:active:not(:disabled),
.button-link:active {
  box-shadow: var(--button-pressed);
  transform: scale(0.97);
}

.button-secondary:active:not(:disabled),
.icon-button:active:not(:disabled) {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
}

/* Wiggle: sometimes, never constantly. Shake is for a field with an error. */
.wiggle {
  animation: wiggle 450ms ease-in-out;
}

.shake {
  animation: shake 350ms ease-in-out;
}

@keyframes wiggle {
  20% { transform: rotate(-3deg); }
  40% { transform: rotate(3deg); }
  60% { transform: rotate(-2deg); }
  80% { transform: rotate(1deg); }
}

@keyframes shake {
  20% { transform: translateX(-6px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(4px); }
}


/* ---------- Keyboard focus ---------- */

/* A black ring around whatever the keyboard is on. :focus-visible shows it
   for keyboard users, not after every mouse click. Fields use their black edge. */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.light-card :focus-visible {
  outline-color: var(--on-ink);
}


/* ---------- Laptops ---------- */

@media (min-width: 1024px) {
  :root {
    --text-display: 3.5rem;   /* 56px */
    --text-title: 2.25rem;    /* 36px */
    --text-card: 1.125rem;    /* 18px */
    --text-body: 1rem;        /* 16px */
    --gutter: 32px;
    --card-padding: 24px;
    --card-gap: 20px;
    --button-height: 44px;
  }

  /* The sidebar stays put while the page scrolls. */
  .shell {
    flex-direction: row;
  }

  .shell-side {
    position: sticky;
    top: var(--banner-height);
    flex: none;
    width: 248px;
    height: calc(100dvh - var(--banner-height));
    padding-bottom: 24px;
  }

  .wordmark {
    min-height: 0;
    padding: 32px 0 32px var(--gutter);
  }

  .shell-nav {
    flex-direction: column;
    gap: 4px;
    padding: 0;
    overflow-x: visible;
  }

  /* The menu fills the sidebar. The page you're on is a glass bar, the others are plain words. */
  .nav-item .icon {
    display: block;
  }

  .nav-item {
    display: flex;
    gap: 12px;
    padding: 0 16px;
    border-radius: 16px;
    margin: 0 16px;
  }

  .sandbox-card {
    display: block;
    margin: auto 16px 0;
    padding: 20px;
    border-radius: var(--radius-card);
  }

  .page {
    padding-top: 32px;
    padding-bottom: 64px;
  }

  .page-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
    margin-bottom: 24px;
  }

  .page-heading {
    min-width: 0;
  }

  .page-actions {
    position: static;
    flex: none;
    padding: 0;
    border-top: 0;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .page-actions > *,
  .page-actions form button {
    flex: none;
    width: auto;
  }

  /* The board: two columns on a small laptop. */
  .board {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-areas:
      "status countdown"
      "stats stats"
      "progress progress"
      "who who"
      "how how";
    align-items: start;
  }

  .status-card { grid-area: status; align-self: stretch; }
  .countdown-card { grid-area: countdown; align-self: stretch; }
  .board-stats { grid-area: stats; }
  .progress-card { grid-area: progress; align-self: stretch; }
  .who-card { grid-area: who; }
  .how-card { grid-area: how; align-self: stretch; }

  /* A laptop row is one line: avatar, name with details, chip on the right. */
  .board-rows li {
    display: flex;
  }

  .row-text {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
  }

  /* The totals stop scrolling and sit in a plain grid. */
  .board-stats {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--card-gap);
    margin: 0;
    padding: 0;
    overflow: visible;
  }

  .status-button {
    display: inline-flex;
  }

  .ring {
    max-width: 320px;
  }

  .ring-svg path {
    stroke-width: 28;
  }
}


/* From a tablet up, the panel is a centered dialog. It has no bottom edge to slide from, so it rises
   a little and fades, and the grab handle isn't needed. */
@media (min-width: 768px) {
  .share-panel {
    width: 420px;
    margin: auto;
  }

  .share-sheet {
    padding: 24px;
    border-radius: var(--radius-card);
  }

  .share-panel[open] .share-sheet {
    animation: dialog-in 200ms var(--ease-out);
  }

  .share-panel.is-closing .share-sheet {
    animation: dialog-out 200ms ease-in forwards;
  }

  .share-grab {
    display: none;
  }

  .share-tiles {
    justify-content: flex-start;
  }
}


/* ---------- Wide laptops: the full dashboard ---------- */

@media (min-width: 1200px) {
  .board {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    grid-template-areas:
      "stats stats stats stats"
      "progress progress countdown how"
      "who who status status";
  }

  .board-stats {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  /* The status card keeps its own height and stays in view beside a long list. */
  .board .status-card {
    position: sticky;
    top: calc(var(--banner-height) + 12px);
    align-self: start;
  }
}


/* ---------- Reduced motion ---------- */

/* If someone has asked their phone or computer for less motion, turn off
   every animation and transition. So any animated element must have its
   final look in its normal rule, and the animation only adds the movement. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }

  /* The share panel still needs to arrive and leave, so it fades instead of sliding. */
  .share-panel[open] .share-sheet {
    animation: fade-in 150ms ease !important;
  }

  .share-panel.is-closing .share-sheet {
    animation: fade-away-now 150ms ease forwards !important;
  }
}
