/* ==========================================================================
   components.css — reusable pieces shared across pages
   Buttons · header/nav · brand · footer · cards · empty states
   ========================================================================== */

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 44px;                 /* touch target floor */
  padding: var(--space-3) var(--space-5);
  border: 2px solid transparent;
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  /* transform/box-shadow carry the physical feedback; colour carries the state.
     Nothing here triggers layout. */
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

/* Lift only where a pointer can actually hover, so touch users never get a
   sticky :hover state left behind after a tap. */
@media (hover: hover) {
  .btn:hover {
    transform: translate3d(0, -2px, 0);
    box-shadow: var(--shadow-md);
  }
}

/* Press returns it below the resting position — the whole point is that the
   button acknowledges the click before the page has responded. */
.btn:active {
  transform: translate3d(0, 1px, 0);
  box-shadow: none;
  transition-duration: 60ms;
}

.btn--primary {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-invert);
}
.btn--primary:hover {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
  color: var(--color-text-invert);
}

.btn--accent {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-primary-deep);
}
.btn--accent:hover {
  background: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
  color: var(--color-primary-deep);
}

.btn--ghost {
  background: transparent;
  border-color: var(--color-primary);
  color: var(--color-primary);
}
.btn--ghost:hover {
  background: var(--color-primary);
  color: var(--color-text-invert);
}

.btn--ghost-light {
  background: transparent;
  border-color: var(--color-text-invert);
  color: var(--color-text-invert);
}
.btn--ghost-light:hover {
  background: var(--color-text-invert);
  color: var(--color-primary-strong);
}

.btn--lg {
  font-size: var(--text-md);
  padding: var(--space-4) var(--space-6);
}

.btn--sm {
  font-size: var(--text-sm);
  padding: var(--space-2) var(--space-4);
}

/* --------------------------------------------------------------------------
   Brand mark
   -------------------------------------------------------------------------- */

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  color: var(--color-primary-strong);
}

/* The global `a:hover` (0,1,1) outranks `.brand` (0,1,0), which was tinting the
   company name the link blue on hover — it read as a rendering bug. Pin every
   interaction state to the same brand blue the header now carries. */
.brand:hover,
.brand:focus,
.brand:active,
.brand:visited {
  color: var(--color-primary-strong);
}

/* The disc is now only doing work in the footer, where the surface is blue.
   On the white header it is invisible by definition, which is the point: the
   mark sits directly on the page's own white. */
.brand__logo {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  padding: 3px;
  background: var(--white);
  border-radius: var(--radius-pill);
  /* box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.25); */
}

.brand__logo--footer {
  width: 52px;
  height: 52px;
  flex-basis: 52px;
  margin-bottom: var(--space-4);
}

.brand__text { display: flex; flex-direction: column; }

.brand__name {
  font-weight: 700;
  font-size: var(--text-md);
  line-height: 1.15;
  letter-spacing: -0.01em;
}

/* The tagline is the licence claim, so it has to be read, not admired.

   It was the logo yellow, which is 1.38:1 on the white bar — at 12px uppercase
   that is not low contrast, it is a smear, and no amount of text-shadow fixes
   it (that was tried; it reads as a glow, not as type). Darkening it to
   --yellow-700 clears AA but gives the header a second, muddier gold competing
   with the CTA for the same job.

   So the type is the same blue as the company name directly above it — the
   exact same token, not a step lighter: at 12px uppercase a near-miss on the
   wordmark's colour reads as a mistake rather than as a hierarchy, and size and
   weight already carry that hierarchy on their own. 14.6:1 on white.

   There was a yellow rule under it, kept on the theory that a 2px block of
   colour has no legibility threshold to meet and could carry the real brand
   yellow next to the mark. In practice it underlined a line of type, which on
   the web means one thing — a link — and the tagline is not one. The yellow now
   lives on the CTA alone. */
.brand__tagline {
  display: inline-block;
  align-self: flex-start;
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--color-primary-strong);   /* same token as .brand__name */
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.4;
}

/* --------------------------------------------------------------------------
   Site header + primary nav
   -------------------------------------------------------------------------- */

