/* Your Social World Explained: site styles.
   Every colour, size and space comes from a token in /css/tokens.css, which is
   generated from src/_data/tokens.json. No literal colour values here.
   Mobile first: base styles are for small screens, min-width queries add.
   Movement lives in the "Motion" section at the end, inside
   prefers-reduced-motion: no-preference; everything above is the static
   state, so with reduced motion states change instantly. */

/* ---- Base ---------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
  font-weight: var(--type-body-weight);
}

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

h1,
h2,
h3 {
  margin: 0 0 var(--space-3);
  color: var(--ink);
}

h1 {
  font-size: var(--type-h1-size);
  line-height: var(--type-h1-line);
  font-weight: var(--type-h1-weight);
}

h2 {
  font-size: var(--type-h2-size);
  line-height: var(--type-h2-line);
  font-weight: var(--type-h2-weight);
}

h3 {
  font-size: var(--type-h3-size);
  line-height: var(--type-h3-line);
  font-weight: var(--type-h3-weight);
}

p {
  margin: 0 0 var(--space-3);
}

a {
  color: var(--brand-blue);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

a:hover {
  text-decoration-thickness: 2px;
}

:focus-visible {
  outline: 3px solid var(--brand-blue);
  outline-offset: 3px;
}

main:focus {
  outline: none;
}

code {
  font-size: 0.9em;
}

.small {
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  font-weight: var(--type-small-weight);
  color: var(--ink-muted);
}

.lead {
  font-size: var(--type-h3-size);
  line-height: var(--type-h3-line);
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--space-3);
  top: calc(-1 * var(--space-6));
  z-index: 10;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--action);
  color: var(--on-action);
  font-weight: 700;
}

.skip-link:focus {
  top: var(--space-3);
}

/* ---- Layout -------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 var(--space-3);
}

.wrap--narrow {
  max-width: 760px;
}

.section {
  padding: var(--space-6) 0;
}

.section--alt {
  background: var(--surface-card);
  border-block: 1px solid var(--border-subtle);
}

.section__note {
  margin-top: var(--space-4);
  color: var(--ink-muted);
}

.page-head {
  padding-bottom: var(--space-4);
}

@media (min-width: 768px) {
  .wrap {
    padding: 0 var(--space-5);
  }
}

/* ---- Type styles --------------------------------------------------------- */

.kicker {
  margin: 0 0 var(--space-2);
  font-size: var(--type-kicker-size);
  line-height: var(--type-kicker-line);
  font-weight: var(--type-kicker-weight);
  letter-spacing: var(--type-kicker-tracking);
  text-transform: uppercase;
  color: var(--brand-blue);
}

/* Title lockup: display title with one Bebas Neue accent word beneath it.
   accent-orange is display-only, and the accent size token never drops below
   36px, above the 24px floor for that colour. */
.lockup {
  margin: 0 0 var(--space-3);
}

.lockup__title {
  display: block;
  font-size: var(--type-display-size);
  line-height: var(--type-display-line);
  font-weight: var(--type-display-weight);
  text-transform: uppercase;
  color: var(--ink);
}

.lockup__accent {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-accent);
  font-size: var(--type-accent-size);
  line-height: var(--type-accent-line);
  font-weight: var(--type-accent-weight);
  letter-spacing: 0.04em;
  color: var(--accent-orange);
}

/* ---- Header and footer --------------------------------------------------- */

.site-header {
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface-card);
}

.site-header__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  padding-block: var(--space-3);
}

.site-brand {
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink);
  text-decoration: none;
}

.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-nav a {
  position: relative;
  display: inline-block;
  padding: var(--space-1) 0;
  font-size: var(--type-small-size);
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
}

/* The underline is a border rather than a background, so it survives forced
   colours. It grows from the left on hover and stays on the current page. */
.site-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2px;
  border-top: 2px solid var(--accent-orange);
  transform: scaleX(0);
  transform-origin: left center;
}

.site-nav a:hover::after,
.site-nav a:focus-visible::after,
.site-nav a[aria-current="page"]::after {
  transform: scaleX(1);
}

.site-footer {
  padding: var(--space-6) 0 var(--space-5);
  border-top: 1px solid var(--border-subtle);
  background: var(--surface-card);
}

