/* Self-hosted webfonts. Replaces the render-blocking fonts.googleapis.com
 * stylesheet (829ms of blocked render on mobile) with first-party woff2 that
 * ship on the same connection as the rest of the page.
 *
 * Inter and Plus Jakarta Sans are variable fonts — one file covers every weight
 * we use. latin-ext only downloads when a glyph in its range actually appears.
 * Caveat is instanced at 600 and subsetted to Latin + pt-BR accents (74KB → 16KB);
 * it is decorative, used only for the hero annotations.
 *
 * To refresh a font: grab the woff2 URL from the Google Fonts css2 endpoint
 * (Chrome UA required, or it serves ttf), curl it into app/assets/fonts/. For
 * Caveat also run, with fonttools installed:
 *   fonttools varLib.instancer caveat.woff2 wght=600 -o caveat600.ttf
 *   pyftsubset caveat600.ttf --flavor=woff2 --layout-features=kern,liga \
 *     --unicodes=<the range below> --output-file=caveat-latin.woff2
 */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/assets/inter-latin-36bd85b5b0f6ef27b8aba400163b86326dbb411f763fd74e0fd04bce8ad3da57.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/assets/inter-latin-ext-a842ada06e040e46e31efd63f0c10196578b41aba14d7c2d5a64cf4b607cef26.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url(/assets/plus-jakarta-sans-latin-8c3f65dac1a6b48296d8b7e9d7fcc80a753f65ccfc36a0b1c923fadd41a9dbaa.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url(/assets/plus-jakarta-sans-latin-ext-5ba5762e630c68545a0b500448096887d16a00a2f371c0525fbe2ac9878af582.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Caveat';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/assets/caveat-latin-1b940b5c7e800739de3800fc60b8d8589eafaa6b36d98551c42f063d6e454322.woff2) format('woff2');
  unicode-range: U+0020-0021, U+0026-0029, U+002C-002E, U+0030-0039, U+003A-003B, U+003F, U+0041-005A, U+0061-007A, U+00C0-00C3, U+00C7, U+00C9-00CA, U+00CD, U+00D3-00D5, U+00DA, U+00E0-00E3, U+00E7, U+00E9-00EA, U+00ED, U+00F3-00F5, U+00FA, U+2013-2014, U+2018-2019, U+201C-201D, U+2026, U+2190-2193;
}
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *


 */

.font-display {
  font-family: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif;
}

.text-highlight {
  background: linear-gradient(180deg, transparent 60%, rgba(4, 120, 87, 0.16) 60%);
  display: inline;
  padding: 0 2px;
}

/* Marker sweep that survives a line break. .hl-marker below is nowrap by
 * construction (absolute SVG over one inline-block), so a phrase long enough
 * to wrap needs this instead: same amber pass, painted per line box. */
.hl-sweep {
  background-image: linear-gradient(180deg, transparent 62%, rgba(253, 230, 138, 0.9) 62%);
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  padding: 0 0.08em;
}

/* Hero showcase — crossfading panels + connector labels. Grid-stacked by the
 * template, so only opacity and transform move; the container height is fixed
 * by the tallest panel and never animates. Inactive panels keep their box out
 * of the pointer/scroll path. */
.hero-swap {
  opacity: 0;
  transform: translateY(6px);
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              visibility 0s linear 0.45s;
}
.hero-swap.is-active {
  opacity: 1;
  transform: none;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              visibility 0s linear 0s;
}

.hero-dot {
  width: 1.25rem;
  height: 0.4rem;
  background-color: oklch(92.8% 0.006 264.531);
  transform: scaleX(0.32);
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), background-color 0.3s ease;
}
.hero-dot:hover {
  background-color: oklch(70.7% 0.022 261.325);
}
.hero-dot[aria-selected="true"] {
  background-color: #047857;
  transform: scaleX(1);
}

