/* ==========================================================================
   Prospira Tweaks - Utility page layer (scoped to body.page-utility)
   Depends on css/prospira.css for tokens, primitives, header, footer, the
   .section rhythm, the .reveal system and the ambient background.

   The page has ONE job: hand over the app. Five blocks, in this order:
     1 hero        headline + the single primary action + one product shot
     2 plans       centred price cards, then the comparison table under them.
                   One block, but NOT one table: cards inside <thead> sat in the
                   right half because the feature-label column holds it open.
                   Features live only in the table, so nothing repeats.
     3 proof       two screenshots, different ones from the hero
     4 trust       the questions that stop a download, not a feature catalogue
     5 get         the download card the hero button points at
   ========================================================================== */

.page-utility {
  --section-gap: clamp(76px, 9vw, 128px);

  /* One spacing scale for the whole page, so the same kind of gap is the same
     number everywhere instead of 34 here, 22 there, 18 somewhere else.
       s-1  label to the thing it labels
       s-2  inside a group of related lines
       s-3  an element to its own caption
       s-4  block to block inside a section
       s-5  major block separation inside a section
     Section to section stays on --section-gap. */
  --s-1: 8px;
  --s-2: 14px;
  --s-3: 20px;
  --s-4: 30px;
  --s-5: clamp(28px, 4vw, 44px);


  /* Accessibility: the global --text-low (alpha 0.48) measures 4.0:1 against
     --bg-0, under the 4.5:1 WCAG AA floor for normal text. This page leans on
     quiet copy (billing cycle, table headers, file details), so it lifts the
     alpha to clear the floor. Scoped on purpose - the global token is shared
     with the home page and is not ours to retune from here. */
  --text-low: rgba(214, 225, 245, 0.6);
}

/* The nav morph and the sticky-header overflow fix now live in css/prospira.css,
   scoped to the pages that load js/landing.js. Nothing to repeat here. */

/* ---------- 1. Hero: waitlist ----------
   The app has not shipped, so the honest primary action is the mailing list, not
   a download. Structure follows the reference the owner picked: gradient bars
   behind, a fact pill, a two-line headline whose second line turns serif italic,
   two lines of lede, the form, then the real social links.

   Two deliberate departures from that reference: the bars are royal, not orange
   (royal is the only accent this site has), and the avatar stack shows game art
   rather than photographs of people. The count itself is the owner's own figure,
   supplied 2026-07-19; it is not derived from anything in this repo, so do not
   "correct" it against the database. */

/* --- waitlist form --- */
.u-wait {
  display: flex;
  gap: 10px;
  width: 100%;
  max-width: 620px;
  margin-top: var(--s-4);
}
.u-wait-input {
  flex: 1;
  min-width: 0;
  min-height: 46px;
  padding: 11px 22px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border-mid);
  background: rgba(10, 14, 30, 0.6);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--text-hi);
  transition: border-color var(--dur-1) var(--ease-out);
}
.u-wait-input::placeholder { color: var(--text-low); }
.u-wait-input:focus {
  outline: none;
  border-color: rgba(140, 170, 255, 0.6);
}
.u-wait-input:focus-visible {
  outline: 2px solid rgba(94, 140, 255, 0.85);
  outline-offset: 2px;
}
.u-wait .btn { flex-shrink: 0; }
.u-wait-msg {
  max-width: 620px;
  margin: 14px 0 0;
  padding: 11px 16px;
  border-radius: var(--r-card);
  border: 1px solid var(--border-mid);
  background: rgba(10, 14, 30, 0.6);
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--text-mid);
}

/* only the two channels that actually exist */
/* Optically centred between the message above and the screenshot below.
   Measured: 42px above, 20px below. The 12px of padding inside each link counts
   toward the visual gap, so the box margin is 31 - 12 = 19. The 11px taken off
   here is added back to the hero's padding-bottom, otherwise the hero simply
   gets shorter and the screenshot rides up with the icons, leaving the gap
   exactly as lopsided as before. */
.u-social {
  display: flex;
  gap: 10px;
  margin-top: 19px;
}
/* 12px of padding round a 20px glyph makes a 44px target. The glyph stays the
   size the reference used; only the hit area grows. */