.site-header {
  /* White, so the logo's own blue ring and yellow keep their full value
     instead of dissolving into a bar of the same blue. */
  background: var(--color-surface-header);
  color: var(--color-text);
  /* On white the bar needs an edge of its own; the blue ticker below supplies
     the rest of the separation. */
  border-bottom: 1px solid var(--color-border);
  /* Sticky rather than relative: a shrink-on-scroll state is pointless on a
     header that has already scrolled away, and on the rate board keeping the
     nav reachable is worth more than the 60px. */
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  transition: box-shadow var(--dur-mid) var(--ease-out),
              background-color var(--dur-mid) var(--ease-out);
}

/* Set by motion.js once the page has scrolled past the header's own height.
   The bar no longer changes colour — a white header that turned blue mid-scroll
   would read as a different bar — so the scrolled state is carried entirely by
   the shadow and the shrink below. */
.site-header.is-scrolled {
  background: var(--color-surface-header-scrolled);
  box-shadow: 0 4px 18px rgb(10 8 40 / 0.10);
}

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

/* The shrink. Padding is a layout property, but it is confined to the header
   subtree and runs only twice per scroll direction, so it never lands in a
   scroll-linked frame budget the way animating a full-page property would. */
.site-header.is-scrolled .site-header__inner { padding-block: var(--space-2); }

.brand__logo,
.brand__tagline {
  transition: transform var(--dur-mid) var(--ease-out),
              opacity var(--dur-mid) var(--ease-out);
}

.site-header.is-scrolled .brand__logo { transform: scale(0.88); }

/* Anchors must clear the sticky bar. */
:target { scroll-margin-top: 6rem; }

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

/* --------------------------------------------------------------------------
   Language switcher
   -------------------------------------------------------------------------- */

