@layer components {

  /* ==== Button =========================================================
     State model read off the design sheet:
       primary   default black -> hover blue -> active blue darkened
       secondary default outline -> hover black fill -> active grey fill
     Blue exists only as a hover accent. It is never a resting surface. */

  .btn {
    --btn-bg: transparent;
    --btn-fg: var(--text-primary);
    --btn-border: var(--border-strong);

    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    min-width: 154px;
    padding: var(--space-6) var(--space-8);
    border: var(--hairline) solid var(--btn-border);
    border-radius: var(--radius-pill);
    background: var(--btn-bg);
    color: var(--btn-fg);
    font-size: var(--size-h6);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition:
      background-color var(--duration-base) var(--ease-out),
      border-color var(--duration-base) var(--ease-out),
      color var(--duration-base) var(--ease-out);

    &:hover { color: var(--btn-fg); }

    &:disabled,
    &[aria-disabled="true"] {
      --btn-bg: var(--color-grey-100);
      --btn-fg: var(--text-disabled);
      --btn-border: var(--border-default);
      cursor: not-allowed;
      pointer-events: none;
    }
  }

  .btn--sm {
    padding: var(--space-3) var(--space-8);
    font-size: var(--size-body);
    font-weight: var(--weight-light);
    line-height: var(--leading-body);
  }

  .btn--block {
    width: 100%;
  }

  .btn--primary {
    --btn-bg: var(--color-black);
    --btn-fg: var(--text-on-dark);
    --btn-border: var(--color-black);

    &:hover:not(:disabled) {
      --btn-bg: var(--color-primary);
      --btn-border: var(--color-primary);
    }
    &:active:not(:disabled) {
      --btn-bg: var(--color-primary-pressed);
      --btn-border: var(--color-primary-pressed);
    }
  }

  .btn--secondary {
    &:hover:not(:disabled) {
      --btn-bg: var(--color-black);
      --btn-fg: var(--text-on-dark);
    }
    &:active:not(:disabled) {
      --btn-bg: var(--color-grey-300);
      --btn-fg: var(--text-on-dark);
      --btn-border: var(--color-grey-300);
    }
  }

  /* ==== Icon button ==================================================== */

  .icon-btn {
    --icon-btn-bg: transparent;
    --icon-btn-fg: var(--color-black);
    --icon-btn-border: var(--border-strong);
    --icon-btn-size: var(--control-icon-md);
    --icon-btn-glyph: 2rem;

    display: inline-grid;
    flex: 0 0 auto;
    place-items: center;
    inline-size: var(--icon-btn-size);
    block-size: var(--icon-btn-size);
    padding: 0;
    border: var(--hairline) solid var(--icon-btn-border);
    border-radius: var(--radius-full);
    background: var(--icon-btn-bg);
    color: var(--icon-btn-fg);
    cursor: pointer;
    transition:
      background-color var(--duration-base) var(--ease-out),
      border-color var(--duration-base) var(--ease-out),
      color var(--duration-base) var(--ease-out);

    & > .icon {
      --icon-size: var(--icon-btn-glyph);
    }

    &:hover:not(:disabled) {
      --icon-btn-bg: var(--color-black);
      --icon-btn-fg: var(--color-white);
    }
    &:active:not(:disabled) {
      --icon-btn-bg: var(--color-grey-300);
      --icon-btn-fg: var(--color-white);
      --icon-btn-border: var(--color-grey-300);
    }
    &:disabled,
    &[aria-disabled="true"] {
      --icon-btn-fg: var(--text-disabled);
      --icon-btn-border: var(--border-default);
      cursor: not-allowed;
      pointer-events: none;
    }
  }

  .icon-btn--lg {
    --icon-btn-size: var(--control-icon-lg);
  }

  .icon-btn--sm {
    --icon-btn-size: var(--control-icon-sm);
    --icon-btn-glyph: 1.5rem;
  }

  /* ==== Card =========================================================== */

  .card {
    border: var(--hairline) solid var(--border-default);
    border-radius: var(--radius-card);
    background: transparent;
  }

  .card--lg {
    border-radius: var(--radius-card-lg);
  }

  /* Chapter card — body above a hairline-separated reference footer. */

  .chapter-card {
    display: flex;
    flex-direction: column;
    background: var(--surface-page);
    border: var(--hairline) solid var(--border-default);
    border-radius: var(--radius-card);
    overflow: hidden;
    text-decoration: none;
    transition: border-color var(--duration-base) var(--ease-out);
  }

  /* Scoped to the anchor on purpose. Every chapter card is a <div> until a
     study is published, and a hover state on something that does not respond
     tells the reader the card is a link when it is not. */
  a.chapter-card:hover {
    border-color: var(--border-strong);
    color: inherit;

    & .icon-btn {
      --icon-btn-bg: var(--color-black);
      --icon-btn-fg: var(--color-white);
    }
  }

  .chapter-card__body {
    position: relative;
    display: flex;
    flex: 1;
    flex-direction: column;
    justify-content: space-between;
    gap: var(--space-16);
    min-block-size: 36rem;
    padding: var(--space-12);
  }

  .chapter-card__head {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
  }

  .chapter-card__title {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    font-size: var(--size-h4);

    /* The heading takes the block's size; the standfirst must not inherit it. */
    & > h3 { font-size: inherit; }
    & > p { font-size: var(--size-body); }
  }

  /* Status and action share the body's bottom row, layered above the card
     surface rather than tucked into the reference footer. */
  .chapter-card__action {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-6);
  }

  /* Reference code, tracked out like a specimen label. */
  .chapter-card__footer {
    padding: var(--space-5) var(--space-12);
    font-size: var(--size-caption);
    letter-spacing: 0.25em;
    color: var(--text-secondary);
  }

  /* Stat card — inverse surface, numeric face, uniform radius. */

  .stat-card {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: var(--space-8);
    min-block-size: 20.5rem;
    padding: var(--space-6);
    border-radius: var(--radius-block);
    background: var(--surface-inverse);
    color: var(--text-on-dark);
  }

  .stat-card__value {
    font-family: var(--font-numeric);
    font-size: var(--size-stat);
    font-weight: var(--weight-bold);
    line-height: var(--leading-tight);
    letter-spacing: -0.02em;
  }

  .stat-card__label {
    max-width: 16ch;
    font-size: var(--size-body-sm);
  }

  /* Feature — icon, label, supporting copy. No container. */

  .feature {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);

    & > .icon {
      --icon-size: 2rem;
    }
  }

  .feature__text {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }

  .feature__title {
    font-size: var(--size-h6);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
  }

  .feature__body {
    font-size: var(--size-body-sm);
    color: var(--text-secondary);
  }

  /* Partner card — quoted logo panel over an inset attribution block. */

  .partner-card {
    --partner-pad: var(--space-4);

    display: flex;
    flex-direction: column;
    padding: var(--partner-pad);
    border: var(--hairline) solid var(--border-default);
    border-radius: var(--radius-card);
  }

  .partner-card__panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-12);
    padding-block: var(--space-20);
    text-align: center;
  }

  /* The logo links to the partner's own site. Opacity is the hover cue because
     the mark is an <img>, not a mask, so it cannot take a colour change the way
     the rest of the system's hovers do. Opens in a new tab: the homepage is the
     one place the signup lives, and losing it to an outbound click is worse
     than the extra tab. */
  .partner-card__link {
    display: inline-block;
    text-decoration: none;
    transition: opacity var(--duration-base) var(--ease-out);

    &:hover { opacity: 0.65; }
  }

  .partner-card__logo {
    inline-size: 240px;
    max-inline-size: 100%;
    block-size: auto;
  }

  .partner-card__note {
    max-inline-size: 280px;
    font-size: var(--size-caption);
    color: var(--text-secondary);
  }

  /* Sits flush inside the card, so its bottom corners are the card's corner
     minus the card's padding — concentric, not a guessed value. */
  .person {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    padding: var(--space-5);
    border-radius:
      var(--radius-inset) var(--radius-inset)
      calc(var(--radius-card-corner) - var(--partner-pad, 0px))
      calc(var(--radius-card-corner) - var(--partner-pad, 0px));
    background: var(--color-grey-100);
  }

  .person__avatar {
    flex: 0 0 auto;
    inline-size: 56px;
    block-size: 56px;
    border-radius: var(--radius-full);
    object-fit: cover;
    background: var(--color-grey-200);
  }

  .partner-card--narrow { max-inline-size: 28rem; }

  .person__name {
    font-size: var(--size-h6);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
  }

  .person__role {
    font-size: var(--size-caption);
    color: var(--text-secondary);
  }

  /* Plan card — tier, price, inclusions, one action. */

  .plan-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-7);
    padding: var(--space-8) var(--space-10);
    border: var(--hairline) solid var(--border-default);
    border-radius: var(--radius-card-lg);
  }

  .plan-card__head {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
  }

  .plan-card__tier {
    font-size: var(--size-h5);
    font-weight: var(--weight-medium);
  }

  .plan-card__price {
    font-size: var(--size-h2);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
  }

  .plan-card__note {
    font-size: var(--size-body-sm);
    color: var(--text-secondary);
  }

  /* flex-grow pushes the action to the card's bottom edge, so the buttons line
     up across cards with different numbers of inclusions. */
  .plan-card__list {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--space-4);
    font-size: var(--size-body-sm);
    color: var(--text-secondary);

    & li {
      display: flex;
      align-items: center;
      gap: var(--space-3);
    }

    & .icon {
      color: var(--text-primary);
    }
  }

  .plan-card > .btn { margin-block-start: auto; }

  /* ==== Field ==========================================================
     Pill input matched to .btn geometry so form rows align. */

  .field {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .field__label {
    font-size: var(--size-h6);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
  }

  .field__input {
    padding: var(--space-6) var(--space-8);
    border: var(--hairline) solid var(--border-default);
    border-radius: var(--radius-pill);
    background: var(--surface-raised);
    font-size: var(--size-body);
    font-weight: var(--weight-light);
    line-height: var(--leading-tight);
    transition: border-color var(--duration-base) var(--ease-out);

    &::placeholder { color: var(--text-disabled); }
    &:hover { border-color: var(--border-strong); }

    &[aria-invalid="true"] {
      border-color: var(--color-primary);
      border-width: 2px;
    }
  }

  .field__error {
    font-size: var(--size-caption);
    color: var(--color-primary);
  }

  /* Wrapper so a leading icon can be centred on the input alone, not on the
     field including its error line. */
  .field__control {
    position: relative;
    display: flex;

    & > .field__input { flex: 1; }
  }

  .field__icon {
    position: absolute;
    inset-inline-start: var(--space-6);
    inset-block-start: 50%;
    translate: 0 -50%;
    --icon-size: 1.25rem;
    color: var(--text-disabled);
    pointer-events: none;
  }

  .field__control:has(> .field__icon) > .field__input {
    padding-inline-start: calc(var(--space-6) + 1.25rem + var(--space-3));
  }
}

