/* ============================================================================
   SWORT interaction layer
   ---------------------------------------------------------------------------
   One place that owns how every clickable thing on the site RESPONDS. Colour per
   variant stays with the variant; this file owns timing, press feedback, focus,
   and touch behaviour, so interaction feels identical everywhere.

   Why this file exists
   --------------------
   The recurring "the button looks fine until you press it" defect had two
   separate causes, and only the first was a colour problem:

   1. COLOUR. An !important rule in contrast.css beat `:hover`, so a primary CTA
      kept black text while its background went transparent. Fixed at the source;
      a cascade-accurate audit now resolves 280 interactive elements across 1,120
      states with zero failures.

   2. NO PRESS FEEDBACK. A "magnetic CTA" script wrote an INLINE transform on
      every pointermove. An inline style outranks any stylesheet, so it silently
      defeated `:active { transform }`: the button gave no response to being
      pressed and slid out from under the cursor by up to 28% of the pointer
      offset. That script is deleted on all three pages that had it.

   The press response here is pure CSS and cannot be overridden by a stray
   inline write, because nothing writes inline transforms to buttons any more.

   Smoothness
   ----------
   - One duration and one easing for all interactive feedback, from the shared
     motion tokens. Hover, active, and focus all settle on the same curve, so
     moving across a row of buttons reads as one system rather than several.
   - transform and colour animate TOGETHER at the same duration. When they had
     different durations there was a window mid-transition where the label and
     the background were briefly close in tone.
   - touch-action: manipulation removes the ~300ms double-tap delay browsers
     otherwise add, which is the single biggest cause of a control feeling
     unresponsive on a phone.
   - The tap highlight is set explicitly. Left alone, mobile Safari and Chrome
     paint their own translucent overlay on tap, which on a dark button reads as
     the label washing out: exactly the symptom being fixed.
   ========================================================================= */

/* ── Selector sets ─────────────────────────────────────────────────────────
   Derived by enumerating every interactive class actually present in the
   rendered markup across all 14 routes, not hand-written. A hand-written list
   is how .btn-primary and .btn-secondary, the two most prominent CTAs on the
   homepage, were missed on the first attempt: buttons.css loaded, reported
   success, and left the hero buttons on the old behaviour.

   $universal  everything clickable, for touch behaviour only
   $pressable  things that should visibly depress, with the shared timing */

/* Touch behaviour: safe and correct on every interactive element, including
   plain text links. */
a, button, [role="button"], label, summary,
input[type="submit"], input[type="button"] {
  -webkit-tap-highlight-color: rgba(255, 255, 255, 0.08);
  touch-action: manipulation;
}
/* On light surfaces a white tap flash is invisible; use a dark one. */
.s-paper a, .s-paper button, .s-paper label,
.surface-paper a, .surface-paper button, .surface-paper label,
.form-surface a, .form-surface button, .form-surface label,
.p-form-surface a, .p-form-surface button, .p-form-surface label {
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0.08);
}

/* The full pressable set. */
.btn-primary, .btn-secondary, .btn-full,
.p-btn, .p-btn-primary, .p-btn-ghost, .p-btn-ink, .p-btn-ink-ghost, .p-btn-full,
.c-btn, .c-btn-primary, .c-btn-ghost,
.nav-cta, .nav-hamburger, .hw-btn,
.slot-btn, .bk-tz-btn, .bk-cal-arrow, .bk-submit, .bk-success-meet,
.bk-radio-label, .bk-budget-label,
.sim-reset-btn, .role-apply-link, .role-card, .route-cell,
.feat-panel-cta, .feat-panel-cta-strong,
.footer-legal-link, .footer-link, .t-link {
  /* One timing contract. Longhands, never `all`, so a transition can never
     animate a layout property and cause jitter. background-color and color run
     at the SAME duration: when they differed there was a window mid-transition
     where the label and its background were briefly close in tone, which is
     what "the text disappears when I press it" looks like even when both end
     states are fine. */
  transition:
    background-color var(--dur-ctrl) var(--ease-out),
    color            var(--dur-ctrl) var(--ease-out),
    border-color     var(--dur-ctrl) var(--ease-out),
    opacity          var(--dur-ctrl) var(--ease-out),
    transform        var(--dur-ctrl) var(--ease-out);
}