/* Legibility layer over the hero photograph. Three passes, not one flat scrim:
 * a flat overlay strong enough for 4.5:1 behind the subhead also kills the
 * image.
 *
 * 1. clears the top edge, where the fixed nav sits over whatever the photo
 *    happens to be doing;
 * 2. opens a white column under the type block, leaving the sides alone;
 * 3. dissolves the last strip into the white of the page below, so the section
 *    boundary is not a hard horizontal line across the picture. It only touches
 *    the photograph — the console frame is a layer above this one and keeps its
 *    hard cut, which is the whole point of the bleed.
 */
.hero-photo-wash {
  background:
    linear-gradient(
      180deg,
      #ffffff 0%,
      rgba(255, 255, 255, 0.7) 8%,
      rgba(255, 255, 255, 0) 22%
    ),
    radial-gradient(
      46% 40% at 50% 26%,
      rgba(255, 255, 255, 0.96) 0%,
      rgba(255, 255, 255, 0.82) 55%,
      rgba(255, 255, 255, 0) 100%
    ),
    linear-gradient(
      0deg,
      #ffffff 0%,
      rgba(255, 255, 255, 0.55) 4%,
      rgba(255, 255, 255, 0) 11%
    );
}

/* On a phone the whole argument stacks into one narrow column, so the type
 * runs far past the ellipse that clears it on a wide screen and lands on the
 * canyon walls. The veil covers most of the fold here and the illustration
 * survives only at the edges and the bottom — on a 390px viewport the image is
 * atmosphere, and legibility outranks it. */
@media (max-width: 640px) {
  .hero-photo-wash {
    background:
      linear-gradient(
        180deg,
        #ffffff 0%,
        rgba(255, 255, 255, 0.85) 13%,
        rgba(255, 255, 255, 0.42) 46%,
        rgba(255, 255, 255, 0) 68%
      ),
      radial-gradient(
        88% 46% at 50% 30%,
        rgba(255, 255, 255, 0.97) 0%,
        rgba(255, 255, 255, 0.85) 58%,
        rgba(255, 255, 255, 0) 100%
      ),
      linear-gradient(
        0deg,
        #ffffff 0%,
        rgba(255, 255, 255, 0.5) 5%,
        rgba(255, 255, 255, 0) 14%
      );
  }
}

/* Editorial / marker / handwritten — used in hero annotations */
.font-handwritten {
  font-family: 'Caveat', 'Bradley Hand', cursive;
  font-weight: 600;
}

/* Wobbly marker sweep — wraps a phrase, draws an SVG stroke behind text.
 * The SVG sits as a layer behind the glyphs so the highlight feels like
 * a real marker pass: visible color, irregular curve, soft edges. */
.hl-marker {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}
.hl-marker > .hl-stroke {
  position: absolute;
  left: -4%;
  right: -4%;
  bottom: -8%;
  width: 108%;
  height: 60%;
  z-index: 0;
  pointer-events: none;
}
.hl-marker > .hl-text {
  position: relative;
  z-index: 1;
}

/* ── Marketing landing page (light theme) ── */

/* Page-wide background gradient — top stays light (hero breathing room),
 * fades into a tinted gray towards the bottom so Features and Final CTA
 * sit on a slightly weightier surface than the hero. Applied to <main>
 * in landing.html.erb so it doesn't bleed into authenticated layouts. */
.landing-bg {
  background:
    linear-gradient(
      180deg,
      #ffffff 0%,
      #ffffff 60%,
      #fcfcfd 85%,
      #f8f9fa 100%
    );
}

/* Page-wide dotted texture — layered over .landing-bg via a second
 * background-image. Keeps the gradient visible underneath while giving
 * the whole page the same editorial dot pattern the hero uses locally.
 * The radial mask softens the edges so the dots don't fight with content
 * at the extremes. */