@layer components {

  /* ==== Icon ===========================================================
     Phosphor Regular, 256-grid, recoloured through a CSS mask so the glyph
     inherits currentColor. No JS and no extra markup, so icons survive with
     scripting off. Icons are decorative — the control carries the label. */

  .icon {
    display: inline-block;
    flex: 0 0 auto;
    inline-size: var(--icon-size, 1.5rem);
    block-size: var(--icon-size, 1.5rem);
    background: currentColor;
    mask: var(--icon) center / contain no-repeat;
    -webkit-mask: var(--icon) center / contain no-repeat;
  }

  .icon--sm { --icon-size: 1.25rem; }
  .icon--lg { --icon-size: 2rem; }

  .icon--arrow-right    { --icon: url("../img/icons/arrow-right.svg"); }
  .icon--arrow-down     { --icon: url("../img/icons/arrow-down.svg"); }
  .icon--arrow-up-right { --icon: url("../img/icons/arrow-up-right.svg"); }
  .icon--seal-check     { --icon: url("../img/icons/seal-check.svg"); }
  .icon--check-circle   { --icon: url("../img/icons/check-circle.svg"); }
  .icon--x              { --icon: url("../img/icons/x.svg"); }
  .icon--handshake      { --icon: url("../img/icons/handshake.svg"); }
  .icon--folder-dashed  { --icon: url("../img/icons/folder-dashed.svg"); }
  .icon--address-book   { --icon: url("../img/icons/address-book.svg"); }
  .icon--flag-banner    { --icon: url("../img/icons/flag-banner-fold.svg"); }
  .icon--envelope       { --icon: url("../img/icons/envelope-simple.svg"); }
  .icon--user           { --icon: url("../img/icons/user.svg"); }
}