/* Press feedback: a small consistent shift. 1px translate, not a scale, because
   scaling a bordered button visibly thickens its border mid-animation. */
.btn-primary:active, .btn-secondary:active, .btn-full:active,
.p-btn:active, .p-btn-full:active,
.c-btn:active, .c-btn-primary:active, .c-btn-ghost:active,
.nav-cta:active, .hw-btn:active,
.slot-btn:active, .bk-tz-btn:active, .bk-cal-arrow:active, .bk-submit:active,
.sim-reset-btn:active, .role-apply-link:active,
.feat-panel-cta:active, .feat-panel-cta-strong:active {
  transform: translateY(1px);
}

/* Keyboard focus must be visible on every one of them, on both surfaces. */
.btn-primary:focus-visible, .btn-secondary:focus-visible, .btn-full:focus-visible,
.p-btn:focus-visible, .p-btn-full:focus-visible, .c-btn:focus-visible,
.nav-cta:focus-visible, .nav-hamburger:focus-visible, .hw-btn:focus-visible,
.slot-btn:focus-visible, .bk-tz-btn:focus-visible, .bk-cal-arrow:focus-visible,
.bk-submit:focus-visible, .sim-reset-btn:focus-visible,
.role-apply-link:focus-visible, .footer-legal-link:focus-visible,
.footer-link:focus-visible, .feat-panel-cta:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}
.s-paper .p-btn:focus-visible, .surface-paper .btn-full:focus-visible,
.p-form-surface .btn-full:focus-visible, .form-surface .btn-full:focus-visible,
.surface-paper .bk-submit:focus-visible, .t-link:focus-visible {
  outline-color: var(--focus-ring-paper);
}

/* Disabled: recolour, never fade. Reducing opacity on a button reduces the
   contrast of its label at the same time, which is how a disabled control ends
   up unreadable rather than merely inactive. */
/* NAMED variants only. A bare `button:disabled` was included here and it was
   too broad: the booking calendar's day cells are <button> elements, so disabled
   and past days got a filled gray-dark background from this rule while the
   contrast floor in contrast.css forced their text to the LIGHT dim value.
   Two of my own rules fighting produced 1.37:1 on real cells. A calendar cell is
   not a filled button and must not be styled as one. */
.btn-primary:disabled, .btn-secondary:disabled, .btn-full:disabled,
.p-btn:disabled, .p-btn-full:disabled, .c-btn:disabled,
.bk-submit:disabled {
  background-color: var(--button-disabled-bg);
  color: var(--button-disabled-text);
  border-color: var(--button-disabled-border);
  cursor: not-allowed;
  transform: none;
  opacity: 1;
}

/* Reduced motion: keep every colour change, drop the travel. The state is still
   communicated, just without movement. */
@media (prefers-reduced-motion: reduce) {
  .btn-primary, .btn-secondary, .btn-full,
  .p-btn, .p-btn-full, .c-btn, .nav-cta, .hw-btn,
  .slot-btn, .bk-tz-btn, .bk-submit, .sim-reset-btn, .role-apply-link,
  .role-card, .route-cell, .feat-panel-cta, .footer-legal-link, .footer-link, .t-link {
    transition:
      background-color var(--dur-micro) linear,
      color            var(--dur-micro) linear,
      border-color     var(--dur-micro) linear;
  }
  .btn-primary:active, .btn-secondary:active, .btn-full:active,
  .p-btn:active, .c-btn:active, .nav-cta:active, .hw-btn:active,
  .slot-btn:active, .bk-submit:active, .sim-reset-btn:active,
  .role-apply-link:active, .feat-panel-cta:active { transform: none; }
}