.landing-dots {
  position: relative;
}
.landing-dots::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: radial-gradient(circle, oklch(92.8% 0.006 264.531) 0.8px, transparent 0.8px);
  background-size: 22px 22px;
  /* Stays visible all the way down — no fade-out at the bottom. The footer's
   * own pattern adds extra intensity at its L/R edges; the middle of the footer
   * keeps showing this same pattern, which is what makes the section→footer
   * transition seamless. */
  -webkit-mask-image: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.5) 60%, rgba(0,0,0,0.55) 100%);
          mask-image: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.5) 60%, rgba(0,0,0,0.55) 100%);
}
.landing-dots > * {
  position: relative;
  z-index: 1;
}

/* White editorial dots for forest sections. The low opacity keeps the
 * texture visible without competing with the calculator or its copy. */
.landing-dots-light {
  position: relative;
}
.landing-dots-light::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.18) 0.8px, transparent 0.8px);
  background-size: 22px 22px;
}
.landing-dots-light > * {
  position: relative;
  z-index: 1;
}

/* Footer-specific dotted texture — denser, darker; only renders at the L/R
 * edges so the middle of the footer shows the unbroken page-wide pattern that
 * flows down from the section above. That's how section→footer feels seamless:
 * no border, no pattern change in the center, just extra intensity hugging
 * the outer edges of the footer.
 *
 * The pattern bleeds upward into the previous section's bottom (top: -56px)
 * so the intense edges appear to grow into the footer rather than appearing
 * abruptly at its top boundary. */
.landing-footer-dots {
  position: relative;
}
.landing-footer-dots::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -56px;
  bottom: 0;
  z-index: 0;
  pointer-events: none;
  background-image: radial-gradient(circle, #9ca3af 1.1px, transparent 1.1px);
  background-size: 14px 14px;
  /* Visible only at the outer 12% on each side; the middle 56% is fully
   * transparent so the page-wide pattern reads through unchanged. */
  -webkit-mask-image:
    linear-gradient(90deg, rgba(0,0,0,1) 0%, rgba(0,0,0,1) 12%, rgba(0,0,0,0) 32%, rgba(0,0,0,0) 68%, rgba(0,0,0,1) 88%, rgba(0,0,0,1) 100%),
    linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 56px, rgba(0,0,0,1) 50%);
          mask-image:
    linear-gradient(90deg, rgba(0,0,0,1) 0%, rgba(0,0,0,1) 12%, rgba(0,0,0,0) 32%, rgba(0,0,0,0) 68%, rgba(0,0,0,1) 88%, rgba(0,0,0,1) 100%),
    linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 56px, rgba(0,0,0,1) 50%);
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}
.landing-footer-dots > * {
  position: relative;
  z-index: 1;
}

/* ── Landing section 03: the proof-card washes ──
 * Each artifact sits on its own wash so the three cards read as three
 * different objects rather than three instances of one. The differentiation
 * is carried by the SHAPE inside (bubbles, hour bars, ten dots) and by where
 * the light falls, not by giving each card its own hue — three hues in one
 * section is a rainbow, and this page runs on a single emerald family.
 *
 * All three land at the same perceived weight, which the first pass did not:
 * emerald at 0.12 over a mint linear layer read twice as heavy as the lighter
 * pair and pulled the eye straight to the third card. The artifact inside has
 * to stay the highest-contrast thing in the panel — the moment the wash
 * competes with it, the card is decoration again. */
.outcome-wash-reply {
  background-image:
    radial-gradient(115% 105% at 0% 0%, rgba(4, 120, 87, 0.13) 0%, rgba(4, 120, 87, 0) 72%),
    linear-gradient(155deg, rgba(236, 253, 245, 0.8) 0%, rgba(249, 250, 251, 0.5) 100%);
}

.outcome-wash-hours {
  background-image:
    radial-gradient(120% 110% at 100% 100%, rgba(4, 120, 87, 0.14) 0%, rgba(4, 120, 87, 0) 74%),
    linear-gradient(155deg, rgba(236, 253, 245, 0.8) 0%, rgba(249, 250, 251, 0.5) 100%);
}

