/* ==========================================================================
   07-UTILITIES — single-purpose, last in the cascade.
   No utility introduces a new visual value; all consume tokens.
   ========================================================================== */

/* --------------------------------------------------------------------------
   ACCESSIBILITY
   -------------------------------------------------------------------------- */

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

/* Skip link — visible the moment it receives focus. */
.u-skip {
  position: absolute;
  inset-inline-start: var(--sp-4);
  inset-block-start: var(--sp-4);
  z-index: var(--z-skiplink);
  transform: translateY(-200%);
  background: var(--c-ink);
  color: var(--c-text-inv);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-md);
  font-family: var(--ff-utility);
  font-size: var(--fs-sm);
  text-decoration: none;
  transition: transform var(--d-fast) var(--e-out);
}

.u-skip:focus { transform: translateY(0); }

/* --------------------------------------------------------------------------
   WIDTH TRACKS — see 04-layout for the grid these hook into.
   -------------------------------------------------------------------------- */

.u-w-text    { max-width: var(--w-text); }
.u-measure   { max-width: var(--measure); }
.u-measure-t { max-width: var(--measure-tight); }

/* --------------------------------------------------------------------------
   TEXT
   -------------------------------------------------------------------------- */

.u-center     { text-align: center; }
.u-start      { text-align: start; }
.u-balance    { text-wrap: balance; }
.u-muted      { color: var(--c-text-muted); }
.u-nowrap     { white-space: nowrap; }
.u-uppercase  { text-transform: uppercase; letter-spacing: var(--tr-wide); }

/* Truncate to n lines without a magic pixel height. */
.u-clamp-2,
.u-clamp-3 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.u-clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
.u-clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }

/* --------------------------------------------------------------------------
   SPACING OVERRIDES — deliberately few. Reach for a layout primitive first.
   -------------------------------------------------------------------------- */

.u-mt-0 { margin-block-start: 0; }
.u-mt-3 { margin-block-start: var(--sp-3); }
.u-mt-5 { margin-block-start: var(--sp-5); }
.u-mt-6 { margin-block-start: var(--sp-6); }
.u-mt-7 { margin-block-start: var(--sp-7); }
.u-mb-0 { margin-block-end: 0; }
.u-mb-5 { margin-block-end: var(--sp-5); }
.u-mi-auto { margin-inline: auto; }

/* --------------------------------------------------------------------------
   SURFACE
   -------------------------------------------------------------------------- */

.u-bg-page    { background-color: var(--surface-page); }
.u-bg-raised  { background-color: var(--surface-raised); }
.u-bg-sunken  { background-color: var(--surface-sunken); }
.u-bg-invert  { background-color: var(--surface-invert); color: var(--c-text-inv); }

.u-radius     { border-radius: var(--r-lg); }
.u-radius-xl  { border-radius: var(--r-xl); }

.u-hairline-top    { border-block-start: 1px solid var(--c-line); }
.u-hairline-bottom { border-block-end: 1px solid var(--c-line); }

/* --------------------------------------------------------------------------
   VISIBILITY
   -------------------------------------------------------------------------- */

@media (max-width: 767px)  { .u-hide-mobile  { display: none !important; } }
@media (min-width: 768px)  { .u-hide-desktop { display: none !important; } }
@media (max-width: 1023px) { .u-hide-lap     { display: none !important; } }
@media (min-width: 1024px) { .u-hide-from-lap{ display: none !important; } }
/* ^ Responsive display utilities are the one place !important is idiomatic:
   they must beat a component's own display value by contract. Kept to four. */

/* No-JS: elements that only make sense once JS has run. */
.no-js .u-js-only { display: none; }
.u-nojs-only { display: none; }
.no-js .u-nojs-only { display: revert; }