/* Forced colors: hand it entirely to the OS. */
@media (forced-colors: active) {
  .btn-primary, .btn-secondary, .btn-full, .p-btn, .c-btn, .nav-cta, .hw-btn {
    forced-color-adjust: auto;
  }
}


/* ── CTAs that were removed from the contrast floor ───────────────────────────
   These live HERE, not in page.css. page.css is loaded by only 4 of the 15
   pages, so the same rules placed there silently did nothing on the homepage and
   the booking page: .feat-panel-cta measured 3.51:1 and .role-card-cta 2.53:1
   because their only surviving rule was a page-level inline #555.

   Placement is part of correctness. A rule in a stylesheet the page does not
   load is indistinguishable from no rule at all.

   Rest colour is the measured dim-on-dark floor so nothing regressed; full white
   on interaction. Nothing here is !important: an !important colour on an
   interactive element is what broke hover states earlier in this project. */
.role-card-cta, .route-cell-cta, .feat-panel-cta, .feat-panel-cta-strong,
.sim-panel-cta {
  color: var(--dim-on-dark);
}
.role-card-cta:focus-visible,
.route-cell-cta:focus-visible,
.feat-panel-cta:focus-visible,
.feat-panel-cta-strong:focus-visible,
.sim-panel-cta:focus-visible { color: var(--swort-white); }
@media (hover: hover) and (pointer: fine) {
  .role-card-cta:hover,
  .route-cell-cta:hover,
  .feat-panel-cta:hover,
  .feat-panel-cta-strong:hover,
  .sim-panel-cta:hover { color: var(--swort-white); }
}
@media (hover: hover) and (pointer: fine) {
  .role-card:hover .role-card-cta,
  .route-cell:hover .route-cell-cta { color: var(--swort-white); }
}
.role-card-cta:active, .route-cell-cta:active,
.feat-panel-cta:active, .sim-panel-cta:active { color: var(--swort-silver); }


/* ── ONE INTERACTION CONTRACT FOR EVERY CONTROL ───────────────────────────
   Measured across 9 routes before writing this: 36 button families resolved to
   NINE different transition signatures. Seven families had `all 0s`, meaning no
   transition at all, so those buttons snapped between states while the ones
   beside them eased. Others animated only background, or only colour, or
   colour and border but not background, so a row of buttons that look like a
   set behaved like three different components.

   Signatures found:
     14 families  background-color, color, border-color, opacity, transform
      5 families  none at all
      5 families  background, color, border-color
      3 families  background, color
      3 families  background, color            (separate easing lists)
      2 families  background only
      2 families  none at all
      1 family    colour only
      1 family    colour and border only

   The fix is one rule, not thirty. Every family below now animates the same
   properties over the same duration on the same curve, and presses with the
   same 1px travel.

   SPECIFICITY, deliberately. The selector doubles each class, so it scores
   (0,2,0) and beats the single-class declarations these pages already carry
   without needing !important. An !important colour on an interactive element
   is what broke the hover states earlier in this project, so the pattern is
   avoided even here where only timing is involved.

   `background` and `background-color` are both listed because the page-level
   rules are split between the two, and a transition names the property it is
   given. Listing only one leaves half the buttons unanimated. */