.outcome-wash-autonomy {
  background-image:
    radial-gradient(115% 105% at 0% 100%, rgba(16, 185, 129, 0.09) 0%, rgba(16, 185, 129, 0) 72%),
    linear-gradient(155deg, rgba(240, 253, 244, 0.6) 0%, rgba(249, 250, 251, 0.5) 100%);
}

/* ── Landing section 04: the intelligence band ──
 * The one dark surface on the site. ink-900 with a fraction more lightness
 * than the console's, because this sits on white and a flat ink-900 rectangle
 * reads as a hole punched in the page rather than a lit surface. The two
 * radial washes put the light behind the middle column, where the streams
 * converge. */
.intelligence-band {
  background-color: #153f35;
  background-image:
    radial-gradient(64% 52% at 50% 8%, rgba(52, 211, 153, 0.13) 0%, rgba(52, 211, 153, 0) 100%),
    radial-gradient(48% 60% at 88% 96%, rgba(4, 120, 87, 0.14) 0%, rgba(4, 120, 87, 0) 100%);
}

/* Dashes travelling the inbound paths. Continuous ambient flow, not a state
 * change, so this is the one animation on the site that stays linear instead
 * of riding the shared ease-out curve — an eased loop visibly stutters at the
 * seam. Offsets, not delays, so all three are always in flight. */
@keyframes band-pulse {
  from { stroke-dashoffset: 340; }
  to   { stroke-dashoffset: 0; }
}
.band-pulse {
  stroke-dasharray: 26 314;
  animation: band-pulse 3.6s linear infinite;
}
.band-pulse-2 { animation-duration: 4.4s; stroke-dashoffset: 120; }
.band-pulse-3 { animation-duration: 3s;   stroke-dashoffset: 240; }

/* "Trabalhando 24/7" — presence, never severity, so it stays emerald and
 * breathes slowly enough to read as a heartbeat rather than an alert. */
@keyframes band-beacon {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}
.band-beacon {
  animation: band-beacon 2.8s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}

@media (prefers-reduced-motion: reduce) {
  /* Each resolves to its lit state rather than its dim one — stopping the
   * loop should not leave the band looking switched off. */
  .band-pulse   { animation: none; stroke-dasharray: none; stroke-dashoffset: 0; opacity: 0.55; }
  .band-beacon  { animation: none; opacity: 1; }
}

/* Pastel mesh gradient blob */
.mesh-blob-pink {
  background: radial-gradient(ellipse at center, rgba(255, 182, 193, 0.4), transparent 70%);
}

/* Emerald brand-accent blob — adds depth + an asymmetric color moment on the
 * demo / conversion page without tipping into a flat colored background. */
.mesh-blob-emerald {
  background: radial-gradient(ellipse at center, rgba(4, 120, 87, 0.22), transparent 70%);
}

/* ── Demo page: live "IA triando" reel ──
 * Self-playing micro-demo driven by demo_reel_controller. Items fade up on
 * cue; the marker strokes draw via WAAPI (stroke-dashoffset). Everything
 * resolves to its final visible state with reduced motion or no JS. */
.reel-item {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.reel-item.reel-in {
  opacity: 1;
  transform: none;
}
.reel-stroke {
  stroke-dasharray: 240;
  stroke-dashoffset: 240;
}
@media (prefers-reduced-motion: reduce) {
  .reel-item { opacity: 1; transform: none; transition: none; }
  .reel-stroke { stroke-dashoffset: 0; }
  /* Autoplay is already off (the controller checks the media query); a manual
   * dot press still swaps, so keep the crossfade and drop the spatial move. */
  .hero-swap,
  .hero-swap.is-active {
    transform: none;
    transition: opacity 0.2s linear, visibility 0s linear;
  }
  .hero-dot { transition: none; }
}

/* Light card with subtle shadow */
.landing-card {
  background: white;
  border: 1px solid oklch(92.8% 0.006 264.531);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(0, 0, 0, 0.03);
  transition: box-shadow 0.3s ease, transform 0.3s ease;
}

.landing-card:hover {
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08), 0 8px 24px rgba(0, 0, 0, 0.04);
  transform: translateY(-2px);
}