.u-social a {
  display: inline-flex;
  padding: 12px;
  border-radius: 10px;
  color: var(--text-low);
  transition: color var(--dur-1) var(--ease-out);
}
.u-social a:hover { color: #cfdcff; }
.u-social a:focus-visible {
  outline: 2px solid rgba(94, 140, 255, 0.85);
  outline-offset: 2px;
  border-radius: 8px;
}

@media (prefers-reduced-motion: reduce) {
  .u-bars span { animation: none; }
}
@media (max-width: 560px) {
  .u-wait { flex-direction: column; }
  .u-wait .btn { width: 100%; }
}

/* ---------- 1b. Showcase ----------
   The hero left roughly 400px of empty band between the social row and the
   plans, which is where the competitor puts its product shot. This uses the
   home hero's glass frame plus the travelling BorderBeam, not the window
   chrome: a bar with three dots reads as "here is a screenshot", the framed
   shot reads as "here is the app".

   The bottom fades out so the shot dissolves into the page instead of ending
   on a hard edge. The mask sits on the FRAME, so the border, the screenshot and
   the beam all dissolve together as one object. It starts at 68%, low enough
   that the beam is at full strength for most of its lap and only dims as it
   passes through the fade, rather than being chopped mid-travel. */
.u-showcase {
  position: relative;
  z-index: 1;
  /* Fixed, not clamped. A viewport-relative lift meant the gap under the social
     row changed with window width, so the icons only sat centred at one size.
     The hero adds this exact number back as padding. */
  margin-top: calc(var(--hero-lift) * -1);
}
.u-showcase-fig { margin: 0; }
/* The caption sits BELOW the masked frame, so it must not inherit the fade.
   Quiet by design: it labels the image, it does not compete with the headline. */
.u-showcase-cap {
  max-width: 1040px;
  margin: var(--s-3) auto 0;
  text-align: center;
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--text-mid);
}
.u-showcase-frame {
  max-width: 1040px;
  /* crop before the fade: a screenshot running off the bottom says there is
     more app down there. js/landing.js measures the beam's own clipped box,
     so the light still traces exactly what you can see. */
  max-height: clamp(300px, 40vw, 520px);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 68%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 68%, transparent 100%);
}
/* No mobile override for the lift: it is now paired with the hero's padding and
   breaking that pair is what decentred the icons in the first place. */

/* ---------- 2. Plans: one table, cards as column headers ----------
   The plan cards sit in <thead>, so a price and the features it buys are one
   object instead of a card block followed by a lookalike table. Ported from the
   shadcn PricingTable shape into plain markup: no wrapper divs pretending to be
   rows, just a real <table> the screen reader can navigate. */
/* The billing-period switch now uses the shared .plan-switch skin from
   css/prospira.css, identical to the home page. Nothing local left. */

/* Two cards, centred as a pair. They used to live in <thead>, which merged the
   price and its features into one object but forced them into the right 52% of
   the table: the feature-label column is empty above, and no amount of tuning
   centres a card grid that a table column is holding open. The features are
   still only in the table, so nothing is said twice. */
/* ONE column rhythm, shared by the cards and the table below them, so a plan's
   card sits exactly over the column of ticks that belongs to it.
     21%  feature labels
     29%  Entry
     29%  Champion
     21%  spacer, empty on purpose
   The trailing 21% is what makes this work: it mirrors the label column, which
   puts the midpoint of the two plan tracks on the page's centre line. Drop it
   and the pair slides right by half the label width, which is the layout that
   got rejected. Cards carry an 8px side margin instead of a grid gap, so the
   16px visual gap never moves a card off its track centre. */
.u-plan-grid,
.u-cmp {
  --col-label: 21%;
  --col-plan: 29%;
  --col-spacer: 21%;
}
.u-plan-grid {
  display: grid;
  grid-template-columns: var(--col-label) var(--col-plan) var(--col-plan) var(--col-label);
  gap: 0;
  max-width: 1040px;
  margin: 0 auto var(--s-4);
}
.u-plan-grid > .u-plan:nth-of-type(1) { grid-column: 2; }
.u-plan-grid > .u-plan:nth-of-type(2) { grid-column: 3; }
.u-plan-grid > .u-plan { margin-inline: 8px; }

