/* ============================================================================
   Rain Tunnel Car Wash — site overrides
   Loaded after styles.css (the compiled utility sheet), so these win.

   styles.css is the stylesheet the old React build compiled. It is kept
   verbatim so the design is unchanged. Everything that needed correcting is
   corrected here, in one file, rather than by editing the compiled output.
   ============================================================================ */

/* ---------------------------------------------------------------------------
   1. Palette — contrast correction (WCAG 1.4.3, AA)

   `styles.css` declares the brand blue as `hsl(210 100% 50%)` = #0080FF.
   That value measures 3.80:1 against white and 3.57:1 against the page
   background, so every use of it that carries text fails the 4.5:1 minimum:

     blue text on a light ground   #0080FF on #FFFFFF   3.80:1
     blue text on the page ground  #0080FF on #F8FAFC   3.57:1
     white text on a solid blue    #FFFFFF on #0080FF   3.80:1

   WAVE reported seven of these on the homepage; the same three pairings
   recur on every template. #0066CC is the same hue one step darker and
   measures 5.57:1 white-on-blue and blue-on-white, so all three pairings
   clear AA from a single token. It is also the blue the site already
   declares in its own `theme-color` meta, and the value `brand-guide.html`
   documents as Primary.

   Overridden here rather than in `styles.css`, which is a vendored artifact.
   Declared before section 5 on purpose: `.high-contrast` re-declares
   `--primary` at the same specificity, so it has to stay later in the file
   to keep winning when the widget is on.
   --------------------------------------------------------------------------- */
:root {
  --primary: 210 100% 40%;   /* #0066CC */
}

/* Body copy inside the solid-blue CTA bands. `text-white/80` composites to
   4.13:1 on #0066CC and `text-white/70` to 3.55:1 — both under 4.5:1 for
   text this size. 0.92 measures 4.93:1 and is the smallest step that clears
   it. Scoped to `.bg-primary` on purpose: the same two utilities also sit on
   the yellow and green tier cards, where MORE white would make things worse,
   and over the hero photo, which is a separate manual check. */
.bg-primary .text-white\/80,
.bg-primary .text-white\/70 { color: rgb(255 255 255 / 0.92); }

/* ---------------------------------------------------------------------------
   2. Focus visibility (WCAG 2.4.7)
   --------------------------------------------------------------------------- */