.related-links {
  display: grid;
  gap: var(--space-3);
  margin: 0 0 var(--space-5);
  padding: 0;
  list-style: none;
}

.related-links a {
  font-weight: 700;
}

.related-links__arrow {
  display: inline-block;
  margin-left: var(--space-1);
}

.related-links__note {
  display: block;
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  color: var(--ink-muted);
}

.site-footer__meta {
  margin-bottom: var(--space-2);
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  color: var(--ink-muted);
}

@media (min-width: 640px) {
  .related-links {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ---- Hero ---------------------------------------------------------------- */

/* The sky sunburst: allowed in the hero only. */
.hero {
  position: relative;
  overflow: hidden;
  padding: var(--space-6) 0;
  background-color: var(--sky-200);
  background-image: repeating-conic-gradient(
    from 0deg at 50% 0%,
    var(--sky-200) 0deg 6deg,
    var(--sky-300) 6deg 12deg
  );
}

.hero__inner {
  display: grid;
  gap: var(--space-5);
  align-items: center;
}

.hero__cover {
  max-width: 240px;
}

.hero__cover img {
  border-radius: var(--radius-input);
}

.hero__subtitle {
  font-weight: 700;
}

.hero__secondary {
  margin-top: var(--space-3);
  font-weight: 700;
}

/* Text on the sunburst sits on a card surface so it keeps full contrast. */
.hero__text {
  padding: var(--space-4);
  border-radius: var(--radius-card);
  background: var(--surface-card);
}

@media (min-width: 768px) {
  .hero__inner {
    grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
    gap: var(--space-6);
  }

  .hero__cover {
    max-width: none;
  }

  .hero__text {
    padding: var(--space-5);
  }
}

/* ---- Buttons and buy links ----------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  border: 2px solid transparent;
  border-radius: var(--radius-pill);
  font-family: inherit;
  font-size: var(--type-body-size);
  font-weight: 700;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
}

.btn--action {
  background: var(--action);
  color: var(--on-action);
}

.btn--action:hover {
  border-color: var(--ink);
}

.buy__list .btn:hover {
  background: var(--action-hover);
}

.buy__list .btn:focus-visible {
  outline: 3px solid var(--border-control);
  outline-offset: 2px;
}

/* The sample form's submit button: label, dots and tick share one grid cell,
   so the button keeps the label's width whichever is showing. */
.btn--stateful {
  display: inline-grid;
  place-items: center;
}

.btn--stateful > * {
  grid-area: 1 / 1;
}

.btn__dots {
  display: flex;
  gap: var(--space-1);
}

.btn__dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.btn__dots,
.btn__tick,
.btn--stateful[data-state="sending"] .btn__label,
.btn--stateful[data-state="sent"] .btn__label {
  opacity: 0;
}

.btn--stateful[data-state="sending"] .btn__dots,
.btn--stateful[data-state="sent"] .btn__tick {
  opacity: 1;
}

/* Equal-width buttons, one, two or four to a row by the space available, so
   a row wraps whole instead of squashing a button or leaving one stretched
   on its own. The breakpoints fit the longest label at its natural width. */
.buy {
  container-type: inline-size;
}

.buy__list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  margin: var(--space-4) 0 var(--space-3);
  padding: 0;
  list-style: none;
}

.buy__list .btn {
  width: 100%;
  white-space: nowrap;
}

/* Each format has a link per store: show the one for data-store on <html>
   (set before first paint by partials/store-init.njk), US when it is unset. */
html:not([data-store="uk"]) .buy__link[data-store-link="uk"],
html[data-store="uk"] .buy__link[data-store-link="us"] {
  display: none;
}

@container (min-width: 19rem) {
  .buy__list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container (min-width: 40rem) {
  .buy__list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.buy__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}

.buy__note {
  margin: 0;
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  font-weight: var(--type-small-weight);
  color: var(--ink-muted);
}

.store-switch {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  font-weight: var(--type-small-weight);
  color: var(--ink-muted);
}

.store-switch[hidden] {
  display: none;
}

/* Hidden in the HTML for visitors without JavaScript; shown from first paint
   once the <head> script has picked a store, so nothing shifts later. */
html[data-store] .store-switch[hidden] {
  display: flex;
}

/* Two equal halves; the pill sits behind the selected store's button, from
   data-store on <html> (US when unset). */
.store-switch__track {
  position: relative;
  display: inline-grid;
  grid-template-columns: 1fr 1fr;
  padding: 2px;
  border: 1px solid var(--border-control);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
}

.store-switch__pill {
  position: absolute;
  top: 2px;
  bottom: 2px;
  left: 2px;
  width: calc(50% - 2px);
  border-radius: var(--radius-pill);
  background: var(--ink);
  transform: translateX(100%);
}

html[data-store="uk"] .store-switch__pill {
  transform: none;
}

.store-switch__btn {
  position: relative;
  min-height: 32px;
  padding: var(--space-1) var(--space-3);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

html:not([data-store="uk"]) .store-switch__btn[data-store="us"],
html[data-store="uk"] .store-switch__btn[data-store="uk"] {
  color: var(--surface-card);
}

@media (forced-colors: active) {
  .store-switch__pill {
    forced-color-adjust: none;
    background: Highlight;
  }

  html:not([data-store="uk"]) .store-switch__btn[data-store="us"],
  html[data-store="uk"] .store-switch__btn[data-store="uk"] {
    forced-color-adjust: none;
    color: HighlightText;
  }
}

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

.card {
  padding: var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  background: var(--surface-card);
}

.section--alt .card {
  background: var(--surface-page);
}

.card > :last-child {
  margin-bottom: 0;
}

.card-grid {
  display: grid;
  gap: var(--space-3);
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 768px) {
  .card-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-4);
  }
}

/* Review cards sit on surface-card even in the alt section, so the
   accent-orange stars keep 3:1 against their background in both themes. */
.review,
.section--alt .review {
  background: var(--surface-card);
}

.review__figure {
  display: flex;
  flex-direction: column;
  height: 100%;
  margin: 0;
}

.review__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  /* The height of the edition tag, so quotes line up with or without one. */
  min-height: calc(var(--type-small-line) + 2px);
  margin-bottom: var(--space-3);
}

