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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--pff-color-bg);
  color: var(--pff-color-fg);
  font-family: var(--pff-font-sans);
  font-size: var(--pff-font-base);
  line-height: var(--pff-font-leading);
}

img,
svg,
video {
  max-width: 100%;
  height: auto;
  display: block;
}

a {
  color: var(--pff-color-accent);
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--pff-font-display);
  line-height: 1.25;
  margin: 0 0 var(--pff-space-s);
}

.pff-container {
  width: 100%;
  max-width: var(--pff-container-current-width);
  margin-inline: auto;
  padding-inline: var(--pff-space-m);
}

.pff-grid {
  display: grid;
  grid-template-columns: repeat(var(--pff-grid-columns), minmax(0, 1fr));
  gap: var(--pff-grid-gap);
}

.pff-grid__cell {
  grid-column: span var(--pff-grid-span, var(--pff-grid-columns));
  min-width: 0;
}

@media (max-width: 48rem) {
  .pff-grid__cell {
    grid-column: span var(--pff-grid-columns);
  }
}

.pff-prose {
  max-width: var(--pff-font-measure);
}

.pff-heading {
  margin: 0;
}

.pff-figure {
  margin: 0;
}

.pff-figure__caption {
  color: var(--pff-color-muted);
  font-size: 0.875rem;
  margin-top: var(--pff-space-2xs);
}

.pff-button {
  display: inline-block;
  padding: var(--pff-space-xs) var(--pff-space-m);
  border: 0;
  border-radius: var(--pff-radius-default);
  background: var(--pff-color-accent);
  color: var(--pff-color-bg);
  font: inherit;
  text-decoration: none;
}

.pff-button--secondary {
  background: transparent;
  color: var(--pff-color-accent);
  box-shadow: inset 0 0 0 1px var(--pff-color-border);
}

.pff-button--disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.pff-header__bar {
  align-items: center;
  display: flex;
  gap: var(--pff-space-m);
  flex-wrap: wrap;
  min-height: 4rem;
  padding-block: var(--pff-space-s);
}

.pff-header__bar .pff-nav {
  margin-inline-start: auto;
}

.pff-nav__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pff-space-m);
  list-style: none;
  margin: 0;
  padding: 0;
}

.pff-nav__link {
  color: var(--pff-color-fg);
  text-decoration: none;
}

.pff-footer {
  border-block-start: 1px solid var(--pff-color-border);
  margin-top: var(--pff-space-2xl);
  padding-block: var(--pff-space-xl);
}

.pff-footer__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pff-space-m);
  list-style: none;
  margin: 0 0 var(--pff-space-s);
  padding: 0;
}

.pff-footer__copyright {
  color: var(--pff-color-muted);
  font-size: 0.875rem;
}

.pff-nav-toggle {
  display: none;
}

@media (max-width: 48rem) {
  .pff-header__bar .pff-nav {
    display: none;
  }

  .pff-nav-toggle {
    display: inline-flex;
    margin-inline-start: auto;
  }
}

/* Partner Stores instant-filter enhancement: the submit button is only
   redundant once the shared script has marked the form enhanced (it then
   submits on select change). Without JavaScript the attribute is never set, so
   the button stays visible and the GET form remains fully usable. */
.pff-stores__filters[data-pff-stores-enhanced] .pff-stores__submit {
  display: none;
}

/* --------------------------------------------------------------------------
 * Motion utility layer (T-MOT-2) — CSS-only scroll-driven reveals with a
 * hard VISIBILITY INVARIANT.
 *
 * Decorative reveals are scroll-driven view() animations inside a double
 * gate: @media (prefers-reduced-motion: no-preference) AND
 * @supports (animation-timeline: view()). The invariant on top of the
 * gates: this layer never animates opacity or any other property that can
 * conceal content. The only animated property is transform (a small rise),
 * so the worst possible degraded render — no JS, unsupported CSS, a
 * full-page capture that never scrolls the timeline, print, a background
 * tab, an inactive timeline mid-scrub — is content offset by the rise
 * distance, never a blank section. Verified against coordinator Chromium
 * captures (browser-review 0815 after-motion): a `from { opacity: 0 }`
 * state DOES blank captures even inside these gates, so fade-in reveals are
 * banned from this layer. `.pff-reveal-fade` therefore carries no core
 * effect any more: it stays a stable class contract that sites may attach
 * visibility-safe signatures to (e.g. Laube's grayscale→colour
 * desaturation); core leaves those elements fully rendered and still.
 *
 * The shared grammar tokens live in tokens.css (--pff-motion-* durations,
 * easings, rise distance, reveal range). A site states its motion character
 * by overriding those tokens in its own :root — site CSS loads after core,
 * so site values win — never by duplicating this layer.
 * ------------------------------------------------------------------------ */

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .pff-reveal,
    .pff-reveal-group > * {
      animation-name: pff-reveal-rise;
      animation-duration: 1ms; /* scrubbed by the timeline; engines want a value */
      animation-timing-function: var(--pff-motion-ease-out);
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 0% entry var(--pff-motion-reveal-range, 35%);
    }
  }
}

/* Transform only — never opacity, filter, clip or anything concealing. */
@keyframes pff-reveal-rise {
  from {
    transform: translateY(var(--pff-motion-rise));
  }
}

/* Link-underline primitive: a 1px underline that draws left→right on
   hover/focus. Essential feedback — not gated behind pff-motion; the global
   reduced-motion kill switch collapses it to instant. */
.pff-link-underline {
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size var(--pff-motion-duration-fast) var(--pff-motion-ease-out);
}

.pff-link-underline:hover,
.pff-link-underline:focus-visible {
  background-size: 100% 1px;
}