/* Reveal animations */
.reveal-hidden {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}

.reveal-visible {
  opacity: 1;
  transform: translateY(0);
}

.reveal-delay-1 { transition-delay: 0.1s; }
.reveal-delay-2 { transition-delay: 0.2s; }
.reveal-delay-3 { transition-delay: 0.3s; }
.reveal-delay-4 { transition-delay: 0.4s; }
.reveal-delay-5 { transition-delay: 0.5s; }

/* The phone field's refusal: one decaying head-shake when the typed number
 * stops being a possible phone (whatsapp_link_controller re-triggers it only
 * on the transition into invalid, never per keystroke). Transform only, and
 * the amber hint carries the words for anyone who skips motion. */
@keyframes input-refuse {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-7px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(2px); }
}

.input-refuse {
  animation: input-refuse 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-reduced-motion: reduce) {
  .input-refuse { animation: none; }
}

@keyframes fade-in-up {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

.animate-fade-in-up {
  animation: fade-in-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.animate-fade-in-up-delay-1 { animation-delay: 0.08s; opacity: 0; }
.animate-fade-in-up-delay-2 { animation-delay: 0.16s; opacity: 0; }
.animate-fade-in-up-delay-3 { animation-delay: 0.24s; opacity: 0; }
.animate-fade-in-up-delay-4 { animation-delay: 0.32s; opacity: 0; }
.animate-fade-in-up-delay-5 { animation-delay: 0.4s; opacity: 0; }

/* WhatsApp pulse ring */
@keyframes pulse-ring {
  0% { transform: scale(1); opacity: 0.5; }
  100% { transform: scale(1.5); opacity: 0; }
}

.whatsapp-pulse {
  position: relative;
}

.whatsapp-pulse::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 9999px;
  background: rgba(37, 211, 102, 0.25);
  animation: pulse-ring 2s ease-out 3;
  z-index: -1;
}

@keyframes kicker-ping {
  75%, 100% { transform: scale(2); opacity: 0; }
}

.kicker-ping {
  animation: kicker-ping 1.4s cubic-bezier(0, 0, 0.2, 1) 3;
}

/* Animated handoff bell — replaces a horizontal product screenshot in the
 * Solution > Handoff tile. Swings 3 times, pauses ~3s, swings again. Paired
 * with a tiny notification dot that pings on each swing cycle. Honors
 * prefers-reduced-motion via the rule below. */
@keyframes bell-swing {
  0%, 75%, 100% { transform: rotate(0); }
  78% { transform: rotate(14deg); }
  82% { transform: rotate(-14deg); }
  86% { transform: rotate(10deg); }
  90% { transform: rotate(-10deg); }
  94% { transform: rotate(5deg); }
}

@keyframes bell-dot-ping {
  0%, 70% { transform: scale(1); opacity: 1; }
  78% { transform: scale(1.6); opacity: 1; }
  90% { transform: scale(2.2); opacity: 0; }
  91%, 100% { transform: scale(1); opacity: 1; }
}

.bell-swing {
  transform-origin: 50% 12%;
  animation: bell-swing 5s ease-in-out infinite;
}

.bell-dot {
  animation: bell-dot-ping 5s ease-in-out infinite;
}

/* Bento tile — same surface as .landing-card but works as a self-contained
 * container for image + caption. Used by Solution and Features sections so
 * both share one tile vocabulary. */
.bento-tile {
  background: white;
  border: 1px solid oklch(92.8% 0.006 264.531);
  border-radius: 1.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(0, 0, 0, 0.03);
  transition: box-shadow 0.3s ease, transform 0.3s ease;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.bento-tile:hover {
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08), 0 8px 24px rgba(0, 0, 0, 0.04);
  transform: translateY(-2px);
}

/* Reduced motion + JS-failure safety net.
 * Reveal classes start at opacity:0; if the IntersectionObserver never fires
 * (slow JS, blocked scripts) or the user prefers reduced motion, surface the
 * content unconditionally. The <noscript> block in landing.html.erb covers
 * the no-JS case; this rule covers the accessibility case. */
@media (prefers-reduced-motion: reduce) {
  .reveal-hidden,
  .animate-fade-in-up,
  [class*="animate-fade-in-up-delay-"] {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
    transition: none !important;
  }
  .whatsapp-pulse::after,
  .kicker-ping,
  .animate-ping,
  .bell-swing,
  .bell-dot {
    animation: none !important;
  }
}

/* Segments carousel — scrollbar hider for the tab pill row (mobile
 * horizontal scroll) and the rail. */
.scrollbar-none::-webkit-scrollbar { display: none; }

/* ── Chat message cascade ─────────────────────────────────────────────────
 * When a carousel card becomes .is-active, the rows inside its chat stack
 * cascade in. Lead bubbles slide from the right; agent bubbles slide from
 * the left; header/date/closure rows fade-up. Stagger is driven by a
 * --row-i custom property set per row in the ERB, so the cascade scales
 * with conversation length without nth-child rules.
 *
 * Animation only runs while .is-active matches; removing .is-active resets
 * the rule, so re-activating a card replays the cascade. The shared
 * landing easing (0.16, 1, 0.3, 1) keeps motion coherent. */
@keyframes chat-row-in {
  from {
    opacity: 0;
    transform: translateY(8px) translateX(var(--row-from-x, 0));
  }
  to {
    opacity: 1;
    transform: translateY(0) translateX(0);
  }
}

.carousel-card.is-active .chat-row {
  animation: chat-row-in 460ms cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: calc(80ms + var(--row-i, 0) * 70ms);
}

.carousel-card.is-active .chat-msg-lead {
  --row-from-x: 10px;
}

.carousel-card.is-active .chat-msg-agent {
  --row-from-x: -10px;
}

.carousel-card.is-active .chat-row-closure {
  animation-duration: 600ms;
}

@media (prefers-reduced-motion: reduce) {
  .carousel-card {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .carousel-card.is-active .chat-row {
    animation: none !important;
  }
}

/* ──────────────────────────────────────────────────────────────────────
 * Sandbox — skill activation flash.
 * Plays once when a skill row receives the .sandbox-activated class via
 * the Stimulus controller. Background sweeps from a soft emerald wash
 * back to transparent on the app's shared easing curve.
 * ────────────────────────────────────────────────────────────────────── */
@keyframes sandbox-skill-activation {
  0% {
    background-color: rgba(4, 120, 87, 0.18);
    box-shadow: inset 2px 0 0 0 rgb(4, 120, 87);
  }
  60% {
    background-color: rgba(4, 120, 87, 0.08);
    box-shadow: inset 2px 0 0 0 rgba(4, 120, 87, 0.6);
  }
  100% {
    background-color: transparent;
    box-shadow: inset 2px 0 0 0 transparent;
  }
}

.sandbox-activated {
  animation: sandbox-skill-activation 1.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@media (prefers-reduced-motion: reduce) {
  .sandbox-activated {
    animation: none;
    background-color: rgba(4, 120, 87, 0.08);
  }
}

/* ── Inbox end-of-queue marker ──────────────────────────────────────────
 * The lead list resolves into a small "timeline terminus": a thread draws
 * down from the last row, lands on an emerald node, the node breathes once,
 * then the copy rises in. Echoes the queue/fila metaphor without a slop icon
 * square. All on the shared cubic-bezier(0.16, 1, 0.3, 1) curve. */
@keyframes inbox-end-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes inbox-end-pop {
  from {
    opacity: 0;
    transform: scale(0.3);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes inbox-end-halo {
  from {
    opacity: 0.55;
    transform: scale(0.6);
  }
  to {
    opacity: 0;
    transform: scale(2.4);
  }
}

@keyframes inbox-end-rise {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.inbox-end__line {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: inbox-end-draw 520ms cubic-bezier(0.16, 1, 0.3, 1) 80ms both;
}

.inbox-end__node,
.inbox-end__halo {
  transform-box: fill-box;
  transform-origin: center;
}

.inbox-end__node {
  animation: inbox-end-pop 360ms cubic-bezier(0.16, 1, 0.3, 1) 540ms both;
}

.inbox-end__halo {
  animation: inbox-end-halo 1200ms cubic-bezier(0.16, 1, 0.3, 1) 780ms both;
}

.inbox-end__title {
  animation: inbox-end-rise 520ms cubic-bezier(0.16, 1, 0.3, 1) 620ms both;
}

.inbox-end__body {
  animation: inbox-end-rise 520ms cubic-bezier(0.16, 1, 0.3, 1) 700ms both;
}

@media (prefers-reduced-motion: reduce) {
  .inbox-end__line {
    stroke-dashoffset: 0;
    animation: none;
  }

  .inbox-end__node,
  .inbox-end__title,
  .inbox-end__body {
    opacity: 1;
    transform: none;
    animation: none;
  }

  .inbox-end__halo {
    display: none;
  }
}

@keyframes inbox-live-pill-in {
  from {
    opacity: 0;
    transform: translate(-50%, -10px) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translate(-50%, 0) scale(1);
  }
}

.inbox-live-pill {
  transform: translate(-50%, 0);
  animation: inbox-live-pill-in 260ms cubic-bezier(0.16, 1, 0.3, 1);
  transition: transform 150ms cubic-bezier(0.16, 1, 0.3, 1);
}

.inbox-live-pill:hover {
  transform: translate(-50%, -2px);
}

@media (prefers-reduced-motion: reduce) {
  .inbox-live-pill {
    animation: none;
    transition: none;
  }
}

/* ── Pricing savings calculator — slider ──────────────────────────────────
 * Brand-green range control for _pricing_calculator. The track fill left of
 * the thumb is driven by a --fill custom property set per input event by
 * savings_calculator_controller. Thumb reuses the app's shared easing curve. */
.savings-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 8px;
  border-radius: 9999px;
  background: linear-gradient(
    90deg,
    #047857 0%,
    #047857 var(--fill, 40%),
    #e5e7eb var(--fill, 40%),
    #e5e7eb 100%
  );
  outline: none;
  cursor: pointer;
}
.savings-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 26px;
  height: 26px;
  border-radius: 9999px;
  background: #ffffff;
  border: 2px solid #047857;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.12), 0 4px 12px rgba(4, 120, 87, 0.3);
  cursor: grab;
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1),
              box-shadow 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.savings-slider::-webkit-slider-thumb:active {
  cursor: grabbing;
  transform: scale(1.12);
}
.savings-slider:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px rgba(4, 120, 87, 0.25);
}
/* Fingers need more than 26px to land on. */
@media (pointer: coarse) {
  .savings-slider::-webkit-slider-thumb,
  .savings-slider::-moz-range-thumb {
    width: 34px;
    height: 34px;
  }
}
/* Firefox: transparent track lets the element's gradient show through. */
.savings-slider::-moz-range-track {
  background: transparent;
  border-radius: 9999px;
  height: 8px;
}
.savings-slider::-moz-range-thumb {
  width: 26px;
  height: 26px;
  border-radius: 9999px;
  background: #ffffff;
  border: 2px solid #047857;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.12), 0 4px 12px rgba(4, 120, 87, 0.3);
  cursor: grab;
}
.savings-slider:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 4px rgba(4, 120, 87, 0.25);
}
@media (prefers-reduced-motion: reduce) {
  .savings-slider::-webkit-slider-thumb {
    transition: none;
  }
}

/* ── Pricing plans block — billing cycle switch ───────────────────────────
 * Segmented mensal/anual control. The price swap is driven by `:has()` on the
 * real radio inputs rather than by JS, so the switch keeps working with
 * scripting off; the Stimulus controller only rewrites CTA hrefs. */
.plan-cycle-switch {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: 2px;
  padding: 4px;
  border-radius: 9999px;
  background: #f3f4f6;
}
.plan-cycle-switch label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 18px;
  border-radius: 9999px;
  font-size: 14px;
  font-weight: 600;
  color: #4b5563;
  cursor: pointer;
  transition: color 0.2s cubic-bezier(0.16, 1, 0.3, 1),
              background-color 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.plan-cycle-switch label:hover {
  color: #111827;
}
.plan-cycle-switch input:checked + label {
  background: #ffffff;
  color: #111827;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.1);
}
.plan-cycle-switch input:focus-visible + label {
  outline: 2px solid #047857;
  outline-offset: 2px;
}
.plan-cycle-save {
  white-space: nowrap;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: #047857;
  background: #ecfdf5;
  padding: 3px 8px;
  border-radius: 9999px;
}

/* Monthly is the server-rendered default; annual reveals on selection.
 * `cycle-annual-block` is the same switch for a whole block-level node that
 * only has something to say on the annual cycle. */
.cycle-scope .cycle-annual,
.cycle-scope .cycle-annual-block {
  display: none;
}
.cycle-scope:has(#plan-cycle-annual:checked) .cycle-annual {
  display: inline;
}
.cycle-scope:has(#plan-cycle-annual:checked) .cycle-annual-block {
  display: block;
}
.cycle-scope:has(#plan-cycle-annual:checked) .cycle-monthly {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .plan-cycle-switch label {
    transition: none;
  }
}

/* ── Calculator invite ──────────────────────────────────────────────────────
   The dialog is the positioning layer and stays unstyled; the card inside it
   is what moves. Transform and opacity only, on the one easing curve the rest
   of the site uses.

   The backdrop is animated separately because ::backdrop cannot be reached
   from the card, and animating it with the card would fade the scrim in from
   nothing every time — the point of the scrim is to arrive first. */
.calculator-invite::backdrop {
  animation: calculator-invite-scrim 240ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes calculator-invite-scrim {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.calculator-invite-card {
  opacity: 0;
  transform: translateY(10px) scale(0.985);
  transition:
    opacity 320ms cubic-bezier(0.16, 1, 0.3, 1),
    transform 320ms cubic-bezier(0.16, 1, 0.3, 1);
}

.calculator-invite-card.is-open {
  opacity: 1;
  transform: none;
}

/* The ground the miniature floats on. Four soft masses rather than one
   gradient sweep: a single linear ramp reads as a SaaS header, and the whole
   point of this panel is that it should look painted.

   Warm and cool are deliberately mixed (apricot and rose against emerald and
   mint) so it never lands on a generic cool gradient.
   Everything sits at high lightness and low chroma because a white card is
   about to be dropped on top of it and has to stay the brightest thing here. */
.calculator-invite-wash {
  background-color: oklch(0.975 0.008 265);
  background-image:
    radial-gradient(62% 78% at 14% 16%, oklch(0.88 0.075 52 / 0.92) 0%, transparent 62%),
    radial-gradient(58% 76% at 86% 8%, oklch(0.80 0.105 168 / 0.85) 0%, transparent 60%),
    radial-gradient(70% 82% at 74% 92%, oklch(0.90 0.065 168 / 0.75) 0%, transparent 64%),
    radial-gradient(78% 70% at 26% 98%, oklch(0.91 0.055 350 / 0.7) 0%, transparent 68%);
}

/* Grain. Without it the masses above are unmistakably CSS; with it they read
   as pigment on paper, which is the difference between the reference and a
   hero gradient. Soft-light keeps it from muddying the lighter areas. */
.calculator-invite-wash::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.42;
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(/%23n)'/%3E%3C/svg%3E");
}

@media (prefers-reduced-motion: reduce) {
  .calculator-invite::backdrop {
    animation: none;
  }

  .calculator-invite-card {
    transform: none;
    transition: opacity 160ms linear;
  }
}