@layer components {

  /* ==== Inverse surface =================================================
     Redefines the semantic aliases instead of adding a variant to every
     component, so cards, text and hairlines adapt by inheritance. */

  .surface-dark {
    --surface-page: var(--color-black);
    --surface-raised: rgb(255 255 255 / 0.06);
    --text-primary: var(--color-white);
    --text-secondary: var(--color-grey-200);
    --border-default: rgb(255 255 255 / 0.28);
    --border-strong: var(--color-white);
    --focus-color: var(--color-white);
    /* Primary blue is 1.5:1 on black — unreadable. On the inverse surface a
       hover brightens instead. */
    --link-hover: var(--color-white);

    background: var(--color-black);
    color: var(--text-primary);

    & .btn--primary {
      --btn-bg: var(--color-white);
      --btn-fg: var(--color-black);
      --btn-border: var(--color-white);

      &:hover:not(:disabled) {
        --btn-bg: var(--color-primary);
        --btn-fg: var(--color-white);
        --btn-border: var(--color-primary);
      }
    }

    & .btn--secondary:hover:not(:disabled) {
      --btn-bg: var(--color-white);
      --btn-fg: var(--color-black);
    }

    & .icon-btn:hover:not(:disabled) {
      --icon-btn-bg: var(--color-white);
      --icon-btn-fg: var(--color-black);
    }
  }

  /* ==== Section ========================================================= */

  .section {
    position: relative;
    padding-block: var(--space-24);
  }

  .section--tight { padding-block: var(--space-16); }
  .section--white { background: var(--color-white); }

  /* The benefits block carries its own bottom padding, so the section must not
     add more — otherwise the dashed grid stops short of the section's edge and
     leaves a dead band of blank surface below it. */
  .section--white:has(> .benefits:last-child) { padding-block-end: 0; }

  .section__head {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    margin-block-end: var(--space-16);
  }

  .section__lede {
    max-inline-size: 48rem;
    font-size: var(--size-body);
    color: var(--text-secondary);
  }

  /* The dashed construction marks from the logo, carried into the page as
     faint vertical guides. Decorative, never a layout dependency. */

  .guides {
    position: relative;

    &::before,
    &::after {
      content: "";
      position: absolute;
      inset-block: 0;
      inline-size: var(--hairline);
      background: repeating-linear-gradient(
        to bottom,
        var(--guide) 0 var(--dash),
        transparent var(--dash) calc(var(--dash) + var(--dash-gap))
      );
      pointer-events: none;
    }

    &::before { inset-inline-start: var(--guide-x1); }
    &::after { inset-inline-start: var(--guide-x2); }
  }

  /* The page's one horizontal box: 1440px max, centred, and never closer to
     the viewport edge than --frame-inset. The hero panel and any section that
     has to line up with it both use this, so they cannot drift apart. */

  .frame {
    inline-size: min(var(--container-max), 100% - 2 * var(--frame-inset));
    margin-inline: auto;
  }

  /* Same box, plus the hero's inner text column. */
  .frame-column {
    padding-inline: var(--column-pad);
  }

  .section > .container { position: relative; z-index: 1; }

  /* ==== Header ========================================================== */

  .site-header {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-8);
    padding-block: 0 var(--space-3);
  }

  /* On a subpage the header is its own dark band rather than the top of the
     hero, so it needs the vertical padding the hero was providing. */
  .site-header--page { padding-block: var(--space-8); }

  .site-nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-10);
    font-size: var(--size-body-sm);

    & a {
      text-decoration: none;

      &:hover { text-decoration: underline; }
    }
  }

  /* ==== Hero =============================================================
     The light panel is the page's masthead: near-full-bleed with a thin black
     frame around it, and the site header is its first row. Horizontal padding
     lives on the rows, not the panel, so a row's dashed rule can run the full
     panel width while its content still sits on the text column. */

  .hero-frame {
    padding-block: var(--frame-inset);
    background: var(--color-black);
  }

  .hero {
    --guide: rgb(0 0 0 / 0.18);
    --guide-x1: var(--column-pad);
    --guide-x2: calc(100% - var(--column-pad));

    display: grid;
    grid-template-rows: auto 1fr;
    min-block-size: min(54rem, calc(100svh - 2 * var(--frame-inset)));
    padding-block: var(--space-16) var(--space-24);
    border-radius: var(--radius-card-lg);
    overflow: hidden;
    background-color: var(--color-grey-100);
    color: var(--color-black);

    & > * { padding-inline: var(--column-pad); }
  }

  .hero .site-header,
  .hero__body {
    position: relative;
  }

  .hero__body {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: var(--space-6);
    align-self: end;
    padding-block-end: var(--space-6);
  }

  .hero__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-12);
  }

  .hero__lede {
    max-inline-size: 60ch;
    font-size: var(--size-body-sm);
    color: var(--text-secondary);
  }

  /* ==== Values =========================================================== */

  .values {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: var(--space-16);
    align-items: start;
  }

  .values__list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    font-size: var(--size-h3);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
  }

  .values__note {
    font-size: var(--size-body);
    color: var(--text-secondary);
  }

  /* ==== Badge ============================================================ */

  .badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-5);
    border-radius: var(--radius-pill);
    background: rgb(0 0 0 / 0.08);
    color: var(--text-secondary);
    font-size: var(--size-caption);
    white-space: nowrap;
  }

  /* ==== Studies rail =====================================================
     Horizontal scroll with snap. Scrollbar is left visible — a rail with no
     affordance is a rail nobody scrolls. */

  /* The rail is full-width, not inside the aligned box, so cards can run past
     the right edge of the screen. Its first card still starts on the text
     column: the same max() the .frame box resolves to, plus the column inset. */

  .rail {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: min(34rem, 85vw);
    gap: var(--space-16);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    padding-inline: var(--column-offset) var(--frame-inset);
    padding-block-end: var(--space-4);
    scroll-padding-inline-start: var(--column-offset);

    & > * { scroll-snap-align: start; }
  }

  /* ==== Numbered rows ==================================================== */

  /* Rules run the full width of the aligned box; the text stays on the column,
     so the inset lives on the row rather than on a wrapper. */

  .numbered {
    position: relative;
  }

  .numbered__row {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    gap: var(--space-12);
    padding-block: var(--space-14);
    padding-inline: var(--column-offset);
  }

  .numbered__title {
    display: flex;
    gap: var(--space-8);
    font-size: var(--size-h3);
    font-weight: var(--weight-medium);
  }

  .numbered__index {
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
  }

  .numbered__body {
    font-size: var(--size-body-sm);
    color: var(--text-secondary);
  }

  /* ==== Plan card, featured ============================================== */

  .plan-card--featured {
    --text-primary: var(--color-black);
    --text-secondary: var(--color-grey-300);
    --border-default: var(--color-grey-200);
    --border-strong: var(--color-black);

    background: var(--color-grey-100);
    color: var(--text-primary);

    & .btn--primary {
      --btn-bg: var(--color-black);
      --btn-fg: var(--color-white);
      --btn-border: var(--color-black);

      &:hover:not(:disabled) {
        --btn-bg: var(--color-primary);
        --btn-border: var(--color-primary);
      }
    }
  }

  .plan-card__period {
    font-size: var(--size-body-sm);
    font-weight: var(--weight-light);
    color: var(--text-secondary);
  }

  /* ==== Prose ============================================================
     Long-form copy for a policy or editorial page. The homepage composes
     bespoke blocks and needs no such thing; a page that is one continuous flow
     of text needs vertical rhythm, and the base layer resets lists away, so
     they have to be reinstated here rather than globally. */

  .prose {
    max-inline-size: var(--measure);
    font-size: var(--size-body);
    line-height: var(--leading-body);

    /* Rhythm from the gap between siblings, so no element carries a trailing
       margin that has to be cancelled at the end of the flow. */
    & > * + * { margin-block-start: var(--space-6); }

    & h2 {
      margin-block-start: var(--space-16);
      font-size: var(--size-h5);
      font-weight: var(--weight-medium);
      line-height: var(--leading-tight);
    }

    & h3 {
      margin-block-start: var(--space-10);
      font-size: var(--size-h6);
      font-weight: var(--weight-medium);
    }

    & ul {
      padding-inline-start: var(--space-6);
      list-style: disc;
    }

    & li + li { margin-block-start: var(--space-3); }

    & strong { font-weight: var(--weight-medium); }
  }

  /* A page title, not the homepage's 100px display type. */
  .page-title {
    font-size: var(--size-h3);
    line-height: var(--leading-tight);
  }

  .page-meta {
    margin-block-start: var(--space-4);
    font-size: var(--size-caption);
    color: var(--text-secondary);
  }

  /* Holds the title and its meta line so the gap down to the body is set once,
     here, rather than by whichever element happens to come first in .prose. */
  .page-head { margin-block-end: var(--space-12); }

  /* ==== Closing CTA ====================================================== */

  .cta {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-8);
  }

  .cta__text {
    max-inline-size: 34ch;
    font-size: var(--size-h4);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
  }

  /* ==== Footer =========================================================== */

  .site-footer {
    padding-block: var(--space-16) var(--space-12);
  }

  .site-footer__top {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) repeat(2, minmax(0, 1fr));
    gap: var(--space-12);
    align-items: start;
  }

  .link-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    font-size: var(--size-body-sm);

    & a {
      text-decoration: none;

      &:hover { text-decoration: underline; }
    }
  }

  .site-footer__legal {
    max-inline-size: 44ch;
    margin-block-start: var(--space-16);
    font-size: var(--size-caption);
    color: var(--text-secondary);
  }
}