.stars {
  margin: 0;
  font-size: 20px;
  line-height: 1;
  letter-spacing: 2px;
  color: var(--accent-orange);
}

/* inline-block so a star can be scaled as it appears (see Motion). */
.stars__star {
  display: inline-block;
}

.stars__star--empty {
  color: var(--border-control);
}

.review__quote {
  flex: 1;
  margin: 0 0 var(--space-4);
  font-size: var(--type-h3-size);
  line-height: var(--type-h3-line);
}

.review__quote p {
  margin: 0;
}

.review__caption {
  display: grid;
  gap: var(--space-1);
}

.review__by {
  font-weight: 700;
}

.review__meta {
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  font-weight: var(--type-small-weight);
  color: var(--ink-muted);
}

/* ---- Reviews carousel ---------------------------------------------------- */

/* A scroll-snap row: swipe, trackpad and keyboard scrolling work natively,
   and it stays a plain scrollable row without JS. reviews.js adds the
   buttons and dots and reads --per-view, so the breakpoints live only here.
   Flex items stretch, so every card is as tall as the tallest and the row
   never changes height as it moves. */
.carousel {
  --dot-current: var(--brand-navy);
  margin-top: var(--space-4);
}

.carousel__viewport {
  --per-view: 1;
  --gap: var(--space-3);
  position: relative;
  display: flex;
  gap: var(--gap);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  border-radius: var(--radius-card);
}

.carousel__slide {
  flex: 0 0 calc((100% - (var(--per-view) - 1) * var(--gap)) / var(--per-view));
  min-width: 0;
  scroll-snap-align: start;
}

/* Without JS, leave room for the scrollbar and let the next card peek in on
   phones, where scrollbars are often hidden. */
.carousel:not(.carousel--js) .carousel__viewport {
  padding-bottom: var(--space-2);
}

@media (max-width: 639px) {
  .carousel:not(.carousel--js) .carousel__slide {
    flex-basis: 85%;
  }
}