.u-cmp-wrap {
  max-width: 1040px;
  margin-inline: auto;
  overflow-x: auto;
  /* Stops the swipe being handed to the page once the table reaches its end.
     Without this, finishing a horizontal drag inside the table pans the whole
     document sideways on a phone, which reads as the page being broken even
     though nothing actually overflows. */
  overscroll-behavior-x: contain;
}
.u-cmp {
  width: 100%;
  min-width: 620px;
  border-collapse: collapse;
  font-size: 0.92rem;
  /* fixed, not auto: with auto layout the colgroup widths are only suggestions
     and the long feature labels stretched the first column past its 21%, which
     dragged both plan columns to the right and out from under their cards. */
  table-layout: fixed;
}
/* ---- the plan card ---- */
.u-plan {
  position: relative;          /* the beam is absolutely placed against this */
  display: flex;
  flex-direction: column;
  padding: 22px;
  border-radius: var(--r-card-lg);
  border: 1px solid var(--border-mid);
  background: var(--card-bg);
  box-shadow: var(--card-shadow);
  text-align: left;
  font-weight: 400;
}
.u-plan.is-lead {
  border-color: var(--border-accent);
  background:
    radial-gradient(120% 150% at 100% 0%, rgba(42, 91, 255, 0.18), transparent 52%),
    radial-gradient(130% 160% at 0% 112%, rgba(30, 58, 174, 0.14), transparent 58%),
    linear-gradient(180deg, rgba(17, 24, 50, 0.5), rgba(10, 15, 34, 0.58));
}
.u-plan-top {
  display: flex;
  align-items: center;
  gap: 9px;
}
/* small ringed glyph, the shadcn plan header shape in Prospira's palette */
.u-plan-icon {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  border: 1px solid var(--tile-border);
  background: var(--tile-bg);
  color: var(--icon-color);
}
.u-plan-icon svg { width: 13px; height: 13px; }
/* the name goes quiet and monospaced so the PRICE is the loudest thing in the
   cell. In a pricing table the number is the decision, not the label. */
.u-plan-top h3 {
  font-family: var(--font-mono);
  font-size: 0.84rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--text-mid);
}
.u-tag {
  margin-left: auto;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border-strong);
  font-size: 0.63rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: #cfdcff;
  white-space: nowrap;
}
/* One positioning line per card. Deliberately says WHO the plan is for, not what
   is in it: the comparison table directly below already lists every feature, and
   repeating it here would be the duplication that made the first draft so long.
   min-height keeps both cards level when one line wraps and the other does not. */
.u-plan-desc {
  margin-top: 10px;
  min-height: 2.8em;
  font-size: 0.88rem;
  line-height: 1.4;
  color: var(--text-mid);
}
.u-price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}
.u-price-val {
  font-size: clamp(1.8rem, 3.4vw, 2.15rem);
  font-weight: 600;
  letter-spacing: -0.035em;
  line-height: 1;
  color: var(--text-hi);
  font-variant-numeric: tabular-nums;
}
.u-price-was {
  font-size: 0.92rem;
  /* line-height 1, same as .u-price-val. Without it this span inherited the
     body's 1.6, and since the row is baseline-aligned its taller line box hung
     below the big number and added 1px to the row. That 1px only appeared on
     Annual, the one period that shows a struck-through price, so the card
     twitched every time you switched to it. */
  line-height: 1;
  color: var(--text-low);
  text-decoration: line-through;
}
.u-price-per {
  display: block;
  margin-top: var(--s-1);
  /* Exactly two lines at this line-height (2 x 1.4em). The annual label,
     "Per month, billed $120 a year", is the longest of the three and wraps to
     two lines once the card drops under ~230px. At the old 2.6em the reserved
     box was shorter than the text, so switching to Annual grew the card by 4px
     and the whole row jumped. Reserve the tallest state and nothing moves. */
  min-height: 2.8em;
  font-size: 0.83rem;
  line-height: 1.4;
  color: var(--text-mid);
}
.u-plan .btn { width: 100%; margin-top: auto; }
.u-plan-msg {
  margin-top: 10px;
  padding: 9px 11px;
  border-radius: 10px;
  border: 1px solid rgba(245, 180, 95, 0.28);
  background: rgba(38, 29, 13, 0.5);
  font-size: 0.8rem;
  line-height: 1.45;
  color: #ffd9a0;
  white-space: normal;
}
.u-plan-msg[hidden] { display: none; }