@layer components {

  /* ==== Narrow viewports =================================================
     Collapse order only. Nothing here changes a colour, size or radius —
     those come from tokens and stay identical across breakpoints. */

  @media (width < 64rem) {
    .values,
    .numbered__row {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--space-6);
    }

    .site-footer__top {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .hero {
      padding-block: var(--space-8) var(--space-12);
      min-block-size: 0;
    }

  }

  @media (width < 40rem) {
    .section { padding-block: var(--space-16); }

    /* The header is the logo alone on a phone. Every destination the nav lists
       is also in the footer, which is a single column at this width, so
       nothing becomes unreachable. */
    .site-nav { display: none; }

    .site-header {
      align-items: center;
      gap: var(--space-6);
    }

    .site-footer__top {
      grid-template-columns: minmax(0, 1fr);
    }

    /* The hero packs the logo, the headline and the lede into one screen, so
       the headline ended up sitting directly under the header's rule. The body
       takes the space rather than the header, so the rule stays with the logo
       it belongs to. */
    .hero__body { padding-block-start: var(--space-12); }

    /* `.hero` clips for its corner radius, so anything wider than the column is
       lost rather than spilled. At 320px with WCAG 1.4.12's letter and word
       spacing applied the headline overran by 12px; breaking a long word is the
       lesser cost. */
    .hero__body h1 { overflow-wrap: break-word; }

    /* At this width two of the three laws wrap onto a second line, so an 8px
       gap between laws was smaller than the line spacing inside one — the
       three stopped reading as three. The gap between items has to beat the
       gap within an item, or the grouping inverts. */
    .values__list { gap: var(--space-8); }
    .values { gap: var(--space-10); }

    /* The studies rail stops being a rail. A horizontal scroller hides two of
       three chapters behind a gesture with no affordance, next to a page that
       is otherwise one column. Stacked, all three are visible and countable,
       and the card loses its 36rem desktop height so the stack stays short. */
    .rail {
      grid-auto-flow: row;
      grid-template-columns: minmax(0, 1fr);
      grid-auto-columns: auto;
      gap: var(--space-6);
      /* Both axes: leaving overflow-y at `auto` would keep `visible` from
         taking effect and the rail would still be a scroll container. */
      overflow: visible;
      scroll-snap-type: none;
      padding-inline: var(--column-offset);
      padding-block-end: 0;
    }

    .chapter-card__body {
      min-block-size: 0;
      gap: var(--space-8);
      padding: var(--space-8);
    }

    .chapter-card__head { gap: var(--space-6); }

    .btn { inline-size: 100%; }

    /* At 320px the email address plus .btn's 154px floor and 32px of side
       padding measured 349px in a 320px viewport — 1.4.10 reflow. The button
       goes full width and drops the floor; the address may break mid-word,
       which beats scrolling the page sideways. */
    .cta .btn {
      inline-size: 100%;
      min-width: 0;
      overflow-wrap: anywhere;
    }
  }
}