/* With JS, the buttons and dots replace the scrollbar. */
.carousel--js .carousel__viewport {
  scrollbar-width: none;
}

.carousel--js .carousel__viewport::-webkit-scrollbar {
  display: none;
}

@media (min-width: 640px) {
  .carousel__viewport {
    --per-view: 2;
  }
}

@media (min-width: 960px) {
  .carousel__viewport {
    --per-view: 3;
    --gap: var(--space-4);
  }
}

.carousel__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.carousel__controls[hidden] {
  display: none;
}

.carousel__btn {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 2px solid var(--border-control);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  color: var(--ink);
  cursor: pointer;
}

.carousel__btn:hover {
  border-color: var(--ink);
}

/* Still focusable, so focus is never lost at either end. */
.carousel__btn[aria-disabled="true"] {
  border-color: var(--border-subtle);
  color: var(--border-control);
  cursor: default;
}

.carousel__dots {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-1);
}

/* A 24px target around a small dot. The current dot is longer as well as
   darker, so position never relies on colour alone. */
.carousel__dot {
  position: relative;
  width: 28px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  cursor: pointer;
}

.carousel__dot::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 10px;
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--border-control);
  transform: translate(-50%, -50%);
}

.carousel__dot[aria-current="true"]::before {
  width: 22px;
  background: var(--dot-current);
}

.carousel__dot:hover:not([aria-current="true"])::before {
  background: var(--ink-muted);
}

/* brand-navy is too dark to see on the dark card surface. */
@media (prefers-color-scheme: dark) {
  .carousel {
    --dot-current: var(--brand-blue);
  }
}

/* A small neutral pill, not a quadra colour. */
.tag {
  display: inline-block;
  padding: 0 var(--space-2);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-pill);
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  font-weight: 700;
  color: var(--ink-muted);
}

/* Placeholders are dashed so they cannot be mistaken for finished content. */
.review--placeholder,
.embed-placeholder {
  border: 2px dashed var(--border-control);
  color: var(--ink-muted);
}

.embed-placeholder {
  padding: var(--space-5) var(--space-4);
  border-radius: var(--radius-card);
  text-align: center;
}

.embed-placeholder > :last-child {
  margin-bottom: 0;
}

.form-area,
.request {
  margin: var(--space-4) 0;
}

/* Inputs inside pasted embeds pick up the input radius where the embed allows. */
.form-area input,
.form-area select,
.form-area textarea {
  border-radius: var(--radius-input);
}

/* ---- Resources ----------------------------------------------------------- */

.toc,
.prose-list {
  padding-left: var(--space-4);
}

.toc li,
.prose-list li {
  margin-bottom: var(--space-2);
}

.glossary dt {
  margin-top: var(--space-4);
  font-size: var(--type-h3-size);
  line-height: var(--type-h3-line);
  font-weight: var(--type-h3-weight);
}

.glossary dd {
  margin: var(--space-1) 0 0;
}

/* Quadra colours only ever mean quadras. */
.quadra-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
  padding: 0;
  list-style: none;
}

.badge {
  display: inline-block;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  font-weight: 700;
}

.badge--alpha {
  background: var(--quadra-alpha);
  color: var(--quadra-alpha-ink);
}

.badge--beta {
  background: var(--quadra-beta);
  color: var(--quadra-on-quadra);
}

/* Small white text needs gamma-deep rather than gamma for contrast. */
.badge--gamma {
  background: var(--quadra-gamma-deep);
  color: var(--quadra-on-quadra);
}

.badge--delta {
  background: var(--quadra-delta);
  color: var(--quadra-on-quadra);
}

/* ---- Prose (privacy) ----------------------------------------------------- */

.prose h2 {
  margin-top: var(--space-5);
}

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

/* ---- Sign-up form -------------------------------------------------------- */

.signup {
  display: grid;
  gap: var(--space-4);
}

.signup > * {
  margin: 0;
}

.field {
  display: grid;
  gap: var(--space-2);
}

.field label {
  font-weight: 700;
}

.field input,
.field select {
  width: 100%;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-input);
  background: var(--surface-card);
  color: var(--ink);
  font: inherit;
}

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