.lang-switch {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.lang-switch__label {
  display: none;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
}

.lang-switch__group {
  display: flex;
  border: 2px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.lang-switch__btn {
  min-width: 44px;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  background: transparent;
  border: 0;
  color: var(--color-primary-strong);
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  transition: background-color 150ms ease-out, color 150ms ease-out;
}

.lang-switch__btn:hover { background: var(--color-primary-tint); }

/* The active language used to be a yellow block. It passed contrast, but on a
   white bar it put a second saturated yellow rectangle a few centimetres from
   the "Today's Rates" CTA — two identical-looking buttons, one of which does
   nothing a visitor came here for. The yellow is now spent only on the CTA,
   and the current language is marked in the brand blue (10.15:1 with white
   type), which reads as "selected" rather than "press me". */
.lang-switch__btn.is-active {
  background: var(--color-primary);
  color: var(--color-text-invert);
}

/* Without this, the global .lang-switch__btn:hover tint (0,2,0) would repaint
   the active button's blue back to a pale lilac on hover. */
.lang-switch__btn.is-active:hover {
  background: var(--color-primary-strong);
  color: var(--color-text-invert);
}

/* Outline would be clipped by the group's overflow:hidden. */
.lang-switch__btn:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 3px var(--color-focus);
}

@media (min-width: 1100px) {
  .lang-switch__label { display: inline; }
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  background: transparent;
  border: 2px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  color: var(--color-primary-strong);
  font-size: var(--text-sm);
  font-weight: 600;
}

/* The accent border was legible on blue; on white it all but vanishes, so the
   hover and open states are carried by the brand blue instead. */
.nav-toggle:hover { border-color: var(--color-primary); }

.nav-toggle__bars {
  position: relative;
  width: 18px;
  height: 2px;
  background: currentColor;
  display: block;
}

.nav-toggle__bars::before,
.nav-toggle__bars::after {
  content: "";
  position: absolute;
  left: 0;
  width: 18px;
  height: 2px;
  background: currentColor;
}

.nav-toggle__bars::before { top: -6px; }
.nav-toggle__bars::after  { top: 6px; }

/* No-JS fallback: the toggle is useless without JS, so hide it and leave the
   nav permanently expanded. .js is set by nav.js before first paint. */
.nav-toggle { display: none; }
.js .nav-toggle { display: inline-flex; }

.primary-nav {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-block: var(--space-3) var(--space-4);
}

.js .primary-nav[hidden] { display: none; }

.primary-nav__list {
  display: flex;
  flex-direction: column;
}

.primary-nav__list a {
  display: block;
  position: relative;
  padding: var(--space-3) var(--space-2);
  color: var(--color-text);
  text-decoration: none;
  font-weight: 600;
  border-bottom: 1px solid var(--color-border);
  transition: color var(--dur-fast) var(--ease-out);
}

.primary-nav__list a:hover { color: var(--color-primary); }

.primary-nav__list a[aria-current="page"] {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

.nav-toggle {
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}

.nav-toggle[aria-expanded="true"] {
  border-color: var(--color-primary);
  background: var(--color-primary-tint);
}

.lang-switch__btn {
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}

.primary-nav__cta { align-self: flex-start; }

/* The accent button has no silhouette of its own at the top of a white page —
   the yellow is bright enough that its edge dissolves into the bar. A rim of
   the darker gold from the same ramp gives it a boundary without introducing a
   colour that is not already in the palette. Header only: on the blue hero and
   CTA bands the button already has all the edge it needs. */
.site-header .btn--accent { border-color: var(--yellow-600); }
.site-header .btn--accent:hover { border-color: var(--yellow-600); }

/* --- phone header ---------------------------------------------------------
   At 360–400px the brand block plus the switcher plus the toggle overran the
   bar and wrapped onto a second row, which on a sticky header costs the top of
   every page. Each part gives up a little so the whole thing stays one row;
   below ~340px it still wraps, and `margin-left:auto` keeps the utilities
   right-aligned when it does. */
@media (max-width: 559px) {
  .site-header__inner { gap: var(--space-2); }

  .site-header__utils { margin-left: auto; }

  .brand { gap: var(--space-2); }

  .brand__logo {
    width: 38px;
    height: 38px;
    flex-basis: 38px;
  }

  .brand__name { font-size: var(--text-base); }

  .brand__tagline {
    font-size: 0.625rem;
    letter-spacing: 0.04em;
  }

  .lang-switch__btn {
    min-width: 40px;
    padding-inline: var(--space-2);
  }

  /* The bars are the affordance at this size; the word stays for screen
     readers so the button keeps its accessible name. */
  .nav-toggle__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

@media (min-width: 900px) {
  .site-header__inner { flex-wrap: nowrap; padding-block: var(--space-4); }

  /* DOM order is brand → utils → nav so the switcher stays reachable next to
     the menu button on mobile; on desktop the nav moves back into the middle. */
  .brand { order: 1; }
  .primary-nav { order: 2; }
  .site-header__utils { order: 3; }

  .nav-toggle,
  .js .nav-toggle { display: none; }

  .primary-nav,
  .js .primary-nav[hidden] {
    display: flex;
    flex-basis: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--space-5);
    padding-block: 0;
    margin-left: auto;
  }

  .primary-nav__list {
    flex-direction: row;
    gap: var(--space-1);
  }

  .primary-nav__list a {
    padding: var(--space-2) var(--space-3);
    border-bottom: 0;
  }

  /* Underline is a scaled pseudo-element rather than an animated border or
     width, so it wipes in on the compositor without reflowing the nav. */
  .primary-nav__list a::after {
    content: "";
    position: absolute;
    left: var(--space-3);
    right: var(--space-3);
    bottom: 0;
    height: 2px;
    /* Was the accent: a yellow rule is 1.38:1 on white and would read as no
       rule at all. The brand blue keeps the indicator visible. */
    background: var(--color-primary);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform var(--dur-mid) var(--ease-out);
  }

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

  /* The current page's underline is a state, not a response to the pointer,
     so it must not appear to wipe in when the page loads. */
  .primary-nav__list a[aria-current="page"]::after { transition: none; }
}

/* --------------------------------------------------------------------------
   Payment-safety ticker

   Sits directly under the sticky header. Brand yellow on deep blue is the
   loudest pair in the palette (12.72:1) and is otherwise reserved for CTAs,
   which is exactly why it earns its place here: this is the one line the site
   cannot afford a customer to skim past.

   The loop is two identical tracks laid side by side, both translated from
   -100% to 0. When the animation restarts, track 2 has arrived precisely where
   track 1 started, so there is no seam and no jump.
   -------------------------------------------------------------------------- */

.pay-ticker {
  /* Deliberately not sticky. The header already occupies top: 0 and its height
     changes on scroll, so a second sticky band would have to chase it; the
     ticker only needs to be the first thing under the header on arrival. */
  background: var(--color-primary-deep);
  color: var(--color-accent);
  border-bottom: 1px solid rgb(249 220 6 / 0.25);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.01em;
}

.pay-ticker__viewport {
  display: flex;
  overflow: hidden;
  padding-block: var(--space-2);
  /* Both edges fade out, so words dissolve rather than being sliced by the
     viewport. Progressive: without mask support the ticker just has hard
     edges, which is the pre-existing behaviour, not a broken one. */
  mask-image: linear-gradient(to right,
                transparent, #000 6%, #000 94%, transparent);
  -webkit-mask-image: linear-gradient(to right,
                transparent, #000 6%, #000 94%, transparent);
}

.pay-ticker__track {
  display: flex;
  flex: 0 0 auto;
  min-width: 100%;
  justify-content: space-around;
  gap: var(--space-7);
  padding-inline: calc(var(--space-7) / 2);
  white-space: nowrap;
  will-change: transform;
  animation: pay-ticker-slide 34s linear infinite;
}

/* Pause on hover so anyone who wants to read the account name properly can. */
.pay-ticker:hover .pay-ticker__track { animation-play-state: paused; }

@keyframes pay-ticker-slide {
  from { transform: translate3d(-100%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

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

.pay-ticker__item strong {
  color: var(--white);
  font-weight: 700;
}

.pay-ticker__dot {
  width: 6px;
  height: 6px;
  flex: 0 0 6px;
  border-radius: var(--radius-pill);
  background: currentColor;
}

@media (min-width: 768px) {
  .pay-ticker { font-size: var(--text-base); }
}

/* --------------------------------------------------------------------------
   Payment policy block (footer)
   -------------------------------------------------------------------------- */

/* This is a warning, not a paragraph of footer copy, so it is pulled off the
   footer's own surface rather than tinted a few percent above it: a yellow-cast
   panel, a full accent border with a heavier spine, and a shadow that lifts the
   whole block. White text sits at 10.7:1 on the resulting background. */
.pay-policy {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
  background: rgb(249 220 6 / 0.10);
  border: 1px solid rgb(249 220 6 / 0.55);
  border-left: 6px solid var(--color-accent);
  border-radius: var(--radius-md);
  padding: var(--space-5);
  margin-bottom: var(--space-6);
  box-shadow: 0 8px 24px rgb(10 8 40 / 0.35);
}

.pay-policy__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-primary-deep);
}

.pay-policy__title {
  font-size: var(--text-md);
  color: var(--color-accent);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: var(--space-2);
}

.pay-policy__lead {
  color: var(--color-text-invert);
  font-size: var(--text-md);
  font-weight: 600;
  margin-bottom: var(--space-3);
}

/* The account name is the single fact this whole block exists to deliver, so
   it is the only thing here allowed to break the footer's type rhythm. */
.pay-policy__name { color: var(--color-accent); }

/* Keeping the name on one line is worth a lot for recognition, but only once
   there is room: at 320px it is wider than the column and would overflow. */
@media (min-width: 480px) {
  .pay-policy__name { white-space: nowrap; }
}

/* Was 0.78 white, which sat at the same weight as the footer's ordinary body
   copy and let the caveat read as boilerplate. */
.pay-policy__note {
  color: rgba(255, 255, 255, 0.93);
  font-size: var(--text-sm);
  line-height: 1.6;
  max-width: 68ch;
}

/* On a phone the icon and the heading belong on one line; the body text then
   runs the full width of the panel rather than a ~40px-narrower column. */
@media (max-width: 559px) {
  .pay-policy {
    display: block;
    padding: var(--space-4);
  }

  .pay-policy__mark {
    flex: none;
    width: 32px;
    height: 32px;
    margin-bottom: var(--space-3);
  }

  .pay-policy__mark svg { width: 18px; height: 18px; }
}

/* --------------------------------------------------------------------------
   Site footer
   -------------------------------------------------------------------------- */

.site-footer {
  background: var(--color-surface-footer);
  color: rgba(255, 255, 255, 0.88);
  margin-top: var(--space-9);
  padding-top: var(--space-7);
  font-size: var(--text-sm);
}

@media (max-width: 559px) {
  .site-footer {
    margin-top: var(--space-7);
    padding-top: var(--space-6);
  }
}

.site-footer a {
  color: var(--yellow-500);
  text-decoration: none;
}
.site-footer a:hover {
  color: var(--yellow-500);
  text-decoration: underline;
}

.site-footer__grid {
  display: grid;
  gap: var(--space-6);
  padding-bottom: var(--space-7);
}

.site-footer__brand .brand__mark--footer { margin-bottom: var(--space-4); }

.site-footer__blurb { max-width: 40ch; }

/* --------------------------------------------------------------------------
   Footer licence plate
   --------------------------------------------------------------------------
   Same three facts as the licence card on the About page, compressed to a
   plate that fits under the footer blurb, and linked to the full block rather
   than repeating it. The number carries the accent colour because that is what
   someone is scanning the footer for; the label and issuer stay quiet.
   -------------------------------------------------------------------------- */

.footer-licence {
  display: inline-flex;
  flex-direction: column;
  gap: 1px;
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: rgb(255 255 255 / 0.05);
  border: 1px solid rgb(255 255 255 / 0.20);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-md);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}

/* `.site-footer a:hover` would otherwise underline all three lines at once,
   which on a bordered plate reads as damage. Only the number underlines. */
.site-footer a.footer-licence,
.site-footer a.footer-licence:hover { text-decoration: none; }

.footer-licence:hover {
  background: rgb(255 255 255 / 0.10);
  border-color: rgb(249 220 6 / 0.45);
  border-left-color: var(--color-accent);
}

.footer-licence:hover .footer-licence__number {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.footer-licence__label {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.72);
}

/* Out-specifies `.site-footer a` so the plate does not inherit the yellow link
   colour wholesale — only the number is meant to be yellow. */
.site-footer .footer-licence__number {
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--color-accent);
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.footer-licence__issuer {
  font-size: var(--text-xs);
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.72);
}

.site-footer__heading {
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-invert);
  margin-bottom: var(--space-4);
}

.site-footer__nav li { margin-bottom: var(--space-2); }

.site-footer__contact p + p { margin-top: var(--space-3); }

.site-footer__note {
  color: rgba(255, 255, 255, 0.72);
  font-size: var(--text-xs);
  max-width: 42ch;
}

.site-footer__legal {
  border-top: 1px solid rgba(255, 255, 255, 0.16);
  padding-block: var(--space-4);
  font-size: var(--text-xs);
  color: rgba(255, 255, 255, 0.72);
}

@media (min-width: 768px) {
  .site-footer__grid {
    grid-template-columns: 2fr 1fr 1.5fr;
    gap: var(--space-7);
  }
}

/* --------------------------------------------------------------------------
   Empty state
   -------------------------------------------------------------------------- */

.empty-state {
  text-align: center;
  background: var(--color-bg-subtle);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-7) var(--space-5);
  margin-block: var(--space-6);
}

.empty-state__title { margin-bottom: var(--space-3); }

.empty-state p {
  color: var(--color-text-muted);
  max-width: 48ch;
  margin-inline: auto;
  margin-bottom: var(--space-5);
}

/* --------------------------------------------------------------------------
   Side card (used on content pages)
   -------------------------------------------------------------------------- */

.side-card {
  background: var(--color-bg-subtle);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-accent);
  border-radius: var(--radius-md);
  padding: var(--space-5);
}