@layer components {

  /* ==== Signup form ====================================================== */

  .signup {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  .signup [hidden] { display: none; }
}

@layer components {

  /* ==== Logo =============================================================
     Masked, not <img>, so the wordmark takes currentColor and works on both
     the light and the inverse surface from one file. */

  .logo {
    display: block;
    inline-size: var(--logo-width, 10rem);
    aspect-ratio: 370 / 101;
    background: currentColor;
    mask: url("../img/logo.svg") left center / contain no-repeat;
    -webkit-mask: url("../img/logo.svg") left center / contain no-repeat;
  }

  .logo--header { --logo-width: clamp(9rem, 7rem + 8.9vw, 15rem); }
  .logo--footer { --logo-width: clamp(7.5rem, 6rem + 4vw, 10rem); }

  /* On a phone the footer is a single full-width column and the mark read as
     undersized against it, so it takes its own width instead of riding the
     desktop clamp down. This has to sit after the rule above: both selectors
     have the same specificity and a media query adds none, so source order is
     what decides it. */
  @media (width < 40rem) {
    .logo--footer { --logo-width: 12rem; }
  }

  /* ==== Grids ============================================================ */

  /* ==== Benefits grid ====================================================
     Three columns sitting on a visible dashed column grid: one line on each
     outer edge and one down the middle of each gutter. The line positions are
     derived from the same column and gap values the grid itself uses, so they
     cannot drift out of register. */

  /* ==== Line grid ========================================================
     The dashed layout grid: four verticals on the content column at equal
     thirds, plus full-bleed horizontal rules top and bottom. The verticals
     need an element of their own — pseudo-elements are spent on the two
     horizontals, and the two axes have different horizontal extents. */

  .lined {
    position: relative;
  }

  .lined__cols {
    position: absolute;
    inset-block: 0;
    inset-inline: var(--column-offset);
    z-index: 0;
    pointer-events: none;

    --v-dash: repeating-linear-gradient(
      to bottom,
      var(--border-default) 0 var(--dash),
      transparent var(--dash) calc(var(--dash) + var(--dash-gap))
    );

    background-image: var(--v-dash), var(--v-dash), var(--v-dash), var(--v-dash);
    background-size: var(--hairline) 100%;
    background-repeat: no-repeat;

    /* Equal thirds: the four lines divide the column into three bands of the
       same width. Content sits flush inside each band, so a gutter shortens a
       column without ever moving a band edge. */
    background-position: 0 0, 33.3333% 0, 66.6667% 0, 100% 0;
  }

  .lined > *:not(.lined__cols) {
    position: relative;
    z-index: 1;
  }

  .benefits {
    --benefit-gap: var(--space-20);
    --benefit-cell-pad: var(--space-6);

    margin-block-start: var(--space-16);
    padding-block: var(--space-24);
  }

  .benefits__inner {
    padding-inline: var(--column-offset);
  }

  /* Spacing here is owned by .benefits' padding, not by the head's margins.
     The inline inset matches a grid cell's, so the heading shares a left edge
     with the first column's content rather than sitting on the band line. */
  .benefits .section__head--sub {
    margin-block: 0 var(--space-24);
    padding-inline-start: var(--benefit-cell-pad);
  }

  /* Cells tile the bands exactly — no column gap. The gutter is cell padding
     instead, so every column's content is inset from its band edge by the same
     amount. With a real gap, column n would start n * (gap / 3) further in
     than the band edge, and the three offsets would never match. */
  .feature-grid {
    --benefit-row-gap: var(--space-30);

    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: 0;
    row-gap: var(--benefit-row-gap);
  }

  .feature-grid > * {
    position: relative;
    padding-inline:
      var(--benefit-cell-pad)
      calc(var(--benefit-gap) - var(--benefit-cell-pad));
  }

  /* Rule between rows, carried across the gutters so it reads as one
     continuous line rather than three segments. Declared on every cell and
     switched on per breakpoint, because which cells end a row depends on the
     column count. */
  .feature-grid > *::after {
    content: none;
    position: absolute;
    inset-block-end: calc(-1 * var(--benefit-row-gap) / 2);
    inset-inline: 0;
    block-size: var(--hairline);
    background: repeating-linear-gradient(
      to right,
      var(--border-default) 0 var(--dash),
      transparent var(--dash) calc(var(--dash) + var(--dash-gap))
    );
  }

  /* Three columns: rows are 1-3 and 4-5. */
  .feature-grid > :nth-child(-n+3)::after { content: ""; }

  /* Two columns: rows are 1-2, 3-4, 5. The dashed column grid is a wide-screen
     device, so it comes off rather than being recomputed for two columns. */
  @media (width < 64rem) {
    .lined__cols { display: none; }

    .feature-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    .feature-grid > *::after { content: none; }
    .feature-grid > :nth-child(-n+4)::after { content: ""; }
  }

  /* One column. */
  @media (width < 40rem) {
    .feature-grid { grid-template-columns: minmax(0, 1fr); }

    .feature-grid > *::after { content: none; }
    .feature-grid > :not(:last-child)::after { content: ""; }
  }

  .plans {
    padding-block: var(--space-16) var(--space-20);
  }

  .plans__inner {
    padding-inline: var(--column-offset);
  }

  .plan-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
    gap: var(--space-8);
    align-items: stretch;
  }

  /* A section label, not a display heading. */
  .section__label {
    font-size: var(--size-h5);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
  }

}