/* The column widths that make the table share the cards' rhythm. These must
   exist for table-layout: fixed to have anything to honour; without them the
   browser splits the four tracks evenly at 25% and the plan columns drift off
   their cards by ~21px. */
.u-col-label { width: var(--col-label); }
.u-col-plan { width: var(--col-plan); }
.u-col-spacer { width: var(--col-spacer); }
/* The spacer is structure, not content, so it must carry NO paint at all. It
   inherits border-top from the generic td rule otherwise, which ran every row
   line out past the Champion column and left the table looking like it had a
   fourth column that was never finished. Killing the top border makes each row
   rule stop exactly at the Champion column's right edge. */
.u-cmp .u-cmp-spacer {
  border: 0;
  padding: 0;
  background: none;
}

/* ---- feature rows ---- */
.u-cmp tbody th {
  padding: 12px 16px 12px 0;
  text-align: left;
  font-weight: 400;
  color: var(--text-mid);
  border-top: 1px solid var(--border-soft);
}
.u-cmp tbody td {
  padding: 12px 8px;
  text-align: center;
  border-top: 1px solid var(--border-soft);
  border-left: 1px solid var(--border-soft);
}
.u-cmp tbody tr:first-child th,
.u-cmp tbody tr:first-child td { border-top: 0; }
.u-cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.u-cell svg { width: 17px; height: 17px; }
/* colour is never the only signal: every cell also carries sr-only text.
   0.42 alpha measured 3.28:1. That clears the 3:1 floor for a graphic, but this
   page holds everything to the 4.5:1 text floor, so it sits at 0.56 (5.15:1). */
.u-yes { color: #6ee7b7; }
.u-no { color: rgba(214, 225, 245, 0.56); }
.u-cell-text { font-size: 0.86rem; color: var(--text-low); }

/* The reassurance row under the table uses the shared .spec-pills component
   from css/prospira.css, the same glass pill the home hero runs. */


.u-plans-cta {
  display: flex;
  justify-content: center;
  margin-top: var(--s-4);
}

/* ---------- 3. Proof ----------
   The two figures reuse the home carousel's window chrome (.app-frame in
   css/prospira.css), so a screenshot is framed the same way everywhere on the
   site. The bespoke .u-frame this section used is gone. */
.u-proof {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr));
  gap: clamp(22px, 3vw, 30px);
  max-width: 1040px;
  margin-inline: auto;
}
.u-proof-item { margin: 0; }
/* no lift on hover, the owner rejected those. Border and shadow only. */
.u-proof-item .app-frame {
  transition: border-color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
}
.u-proof-item .app-frame:hover {
  border-color: rgba(120, 160, 255, 0.4);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 46px 100px -44px rgba(0, 0, 0, 0.9);
}
.u-proof-item figcaption { margin-top: var(--s-3); }
.u-kicker {
  display: block;
  margin-bottom: var(--s-1);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-low);
}
.u-proof-item h3 {
  font-size: 1.16rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.3;
  color: var(--text-hi);
}
.u-proof-item p {
  margin-top: var(--s-1);
  max-width: 46ch;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--text-mid);
}


/* ---------- 4. Trust ----------
   No rules here on purpose: the FAQ now uses the shared .home-faq-* skin
   from css/prospira.css, same as the home page. */

/* ---------- 5. Get the app ----------
   Trimmed to the shape competitors use for a release card: identity and version
   on the left, the action on the right, the checksum on its own full-width row.
   The earlier version carried a four-column spec grid and a requirements
   footnote, which was fourteen pieces of information for one download. */
