/* ==========================================================================
   RoofLeadHQ — interaction layer
   ==========================================================================

   WHAT THIS FILE IS. The tactile half of the design system: how a control
   answers a pointer, how a field answers focus, how a section arrives, and how
   a form reports what happened. Colour, type, spacing and elevation stay in
   tokens.css; this file only ever animates or states an INTERACTION.

   WHY IT IS A SEPARATE FILE. tokens.css is loaded by every page on the site and
   is the locked design contract (2026-07-22). Interaction states are not
   tokens, they change more often, and a reviewer should be able to read the
   whole motion surface of the site in one place — and revert it in one line if
   it is wrong.

   THE BRAND BRIEF THIS IMPLEMENTS: fast, confident, operational, practical,
   premium. Motion here reinforces SPEED. Every transition on a conversion path
   is at or under 140ms, nothing delays a click, nothing decorative moves on its
   own, and there are no ambient loops. If a change here would make a roofer
   wait a frame longer to reach the signup form, it does not belong in this
   file.

   LOAD ORDER: after tokens.css (which it reads variables from) and after
   styles.css (which it deliberately overrides in two places, both marked).
   ========================================================================== */

:root {
  /*
   * Press timing. --rlhq-duration-fast (140ms) is right for a hover; a press
   * needs to be quicker than the eye, or the button reads as hesitating. 80ms
   * is felt, not seen.
   *
   * NOTE these are NOT collapsed by the reduced-motion block in tokens.css,
   * which only rewrites --rlhq-duration and --rlhq-duration-fast. They are
   * re-declared under the reduce query at the bottom of this file.
   */
  --rlhq-duration-press: 80ms;
  --rlhq-ease-press: cubic-bezier(0.3, 0, 0.2, 1);

  /* One lift value and one compression value for the whole site. */
  --rlhq-lift: -1px;
  --rlhq-lift-card: -2px;
  --rlhq-press-scale: 0.985;

  /* Entrance travel. Short: a section should look like it settled, not flew. */
  --rlhq-enter-shift: 10px;
}

/* ==========================================================================
   1. Controls — hover lifts, press compresses, release settles
   ========================================================================== */

/*
 * OVERRIDE, DELIBERATE. styles.css sets `transition: all 0.25s ease` on every
 * <a> and <button> on the site. That is why CTAs on this site currently feel a
 * quarter-second behind the cursor: `all` animates the transform too, so a
 * press takes 250ms to compress and another 250ms to release, by which point
 * the click has already happened and the button appears to respond to nothing.
 *
 * The rule below leaves colour and background on their existing 250ms — that
 * part is fine and is the site's established feel — and pulls transform and
 * box-shadow onto interaction timing.
 */
/*
 * The doubled class names are load-bearing, not a typo. Tailwind's play CDN
 * injects its utilities into a <style> element at runtime — i.e. AFTER this
 * stylesheet in document order — so a Tailwind utility of EQUAL specificity
 * wins on order alone. `.transition-shadow` and `.transition-colors` sit on
 * these same elements and would silently take the transition-property back,
 * which is exactly what happened to .plan-card in testing: the hover lift was
 * declared, applied, and snapped with no transition because the property list
 * had been replaced. Doubling the class raises specificity to (0,2,0) and
 * settles it without !important.
 */
.primary-cta.primary-cta,
.secondary-cta.secondary-cta,
.rlhq-btn.rlhq-btn,
[data-plan-cta] > span,
.rlhq-submit.rlhq-submit {
  transition-property: background-color, background, color, border-color, box-shadow, opacity, transform;
  transition-duration: var(--rlhq-duration), var(--rlhq-duration), var(--rlhq-duration),
    var(--rlhq-duration), var(--rlhq-duration-fast), var(--rlhq-duration), var(--rlhq-duration-fast);
  transition-timing-function: var(--rlhq-ease);
}

/*
 * Hover is scoped to devices with a real pointer. On a phone, :hover latches
 * after a tap: the button stays lifted until something else is touched, which
 * looks like a rendering fault rather than feedback — and this site's primary
 * CTA is an SMS link that a roofer taps and leaves the page from, so the stuck
 * state would be the last thing they see.
 */
@media (any-hover: hover) {
  .primary-cta:hover:not(:disabled),
  [data-plan-cta]:hover > span {
    transform: translateY(var(--rlhq-lift));
    box-shadow: var(--rlhq-shadow-md);
  }

  .secondary-cta:hover:not(:disabled) {
    transform: translateY(var(--rlhq-lift));
  }
}