@layer components {

  .section__head--sub {
    margin-block: var(--space-20) var(--space-12);
  }

  .section__head--sub > h3 { font-size: var(--size-h5); }
}

@layer components {

  /* The dark band under the hero continues the hero's dashed guides, so its
     text column lands on the same vertical rules as the headline above it. */

  .values-section {
    --guide: rgb(255 255 255 / 0.18);
    --guide-x1: var(--column-pad);
    --guide-x2: calc(100% - var(--column-pad));

    /* Padding sits on the aligned box, not the section, so the guides run the
       full height of the band rather than stopping at the text. */
    & > .frame-column { padding-block: var(--space-24); }
  }

  /* Has to live here, after the rule above. Written as a nested `&` selector,
     that base rule compiles to `:is(.values-section) > .frame-column` — the
     same 0-2-0 specificity as this one — and a media query adds none. When this
     override sat 274 lines earlier in the same layer the base simply won on
     source order, and the band kept its 6rem desktop padding on every phone. */
  @media (width < 64rem) {
    .values-section > .frame-column { padding-block: var(--space-16); }
  }
}

@layer components {

  /* ==== Layer stack ======================================================
     Methodology diagram: five dashed isometric planes, each labelled at its
     right vertex. Rows reuse the numbered grid, so a label lands on the same
     column as the paragraph above it. Each plane's fill occludes the one
     below, so the earlier item must paint on top — hence explicit z-index
     rather than DOM order, which stays in reading order. */

  .layers {
    /* Half of a plane sits outside its own row, so the stack has to reserve
       that overhang. Derived from the column ratio against the container's
       own width, so it tracks the grid instead of being a magic number. */
    position: relative;
    container-type: inline-size;
    --layer-step: var(--space-20);
    --layer-overhang: calc((23.7cqi - var(--layer-step)) / 2);

    padding-inline: var(--column-offset);
    padding-block: var(--layer-overhang);
    margin-block-end: var(--space-14);
  }

  .layers__item {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    align-items: center;
    gap: var(--space-12);
    block-size: var(--layer-step);
  }

  .layers__item:nth-child(1) { z-index: 5; }
  .layers__item:nth-child(2) { z-index: 4; }
  .layers__item:nth-child(3) { z-index: 3; }
  .layers__item:nth-child(4) { z-index: 2; }
  .layers__item:nth-child(5) { z-index: 1; }

  .layers__plane {
    position: relative;

    & > svg {
      position: absolute;
      inset-inline: 0 var(--space-8);
      inset-block-start: 50%;
      translate: 0 -50%;
      aspect-ratio: 626 / 298;
      block-size: auto;
      fill: var(--surface-page);
      stroke: var(--text-primary);
      stroke-width: var(--dash-plane-weight);
      stroke-dasharray: var(--dash-plane) var(--dash-plane-gap);
    }

    /* Dash weight stays identical however wide the column gets. */
    & polygon { vector-effect: non-scaling-stroke; }
  }

  .layers__label {
    font-size: var(--size-h4);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
  }

  /* Holds the <symbol> the planes reference. Renders nothing itself. */
  .svg-sprite {
    position: absolute;
    inline-size: 0;
    block-size: 0;
    overflow: hidden;
  }

  /* The diagram belongs to the row above it, so no rule divides them. */
  .numbered:has(~ .layers) > .numbered__row:last-child {
    padding-block-end: var(--space-8);
    border-block-end: none;
  }

  @media (width < 64rem) {
    .layers {
      --layer-step: var(--space-12);
      --layer-overhang: calc((28cqi - var(--layer-step)) / 2);
    }

    .layers__item {
      grid-template-columns: minmax(0, 42%) minmax(0, 1fr);
      gap: var(--space-6);
    }

    .layers__label {
      font-size: var(--size-h6);
      text-wrap: balance;
    }
  }

  /* On a phone the two longest labels wrap to two lines, which at a 48px row
     height left them touching the label above and below. The row height is
     the spacing here, so it is the row height that has to grow. */
  @media (width < 40rem) {
    .layers { --layer-step: var(--space-20); }
  }
}