.side-card + .side-card { margin-top: var(--space-4); }

.side-card__title {
  font-size: var(--text-md);
  margin-bottom: var(--space-2);
}

.side-card__body {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin-bottom: var(--space-4);
}

/* --------------------------------------------------------------------------
   Interaction polish (Phase 3)
   -------------------------------------------------------------------------- */

.site-footer a,
.rate-card__link,
.prose a,
.side-card a:not(.btn) {
  transition: color var(--dur-fast) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}

/* Footer links get the underline on hover rather than permanently, which keeps
   the columns quiet while still signalling they are links on approach. */
.site-footer__nav a,
.site-footer__contact a {
  text-decoration-color: transparent;
  text-decoration-line: underline;
  text-underline-offset: 3px;
  transition: text-decoration-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}

.site-footer__nav a:hover,
.site-footer__contact a:hover { text-decoration-color: currentColor; }

/* Shared card lift. Applied to the surfaces that behave like cards across
   pages; the shadow does the depth, the transform does the movement, and
   neither touches layout. */
.feature,
.service,
.outlet-card,
.side-card {
  transition: transform var(--dur-mid) var(--ease-out),
              box-shadow var(--dur-mid) var(--ease-out),
              border-color var(--dur-mid) var(--ease-out);
}

@media (hover: hover) {
  .feature:hover,
  .service:hover,
  .outlet-card:hover,
  .side-card:hover {
    transform: translate3d(0, -3px, 0);
    box-shadow: var(--shadow-md);
  }
}