.bk-cal-arrow.bk-cal-arrow, .bk-submit.bk-submit, .bk-tz-btn.bk-tz-btn,
.slot-btn.slot-btn,
.c-btn.c-btn, .c-hero-cta.c-hero-cta, .c-opp-cta.c-opp-cta,
.feat-panel-cta.feat-panel-cta, .found-cta.found-cta, .g-group-cta.g-group-cta,
.hw-btn.hw-btn, .hw-lede-cta.hw-lede-cta,
.l-nav-cta.l-nav-cta, .nav-cta.nav-cta, .p-nav-cta.p-nav-cta,
.net-btn.net-btn, .p-btn.p-btn, .p-btn-full.p-btn-full, .p-hero-cta.p-hero-cta,
.part-cta.part-cta, .prog-strip-cta.prog-strip-cta,
.role-card-cta.role-card-cta, .route-cell-cta.route-cell-cta,
.sim-reset-btn.sim-reset-btn, .sim-panel-cta.sim-panel-cta,
.swc-btn.swc-btn, .swl-btn.swl-btn, .bk-retry.bk-retry,
.btn-primary.btn-primary, .btn-secondary.btn-secondary, .btn-full.btn-full,
/* Cookie Preferences appears with four different classes across the site, and
   one instance with no class at all. data-consent-open is what they actually
   share, so it is the honest selector; doubling it matches the specificity of
   the class pairs above. All nine were the only visible controls left on the
   site with no transition whatsoever. */
.mnav-cta.mnav-cta, .mnav-close.mnav-close,
[data-consent-open][data-consent-open] {
  transition:
    background-color var(--dur-ctrl) var(--ease-out),
    background       var(--dur-ctrl) var(--ease-out),
    color            var(--dur-ctrl) var(--ease-out),
    border-color     var(--dur-ctrl) var(--ease-out),
    opacity          var(--dur-ctrl) var(--ease-out),
    transform        var(--dur-ctrl) var(--ease-out);
}

/* The same press on all of them. Travel, not scale: scaling a bordered button
   visibly thickens its border part-way through the animation. */
.bk-cal-arrow.bk-cal-arrow:active, .bk-submit.bk-submit:active,
.bk-tz-btn.bk-tz-btn:active, .slot-btn.slot-btn:active,
.c-btn.c-btn:active, .c-hero-cta.c-hero-cta:active, .c-opp-cta.c-opp-cta:active,
.feat-panel-cta.feat-panel-cta:active, .found-cta.found-cta:active,
.g-group-cta.g-group-cta:active, .hw-btn.hw-btn:active,
.hw-lede-cta.hw-lede-cta:active, .l-nav-cta.l-nav-cta:active,
.nav-cta.nav-cta:active, .p-nav-cta.p-nav-cta:active, .net-btn.net-btn:active,
.p-btn.p-btn:active, .p-btn-full.p-btn-full:active, .p-hero-cta.p-hero-cta:active,
.part-cta.part-cta:active, .prog-strip-cta.prog-strip-cta:active,
.role-card-cta.role-card-cta:active, .route-cell-cta.route-cell-cta:active,
.sim-reset-btn.sim-reset-btn:active, .sim-panel-cta.sim-panel-cta:active,
.swc-btn.swc-btn:active, .swl-btn.swl-btn:active, .bk-retry.bk-retry:active,
.btn-primary.btn-primary:active, .btn-secondary.btn-secondary:active,
.btn-full.btn-full:active, .mnav-cta.mnav-cta:active,
.mnav-close.mnav-close:active,
[data-consent-open][data-consent-open]:active {
  transform: translateY(1px);
}

/* Reduced motion keeps every colour change and drops the travel, matching the
   rule above for the original set. */
@media (prefers-reduced-motion: reduce) {
  .bk-cal-arrow.bk-cal-arrow:active, .bk-submit.bk-submit:active,
  .bk-tz-btn.bk-tz-btn:active, .slot-btn.slot-btn:active,
  .c-btn.c-btn:active, .c-hero-cta.c-hero-cta:active, .c-opp-cta.c-opp-cta:active,
  .feat-panel-cta.feat-panel-cta:active, .found-cta.found-cta:active,
  .g-group-cta.g-group-cta:active, .hw-btn.hw-btn:active,
  .hw-lede-cta.hw-lede-cta:active, .l-nav-cta.l-nav-cta:active,
  .nav-cta.nav-cta:active, .p-nav-cta.p-nav-cta:active, .net-btn.net-btn:active,
  .p-btn.p-btn:active, .p-btn-full.p-btn-full:active, .p-hero-cta.p-hero-cta:active,
  .part-cta.part-cta:active, .prog-strip-cta.prog-strip-cta:active,
  .role-card-cta.role-card-cta:active, .route-cell-cta.route-cell-cta:active,
  .sim-reset-btn.sim-reset-btn:active, .sim-panel-cta.sim-panel-cta:active,
  .swc-btn.swc-btn:active, .swl-btn.swl-btn:active, .bk-retry.bk-retry:active,
  .btn-primary.btn-primary:active, .btn-secondary.btn-secondary:active,
  .btn-full.btn-full:active, .mnav-cta.mnav-cta:active,
  .mnav-close.mnav-close:active,
  [data-consent-open][data-consent-open]:active { transform: none; }
}