@layer components {

  /* ==== Dashed rules =====================================================
     One definition for every horizontal dashed rule on the site. Drawn as a
     repeating gradient rather than `border: dashed`, because the native
     pattern is browser-chosen: at 1px it renders roughly 2 on / 2 off, which
     is far denser than the design's 12 / 7. */

  .benefits::before,
  .plans::before,
  .plans::after,
  .numbered::before,
  .numbered__row::after,
  .hero .site-header::after,
  .hero__body::after,
  .chapter-card__body::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    block-size: var(--hairline);
    background: repeating-linear-gradient(
      to right,
      var(--dash-color, var(--border-default)) 0 var(--dash),
      transparent var(--dash) calc(var(--dash) + var(--dash-gap))
    );
    pointer-events: none;
  }

  .benefits::before,
  .plans::before,
  .numbered::before { inset-block-start: 0; }

  .plans::after,
  .numbered__row::after,
  .hero .site-header::after,
  .hero__body::after,
  .chapter-card__body::after { inset-block-end: 0; }

  /* Inside the hero panel the rules follow the panel's own guide colour. */
  .hero .site-header,
  .hero__body { --dash-color: var(--guide); }

  /* The last row before the diagram has no rule: the diagram belongs to it. */
  .numbered:has(~ .layers) > .numbered__row:last-child::after { content: none; }
}

@layer components {

  /* ==== Dialog ===========================================================
     Deliberate local exception. This is a reproduction of a component Marek
     picked, so its scale is its own: 14/18px text, 8-12px radii, 600 weight.
     None of that matches the site's system (13/16/18, pill and 16/28/30px
     radii), and it is not meant to — the values live here and nowhere else.
     Do not "correct" them to the tokens.

     Native <dialog>, so the browser owns the top layer, focus trapping and
     Escape. Opened by JS; the trigger is a real mailto link, so the signup
     still works with scripting off. */

  .modal {
    --modal-radius: 12px;
    --modal-control-radius: 10px;
    --modal-pad: 32px;
    --modal-gap: 20px;
    --modal-text: 0.875rem;
    --modal-muted: var(--color-grey-300);

    max-inline-size: min(400px, 100% - 2 * var(--frame-inset));
    padding: 0;
    border: var(--hairline) solid var(--color-grey-200);
    border-radius: var(--modal-radius);
    background: var(--color-white);
    color: var(--color-black);
    box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.05), 0 4px 6px -4px rgb(0 0 0 / 0.05);

    &::backdrop { background: rgb(0 0 0 / 0.8); }

    &[open] { animation: modal-in var(--duration-base) var(--ease-out); }
    &[open]::backdrop { animation: modal-fade var(--duration-base) var(--ease-out); }
  }

  @keyframes modal-in {
    from { opacity: 0; scale: 0.95; translate: 0 -2%; }
  }

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

  .modal__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--modal-gap);
    padding: var(--modal-pad);
    text-align: center;
  }

  .modal__close {
    position: absolute;
    inset-block-start: 12px;
    inset-inline-end: 12px;
    display: grid;
    place-items: center;
    inline-size: 28px;
    block-size: 28px;
    padding: 0;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--color-black);
    opacity: 0.6;
    cursor: pointer;
    transition: opacity var(--duration-fast) var(--ease-out);

    &:hover { opacity: 1; }
    & .icon { --icon-size: 16px; }
  }

  /* Ring inside a hairline circle, as drawn in the reference. */
  .modal__badge {
    display: grid;
    place-items: center;
    inline-size: 48px;
    block-size: 48px;
    border: var(--hairline) solid var(--color-grey-200);
    border-radius: var(--radius-full);

    &::before {
      content: "";
      inline-size: 18px;
      block-size: 18px;
      border: 5px solid var(--color-black);
      border-radius: var(--radius-full);
    }
  }

  .modal__title {
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1.35;
    letter-spacing: -0.015em;
  }

  .modal__text {
    font-size: var(--modal-text);
    line-height: 1.45;
    color: var(--modal-muted);
  }

  .modal__form {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin-block-start: 4px;
    inline-size: 100%;
    text-align: start;
  }

  /* The dialog's own field and button geometry, not the site's pill controls. */
  .modal__input {
    inline-size: 100%;
    padding: 11px 14px 11px 42px;
    border: var(--hairline) solid var(--color-grey-200);
    border-radius: var(--modal-control-radius);
    background: var(--color-white);
    font-size: var(--modal-text);
    font-weight: var(--weight-light);
    line-height: 1.5;

    /* grey-200 measured 2.996:1 on white — under both 1.4.3 for the
       placeholder text and 1.4.11 for the control's own boundary. grey-250 is
       4.54:1, so it clears text contrast while still reading lighter than
       entered copy; hover keeps darkening to grey-300. */
    border-color: var(--color-grey-250);

    &::placeholder { color: var(--color-grey-250); }
    &:hover { border-color: var(--color-grey-300); }

    &[aria-invalid="true"] { border-color: var(--color-primary); }
  }

  .modal__field {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .modal__icon {
    position: absolute;
    inset-inline-start: 14px;
    inset-block-start: 23px;
    translate: 0 -50%;
    --icon-size: 16px;
    color: var(--modal-muted);
    pointer-events: none;
  }

  .modal__submit {
    inline-size: 100%;
    padding-block: 11px;
    border: 0;
    border-radius: var(--modal-control-radius);
    background: var(--color-black);
    color: var(--color-white);
    font-size: var(--modal-text);
    font-weight: 600;
    line-height: 1.5;
    cursor: pointer;
    transition: background-color var(--duration-base) var(--ease-out);

    &:hover { background: var(--color-primary); }
  }

  .modal__error {
    font-size: 0.75rem;
    color: var(--color-primary);
  }

  /* Confirmation state: the ring becomes a tick. Same 48px circle, so the
     panel does not reflow when the form is replaced by the result. */
  .modal__badge--done {
    &::before { display: none; }
    & .icon { --icon-size: 24px; }
  }

  /* A failure that belongs to the submission rather than to one field —
     the server was unreachable, or it refused the whole request. */
  .modal__alert {
    inline-size: 100%;
    padding: 10px 12px;
    border: var(--hairline) solid var(--color-primary);
    border-radius: var(--modal-control-radius);
    font-size: 0.75rem;
    line-height: 1.45;
    text-align: start;
    color: var(--color-primary);
  }

  .modal__fine {
    font-size: 0.75rem;
    color: var(--modal-muted);
  }

  .modal [hidden] { display: none; }
}