/* --------------------------------------------------------------------------
   MOTION HOOKS
   --------------------------------------------------------------------------
   The hidden state is gated on `.is-armed`, a class motion.js puts on an
   element at the same moment it starts observing it. That is the whole safety
   argument: an element can only be hidden by code that is already running and
   already committed to revealing it. There is no timer to lose, and no CDN
   whose failure can leave a heading permanently blank.

   `--i` is a per-child index written by the splitter; it is the only thing
   that varies between children. Durations and easings stay in tokens.
   -------------------------------------------------------------------------- */

/* Block wipe. clip-path is composited and does not touch layout. */
[data-reveal].is-armed {
  clip-path: inset(0 0 100% 0);
  will-change: clip-path;
  transition: clip-path var(--d-reveal) var(--e-wipe);
}

[data-reveal].is-armed[data-motion-dir="down"]  { clip-path: inset(100% 0 0 0); }
[data-reveal].is-armed[data-motion-dir="left"]  { clip-path: inset(0 100% 0 0); }
[data-reveal].is-armed[data-motion-dir="right"] { clip-path: inset(0 0 0 100%); }

[data-reveal].is-inview { clip-path: inset(0 0 0 0); }

/* Line and character masks. The overflow-hidden parent is what makes this
   read as a wipe from behind an edge rather than a fade. */
[data-reveal-lines] .line,
[data-reveal-chars] .word {
  display: block;
  overflow: hidden;
}

[data-reveal-chars] .word { display: inline-block; }

[data-reveal-lines] .line > span,
[data-reveal-chars] .char {
  display: inline-block;
  will-change: transform;
  transition:
    transform var(--d-reveal) var(--e-out),
    opacity   var(--d-slow)   var(--e-out);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}

[data-reveal-chars] .char {
  transition-delay: calc(var(--i, 0) * 18ms);
}

.is-armed[data-reveal-lines] .line > span,
.is-armed[data-reveal-chars] .char {
  transform: translate3d(0, 110%, 0);
  opacity: 0;
}

.is-inview[data-reveal-lines] .line > span,
.is-inview[data-reveal-chars] .char {
  transform: translate3d(0, 0, 0);
  opacity: 1;
}

/* Once the entrance has played the compositor layer is dead weight. */
.is-settled,
.is-settled .line > span,
.is-settled .char { will-change: auto; }

/* And the clip must be REMOVED, not left at inset(0 0 0 0).
   A finished wipe looks identical either way, but inset(0…) still clips at the
   border box — so anything a card paints outside itself is cut off for the rest
   of the page's life. That silently breaks the hover shadow on .c-story and
   .p-home__monthly-reason, and any focus ring that sits outside the border.
   Dropping the property restores normal painting.

   will-change is repeated here at matching specificity. The bare `.is-settled`
   rule above (0,1,0) loses to `[data-reveal].is-armed` (0,2,0), so the
   compositor layer was never actually released on wiped elements — measured
   still reporting `will-change: clip-path` long after the animation finished.
   That is a permanent layer per card, for nothing. */
[data-reveal].is-settled {
  clip-path: none;
  will-change: auto;
}

/* Belt and braces: reduced motion never hides anything, whatever the class.
   (main.js does not even import motion.js in that case, so this is only a
   guard against a class surviving a preference change mid-session.) */
@media (prefers-reduced-motion: reduce) {
  [data-reveal].is-armed { clip-path: none; will-change: auto; transition: none; }
  .is-armed[data-reveal-lines] .line > span,
  .is-armed[data-reveal-chars] .char {
    transform: none;
    opacity: 1;
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   PRINT — donors do print receipts and policy pages.
   -------------------------------------------------------------------------- */

@media print {
  .c-header,
  .c-footer,
  .c-quick-donate,
  .c-cookie-bar,
  .c-donate-bar,
  .c-cursor { display: none; }

  body {
    background: var(--c-print-bg);
    color: var(--c-print-fg);
    font-size: var(--fs-print);
  }
  .l-section { padding-block: 1rem; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; }
}