.field__row select {
  flex: 1;
  min-width: 0;
}

/* Room for the type badge is always kept, so choosing a type moves nothing. */
.type-badge-slot {
  position: relative;
  flex: none;
  width: 56px;
  height: 56px;
  border: 2px dashed var(--border-subtle);
  border-radius: 50%;
}

/* Like the badges on the cover: the three-letter code over the four-letter
   code, in the quadra colour with a white rim and a tint ring. Text colours
   follow the .badge rules: alpha-ink on alpha, and gamma-deep rather than
   gamma under small white text. */
.type-badge {
  position: absolute;
  inset: -2px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 3px solid var(--quadra-on-quadra);
  border-radius: 50%;
  line-height: 1;
  text-align: center;
}

.type-badge__code {
  font-size: 16px;
  font-weight: 800;
}

.type-badge__four {
  margin-top: 2px;
  font-size: 11px;
  font-weight: 700;
}

.type-badge--alpha {
  background: var(--quadra-alpha);
  color: var(--quadra-alpha-ink);
  box-shadow: 0 0 0 2px var(--quadra-alpha-tint);
}

.type-badge--beta {
  background: var(--quadra-beta);
  color: var(--quadra-on-quadra);
  box-shadow: 0 0 0 2px var(--quadra-beta-tint);
}

.type-badge--gamma {
  background: var(--quadra-gamma-deep);
  color: var(--quadra-on-quadra);
  box-shadow: 0 0 0 2px var(--quadra-gamma-tint);
}

.type-badge--delta {
  background: var(--quadra-delta);
  color: var(--quadra-on-quadra);
  box-shadow: 0 0 0 2px var(--quadra-delta-tint);
}

.field__help {
  margin: 0;
  font-size: var(--type-small-size);
  line-height: var(--type-small-line);
  font-weight: var(--type-small-weight);
  color: var(--ink-muted);
}

.signup .btn {
  justify-self: start;
}

.signup .btn:disabled {
  cursor: progress;
  opacity: 0.7;
}