.u-get-card {
  position: relative;
  max-width: 880px;
  margin-inline: auto;
  padding: clamp(22px, 3vw, 30px);
  border-radius: var(--r-panel);
  border: 1px solid var(--border-mid);
  background:
    radial-gradient(90% 140% at 50% 0%, rgba(42, 91, 255, 0.14), transparent 62%),
    var(--card-bg);
  box-shadow: var(--card-shadow);
  overflow: hidden;
}
/* the same top rim-light every other glass surface on the site carries */
.u-get-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--glass-hairline);
}
.u-get-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px 28px;
}
.u-get-id { min-width: 0; flex: 1 1 320px; }
.u-get-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.u-get-title h3 {
  font-size: 1.18rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--text-hi);
}
/* version sits with the name, the way a build number belongs to its build */
.u-get-ver {
  padding: 3px 9px;
  border-radius: 7px;
  border: 1px solid var(--border-strong);
  background: rgba(42, 91, 255, 0.08);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: #b9ccff;
}
.u-get-ver.is-pending {
  border-color: var(--border-soft);
  background: rgba(150, 180, 255, 0.04);
  color: var(--text-low);
}
.u-get-head p {
  margin-top: 9px;
  max-width: 50ch;
  font-size: 0.93rem;
  line-height: 1.6;
  color: var(--text-mid);
}
.u-get-action {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  flex-shrink: 0;
}
.u-get-action .u-get-meta {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-low);
}
.u-get-action .u-get-meta span { margin: 0 5px; }

/* Checksum. The ghost rule is exactly as wide as the hash that will replace it:
   64ch of the same monospace face, plus the 64 letter-spacing gaps that ch does
   not count. Showing the empty field states the promise instead of hiding it. */
.u-get-hash {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--border-soft);
}
.u-hash-label {
  display: block;
  margin-bottom: 10px;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-low);
}
.u-hash {
  overflow-x: auto;
  padding: 12px 14px;
  border-radius: 10px;
  border: 1px solid var(--border-soft);
  background: rgba(2, 4, 12, 0.42);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.5;
  letter-spacing: 0.01em;
  color: #cfdcff;
  word-break: break-all;
}
.u-hash-ghost {
  display: block;
  width: calc(64ch + 64 * 0.01em);
  max-width: 100%;
  height: 1.2em;
  border-bottom: 1px dashed rgba(150, 180, 255, 0.22);
}

/* ---------- Responsive ---------- */
@media (max-width: 860px) {
  .u-plan-grid { grid-template-columns: minmax(0, 1fr); max-width: 380px; gap: 16px; }
  .u-plan-grid > .u-plan:nth-of-type(1),
  .u-plan-grid > .u-plan:nth-of-type(2) { grid-column: 1; }
  .u-plan-grid > .u-plan { margin-inline: 0; }

  /* The table stops scrolling sideways here, it fits instead.
     A scrollable table on a phone has a nasty property: the scroll POSITION
     persists. Nudge it right once and the section stays offset from everything
     above and below it for the rest of the visit, which reads as the page being
     dragged out of line. Containing the overscroll does not help, because the
     problem is the position that is left behind, not the gesture.
     Two things make it fit: the spacer column goes to zero (it only existed to
     centre the plan pair under the cards, and below this width the cards stack
     so there is nothing to centre against), and min-width is dropped so
     table-layout: fixed distributes the real width across three columns. */
  .u-cmp {
    --col-label: 46%;
    --col-plan: 27%;
    --col-spacer: 0%;
    min-width: 0;
  }
  .u-cmp .u-cmp-spacer { width: 0; padding: 0; }
  /* tighter cells so 27% still reads comfortably at phone widths */
  .u-cmp caption { padding: 14px 12px; }
  .u-cmp thead th { padding: 12px 8px; }
  .u-cmp tbody th { padding: 12px 10px 12px 0; }
  .u-cmp tbody td { padding: 12px 4px; }
}
@media (max-width: 700px) {
  .u-get-action { align-items: flex-start; width: 100%; }
  .u-get-action .btn { width: 100%; }
}
@media (max-width: 560px) {
  .u-hero-cta { flex-direction: column; gap: 16px; }
  .u-hero-cta .btn { width: 100%; justify-content: center; }
}


/* The three states js/waitlist.js drives. They existed only on the home page's
   copy of this control, so on /utility/ the invalid border, the sending dim and
   the error colour all did nothing. */
.u-wait-input[aria-invalid="true"] { border-color: rgba(255, 130, 130, 0.7); }
.u-wait.is-sending { opacity: 0.75; }
.u-wait-msg.is-error { color: #ffb4b4; }