/* ── PRESSED STATE: A COLOUR CHANGE, NOT JUST A NUDGE ─────────────────────
   Measured on production across seven routes: 46 of 46 button families changed
   NOTHING on :active. Every one of them had transform: translateY(1px) and no
   colour at all, so pressing a button moved it a pixel and that was the entire
   feedback. On a trackpad that is close to invisible.

   Why the earlier check missed it: the state was forced as ['hover'] and then
   ['active'] without clearing in between, so what looked like an active state
   was the hover state still applied. A test that never resets is a test that
   confirms whatever it saw first.

   Why the contrast gate missed it: a state that does not change cannot fail a
   contrast check. It inherits a passing colour and looks fine. The rendered
   gate now asserts that pressable controls actually CHANGE, which is the only
   way this class of defect is visible to a tool.

   Specificity: doubled class plus :active scores (0,3,0) and so beats the
   hover rules at (0,2,0). It has to, because on a pointer device the press
   happens while the control is already hovered.

   Four groups, because the correct pressed colour depends on what the button
   is, not on what it is called. No opacity anywhere: fading a button fades its
   label at the same time, which is how a pressed control becomes unreadable. */

/* A. Filled light on a dark surface. Rest is white with dark text; press
      settles to a neutral grey and keeps the dark label. */
.btn-primary.btn-primary:active,
.p-btn-primary.p-btn-primary:active,
.c-btn-primary.c-btn-primary:active,
.hw-btn.hw-btn[aria-checked="true"]:active,
.hw-btn.hw-btn[aria-pressed="true"]:active {
  background-color: #c6c6c6;
  color: #0a0a0a;
  border-color: #c6c6c6;
}

/* B. Filled dark. Rest is near-black with light text; press lifts the surface
      rather than dropping it, so the movement is visible against the page. */
.btn-full.btn-full:active,
.p-btn-full.p-btn-full:active,
.p-btn-ink.p-btn-ink:active,
.bk-submit.bk-submit:active,
.mnav-cta.mnav-cta:active {
  background-color: #333333;
  color: #f4f4f4;
  border-color: #333333;
}

/* C. Outline and ghost variants: a RING, not a fill.

      Two colour-based attempts failed here and the reason is structural, not a
      tuning problem. These variants INVERT on hover: rest is a light label on a
      transparent field, hover is a dark label on a solid one. A press happens
      while hovered, so any rule that replaces the background is fighting the
      hover state, and whichever colour it picks is wrong for one of the two.

        attempt 1, a hardcoded wash per assumed surface   1.05:1
          .swc-btn is the consent banner and .net-btn and .p-hero-cta sit on
          paper, so "outline on dark" was simply the wrong bucket for them
        attempt 2, a tint mixed from currentColor          1.12:1
          self-correcting against the REST state, but it discards the hover
          fill and strands the inverted label on the bare page

      An inset ring drawn in the control's own text colour cannot have this
      problem: it never touches the background the label sits on, so the
      contrast at press is exactly the contrast at hover, whatever that is. It
      is visible on every surface because it is drawn in a colour already known
      to be readable there. Paired with the 1px travel it reads clearly as a
      press.

      box-shadow rather than border, because a border would change the box size
      and shift the label. */