:focus-visible {
  outline: 3px solid hsl(var(--ring));
  outline-offset: 2px;
  border-radius: 0.25rem;
}
/* A blue ring is invisible on the blue header — use white there. */
header :focus-visible,
.bg-primary :focus-visible,
[data-mobile-rail] :focus-visible {
  outline: 3px solid #fff;
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   3. Anchor offset — the header is fixed and 6rem tall
   --------------------------------------------------------------------------- */
[id] { scroll-margin-top: calc(var(--header-height) + 1.5rem); }
html { scroll-behavior: smooth; }

/* ---------------------------------------------------------------------------
   4. FAQ disclosure
   --------------------------------------------------------------------------- */
[data-acc-trigger][aria-expanded="true"] [data-acc-chevron] { transform: rotate(180deg); }

/* ---------------------------------------------------------------------------
   5. Accessibility widget states

   Note: this widget is a convenience, not a compliance measure. Every rule
   it toggles is a genuine CSS change here; the underlying HTML meets
   WCAG 2.1 AA on its own. See ACCESSIBILITY.md, "Overlays are not compliance".
   --------------------------------------------------------------------------- */

/* Reduce Motion. This toggle had no CSS behind it in the compiled sheet —
   the button appeared to work and did nothing. */
.reduced-motion *,
.reduced-motion *::before,
.reduced-motion *::after {
  animation-duration: 1ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 1ms !important;
}
.reduced-motion { scroll-behavior: auto !important; }

/* Honour the OS setting with no widget interaction at all. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
  html { scroll-behavior: auto; }
}

/* High Contrast. The compiled sheet used `filter: contrast(1.4)` on the root
   element, which creates a containing block and breaks `position: fixed` —
   the sticky header and this widget would scroll away with the page. Drive
   the theme tokens instead. */
.high-contrast { filter: none !important; }
.high-contrast body { background: #000 !important; color: #fff !important; }
.high-contrast {
  --background: 0 0% 0%;
  --foreground: 0 0% 100%;
  --card: 0 0% 6%;
  --card-foreground: 0 0% 100%;
  --popover: 0 0% 6%;
  --popover-foreground: 0 0% 100%;
  --muted: 0 0% 14%;
  --muted-foreground: 0 0% 90%;
  --primary: 210 100% 74%;
  --primary-foreground: 0 0% 0%;
  --secondary: 50 100% 72%;
  --secondary-foreground: 0 0% 0%;
  --accent: 160 84% 66%;
  --accent-foreground: 0 0% 0%;
  --border: 0 0% 100%;
  --input: 0 0% 100%;
  --ring: 50 100% 72%;
}
.high-contrast .text-white\/90,
.high-contrast .text-white\/80,
.high-contrast .text-white\/70,
.high-contrast .text-background\/70,
.high-contrast .text-muted-foreground { color: #fff !important; }
.high-contrast .bg-primary\/70 { background-color: #000 !important; }
.high-contrast header { border-bottom: 2px solid #fff; }
.high-contrast .text-gradient {
  background: none !important;
  -webkit-text-fill-color: currentColor !important;
  color: #fff !important;
}

.large-text { font-size: 125%; }

.highlight-links a {
  background-color: #ffd70033 !important;
  outline: 3px solid gold !important;
  outline-offset: 2px;
}

[data-a11y][aria-pressed="true"] {
  background-color: hsl(var(--primary)) !important;
  color: #fff !important;
}

/* ---------------------------------------------------------------------------
   6. Tap targets — 44px minimum on anything interactive (WCAG 2.5.5 / 2.5.8)
   --------------------------------------------------------------------------- */
header nav a,
[data-mobile-rail] a,
footer a[aria-label] {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

/* ---------------------------------------------------------------------------
   7. Print
   --------------------------------------------------------------------------- */
@media print {
  header,
  [data-mobile-rail],
  [data-a11y-widget],
  .print\:hidden { display: none !important; }
  body { padding-top: 0 !important; }
}

/* ============================================================================
   9. Utilities the compiled sheet does not contain

   styles.css was produced by Tailwind scanning the old React source, so it
   only holds the utilities that source happened to use. Any class written by
   hand after the fact silently does nothing. These are the ones this site
   needs, defined explicitly so there is no build step to run.
   ============================================================================ */

/* sizing - 44px is the minimum tap target */
.h-11        { height: 2.75rem; }
.w-11        { width: 2.75rem; }
.min-h-11    { min-height: 2.75rem; }
.min-h-12    { min-height: 3rem; }
.min-h-40    { min-height: 10rem; }
.h-\[120px\] { height: 120px; }
.min-h-\[200px\] { min-height: 200px; }

/* layering */
.z-40 { z-index: 40; }

/* radius + borders */
.rounded      { border-radius: 0.25rem; }
.border-b-2   { border-bottom-width: 2px; }
.border-gray-300  { border-color: #d1d5db; }
.border-white\/40 { border-color: rgb(255 255 255 / 0.4); }

/* backgrounds */
.bg-background\/10 { background-color: hsl(var(--background) / 0.1); }
.bg-foreground\/95 { background-color: hsl(var(--foreground) / 0.95); }
.bg-center         { background-position: center; }

/* position */
.top-10    { top: 2.5rem; }
.bottom-10 { bottom: 2.5rem; }
.left-10   { left: 2.5rem; }
.right-10  { right: 2.5rem; }

/* spacing */
.pt-32 { padding-top: 8rem; }
.pb-48 { padding-bottom: 12rem; }
.pb-28 { padding-bottom: 7rem; }
.pb-12 { padding-bottom: 3rem; }
.pl-5  { padding-left: 1.25rem; }
.pr-4  { padding-right: 1rem; }

/* type */
.text-7xl { font-size: 4.5rem; line-height: 1; }

/* overflow */
.overflow-x-auto { overflow-x: auto; }

/* interaction */
.hover\:bg-primary:hover     { background-color: hsl(var(--primary)); }
.hover\:bg-white\/10:hover   { background-color: rgb(255 255 255 / 0.1); }
.hover\:text-gray-900:hover  { color: #111827; }
.hover\:text-white:hover     { color: #fff; }
.focus\:z-\[100\]:focus      { z-index: 100; }
.focus\:text-foreground:focus { color: hsl(var(--foreground)); }

@media (min-width: 768px) {
  .md\:bottom-6  { bottom: 1.5rem; }
  .md\:pb-16     { padding-bottom: 4rem; }
  .md\:text-8xl  { font-size: 6rem; line-height: 1; }
}

/* ============================================================================
   10. Legal page body copy

   The Privacy Policy and Terms pages carry `prose prose-lg`, but the Tailwind
   Typography plugin was never part of the build - those classes have never
   resolved to anything, on this site or the React one. Minimal, readable
   defaults so the legal pages meet the same standard as every other page.
   ============================================================================ */
.prose { color: hsl(var(--foreground)); max-width: 65ch; }
.prose-lg { font-size: 1.0625rem; line-height: 1.75; }
.prose h2 { font-size: 1.5rem; font-weight: 700; margin-top: 2.5rem; margin-bottom: 0.75rem; }
.prose h3 { font-size: 1.25rem; font-weight: 700; margin-top: 1.75rem; margin-bottom: 0.5rem; }
.prose p  { margin-bottom: 1.125rem; }
.prose ul, .prose ol { margin: 0 0 1.125rem 1.5rem; }
.prose ul { list-style: disc; }
.prose ol { list-style: decimal; }
.prose li { margin-bottom: 0.4rem; }
.prose a  { color: hsl(var(--primary)); text-decoration: underline; }
.prose a:hover { text-decoration: none; }
.prose strong { font-weight: 700; }

/* ---------------------------------------------------------------------------
   13. Long URLs in legal copy

   The Privacy Policy and Terms print full URLs (optspot.com/tnc, Google's
   policy pages). At 320px an unbroken URL pushes the page into horizontal
   scrolling, which fails WCAG 1.4.10 reflow.
   --------------------------------------------------------------------------- */
.prose a,
.prose p,
.prose li { overflow-wrap: anywhere; }

/* ============================================================================
   14. Scroll reveals

   The React build used framer-motion: initial {opacity:0, y:20} animating to
   {opacity:1, y:0} over 0.6s, once per element. Rebuilt here as CSS plus an
   IntersectionObserver, with the same offsets and duration.

   The hidden state is scoped to .js-reveal, which site.js sets on <html>
   before paint. With JavaScript off the class never lands and every element
   renders at its final state, so no real content is gated behind a scroll
   event (ACCESSIBILITY.md).
   ============================================================================ */
.js-reveal [data-reveal] {
  opacity: 0;
  transition: opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: opacity, transform;
}
.js-reveal [data-reveal="up"]    { transform: translateY(24px); }
.js-reveal [data-reveal="down"]  { transform: translateY(-24px); }
.js-reveal [data-reveal="left"]  { transform: translateX(24px); }
.js-reveal [data-reveal="right"] { transform: translateX(-24px); }
.js-reveal [data-reveal="scale"] { transform: scale(0.92); }
.js-reveal [data-reveal="fade"]  { transform: none; }

.js-reveal [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* A sideways offset adds to the scroll width on a narrow screen, and the
   transform also becomes a containing block for the decorative blobs that
   sit outside the viewport - both push past 320px and break reflow
   (WCAG 1.4.10). Reveal vertically on phones. */
@media (max-width: 767px) {
  .js-reveal [data-reveal="left"],
  .js-reveal [data-reveal="right"] { transform: translateY(24px); }
}

/* Reduced motion - widget toggle or OS setting - shows the final state at once,
   never a frozen half-animation or an empty space. */
.reduced-motion [data-reveal],
.reduced-motion [data-reveal].is-revealed {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}
@media (prefers-reduced-motion: reduce) {
  .js-reveal [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* ============================================================================
   15. Hover and press feedback

   framer-motion drove these per element (whileHover scale 1.05 / y -5 / y -10,
   whileTap scale 0.95-0.98). Reproduced as CSS on the same element families so
   they work without JS and pick up :focus-visible for keyboard users.
   ============================================================================ */
@media (hover: hover) {

  /* CTA buttons and button-styled links */
  main a[class*="rounded-md"][class*="px-"],
  header a[class*="rounded-md"][class*="px-"],
  main button[class*="rounded-md"][class*="px-"] {
    transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
  }
  main a[class*="rounded-md"][class*="px-"]:hover,
  header a[class*="rounded-md"][class*="px-"]:hover,
  main button[class*="rounded-md"][class*="px-"]:hover {
    transform: scale(1.05);
  }

  /* Content cards lift */
  main [class*="bg-card"][class*="rounded-xl"],
  main [class*="bg-card"][class*="rounded-2xl"],
  main [class*="bg-white"][class*="rounded-2xl"] {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
  }
  main [class*="bg-card"][class*="rounded-xl"]:hover,
  main [class*="bg-card"][class*="rounded-2xl"]:hover,
  main [class*="bg-white"][class*="rounded-2xl"]:hover {
    transform: translateY(-5px);
    box-shadow: 0 20px 40px -12px rgb(0 0 0 / 0.22);
  }

  /* Pricing tiers lift further, as they did before */
  main [class*="rounded-2xl"][class*="shadow-2xl"]:hover,
  main [class*="rounded-3xl"][class*="shadow-2xl"]:hover {
    transform: translateY(-10px);
  }

  /* Gradient icon tiles */
  main [class*="rounded-2xl"][class*="bg-gradient-to-br"] {
    transition: transform 0.3s ease;
  }
  main [class*="rounded-2xl"][class*="bg-gradient-to-br"]:hover {
    transform: scale(1.08) rotate(-3deg);
  }

  /* Social icons in the footer */
  footer a[aria-label] { transition: transform 0.2s ease, background-color 0.2s ease; }
  footer a[aria-label]:hover { transform: translateY(-3px); }
}

/* Press feedback, pointer and keyboard alike */
main a[class*="rounded-md"][class*="px-"]:active,
header a[class*="rounded-md"][class*="px-"]:active,
main button[class*="rounded-md"][class*="px-"]:active {
  transform: scale(0.98);
}

/* Keyboard users get the same affordance as the mouse (ACCESSIBILITY.md). */
main a[class*="rounded-md"][class*="px-"]:focus-visible,
header a[class*="rounded-md"][class*="px-"]:focus-visible {
  transform: scale(1.05);
}
.reduced-motion *:hover,
.reduced-motion *:active,
.reduced-motion *:focus-visible { transform: none !important; }

/* ============================================================================
   16. Manage My Plan

   This page never lived in the React bundle - it was a standalone file with
   its own CSS. These rules reproduce that page's exact treatment (hero
   gradient over the droplet photo, glass badge, gradient icon circles, yellow
   help panel) so the rebuild matches what is live.
   ============================================================================ */
.mmp-hero {
  background:
    linear-gradient(135deg, rgba(30, 58, 138, 0.9) 0%, rgba(59, 130, 246, 0.85) 100%),
    image-set(url('/assets/manage-plan-hero.webp') type('image/webp'),
              url('/assets/manage-plan-hero.jpg') type('image/jpeg'));
  background-size: cover;
  background-position: center;
  padding: 10rem 1.5rem 12rem;
  text-align: center;
  position: relative;
}
.mmp-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: rgb(255 255 255 / 0.15);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  padding: 0.5rem 1.25rem;
  border-radius: 9999px;
  color: #fff;
  font-size: 0.9rem;
  margin-bottom: 1.5rem;
}
.mmp-hero h1 {
  font-family: 'Bebas Neue', sans-serif;
  font-size: 4rem;
  color: #fff;
  margin-bottom: 1rem;
  letter-spacing: 2px;
  line-height: 1.05;
}
.mmp-hero h1 span { color: #FFD700; }
.mmp-hero > p {
  color: rgb(255 255 255 / 0.9);
  font-size: 1.25rem;
  max-width: 600px;
  margin: 0 auto;
  line-height: 1.6;
}
.mmp-arch { position: absolute; bottom: 0; left: 0; right: 0; line-height: 0; }
.mmp-arch svg { width: 100%; height: 120px; display: block; }

.mmp-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  margin-bottom: 3rem;
  list-style: none;
}
.mmp-card {
  background: #fff;
  border-radius: 1rem;
  padding: 1.5rem;
  text-align: center;
  box-shadow: 0 4px 20px rgb(0 0 0 / 0.08);
  border: 1px solid rgb(0 0 0 / 0.05);
  transition: transform 0.2s, box-shadow 0.2s;
}
.mmp-card-icon {
  width: 56px;
  height: 56px;
  background: linear-gradient(135deg, #3b82f6, #1e3a8a);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 1rem;
  font-size: 1.5rem;
}
.mmp-card h2 { font-weight: 700; font-size: 1rem; color: #1a1a2e; margin-bottom: 0.5rem; }
.mmp-card p  { color: #6b7280; font-size: 0.875rem; line-height: 1.5; }

.mmp-formcard {
  background: #fff;
  border-radius: 1.5rem;
  box-shadow: 0 10px 40px rgb(0 0 0 / 0.1);
  padding: 1.5rem;
  border: 1px solid rgb(0 0 0 / 0.05);
}
.mmp-help {
  margin-top: 3rem;
  text-align: center;
  padding: 2rem;
  background: linear-gradient(135deg, #fef9c3 0%, #fef08a 100%);
  border-radius: 1rem;
}

@media (hover: hover) {
  .mmp-card:hover { transform: translateY(-5px); box-shadow: 0 12px 30px rgb(0 0 0 / 0.14); }
}
@media (max-width: 768px) {
  .mmp-hero { padding: 8rem 1.25rem 9rem; }
  .mmp-hero h1 { font-size: 2.5rem; }
  .mmp-cards { grid-template-columns: 1fr; }
}

/* ============================================================================
   17. Rising bubbles

   Restores the old bundle's decorative bubbles. framer-motion ran them as
   initial {y:"100vh", opacity:0} -> animate {y:"-100vh", opacity:[0,.6,.6,0]}
   over 8s, linear, repeating forever. framer-motion spaces an array of
   keyframes evenly, hence the 0 / 33 / 66 / 100 stops.

   Each bubble carries a negative animation-delay from the build so the set is
   already spread across the cycle instead of starting in lockstep.
   ============================================================================ */
@keyframes rt-bubble-rise {
  0%   { transform: translateY(100vh); opacity: 0; }
  33%  { opacity: 0.6; }
  66%  { opacity: 0.6; }
  100% { transform: translateY(-100vh); opacity: 0; }
}
[data-bubble] {
  animation: rt-bubble-rise 8s linear infinite;
  will-change: transform, opacity;
  pointer-events: none;
}
/* Continuous background motion is exactly what a reduced-motion request is
   about, so these do stop - unlike the hover feedback above. */
.reduced-motion [data-bubble] { animation: none; opacity: 0.35; transform: none; }
@media (prefers-reduced-motion: reduce) {
  [data-bubble] { animation: none; opacity: 0.35; transform: none; }
}

/* ============================================================================
   18. Equal-height cards in a grid

   Cards carry h-full, but each grid cell holds a wrapper div (originally a
   framer-motion element, now the reveal target) whose own height is its
   content. The percentage therefore resolved against the short wrapper, not
   the stretched row, leaving cards in the same row at different heights.
   Making the wrapper a stretch container fixes it for every such grid.
   ============================================================================ */
/* The grid cell is a bare, class-less div left behind by framer-motion, so
   a card's h-full resolved against that short wrapper instead of the
   stretched row. Make the wrapper a stretch container.

   Scoped to grids that actually contain an h-full card. Without that guard it
   also caught plain layout columns - the footer's Hours column, for one,
   whose heading and list then flex-grew apart and left a gap under HOURS. */
[class*="grid"]:has([class*="h-full"]) > div:not([class]),
[class*="grid"]:has([class*="h-full"]) > div[class=""] {
  display: flex;
  flex-direction: column;
  align-self: stretch;
}
[class*="grid"]:has([class*="h-full"]) > div:not([class]) > *,
[class*="grid"]:has([class*="h-full"]) > div[class=""] > * { flex: 1 1 auto; }

/* Cards that opt into h-full fill whatever cell they land in. */
[class*="grid"] [class*="h-full"] { height: 100%; }

/* ============================================================================
   19. Location page: map matches the height of the card stack beside it
   ============================================================================ */
@media (min-width: 1024px) {
  /* the row was items-start, so the map column never stretched */
  .rt-map-row { align-items: stretch !important; }
  .rt-map-row > :first-child { display: flex; flex-direction: column; }
  .rt-map-row .rt-map { flex: 1 1 auto; margin-bottom: 0 !important; }
  .rt-map iframe { height: 100% !important; min-height: 420px; }
}

/* ============================================================================
   20. Manage My Plan: cards overlap the bottom of the hero
   ============================================================================ */
.mmp-hero { padding-bottom: 16rem; }
.mmp-body {
  position: relative;
  z-index: 2;
  margin-top: -11rem;
  padding-top: 0 !important;
  /* Transparent, so the hero's white arch still reads behind the cards
     instead of being covered by an opaque band. The page background is the
     same colour, so everything below the arch looks unchanged. */
  background: transparent !important;
}
@media (max-width: 768px) {
  .mmp-hero { padding-bottom: 13rem; }
  .mmp-body { margin-top: -9rem; }
}

/* ============================================================================
   21. Hero entrance

   Hero content did not wait for a scroll. framer-motion played it on mount:
   initial {opacity:0, y:20..40} -> animate {opacity:1, y:0}, staggered by
   0.2s-0.8s so the badge, headline, subhead and CTA cascaded in.

   Done with a CSS animation rather than JS, so it plays with scripting off.
   The hidden start state lives only inside the no-preference media query, so
   a reduced-motion visitor gets the finished state with nothing to un-hide.
   ============================================================================ */
@keyframes rt-enter-up {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}
@keyframes rt-enter-down {
  from { opacity: 0; transform: translateY(-20px); }
  to   { opacity: 1; transform: none; }
}
@keyframes rt-enter-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: no-preference) {
  [data-enter] {
    opacity: 0;
    animation-duration: 0.6s;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    animation-fill-mode: forwards;
    animation-delay: var(--enter-delay, 0s);
    will-change: opacity, transform;
  }
  [data-enter="up"]   { animation-name: rt-enter-up; }
  [data-enter="down"] { animation-name: rt-enter-down; }
  [data-enter="fade"],
  [data-enter="scale"] { animation-name: rt-enter-fade; }
}

/* Widget toggle wins over the animation. */
.reduced-motion [data-enter] {
  opacity: 1 !important;
  transform: none !important;
  animation: none !important;
}