/*
 * THE PRESS. Compression is scale, never a downward translate: translating down
 * by a pixel is visually identical to the hover lift ending, so the click never
 * registers as received. Release runs at --rlhq-duration-fast (the base rule
 * above), so the control compresses faster than it returns — the asymmetry is
 * what makes it feel like a weighted button rather than a rectangle.
 */
.primary-cta:active:not(:disabled),
.secondary-cta:active:not(:disabled),
.rlhq-btn:active:not(:disabled),
.rlhq-submit:active:not(:disabled),
[data-plan-cta]:active > span {
  transform: translateY(0) scale(var(--rlhq-press-scale));
  transition-duration: var(--rlhq-duration-press);
  transition-timing-function: var(--rlhq-ease-press);
}

.primary-cta:active:not(:disabled),
[data-plan-cta]:active > span {
  box-shadow: var(--rlhq-shadow-sm);
}

/*
 * DISABLED IS QUIETER, NOT MERELY FAINTER. Opacity alone leaves a disabled
 * ember button glowing at 60% — still the brightest thing on the form, still
 * pulling the eye to a control that cannot be used. Desaturating the fill and
 * dropping the shadow lets it recede, and the transition means the moment a
 * form becomes valid the button visibly comes UP rather than snapping on.
 */
button:disabled,
.rlhq-btn:disabled,
.rlhq-submit:disabled,
input:disabled,
select:disabled,
textarea:disabled {
  cursor: not-allowed;
  filter: saturate(0.5);
  box-shadow: none;
}

button:disabled,
.rlhq-submit:disabled {
  transform: none;
}

button:disabled:hover,
button:disabled:active,
.rlhq-submit:disabled:hover,
.rlhq-submit:disabled:active {
  transform: none;
  box-shadow: none;
}

/* ==========================================================================
   2. Fields — focus that arrives rather than snaps
   ========================================================================== */

.rlhq-input,
.form-input,
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="url"],
input[type="number"],
select,
textarea {
  transition: border-color var(--rlhq-duration-fast) var(--rlhq-ease),
    box-shadow var(--rlhq-duration-fast) var(--rlhq-ease),
    background-color var(--rlhq-duration-fast) var(--rlhq-ease);
}

/*
 * A field never moves. Not on hover, not on focus, not on error. A caret sits
 * in these, and a box that shifts under a caret is a usability defect wearing
 * polish. Only the border and the ring change.
 */
@media (any-hover: hover) {
  .rlhq-input:hover:not(:focus):not(:disabled),
  textarea:hover:not(:focus):not(:disabled) {
    border-color: var(--rlhq-zinc);
  }
}

/* The choice rows (plan radios, legal checkboxes) are controls, so they get the
   same press acknowledgement the buttons do. */
.rlhq-choice {
  transition: border-color var(--rlhq-duration-fast) var(--rlhq-ease),
    background-color var(--rlhq-duration-fast) var(--rlhq-ease),
    transform var(--rlhq-duration-fast) var(--rlhq-ease);
}

.rlhq-choice:active {
  transform: scale(var(--rlhq-press-scale));
  transition-duration: var(--rlhq-duration-press);
  transition-timing-function: var(--rlhq-ease-press);
}

/* ==========================================================================
   3. Cards — depth only where there is something to click
   ========================================================================== */

/*
 * PRICING CARDS. These lift, and the reason they are allowed to when the record
 * cards elsewhere are not is that the whole card is a priced offer terminating
 * in a Sign Up button — the surface and the action mean the same thing, so
 * depth under the cursor is an honest cue. It is 2px and a shadow; the border
 * does not change colour, because the ember hairline on Growth is carrying the
 * "most popular" distinction and a hover that mimics it would blur the two.
 *
 * :focus-within is included so tabbing to the Sign Up link raises the card the
 * same way hovering it does.
 */
.plan-card.plan-card {
  transition: transform var(--rlhq-duration-fast) var(--rlhq-ease),
    box-shadow var(--rlhq-duration-fast) var(--rlhq-ease);
}

@media (any-hover: hover) {
  .plan-card:hover {
    transform: translateY(var(--rlhq-lift-card));
  }
}

.plan-card:focus-within {
  box-shadow: var(--rlhq-shadow-md);
}