.btn-secondary.btn-secondary:active,
.p-btn-ghost.p-btn-ghost:active,
.c-btn-ghost.c-btn-ghost:active,
.nav-cta.nav-cta:active,
.l-nav-cta.l-nav-cta:active,
.p-nav-cta.p-nav-cta:active,
.p-hero-cta.p-hero-cta:active,
.c-hero-cta.c-hero-cta:active,
.hw-btn.hw-btn:active,
.hw-lede-cta.hw-lede-cta:active,
.bk-retry.bk-retry:active,
.slot-btn.slot-btn:active,
.sim-reset-btn.sim-reset-btn:active,
.net-btn.net-btn:active,
.swc-btn.swc-btn:active,
.swl-btn.swl-btn:active,
.mnav-close.mnav-close:active,
.p-btn-ink-ghost.p-btn-ink-ghost:active,
.c-opp-cta.c-opp-cta:active,
.found-cta.found-cta:active,
.part-cta.part-cta:active,
.g-group-cta.g-group-cta:active,
.bk-tz-btn.bk-tz-btn:active,
.bk-cal-arrow.bk-cal-arrow:active,
.t-link.t-link:active,
.route-cell-cta.route-cell-cta:active,
.role-card-cta.role-card-cta:active,
.feat-panel-cta.feat-panel-cta:active,
.prog-strip-cta.prog-strip-cta:active,
.swc-close.swc-close:active,
.swc-btn-dark.swc-btn-dark:active,
.mnav-trigger.mnav-trigger:active,
[data-consent-open][data-consent-open]:active {
  box-shadow: inset 0 0 0 2px currentColor;
}


/* ── THE LABEL LEADS THE BACKGROUND ON PRESS ──────────────────────────────
   Sampled every 25ms through a real press on production after the pressed
   colours landed:

     hover      white label, transparent field          21.00:1
     press+25   rgb(110,110,110) on rgba(198,198,198,.59)  2.99:1   <-- dip
     press+50   rgb(43,43,43)  on rgba(198,198,198,.87)    8.29:1
     press+250  rgb(10,10,10)  on rgb(198,198,198)        11.59:1

   Both ends are fine and the middle is not. The label travels white to near
   black while the field travels transparent to light grey, so for about a
   frame they pass THROUGH each other. That crossing is the flash people
   describe as the text disappearing when they click, and it survives even
   though the two properties share a duration: equal durations make the crossing
   symmetric, they do not prevent it.

   Shortening the colour transition was not enough. At 70ms linear the label was
   still at rgb(138,138,138) after one frame while the field had already reached
   60% of a light grey, which composites to about rgb(122,122,122): 2.02:1, and
   marginally WORSE than before. The reason is that --ease-out front-loads the
   background, so the field brightens fastest at exactly the moment the label is
   mid-journey. Colour cannot outrun it by tuning.

   So the label does not travel at all on press: it snaps. Against a 200ms field
   fade a 0ms colour change is imperceptible as a separate event, and it makes
   the crossing arithmetically impossible, because the label is already at its
   destination before the field has moved. Only the pressed state is retimed;
   releasing keeps the shared timing, since on the way back the label is leaving
   a settled state rather than racing one. */
.btn-primary.btn-primary:active,
.p-btn-primary.p-btn-primary:active,
.c-btn-primary.c-btn-primary:active,
.hw-btn.hw-btn[aria-checked="true"]:active,
.hw-btn.hw-btn[aria-pressed="true"]:active,
.btn-full.btn-full:active,
.p-btn-full.p-btn-full:active,
.p-btn-ink.p-btn-ink:active,
.bk-submit.bk-submit:active,
.mnav-cta.mnav-cta:active {
  transition:
    color            0s,
    background-color var(--dur-ctrl) var(--ease-out),
    background       var(--dur-ctrl) var(--ease-out),
    border-color     var(--dur-ctrl) var(--ease-out),
    transform        var(--dur-ctrl) var(--ease-out);
}