/* Honeypot: off-screen rather than display:none, which some bots skip. */
.signup__trap {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.signup__status:empty {
  display: none;
}

.signup__status,
.signup__notice {
  padding: var(--space-3);
  border: 1px solid var(--border-subtle);
  border-left-width: 4px;
  border-radius: var(--radius-input);
  font-weight: 500;
}

.signup__status--success,
.signup__notice--success {
  border-left-color: var(--brand-blue);
}

.signup__status--error,
.signup__notice--error {
  border-left-color: var(--action);
}

/* No-JS result: hidden until the function's redirect targets it. */
.signup__notice {
  display: none;
  margin: var(--space-4) 0 0;
}

.signup__notice:target {
  display: block;
}

/* ---- Motion -------------------------------------------------------------- */

/* Only transform and opacity move (colours may ease on hover). Entrances ease
   out, state changes ease in and out, and nothing loops except the sending
   dots, which stop as soon as the request ends. All of it is skipped under
   prefers-reduced-motion. */

@keyframes motion-rise-12 {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}

@keyframes motion-rise-8 {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

@keyframes motion-pop {
  from { opacity: 0; transform: scale(0.85); }
  to { opacity: 1; transform: none; }
}

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

@keyframes motion-fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes motion-dip {
  50% { opacity: 0.35; }
}

@keyframes motion-shake {
  0%, 100% { transform: none; }
  20% { transform: translateX(-4px); }
  50% { transform: translateX(4px); }
  80% { transform: translateX(-2px); }
}

@keyframes motion-dot {
  from { opacity: 0.35; transform: scale(0.75); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  /* Reviews carousel: buttons, dots and arrow keys glide between cards, and
     the current dot stretches. Its cards and stars use the scroll reveal. */
  .carousel__viewport {
    scroll-behavior: smooth;
  }

  .carousel__dot::before {
    transition:
      width var(--motion-slide) var(--motion-ease-in-out),
      background-color var(--motion-slide) var(--motion-ease-in-out);
  }

  /* Buy buttons: lift on hover, press on click. */
  .buy__list .btn {
    transition:
      transform var(--motion-fast) var(--motion-ease-in-out),
      background-color var(--motion-fast) var(--motion-ease-in-out),
      border-color var(--motion-fast) var(--motion-ease-in-out);
  }

  .buy__list .btn:active {
    transform: translateY(1px) scale(0.98);
  }

  /* Store switch: the pill slides; the buy buttons dip when the store changes. */
  .store-switch__pill {
    transition: transform var(--motion-slide) var(--motion-ease-in-out);
  }

  .store-switch__btn {
    transition: color var(--motion-slide) var(--motion-ease-in-out);
  }

  .buy__list--swapped {
    animation: motion-dip var(--motion-fade) var(--motion-ease-in-out);
  }

  /* Hero cover: rises in once on load. */
  .hero__cover {
    animation: motion-rise-12 var(--motion-entrance) var(--motion-ease-out) both;
  }

  /* Nav underline and "Go further" arrows. */
  .site-nav a::after {
    transition: transform var(--motion-slide) var(--motion-ease-in-out);
  }

  .related-links__arrow {
    transition: transform var(--motion-fast) var(--motion-ease-in-out);
  }

  .related-links a:hover .related-links__arrow,
  .related-links a:focus-visible .related-links__arrow {
    transform: translateX(3px);
  }

  /* Type badge: pops in, cross-fades on a change of type. */
  .type-badge--pop {
    animation: motion-pop var(--motion-pop) var(--motion-ease-out) both;
  }

  .type-badge--swap {
    animation: motion-fade-in var(--motion-fade) var(--motion-ease-in-out) both;
  }

  .type-badge--out {
    animation: motion-fade-out var(--motion-fade) var(--motion-ease-in-out) both;
  }

  /* Submit button states and messages. */
  .btn__label,
  .btn__dots,
  .btn__tick {
    transition: opacity var(--motion-fast) var(--motion-ease-in-out);
  }

  .btn--stateful[data-state="sending"] .btn__dots span {
    animation: motion-dot calc(var(--motion-entrance) * 2) var(--motion-ease-in-out) infinite alternate both;
  }

  .btn--stateful[data-state="sending"] .btn__dots span:nth-child(2) {
    animation-delay: var(--motion-fade);
  }

  .btn--stateful[data-state="sending"] .btn__dots span:nth-child(3) {
    animation-delay: calc(var(--motion-fade) * 2);
  }

  .btn--stateful[data-state="sent"] .btn__tick {
    animation: motion-pop var(--motion-pop) var(--motion-ease-out) both;
  }

  .signup__status--success {
    animation: motion-rise-8 var(--motion-slide) var(--motion-ease-out) both;
  }

  .signup__status--error {
    animation: motion-fade-in var(--motion-fade) var(--motion-ease-out) both;
  }

  .signup--shake {
    animation: motion-shake var(--motion-entrance) var(--motion-ease-in-out);
  }
}

/* Pointer devices only: the cover tilts and lifts slightly under the mouse. */
@media (prefers-reduced-motion: no-preference) and (hover: hover) {
  .buy__list .btn:hover {
    transform: translateY(-2px);
  }

  .buy__list .btn:hover:active {
    transform: translateY(1px) scale(0.98);
  }

  .hero__cover img {
    transition: transform var(--motion-reveal) var(--motion-ease-in-out);
  }

  .hero__cover:hover img {
    transform: perspective(900px) rotateX(1deg) rotateY(-2deg) translateY(-4px);
  }
}

/* Scroll reveals (js/motion.js marks what to hide). Screen only, so a printed
   page never comes out with hidden sections. */
@media screen and (prefers-reduced-motion: no-preference) {
  [data-reveal],
  [data-reveal-item] {
    transition:
      opacity var(--motion-reveal) var(--motion-ease-out) var(--reveal-delay, 0ms),
      transform var(--motion-reveal) var(--motion-ease-out) var(--reveal-delay, 0ms);
  }

  [data-reveal="pending"] {
    opacity: 0;
    transform: translateY(12px);
  }

  .review[data-reveal-item="pending"] {
    opacity: 0;
    transform: translateY(8px);
  }

  .stars__star[data-reveal-item="pending"] {
    opacity: 0;
    transform: scale(0.6);
  }
}