/* ==========================================================================
   4. Section entrances — scroll-driven, and unable to hide anything
   ==========================================================================

   Driven by `animation-timeline: view()`, so there is no observer, no library
   and no JavaScript. The opacity:0 start state exists ONLY inside the keyframes
   and the keyframes only apply where BOTH @supports and a motion-tolerant
   visitor are present — so on a browser without scroll-driven animation
   (Safari below 26, older Firefox), with JS off, or with reduced motion set,
   the section renders exactly as it does today. The failure mode is "no
   animation", never "no content", which is the only acceptable trade on pages
   whose job is to get a roofer to a signup form.

   The reduced-motion block in tokens.css cannot be relied on here: it collapses
   animation-DURATION, and duration is precisely what a scroll-driven animation
   ignores. Hence the explicit no-preference guard.

   The range is short — the entrance finishes while the section is still
   arriving, so nothing is ever mid-fade at the moment it becomes readable. */

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    [data-reveal] {
      animation: rlhq-enter var(--rlhq-duration) var(--rlhq-ease) both;
      animation-timeline: view();
      animation-range: entry 8% cover 24%;
    }

    /* Stagger expressed as a longer RANGE, not a delay: a scroll-driven
       animation has no wall clock, so animation-delay would apply to a timeline
       it is not being driven by and every child would move as one. */
    [data-reveal-group] > * {
      animation: rlhq-enter var(--rlhq-duration) var(--rlhq-ease) both;
      animation-timeline: view();
      animation-range: entry 8% cover 24%;
    }
    [data-reveal-group] > *:nth-child(2) { animation-range: entry 8% cover 30%; }
    [data-reveal-group] > *:nth-child(3) { animation-range: entry 8% cover 36%; }
    [data-reveal-group] > *:nth-child(4) { animation-range: entry 8% cover 42%; }
    [data-reveal-group] > *:nth-child(n + 5) { animation-range: entry 8% cover 46%; }
  }
}

@keyframes rlhq-enter {
  from {
    opacity: 0;
    transform: translateY(var(--rlhq-enter-shift));
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ==========================================================================
   5. Form outcomes — one shape for success, one for failure
   ==========================================================================

   Both are driven off classes and attributes forms.js and script.js ALREADY
   toggle (.rlhq-show, [hidden], the inserted role="status" panel). No
   JavaScript was changed to add motion, which is deliberate: the submit path,
   the founder notification, the customer acknowledgement and the paid report
   flow are all live and verified, and a purely presentational change must not
   touch the code that carries them. */

.rlhq-next-step.rlhq-show,
.rlhq-form-status.rlhq-show,
#contact-form [role="status"] {
  animation: rlhq-settle var(--rlhq-duration) var(--rlhq-ease) both;
}

/*
 * Failure gets the SAME arrival as success — no shake, no flash, no colour
 * pulse. A roofer who has just been told nothing was sent needs to read the
 * sentence and try again; a page that dramatises the error makes it feel like a
 * bigger problem than a retry.
 */
#contact-error:not([hidden]) {
  animation: rlhq-settle var(--rlhq-duration) var(--rlhq-ease) both;
}

/* The per-field error line is small and appears next to the field being fixed,
   so it gets a shorter, quieter version of the same move. */
.rlhq-field-error.rlhq-show {
  animation: rlhq-settle var(--rlhq-duration-fast) var(--rlhq-ease) both;
}

@keyframes rlhq-settle {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ==========================================================================
   6. Reduced motion
   ==========================================================================

   tokens.css already collapses durations and animations site-wide. This block
   covers what that one cannot reach: the press/lift transforms declared here
   (which are transitions on custom properties it does not rewrite) and the
   scroll-driven entrances (which ignore duration entirely — they are simply
   never declared, because §4 sits inside a no-preference query). */

@media (prefers-reduced-motion: reduce) {
  :root {
    --rlhq-duration-press: 0ms;
    --rlhq-lift: 0px;
    --rlhq-lift-card: 0px;
    --rlhq-press-scale: 1;
    --rlhq-enter-shift: 0px;
  }

  .primary-cta:hover,
  .primary-cta:active,
  .secondary-cta:hover,
  .secondary-cta:active,
  .rlhq-btn:active,
  .rlhq-submit:active,
  .rlhq-choice:active,
  .plan-card:hover,
  [data-plan-cta]:hover > span,
  [data-plan-cta]:active > span {
    transform: none;
  }
}