@layer components {

  /* ==== The draw-in =======================================================
     One motion idea, reused everywhere it fits.

     The logo is the word PLAYBOOK inside dashed construction marks, and the
     page carries those marks through as its grid. So the marks are *drawn*
     rather than shown: the page drafts its own frame around the content, and
     the content itself never moves, fades or scales. Solid borders never
     animate — dashed means construction, solid means the edge of an object,
     and animating both would collapse the distinction.

     Only `clip-path` is animated, which the compositor can run without
     touching layout. The 0/100% in the keyframes are clip geometry, not design
     values, which is why they are literals here and not tokens.

     Both gates matter. The global prefers-reduced-motion block collapses
     animation *duration*, which does nothing to a scroll-driven animation
     whose progress comes from scroll position rather than time — so the whole
     block is gated on `no-preference` instead. `@supports` covers the browsers
     without scroll-driven animation. In either case the fallback is the static
     page exactly as it was before any of this existed. */

  @keyframes draw-x {
    from { clip-path: inset(0 100% 0 0); }
    to { clip-path: inset(0); }
  }

  @keyframes draw-y {
    from { clip-path: inset(0 0 100% 0); }
    to { clip-path: inset(0); }
  }

  /* The hero is above the fold, so it has no scroll to be driven by and runs
     on time instead. Verticals first, then the two rules under the header and
     the headline, so the frame appears before the lines that divide it. */
  @media (prefers-reduced-motion: no-preference) {
    .hero.guides::before,
    .hero.guides::after {
      animation: draw-y var(--duration-draw) var(--ease-out) both;
    }

    .hero .site-header::after {
      animation: draw-x var(--duration-draw) var(--ease-out) var(--stagger-draw) both;
    }

    .hero__body::after {
      animation: draw-x var(--duration-draw) var(--ease-out) calc(2 * var(--stagger-draw)) both;
    }
  }

  /* Everything below the hero is drawn by scroll position. `linear` rather
     than --ease-out on purpose: the reader's scroll is already the easing, and
     a curve on top of it reads as the line lagging behind the page. */
  @media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) {

      /* Verticals: the laws band's guides, and the four-line column grid on
         the benefits and Participate blocks. */
      .values-section > .frame-column.guides::before,
      .values-section > .frame-column.guides::after,
      .lined__cols {
        animation: draw-y linear both;
        animation-timeline: view();
        animation-range: var(--draw-range);
      }

      /* Horizontals: every shared dashed rule except the hero's two, which are
         already running on load above. */
      .benefits::before,
      .plans::before,
      .plans::after,
      .numbered::before,
      .numbered__row::after,
      .feature-grid > *::after {
        animation: draw-x linear both;
        animation-timeline: view();
        animation-range: var(--draw-range);
      }

      /* The studies rail is deliberately excluded. `.chapter-card` sets
         `overflow: hidden` for its corner radius, which makes the card itself a
         scroll container — nearer than `.rail`, and enough on its own. A
         `view()` timeline inside it resolves against the card rather than the
         page, and since the card never scrolls, every rule froze at 31% drawn
         and stayed there. A permanently half-drawn line reads as a bug, so
         these stay static. Note that the `< 40rem` override returning `.rail`
         to `overflow: visible` does not make them animatable — the card is
         still a scroll container. The device covers the page's vertical flow;
         anything inside a scroll container is outside it. */

      /* The methodology planes wipe in with their dashed stroke, each on its
         own timeline so the stack cascades by scroll position rather than by
         a fixed delay — the topmost plane would otherwise sit blank for
         several hundred pixels of scroll. */
      .layers__plane > svg {
        animation: draw-x linear both;
        animation-timeline: view();
        animation-range: var(--draw-range);
      }
    }
  }

  /* The interaction half of the same idea: a rule drawing along its length,
     at control speed. Replaces `text-decoration: underline` on hover, which
     appeared all at once. Buttons and fields keep their colour-only hover —
     a fill already says what a control is doing. */
  .site-nav a,
  .link-list a {
    position: relative;

    &:hover { text-decoration: none; }

    &::after {
      content: "";
      position: absolute;
      inset-inline: 0;
      inset-block-end: calc(-1 * var(--underline-offset));
      block-size: var(--hairline);
      background: currentColor;
      clip-path: inset(0 100% 0 0);
      transition: clip-path var(--duration-base) var(--ease-out);
      pointer-events: none;
    }

    &:hover::after { clip-path: inset(0); }
  }
}
