/**
 * Automatq custom components — loaded AFTER assets/css/main.css.
 *
 * assets/css/main.css is generated from assets/scss/ and is deliberately left
 * pristine (see README "Editing rules"), so everything hand-authored for
 * Automatq lives here instead. Two rules for this file:
 *
 *   1. Every selector is namespaced `.aq-*`. Nothing here may match template
 *      markup, so the Cunnet stylesheet and its scroll/motion system keep
 *      working exactly as shipped.
 *   2. Colour, type and easing come from the template's own custom properties
 *      (--tp-*, --basic-ease). The components inherit the theme rather than
 *      importing a second palette.
 */

/* ==================================================================== *
 * Missed-call text-back demo
 *
 * A native re-build of the React product graphic at
 * moscow/src/components/graphics/MissedCallTextBack.jsx, re-skinned into the
 * Cunnet light palette: black device, white screen, --tp-theme-primary for the
 * missed state, black bubble for the automated reply, grey for the human one.
 * The phase loop is driven by assets/js/automatq.js via [data-aq-phase].
 * ==================================================================== */

.aq-demo {
  --aq-ink: var(--tp-common-black);
  --aq-paper: var(--tp-common-white);
  --aq-muted: var(--tp-grey-1);
  --aq-faint: var(--tp-grey-3);
  --aq-line: var(--tp-border-1);
  --aq-alert: var(--tp-theme-primary);
  /* Review stars are gold by universal convention. The sky accent reads as a
     link and the alert red reads as an error, so neither works here.
     Precedent for a literal in this file: .aqp-dot--y (#fbbf24). */
  --aq-star: #f5a623;
  --aq-ease: var(--basic-ease, cubic-bezier(0.625, 0.05, 0, 1));
}

/* ---- Device ------------------------------------------------------- */

.aq-phone {
  position: relative;
  width: 268px;
  max-width: 100%;
  margin: 0 auto;
}

/* A slight 3D turn makes the device read as an object in a scene rather than a
   flat rectangle — upstream PhoneMockup used this inside feature bands and kept
   it off in the hero. Here it also stops a second phone on the same page from
   reading as a duplicate of the first. */
.aq-phone--tilt {
  transform: perspective(1400px) rotateY(-13deg) rotateZ(1.2deg);
}

.aq-phone__frame {
  position: relative;
  padding: 10px;
  border-radius: 46px;
  background: linear-gradient(160deg, #3a3a3a 0%, var(--aq-ink) 55%);
  box-shadow: 0 30px 60px -22px rgba(30, 30, 30, 0.45),
    0 10px 24px -12px rgba(30, 30, 30, 0.3);
}

/* Side buttons — pure decoration, hidden from assistive tech. */
.aq-phone__btn {
  position: absolute;
  width: 3px;
  border-radius: 2px;
  background: #2b2b2b;
}
.aq-phone__btn--a { left: -2px; top: 118px; height: 34px; }
.aq-phone__btn--b { left: -2px; top: 162px; height: 34px; }
.aq-phone__btn--c { right: -2px; top: 140px; height: 56px; }

.aq-phone__screen {
  position: relative;
  overflow: hidden;
  border-radius: 36px;
  background: var(--aq-paper);
  aspect-ratio: 9 / 19;
  color: var(--aq-ink);
}

/* aspect-ratio fallback for older Safari. */
@supports not (aspect-ratio: 9 / 19) {
  .aq-phone__screen { height: 524px; }
}

.aq-phone__notch {
  position: absolute;
  top: 8px;
  left: 50%;
  z-index: 3;
  width: 84px;
  height: 22px;
  transform: translateX(-50%);
  border-radius: 999px;
  background: var(--aq-ink);
}

.aq-phone__status {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 11px 20px 0;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: -0.01em;
  opacity: 0.75;
}
.aq-phone__status span + span { display: inline-flex; align-items: center; gap: 5px; }

/* Both screens stack in the same box below the status bar and cross-fade. */
.aq-phone__stage {
  position: absolute;
  top: 40px;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1;
}

.aq-screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, visibility 0.25s ease;
}
.aq-screen.is-on {
  opacity: 1;
  visibility: visible;
}

/* ---- Call screen -------------------------------------------------- */

.aq-call {
  align-items: center;
  justify-content: center;
  gap: 38px;
  padding: 0 22px 26px;
  text-align: center;
}

.aq-call__avatar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 76px;
  height: 76px;
  margin: 0 auto 14px;
  border-radius: 50%;
  background: var(--tp-grey-2);
  color: var(--aq-muted);
}

/* The expanding ring only exists while the call is ringing. */
.aq-call__ring {
  position: absolute;
  inset: 0;
  border: 1px solid var(--aq-ink);
  border-radius: 50%;
  opacity: 0;
}
[data-aq-phase="ringing"] .aq-call__ring {
  animation: aq-ring 1.6s var(--aq-ease) infinite;
}

@keyframes aq-ring {
  0%   { transform: scale(1);    opacity: 0.5; }
  100% { transform: scale(1.55); opacity: 0; }
}

.aq-call__number {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.3;
}

.aq-call__state {
  margin-top: 6px;
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--aq-faint);
  transition: color 0.3s ease;
}
[data-aq-phase="missed"] .aq-call__state {
  color: var(--aq-alert);
}

.aq-call__actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0 14px;
  transition: opacity 0.4s var(--aq-ease);
}
/* Both keys dim once the call has gone unanswered. */
[data-aq-phase="missed"] .aq-call__actions {
  opacity: 0.25;
}

.aq-call__key {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  color: var(--tp-common-white);
  font-size: 17px;
}
.aq-call__key--decline { background: var(--aq-alert); }
.aq-call__key--decline svg { transform: rotate(135deg); }
.aq-call__key--answer { background: #14a44d; }

/* ---- Messages screen ---------------------------------------------- */

.aq-thread__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 16px 10px;
  border-bottom: 1px solid var(--aq-line);
}
.aq-thread__head > svg {
  flex: 0 0 auto;
  color: var(--aq-faint);
}
.aq-thread__avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--tp-grey-2);
  color: var(--aq-muted);
}
.aq-thread__who {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* Bottom-anchored, like a real thread. */
.aq-thread__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  justify-content: flex-end;
  gap: 7px;
  padding: 0 12px 8px;
}

.aq-thread__system {
  margin-bottom: 2px;
  text-align: center;
  font-size: 9px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--aq-faint);
}

.aq-row {
  display: flex;
}
.aq-row--out { justify-content: flex-end; }
.aq-row--in  { justify-content: flex-start; }

.aq-bubble {
  max-width: 85%;
  padding: 9px 13px;
  border-radius: 17px;
  font-size: 11px;
  line-height: 1.55;
  letter-spacing: -0.01em;
}
/* Black = sent by the business, automatically. Grey = the human replying. */
.aq-bubble--out {
  border-bottom-right-radius: 5px;
  background: var(--aq-ink);
  color: var(--tp-common-white);
}
.aq-bubble--in {
  border-bottom-left-radius: 5px;
  background: var(--tp-grey-2);
  color: var(--aq-ink);
}

.aq-thread__meta {
  padding-right: 4px;
  text-align: right;
  font-size: 9px;
  color: var(--aq-faint);
}

/* Messages appear as the loop reaches their phase.
 *
 * Taken out of flow entirely until then, so the thread grows upward like a
 * real one instead of holding a reserved gap where the next message will land.
 * Because the start state is `display: none`, this has to be an animation and
 * not a transition — a transition from a never-rendered box does not play.
 * `:not(.is-on)` rather than a display value on `.is-on` keeps each element's
 * natural display (flex for the rows, block for the timestamp).
 */
/* Gated on [data-aq-phase] — the runtime sets that on its very first paint, so
   in normal operation this behaves exactly as before. If the script never runs
   (blocked, cached stale, failed to load) no phase is ever set, and without the
   guard every message would stay display:none and every screen hidden: the
   homepage would show a blank white device. See the fallback below. */
[data-aq-phase] .aq-reveal:not(.is-on) {
  display: none;
}
/* Transient by definition — hidden unless the loop is actively on that phase. */
[data-aq-only]:not(.is-on) {
  display: none;
}
.aq-reveal.is-on {
  animation: aq-pop 0.35s var(--aq-ease) both;
}

/* No-JS fallback: show the first screen, fully populated and resolved, instead
   of nothing. Same intent as the reduced-motion branch — the graphic should
   still tell the story when it cannot move. */
[data-aq-demo]:not([data-aq-phase]) .aq-screen:first-child {
  opacity: 1;
  visibility: visible;
}
[data-aq-demo]:not([data-aq-phase]) .aq-star {
  color: var(--aq-star);
  transform: none;
}

@keyframes aq-pop {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* Typing indicator — occupies the outbound bubble's slot before it sends. */
.aq-typing {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  padding: 12px 15px;
  border-radius: 17px;
  border-bottom-right-radius: 5px;
  background: var(--aq-ink);
}
.aq-typing span {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  animation: aq-typing 1.1s ease-in-out infinite;
}
.aq-typing span:nth-child(2) { animation-delay: 0.15s; }
.aq-typing span:nth-child(3) { animation-delay: 0.3s; }

@keyframes aq-typing {
  0%, 60%, 100% { transform: translateY(0);    opacity: 0.45; }
  30%           { transform: translateY(-4px); opacity: 1; }
}

/* Composer — decorative; it is what makes the screen read as a messages app
   rather than bubbles floating in a white rectangle. */
.aq-thread__composer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px 20px;
}
.aq-thread__field {
  flex: 1 1 auto;
  padding: 7px 13px;
  border: 1px solid var(--aq-line);
  border-radius: 999px;
  font-size: 10px;
  color: var(--aq-faint);
}
.aq-thread__send {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: var(--aq-ink);
  color: var(--tp-common-white);
}

/* ---- Review-funnel screen ------------------------------------------ *
 * Port of moscow/src/components/graphics/ReviewFunnel.jsx, re-skinned light to
 * match the missed-call device on the same page (the original was dark).
 * -------------------------------------------------------------------- */

.aq-rating {
  margin-top: 4px;
  padding: 14px 12px;
  border: 1px solid var(--aq-line);
  border-radius: 16px;
  background: var(--tp-grey-4);
  text-align: center;
  transition: opacity 0.5s var(--aq-ease), transform 0.5s var(--aq-ease);
}
/* On screen from the first frame but not yet opened, so the phone is never
   mostly-empty — the upstream graphic kept this card permanently mounted for
   exactly this reason. */
[data-aq-phase="done"] .aq-rating,
[data-aq-phase="request"] .aq-rating {
  opacity: 0.45;
  transform: translateY(2px);
}

.aq-rating__label {
  margin: 0 0 9px;
  font-size: 9px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--aq-faint);
}

.aq-stars {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

/* All five fill together on `posted`: there is no partial rating, because we
   do not branch on what the customer taps. --i is set inline per star. */
.aq-star {
  display: inline-flex;
  color: var(--tp-grey-3);
  transform: scale(0.88);
  transition: color 260ms cubic-bezier(0.16, 1, 0.3, 1) calc(var(--i, 0) * 130ms),
    transform 260ms cubic-bezier(0.16, 1, 0.3, 1) calc(var(--i, 0) * 130ms);
}
[data-aq-phase="posted"] .aq-star {
  color: var(--aq-star);
  transform: scale(1);
}

.aq-posted {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 12px;
  font-size: 10px;
  line-height: 1.4;
  color: var(--aq-muted);
}
.aq-posted svg {
  flex: 0 0 auto;
  color: #14a44d;
}

/* ---- Campaign launch screens --------------------------------------- *
 * Same chassis and same thread furniture as the two demos above; only the
 * screen contents are new. Screen A is a control surface (library + audience
 * + one button), screen B is a send log — a single templated message over a
 * list of recipients. The point of the band is scale, so nothing in here is
 * a conversation: that is what stops a third phone reading as a repeat.
 * -------------------------------------------------------------------- */

/* Mirror of .aq-phone--tilt. Band 1 is flat on the right, band 2 turns left
   on the left, this one turns right on the right: a triptych rather than a
   third copy. Same rotation magnitude, so the three read as one family. */
.aq-phone--tilt-r {
  transform: perspective(1400px) rotateY(13deg) rotateZ(-1.2deg);
}

/* Campaign library — a menu, deliberately plain. */
.aq-camps {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.aq-camp {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 11px;
  border: 1px solid var(--aq-line);
  border-radius: 13px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--aq-muted);
}
.aq-camp--on {
  border-color: var(--aq-alert);
  background: var(--tp-grey-4);
  color: var(--aq-ink);
}
.aq-camp svg {
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--aq-alert);
}

/* Audience card. Same border, radius and paper as .aq-rating on purpose —
   the three devices share one card language. This is the element doing the
   "bulk" work, so the count gets real size. */
.aq-audience {
  margin-top: 2px;
  padding: 10px 12px;
  border: 1px solid var(--aq-line);
  border-radius: 14px;
  background: var(--tp-grey-4);
}
/* Count and its label on one line, the exclusion on its own ruled line
   below. Side-by-side leaves the exclusion ~90px to wrap into, which breaks
   it over three ragged lines on a 240px device. */
.aq-audience__row {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin: 0;
}
.aq-audience__n {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--aq-ink);
  font-variant-numeric: tabular-nums;
}
.aq-audience__t {
  font-size: 9px;
  line-height: 1.4;
  color: var(--aq-faint);
}
.aq-audience__x {
  margin: 5px 0 0;
  padding-top: 5px;
  border-top: 1px solid var(--aq-line);
  font-size: 9px;
  line-height: 1.4;
  color: var(--aq-faint);
}

/* The one click. Black like .aq-bubble--out (the business acting on its own),
   turning to --aq-alert for the press. It sits in the composer slot the other
   two demos give to a text field: there is nothing to type here, and that
   substitution is the argument. */
.aq-oneclick {
  position: relative;
  flex: 1 1 auto;
  padding: 10px 14px;
  border-radius: 999px;
  background: var(--aq-ink);
  color: var(--tp-common-white);
  text-align: center;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: -0.01em;
  transition: background-color 0.25s var(--aq-ease),
    transform 0.25s var(--aq-ease);
}
[data-aq-phase="send"] .aq-oneclick {
  background: var(--aq-alert);
  transform: scale(0.97);
}

/* Tap pulse — borrows the call screen's ring keyframes rather than adding a
   second set that does the same thing. */
.aq-tap {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border: 1px solid var(--tp-common-white);
  border-radius: 50%;
  opacity: 0;
}
[data-aq-phase="send"] .aq-tap {
  animation: aq-ring 0.9s var(--aq-ease) 2;
}

/* Recipients. Permanently mounted and faded rather than revealed, so the list
   never reflows as it fills — same reason .aq-rating stays mounted. The
   per-row delay is the .aq-star stagger idiom; --i is set inline. */
.aq-recips {
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
}
.aq-recip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 2px;
  border-top: 1px solid var(--aq-line);
  font-size: 10px;
  letter-spacing: -0.01em;
  opacity: 0;
  transform: translateY(5px);
  transition: opacity 0.32s var(--aq-ease) calc(var(--i, 0) * 150ms),
    transform 0.32s var(--aq-ease) calc(var(--i, 0) * 150ms);
}
[data-aq-phase="sending"] .aq-recip,
[data-aq-phase="sent"] .aq-recip {
  opacity: 1;
  transform: none;
}
.aq-recip .aq-thread__avatar {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  font-size: 9px;
  font-weight: 600;
}
.aq-recip__who {
  flex: 1 1 auto;
  font-weight: 600;
}
/* The tail row carries no avatar, so it indents by the avatar's width plus
   the row gap to stay on the same left edge as the names above it. */
.aq-recip--rest {
  padding-left: 32px;
  color: var(--aq-muted);
}
.aq-recip__state {
  flex: 0 0 auto;
  font-size: 9px;
  color: var(--aq-faint);
}

/* ---- Step list beside the device ----------------------------------- */

.aq-steps {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--aq-line);
}

.aq-step {
  display: flex;
  gap: 20px;
  padding: 18px 0;
  border-bottom: 1px solid var(--aq-line);
}

.aq-step__num {
  flex: 0 0 auto;
  min-width: 26px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.7;
  color: var(--aq-faint);
  font-variant-numeric: tabular-nums;
  transition: color 0.4s var(--aq-ease);
}

.aq-step__title {
  margin: 0 0 2px;
  font-family: var(--tp-ff-sequel-semi-bold);
  font-size: 18px;
  line-height: 1.35;
  letter-spacing: -0.02em;
  color: var(--aq-ink);
}

.aq-step__dec {
  margin: 0;
  font-size: 14px;
  line-height: 1.65;
  color: var(--aq-muted);
}

/* The active step tracks the phone. A left rule slides in rather than the row
   changing colour, so the list stays calm while it advances. */
.aq-step__body {
  position: relative;
  padding-left: 0;
  transition: padding-left 0.45s var(--aq-ease);
}
.aq-step__body::before {
  content: "";
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 0;
  width: 2px;
  background: var(--aq-alert);
  opacity: 0;
  transform: scaleY(0.4);
  transform-origin: top;
  transition: opacity 0.35s var(--aq-ease), transform 0.45s var(--aq-ease);
}
.aq-step.is-active .aq-step__body {
  padding-left: 16px;
}
.aq-step.is-active .aq-step__body::before {
  opacity: 1;
  transform: scaleY(1);
}
.aq-step.is-active .aq-step__num {
  color: var(--aq-alert);
}

/* ---- Caption ------------------------------------------------------- */

.aq-demo__note {
  margin: 0;
  font-size: 13px;
  line-height: 1.7;
  color: var(--aq-faint);
}

/* ---- Responsive ---------------------------------------------------- */

@media (max-width: 991px) {
  .aq-phone { margin-top: 50px; }
  .aq-step__title { font-size: 17px; }
  /* Flat below the breakpoint: the tilt exists to separate two side-by-side
     devices, and on a narrow screen it only costs legibility. */
  .aq-phone--tilt,
  .aq-phone--tilt-r { transform: none; }
}

@media (max-width: 575px) {
  .aq-phone { width: 240px; }
  .aq-step { gap: 14px; }
}

/* ---- Reduced motion ------------------------------------------------ *
 * The loop is stopped in JS (pinned to the resolved final frame). Here we
 * additionally kill the decorative keyframes and the reveal offsets, so the
 * graphic still communicates the outcome without any movement.
 * -------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .aq-call__ring,
  .aq-typing span,
  .aq-tap,
  .aq-reveal.is-on {
    animation: none;
  }
  .aq-screen,
  .aq-step__body,
  .aq-step__body::before,
  .aq-call__actions,
  .aq-call__state,
  .aq-step__num,
  .aq-rating,
  .aq-star,
  .aq-oneclick,
  .aq-recip {
    transition: none;
  }
  /* Pinned to the final frame, so the stars are simply lit and full size. */
  .aq-star {
    transform: none;
  }
  /* Same reason: the send log is pinned to "sent", so every recipient row is
     already delivered rather than waiting on a stagger that never runs. */
  .aq-recip {
    opacity: 1;
    transform: none;
  }
}

/* ==================================================================== *
 * Premium polish layer (design pass, 2026-07-29)
 *
 * Amends rule 1 above: this layer intentionally restyles an enumerated,
 * minimal set of template selectors — the brand accent variable, the hero
 * video, card/thumb radius, and the live-demos tab pills. Everything else
 * stays namespaced (.aqp-*).
 * ==================================================================== */

:root {
  /* Site accent: Cunnet's red -> Automatq sky (matches the Cal.com modal). */
  --tp-theme-primary: #0369a1;

  --aqp-ink: #1e1e1e;
  --aqp-sky: #0ea5e9;
  --aqp-line: rgba(30, 30, 30, 0.08);
  --aqp-line-strong: rgba(30, 30, 30, 0.18);
  --aqp-muted: rgba(30, 30, 30, 0.55);
  --aqp-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
  --aqp-shadow-soft: 0 10px 34px rgba(15, 23, 42, 0.07);
  --aqp-radius: 18px;
}

/* The missed-call state inside the phone demo stays alarm-red — a missed
   call is a warning, not a brand moment. Scoped to that one demo: on bare
   .aq-demo it also reddened the review funnel's active step, where red means
   nothing. Declared after the .aq-demo block above so it still wins. */
[data-aq-demo="missed-call"] {
  --aq-alert: #fa2837;
}

::selection {
  background: var(--tp-theme-primary);
  color: #fff;
}
:focus-visible {
  outline: 2px solid var(--aqp-sky);
  outline-offset: 3px;
}

/* Hero video: lift it out of screen-recording territory. */
.ca-hero-video video {
  border-radius: 14px;
  filter: brightness(1.06) contrast(1.03) saturate(1.06);
  box-shadow: var(--aqp-shadow);
}

/* One radius language for card media. */
.mg-portfolio-thumb,
.ca-portfolio-thumb {
  border-radius: 12px;
  overflow: hidden;
}

/* ---- Live-demos page ------------------------------------------------ */

/* Industry picker: roomier pills, gentle motion.
 *
 * .aq-mac-tabs is the homepage laptop band's picker. It rides these rules
 * rather than duplicating them, but deliberately does NOT use
 * .tp-animate-tab: that class hides inactive panes with visibility rather
 * than display, which keeps every pane's box alive — and a box means a lazy
 * iframe inside it still fetches, and a loaded one keeps compositing. See
 * the .aq-mac block below. */
.tp-animate-tab .nav-tabs,
.aq-mac-tabs .nav-tabs {
  gap: 10px;
  border: 0;
}
.tp-animate-tab .nav-tabs .nav-link,
.aq-mac-tabs .nav-tabs .nav-link {
  padding: 16px 30px;
  font-size: 16px;
  border-radius: 60px;
  transition: transform 0.25s ease, box-shadow 0.25s ease,
    background-color 0.25s ease, color 0.25s ease;
}
.tp-animate-tab .nav-tabs .nav-link:hover,
.aq-mac-tabs .nav-tabs .nav-link:hover {
  transform: translateY(-2px);
}
.tp-animate-tab .nav-tabs .nav-link.active,
.aq-mac-tabs .nav-tabs .nav-link.active {
  box-shadow: 0 14px 30px rgba(15, 23, 42, 0.18);
}

/* Browser-chrome frame around the live iframe (the app's BrowserFrame). */
.aqp-browser {
  border: 1px solid var(--aqp-line);
  border-radius: var(--aqp-radius);
  background: #fff;
  box-shadow: var(--aqp-shadow);
  overflow: hidden;
}
.aqp-browser-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 20px;
  border-bottom: 1px solid var(--aqp-line);
  background: #fafafa;
}
.aqp-dots {
  display: flex;
  gap: 6px;
  flex: 0 0 auto;
}
.aqp-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
}
.aqp-dot--r { background: #f87171; }
.aqp-dot--y { background: #fbbf24; } /* real macOS control, stays amber */
.aqp-dot--g { background: #34d399; }
.aqp-browser-url {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 0 6px;
  padding: 7px 18px;
  border: 1px solid var(--aqp-line);
  border-radius: 60px;
  background: #fff;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--aqp-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.aqp-browser-url::before {
  content: "";
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #34d399;
}
.aqp-browser-open {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--aqp-muted);
  transition: color 0.2s ease;
}
.aqp-browser-open:hover { color: var(--aqp-ink); }
.aqp-browser iframe {
  display: block;
  width: 100%;
  height: min(70vh, 720px);
  border: 0;
  background: #fff;
}

/* Small-caps section labels */
.aqp-label {
  display: block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--aqp-muted);
  margin-bottom: 14px;
}

/* Feature checklist */
.aqp-features {
  list-style: none;
  margin: 0;
  padding: 0;
}
.aqp-features li {
  position: relative;
  padding: 9px 0 9px 32px;
  border-bottom: 1px solid var(--aqp-line);
  font-size: 15px;
  line-height: 1.55;
}
.aqp-features li:last-child { border-bottom: 0; }
.aqp-features li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 12px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(3, 105, 161, 0.10)
    url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M4 8.4 6.8 11 12 5.4" fill="none" stroke="%230369a1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>')
    center / 12px no-repeat;
}

/* CTA cards — the app's stacked card language. */
.aqp-card {
  display: block;
  border: 1px solid var(--aqp-line);
  border-radius: var(--aqp-radius);
  background: #fff;
  padding: 26px 28px;
  margin-bottom: 18px;
  box-shadow: var(--aqp-shadow-soft);
  transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}
a.aqp-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--aqp-shadow);
  border-color: var(--aqp-line-strong);
}
.aqp-card-eyebrow {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--aqp-muted);
  margin-bottom: 10px;
}
.aqp-card-title {
  display: block;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--aqp-ink);
  margin-bottom: 8px;
}
a.aqp-card .aqp-card-title::after { content: " \2192"; }
.aqp-card-note {
  display: block;
  font-size: 13px;
  line-height: 1.55;
  color: var(--aqp-muted);
  margin: 0;
}
.aqp-card--primary {
  background: var(--aqp-ink);
  border-color: var(--aqp-ink);
}
.aqp-card--primary .aqp-card-eyebrow { color: rgba(255, 255, 255, 0.55); }
.aqp-card--primary .aqp-card-title { color: #fff; }
.aqp-card--primary .aqp-card-note { color: rgba(255, 255, 255, 0.65); }
.aqp-card--sky .aqp-card-eyebrow { color: var(--tp-theme-primary); }

/* Roadmap coming-soon cards */
.aqp-roadmap-card {
  border: 1px solid var(--aqp-line);
  border-radius: 16px;
  background: #fff;
  padding: 22px 18px;
  text-align: center;
  opacity: 0.62;
  margin-bottom: 18px;
  transition: opacity 0.4s ease, transform 0.3s ease, box-shadow 0.3s ease;
}
.aqp-roadmap-card:hover {
  opacity: 1;
  transform: translateY(-3px);
  box-shadow: var(--aqp-shadow-soft);
}
.aqp-roadmap-name {
  display: block;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--aqp-ink);
  margin-bottom: 6px;
}
.aqp-roadmap-soon {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--aqp-muted);
}

/* Pricing card tagline — reserves two lines so the big price figure lands on
   the same baseline across a row. Tier taglines come from tiers.json and vary
   between one and two lines, which otherwise leaves the prices stepped. */
.aqp-price-sub {
  min-height: 3.1em;
}

@media (max-width: 767px) {
  .aqp-browser iframe { height: min(62vh, 560px); }
  .tp-animate-tab .nav-tabs .nav-link,
  .aq-mac-tabs .nav-tabs .nav-link { padding: 12px 20px; font-size: 14px; }
  .aqp-card { padding: 22px; }
  /* Cards are full-width and stacked below this, so nothing to align. */
  .aqp-price-sub { min-height: 0; }
}

/* ==================================================================== *
 * Laptop mockup — the homepage "Functional websites" band
 *
 * A pure-CSS 16:10 laptop whose screen is a live cross-origin iframe of a
 * real client site. Structural sibling of .aq-phone above (the bezel wraps
 * its children, no image asset), authored here rather than ported.
 *
 * RENDERING SCALE. The framed site lays out at a fixed 1440px logical
 * viewport and is scaled down with transform, so every site shows its real
 * desktop layout instead of collapsing to its tablet breakpoint at ~900px.
 * transform is paint-only, so the framed document genuinely sees
 * window.innerWidth === 1440.
 *
 * --aq-mac-k (unitless) is the ONLY value that changes per breakpoint;
 * --aq-mac-w is derived from it. It has to be this way round: calc() cannot
 * divide a length by a length to produce a number, so a scale factor can
 * never be computed from a fluid width without JS. Setting k and deriving w
 * makes "w == 1440 * k" true by construction. Every other dimension is a
 * ratio of --aq-mac-w, so the device scales as one object and each media
 * query holds exactly one number.
 *
 * Runtime: none. Standard Bootstrap tab markup plus data-demo-src is driven
 * by the existing initDemoIframes() in assets/js/automatq.js.
 * ==================================================================== */

.aq-mac {
  /* Logical viewport. 1440x900 is 16:10 — the aperture ratio — and the width
     every one of these sites treats as desktop. The five stored screenshots
     are 1100x687 (1.6012), so they drop into the same aperture uncropped. */
  --aq-mac-vw: 1440px;

  /* Widest desktop scrollbar. The iframe is this much wider than what the
     aperture reveals, so the gutter is clipped instead of drawn down the
     right edge of the "screen". Avoids the obsolete scrolling="no". */
  --aq-mac-gutter: 18px;

  /* Vertical bleed, clipped. This is what makes display-only viable:
     position:fixed chrome anchors to the IFRAME's viewport, so rendering at
     1100px tall and revealing only the top 900px pushes every bottom-anchored
     overlay out of view. Concretely it clips zoltanbuilds.ca's cookie pill
     (fixed, bottom: calc(72px + .75rem)) — which can never be dismissed from
     out here, because localStorage is partitioned per top-level site so the
     framed copy never sees its own consent record. Also catches chat FABs and
     geo banners, on all five sites, now and later. */
  --aq-mac-overscan: 200px;

  /* The one per-breakpoint number. Base value is the 576-767 step. */
  --aq-mac-k: 0.28;

  /* Derived — never set this by hand. */
  --aq-mac-w: calc(var(--aq-mac-vw) * var(--aq-mac-k));

  /* Proportions, taken off a real 16:10 laptop. */
  --aq-mac-bezel: calc(var(--aq-mac-w) * 0.0115);
  --aq-mac-chin: calc(var(--aq-mac-w) * 0.026);
  --aq-mac-ledge: calc(var(--aq-mac-w) * 0.022);
  --aq-mac-thick: calc(var(--aq-mac-w) * 0.0135);
  --aq-mac-radius: calc(var(--aq-mac-w) * 0.016);

  display: flex;
  flex-direction: column;
  align-items: center;
}

/* ---- Lid ------------------------------------------------------------ */

/* Graphite, matching .aq-phone__frame's gradient: index.html already carries
   three dark phones, and a silver laptop would be a second device language
   on one page. No wordmark, deliberately. */
.aq-mac__lid {
  position: relative;
  padding: var(--aq-mac-bezel) var(--aq-mac-bezel) var(--aq-mac-chin);
  border-radius: var(--aq-mac-radius) var(--aq-mac-radius) 0 0;
  background: linear-gradient(168deg, #3a3a3a 0%, var(--aqp-ink) 62%);
  box-shadow: 0 34px 70px -26px rgba(15, 23, 42, 0.45),
    0 12px 28px -14px rgba(15, 23, 42, 0.3);
}

/* Camera — decoration, no markup of its own. */
.aq-mac__lid::before {
  content: "";
  position: absolute;
  top: calc(var(--aq-mac-bezel) * 0.34);
  left: 50%;
  width: calc(var(--aq-mac-w) * 0.0042);
  height: calc(var(--aq-mac-w) * 0.0042);
  transform: translateX(-50%);
  border-radius: 50%;
  background: #5a5f66;
}

/* ---- Screen aperture ------------------------------------------------ */

/* The clip box for the scaled render. The corner radius is deliberately
   tiny: a real screen is square inside its bezel, and a near-square corner
   also sidesteps Safari's imprecise clipping of a transformed child against
   a large radius. The big radius lives on the lid. */
.aq-mac__screen {
  position: relative;
  overflow: hidden;
  width: var(--aq-mac-w);
  max-width: 100%;
  aspect-ratio: 16 / 10;
  border-radius: 3px;
  background: #fff;
}

@supports not (aspect-ratio: 16 / 10) {
  .aq-mac__screen { height: calc(var(--aq-mac-w) * 0.625); }
}

/* One flat diagonal sheen so the screen reads as glass. Above the frame, and
   pointer-events:none so it is never a hit target either. */
.aq-mac__screen::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(104deg,
    rgba(255, 255, 255, 0.14) 0%,
    rgba(255, 255, 255, 0) 34%);
}

/* The live site. Absolute so its 1440px box never sizes the aperture, and
   scaled from the top-left so the render registers to the corner exactly. */
.aq-mac__site {
  position: absolute;
  top: 0;
  left: 0;
  width: calc(var(--aq-mac-vw) + var(--aq-mac-gutter));
  height: calc(900px + var(--aq-mac-overscan));
  border: 0;
  background: #fff;
  transform: scale(var(--aq-mac-k));
  transform-origin: top left;

  /* Display-only. Without this the frame is a hit target and swallows wheel
     events, which on this page means trapping the reader inside a third-party
     document while GSAP ScrollSmoother drives the outer scroll. The cost is
     no text selection and no hover states inside — both invisible at this
     scale. "Open ↗" in the caption is the way in, and it is a real link
     outside the device.

     Note this blocks POINTER input only. Keyboard focus still walks into the
     framed document, which is why the wrapper carries `inert` in the markup. */
  pointer-events: none;
}

/* Screenshot stand-in. Used below 768px, and pre-wired as the fallback for
   any site that starts refusing to be framed (see .is-still below). */
.aq-mac__still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* ---- Base + foot ---------------------------------------------------- */

.aq-mac__base {
  position: relative;
  width: calc(var(--aq-mac-w)
    + var(--aq-mac-bezel) * 2
    + var(--aq-mac-ledge) * 2);
  max-width: 100%;
  height: var(--aq-mac-thick);
  border-radius: 0 0 calc(var(--aq-mac-thick) * 0.6) calc(var(--aq-mac-thick) * 0.6);
  background: linear-gradient(180deg, #4a4f55 0%, #2a2d31 38%, var(--aqp-ink) 100%);
  box-shadow: 0 26px 40px -22px rgba(15, 23, 42, 0.5);
}

/* Hinge seam where the lid meets the base. */
.aq-mac__base::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: calc(var(--aq-mac-w) * 0.3);
  height: calc(var(--aq-mac-thick) * 0.18);
  transform: translateX(-50%);
  border-radius: 0 0 2px 2px;
  background: #17191c;
}

/* Thumb notch cut into the front edge. */
.aq-mac__base::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 50%;
  width: calc(var(--aq-mac-w) * 0.1);
  height: calc(var(--aq-mac-thick) * 0.42);
  transform: translateX(-50%);
  border-radius: calc(var(--aq-mac-thick) * 0.3) calc(var(--aq-mac-thick) * 0.3) 0 0;
  background: #16181b;
}

/* Contact shadow — sells "sitting on a surface" without a second box-shadow. */
.aq-mac__foot {
  width: calc(var(--aq-mac-w) * 0.86);
  height: calc(var(--aq-mac-w) * 0.012);
  margin-top: calc(var(--aq-mac-w) * 0.004);
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%,
    rgba(15, 23, 42, 0.22) 0%,
    rgba(15, 23, 42, 0) 72%);
}

/* ---- Caption -------------------------------------------------------- */

/* Lives OUTSIDE the aria-hidden/inert device, so it carries everything a
   screen reader or keyboard user needs: name, address, and the way in.
   Composed from existing polish primitives — .aqp-card-title,
   .aqp-browser-url (which brings its own green live dot) and
   .aqp-browser-open. */
.aq-mac-cap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px 18px;
  margin-top: 34px;
  text-align: center;
}
.aq-mac-cap__brand { margin-bottom: 0; }
/* .aqp-browser-url is flex:1 1 auto for the demos-page bar; here it is a chip. */
.aq-mac-cap__url { flex: 0 0 auto; max-width: 100%; }
.aq-mac-cap__meta { flex: 1 0 100%; }

/* Panes are absolutely positioned, so the tab-content's height comes from
   whichever pane is active. Reserving the caption's height stops a jump when
   one brand name wraps and another does not — and a stable height means
   ScrollSmoother's ResizeObserver never fires a ScrollTrigger refresh, which
   would re-evaluate all 27 tp_fade_anim triggers on the page. */
@media (min-width: 576px) {
  .aq-mac-cap { min-height: 84px; }
}

/* ---- Content routing ------------------------------------------------- */

/* Below 768 the frame comes out and the screenshot takes over. display:none
   (not visibility) is the point: no box means loading="lazy" never starts the
   fetch, so a phone pays nothing for five third-party sites, and a desktop
   never fetches the JPEG. Each viewport loads exactly one asset. */
.aq-mac__site { display: none; }
.aq-mac__still { display: block; }

/* Inactive panes hide both children. Plain Bootstrap .tab-pane is already
   display:none, but stating it here keeps the rule true even if someone
   later swaps in a cross-fading pane class. */
.aq-mac-pane:not(.active) .aq-mac__site,
.aq-mac-pane:not(.active) .aq-mac__still { display: none; }

/* ---- Scale ladder ---------------------------------------------------- *
 * One number per step. Cross-checked against the col-12 content width, which
 * is (.container border box - 30px) because --bs-gutter-x is 30px here, and
 * sized against the NARROWEST viewport in each range (.container caps at
 * 1750px but is viewport-limited from 1400 to 1780):
 *
 *   step      k     aperture w   base w   available    headroom
 *   >=576   0.28      403.2       430       510           80
 *   >=768   0.40      576.0       614       690           76
 *   >=992   0.56      806.4       860       930           70
 *   >=1200  0.68      979.2      1044      1110           66
 *   >=1400  0.80     1152.0      1229      1370 @1400vw  141
 *
 * The ladder stops at 0.80 because 1152 * 0.625 = 720px exactly, the same
 * screen height .aqp-browser iframe already caps at on the demos page. Keep k
 * as high as the column allows — a gentler downsample is a sharper render.
 * -------------------------------------------------------------------- */

@media (min-width: 768px) {
  .aq-mac { --aq-mac-k: 0.4; }
  .aq-mac__site { display: block; }
  .aq-mac__still { display: none; }
}
@media (min-width: 992px)  { .aq-mac { --aq-mac-k: 0.56; } }
@media (min-width: 1200px) { .aq-mac { --aq-mac-k: 0.68; } }
@media (min-width: 1400px) { .aq-mac { --aq-mac-k: 0.8; } }

/* Under 576 the laptop silhouette costs more width than it earns, so the
   device degrades to its own lid: a full-width bezelled card holding the
   screenshot. Same markup, five properties. --aq-mac-w is overridden directly
   here — the only place that is allowed — because the card is fluid and no
   transform scale is in play at this width. */
@media (max-width: 575.98px) {
  .aq-mac {
    --aq-mac-w: calc(100vw - 62px);
    width: 100%;
  }
  .aq-mac__lid {
    width: 100%;
    padding: 8px;
    border-radius: 14px;
  }
  .aq-mac__lid::before { display: none; }
  .aq-mac__screen { border-radius: 8px; }
  .aq-mac__base,
  .aq-mac__foot { display: none; }
  .aq-mac-cap { margin-top: 24px; gap: 8px 14px; }
}

/* ---- Pre-wired degradation ------------------------------------------ *
 * There is no CSS selector for "the iframe was refused", so this cannot be
 * automatic without JS. Instead every pane already ships its screenshot:
 * adding `is-still` to a pane pins it to that screenshot at every width.
 * Specificity (0,3,0) beats the (0,2,0) breakpoint rules above and it is
 * declared after them, so no !important is needed.
 *
 * Reach for this when a client site starts sending a real X-Frame-Options or
 * CSP frame-ancestors header, goes behind Vercel deployment protection, or
 * simply goes down. One word of diff.
 * -------------------------------------------------------------------- */
.aq-mac-pane.is-still .aq-mac__site { display: none; }
.aq-mac-pane.is-still .aq-mac__still { display: block; }
.aq-mac-pane.is-still:not(.active) .aq-mac__still { display: none; }

/* ==================================================================== *
 * Video facade — the operator walkthroughs on the product pages
 *
 * GA4_ID and GADS_ID are empty in assets/js/automatq.js: analytics is fully
 * off and no consent banner ever renders. privacy.html documents that. A
 * bare YouTube iframe would set third-party cookies on page load and quietly
 * undo the whole stance, so nothing is requested from YouTube until the
 * visitor presses play. initVideoFacades() then builds the iframe against
 * youtube-nocookie.com.
 *
 * Not main.js's .popup-video: magnific's iframe module hardcodes
 * `//www.youtube.com/embed/%id%` for any URL containing "youtube.com", which
 * is the exact host being avoided — and a nocookie URL misses that pattern
 * and gets iframed raw. Both branches are wrong.
 *
 * The poster is drawn in CSS rather than being an image. There is no
 * per-product art, check-links.py validates poster= and src= targets, and
 * vercel.json proxies missing /assets/* to an app host that is currently
 * behind SSO — so a missing raster would fail silently through a proxy.
 * A gradient has no such failure mode. Never hotlink i.ytimg.com either:
 * that is a third-party request on load, which defeats the facade before
 * anyone clicks.
 * ==================================================================== */

.aq-video {
  position: relative;
  aspect-ratio: 16 / 9;
  margin: 0;
  border-radius: var(--aqp-radius);
  overflow: hidden;
  box-shadow: var(--aqp-shadow);
  background: var(--aqp-ink);
}
@supports not (aspect-ratio: 16 / 9) {
  .aq-video { height: 0; padding-bottom: 56.25%; }
}

.aq-video__poster,
.aq-video__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.aq-video__poster {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 32px;
  text-align: center;
  cursor: pointer;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(14, 165, 233, 0.35), transparent 60%),
    linear-gradient(160deg, #101418 0%, var(--aqp-ink) 100%);
  color: var(--tp-common-white);
  transition: background-color 0.3s ease;
}
.aq-video__grid {
  position: absolute;
  inset: 0;
  opacity: 0.1;
  pointer-events: none;
  background:
    repeating-linear-gradient(0deg, #fff 0 1px, transparent 1px 44px),
    repeating-linear-gradient(90deg, #fff 0 1px, transparent 1px 44px);
}
/* Optional self-hosted still. Sits under the gradient, never hotlinked. */
.aq-video__still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.42;
}

.aq-video__text { position: relative; }
.aq-video__eyebrow {
  display: block;
  margin-bottom: 6px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.26em;
  color: rgba(255, 255, 255, 0.6);
}
.aq-video__name {
  display: block;
  font-family: var(--tp-ff-sequel-medium);
  font-size: 26px;
  line-height: 1.15;
  letter-spacing: -0.02em;
}
.aq-video__play {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 74px;
  height: 74px;
  padding-left: 5px;
  border-radius: 50%;
  background: var(--aqp-baby);
  color: var(--aqp-ink);
  transition: transform 0.3s var(--aq-ease, ease);
}
.aq-video__poster:hover .aq-video__play,
.aq-video__poster:focus-visible .aq-video__play { transform: scale(1.06); }

.aq-video__cta {
  position: relative;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.aq-video__len { color: rgba(255, 255, 255, 0.55); font-weight: 400; }
.aq-video__privacy {
  position: relative;
  max-width: 34ch;
  font-size: 10px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.45);
}
.aq-video__cap { margin-top: 16px; }

/* A malformed or absent id must never build an embed that renders a 404
   inside a black box. The generator omits the section entirely when the id
   is empty; this only fires on a hand edit. */
.aq-video[data-aq-state="pending"] .aq-video__poster { cursor: default; }
.aq-video[data-aq-state="pending"] .aq-video__play,
.aq-video[data-aq-state="pending"] .aq-video__cta { display: none; }

@media (max-width: 767px) {
  .aq-video__name { font-size: 19px; }
  .aq-video__play { width: 58px; height: 58px; }
  .aq-video__privacy { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .aq-video__poster:hover .aq-video__play { transform: none; }
}

/* ==================================================================== *
 * Intake walkthrough — one question per screen
 *
 * Vanilla port of moscow/src/components/MissedLeadIntake.jsx (7 steps,
 * plain useState, hand-rolled validation). framer-motion in the original is
 * cosmetic, so the step transition is a CSS animation here.
 *
 * The runtime lives in assets/js/automatq.js (initIntakeFlow).
 * ==================================================================== */

.aq-flow {
  --aq-flow-ink: var(--tp-common-black);
  --aq-flow-line: var(--tp-border-1);
  --aq-flow-muted: var(--tp-grey-1);
  --aq-flow-accent: var(--tp-theme-primary);
  max-width: 760px;
  margin: 0 auto;
}

/* ---- Progress ------------------------------------------------------ */

.aq-flow__progress {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 34px;
}
.aq-flow__bar {
  flex: 1 1 auto;
  height: 3px;
  border-radius: 3px;
  background: var(--tp-grey-2);
  overflow: hidden;
}
.aq-flow__bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: var(--aq-flow-accent);
  transition: width 0.45s var(--basic-ease, cubic-bezier(0.625, 0.05, 0, 1));
}
.aq-flow__count {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--tp-grey-3);
  font-variant-numeric: tabular-nums;
}

/* ---- Step ---------------------------------------------------------- */

.aq-flow__step { display: none; }
.aq-flow__step.is-on {
  display: block;
  animation: aq-flow-in 0.4s var(--basic-ease, cubic-bezier(0.625, 0.05, 0, 1)) both;
}

@keyframes aq-flow-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.aq-flow__title {
  font-family: var(--tp-ff-sequel-semi-bold);
  font-size: 30px;
  line-height: 1.22;
  letter-spacing: -0.02em;
  color: var(--aq-flow-ink);
  margin: 0 0 10px;
}
.aq-flow__helper {
  font-size: 15px;
  line-height: 1.6;
  color: var(--aq-flow-muted);
  margin: 0 0 28px;
}

/* ---- Choices ------------------------------------------------------- */

.aq-flow__choices {
  display: grid;
  gap: 10px;
  margin: 0 0 8px;
  padding: 0;
  list-style: none;
}

/* A real radio, visually replaced — keeps keyboard and screen-reader
   behaviour that a div-with-click would throw away. */
.aq-flow__choice { position: relative; }
.aq-flow__choice input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}
.aq-flow__choice label {
  display: block;
  padding: 17px 22px;
  border: 1px solid var(--aq-flow-line);
  border-radius: 14px;
  background: #fff;
  font-size: 16px;
  line-height: 1.45;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}
.aq-flow__choice label:hover { border-color: rgba(30, 30, 30, 0.28); }
.aq-flow__choice input:checked + label {
  border-color: var(--aq-flow-accent);
  background: rgba(3, 105, 161, 0.04);
}
.aq-flow__choice input:focus-visible + label {
  outline: 2px solid var(--aq-flow-accent);
  outline-offset: 2px;
}
.aq-flow__choice span {
  display: block;
  margin-top: 4px;
  font-size: 13px;
  color: var(--tp-grey-3);
}

/* ---- Text inputs --------------------------------------------------- */

.aq-flow__field { margin-bottom: 16px; }
.aq-flow__field label {
  display: block;
  margin-bottom: 7px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--tp-grey-3);
}
.aq-flow__field input,
.aq-flow__field textarea {
  width: 100%;
  padding: 15px 18px;
  border: 1px solid var(--aq-flow-line);
  border-radius: 12px;
  background: #fff;
  font-size: 16px; /* 16px avoids the iOS zoom-on-focus jump */
  color: var(--aq-flow-ink);
}
.aq-flow__field textarea { min-height: 104px; resize: vertical; }
.aq-flow__field input:focus,
.aq-flow__field textarea:focus {
  outline: none;
  border-color: var(--aq-flow-accent);
}

/* Conditional follow-up (e.g. "Other" trade), revealed by the runtime. */
.aq-flow__reveal { display: none; }
.aq-flow__reveal.is-on { display: block; }

/* ---- Actions ------------------------------------------------------- */

.aq-flow__actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 30px;
}
.aq-flow__back {
  border: 0;
  background: none;
  padding: 8px 2px;
  font-size: 14px;
  color: var(--tp-grey-3);
  cursor: pointer;
}
.aq-flow__back:hover { color: var(--aq-flow-ink); }
.aq-flow__back[hidden] { display: none; }

.aq-flow__error {
  margin-top: 14px;
  font-size: 14px;
  color: #fa2837;
}
.aq-flow__error:empty { display: none; }

.aq-flow__note {
  margin-top: 22px;
  font-size: 13px;
  line-height: 1.65;
  color: var(--tp-grey-3);
}

/* ---- Done ---------------------------------------------------------- */

.aq-flow__done { display: none; }
.aq-flow__done.is-on {
  display: block;
  animation: aq-flow-in 0.4s var(--basic-ease, cubic-bezier(0.625, 0.05, 0, 1)) both;
}

@media (max-width: 575px) {
  .aq-flow__title { font-size: 24px; }
  .aq-flow__choice label { padding: 15px 18px; font-size: 15px; }
}

@media (prefers-reduced-motion: reduce) {
  .aq-flow__step.is-on,
  .aq-flow__done.is-on { animation: none; }
  .aq-flow__bar span { transition: none; }
}

/* ==================================================================== *
 * Header client-login link
 *
 * Sits in .tp-header-right ahead of the search button (21 pages) or the
 * "Start here" CTA (index). Deliberately a text link, not a second .tp-btn:
 * the column is a col-xl-3 and a second pill would crowd the primary action.
 * Hidden below md, where the hamburger takes over and the cloned nav menu
 * carries the same link.
 * ==================================================================== */

.aq-login-link {
  font-family: var(--tp-ff-inter);
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--tp-common-black);
  white-space: nowrap;
  margin-right: 18px;
  padding: 8px 2px;
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s var(--basic-ease, cubic-bezier(0.625, 0.05, 0, 1));
}

.aq-login-link:hover,
.aq-login-link:focus-visible {
  color: var(--tp-common-black);
  border-bottom-color: var(--tp-common-black);
}

@media (prefers-reduced-motion: reduce) {
  .aq-login-link { transition: none; }
}

/* ==================================================================== *
 * Client login page (login.html)
 *
 * The help destination for "I can't get in", and where a search for
 * "automatq login" lands. The chrome links go straight to the portal; this
 * page carries the password-reset copy that a bare redirect can't.
 * ==================================================================== */

.aq-login__title {
  font-family: var(--tp-ff-sequel-roman);
  font-size: 44px;
  line-height: 1.15;
  letter-spacing: -0.03em;
  color: var(--tp-common-black);
  margin-bottom: 14px;
}

.aq-login__lede {
  font-size: 17px;
  line-height: 1.65;
  color: var(--tp-grey-1);
  margin-bottom: 32px;
}

.aq-login__help {
  list-style: none;
  margin: 40px 0 0;
  padding: 0;
  border-top: 1px solid var(--tp-border-1);
}

.aq-login__help li {
  padding: 18px 0;
  border-bottom: 1px solid var(--tp-border-1);
  font-size: 15px;
  line-height: 1.6;
  color: var(--tp-grey-1);
}

.aq-login__help strong {
  display: block;
  font-weight: 600;
  color: var(--tp-common-black);
  margin-bottom: 3px;
}

.aq-login__note {
  margin-top: 26px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--tp-grey-3);
}

.aq-login__note a {
  color: var(--tp-common-black);
  border-bottom: 1px solid var(--tp-common-black);
}

@media (max-width: 575px) {
  .aq-login__title { font-size: 32px; }
}

/* ==================================================================== *
 * Baby-blue accent, matched to the moscow app
 *
 * moscow's brand accent is --color-accent-sky #7dd3fc (src/index.css:31).
 * It works there because moscow is a dark theme; this template is a light
 * one, and #7dd3fc on white is 1.67:1 -- invisible. Against the ink it is
 * 10:1. So the accent splits by role rather than being one token:
 *
 *   fills  -> #7dd3fc with ink content (buttons, sticky menu, selection).
 *             This is moscow's own button pattern: light plate, dark label.
 *   text   -> --tp-theme-primary stays #0369a1 (5.93:1 on white, AA). Baby
 *             blue as accent *text* on this canvas cannot be read.
 *
 * Changing --tp-theme-primary itself was the obvious move and is wrong: the
 * template spends it 121 times in main.css, 50 of those as `color` on light
 * backgrounds.
 * ==================================================================== */

:root {
  --aqp-baby: #7dd3fc;
}

/* Primary accent button: baby plate, ink label. */
.tp-btn-red,
.tp-btn-red:hover {
  background-color: var(--aqp-baby);
  color: var(--aqp-ink);
}
.tp-btn-red .text-1,
.tp-btn-red .text-2,
.tp-btn-red i {
  color: var(--aqp-ink);
}

/* Sticky hamburger: its two bars ship white, which vanishes on the plate. */
.hamburger-open-btn,
.tp-header-sidebar-btn-bg {
  background-color: var(--aqp-baby);
}
.hamburger-open-btn span,
.tp-header-sidebar-btn-bg span {
  background-color: var(--aqp-ink);
}

/* Decorative fills carry no text, so they take the accent straight. */
.swiper-pagination-progressbar-fill {
  background-color: var(--aqp-baby);
}

::selection {
  background: var(--aqp-baby);
  color: var(--aqp-ink);
}

/* ---- Hero word rotator ---------------------------------------------- *
 * "We Catch [Missed|Lost|Cold] Leads". The rotating word and the clip
 * animation's cursor bar both took --tp-theme-primary, so the hero kept the
 * old deep sky after the accent change. It sits on the #f6f6f6 hero band,
 * where baby blue as *text* is ~1.6:1 -- so the word becomes a baby-blue
 * plate with ink on it, the same trade the accent buttons make.
 *
 * The template still ships class="no-js" on <html>, which is what actually
 * colours these <b>s (main.css:2177). Overriding the lit selector only would
 * miss it, so both are set here.
 * -------------------------------------------------------------------- */

.ca-hero-title .cd-words-wrapper {
  background: var(--aqp-baby);
  border-radius: 12px;
  /* No left padding and no negative margin: the plate's left edge sits on the
     glyph origin, so it shares one edge with "Catch" and "Leads" and stays
     inside the column rule. Padding on the left indents the word off that
     axis; cancelling it with a negative margin instead pushed the plate ~50px
     out past the column. Only the right side gets breathing room. */
  padding: 0 0.14em 0 0;
  color: var(--aqp-ink);
}

.ca-hero-title .cd-words-wrapper b,
.no-js .ca-hero-title .cd-words-wrapper b {
  color: var(--aqp-ink);
}

/* The clip animation's cursor bar rides on the plate now, so it takes ink. */
.ca-hero-title.cd-headline.clip .cd-words-wrapper::after {
  background-color: var(--aqp-ink);
}

/* ==================================================================== *
 * Hover states
 *
 * --tp-theme-primary still drove every hover, so links flashed the old
 * deep sky. Same constraint as everywhere else on this canvas: baby blue
 * as hover *text* is 1.67:1 and unreadable, so hovered text stays ink and
 * the accent arrives as an underline or a plate.
 *
 * --tp-theme-primary itself is left alone. It is the only AA-safe accent
 * for text at rest (5.93:1) and 121 rules in main.css lean on it.
 *
 * Only selectors that appear on our 23 pages are listed. The ~65 rules
 * under .cunnet-dark, cart, shop and product-details belong to template
 * demos we do not ship.
 * ==================================================================== */

.ca-hero-service ul li a:hover .explore-text,
.tp-footer-email a:hover,
.tp-contact-us-info-details a:hover,
.tp-contact-tel:hover,
.tp-contact-mail:hover,
.inner-service-2-wrap .about-me-resume-title a:hover,
.tp-breadcrumb-list ul li a:hover,
.sidebar-widget-category ul li a:hover,
.sidebar-widget-category ul li a:hover span:last-child,
.tp-team-details-info-contact a:hover,
.tp-pd-2-categories span a:hover,
.postbox__comment-agree label a:hover {
  color: var(--aqp-ink);
  text-decoration: underline;
  text-decoration-color: var(--aqp-baby);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

/* Fill hovers were already a background swap; they just need the baby
   plate and ink content instead of the old sky under white. */
.ca-hero-btn:hover,
.basic-pagination ul li:hover a,
.tagcloud a:hover,
.cs-blog-tag:hover,
.rc-post-category a:hover,
.postbox-details-social a:hover,
.postbox-details-navigation a:hover i,
.tp-portfolio-revealing-slider-control:hover {
  background: var(--aqp-baby);
  background-color: var(--aqp-baby);
  color: var(--aqp-ink);
}

/* input:focus borders deliberately keep the deeper sky -- a baby-blue
   focus ring on white is 1.67:1, which would be a real regression. */

/* ==================================================================== *
 * Hero CTA pair
 *
 * The hero carried one button; it now carries the sprint CTA plus the
 * brochure download. .ca-hero-btn is width:100% in main.css (7465), so the
 * two are given a flex basis instead: they share a row while the column can
 * hold both labels, and drop to full-width stacked below that. The basis is
 * set off "Download the brochure", the longer of the two labels.
 *
 * The secondary is the same button with the fill removed rather than a
 * different component, so the baby-plate hover above still applies to both.
 * ==================================================================== */

.ca-hero-btn-group {
  gap: 12px;
}
.ca-hero-btn-group > .ca-hero-btn {
  flex: 1 1 240px;
  width: auto;
}
.ca-hero-btn-alt {
  background: transparent;
  border: 1px solid var(--aqp-ink, #1e1e1e);
}

/* ==================================================================== *
 * Products mega menu
 *
 * Structure and sizing come from the template (.tp-megamenu is 720px with
 * 30px padding, main.css). Only the accent and the feature link are added
 * here. The panel keeps `tp-submenu` so main.js's mobile clone can toggle
 * it -- that JS has no mega-menu branch.
 * ==================================================================== */

.tp-megamenu-list ul li a:hover {
  background-color: rgba(125, 211, 252, 0.18);
  color: var(--aqp-ink);
  border-radius: 8px;
}

/* The flagship offer lost its top-level slot, so it gets a baby-blue mark to
   stay findable inside the panel. text-decoration rather than an inset
   box-shadow: the label wraps to two lines in a 720/3 column, and a shadow
   highlight only paints the element box, so it renders as one curved smear
   across both lines instead of marking each. */
.aq-mega-feature {
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--aqp-baby);
  text-decoration-thickness: 3px;
  text-underline-offset: 3px;
}
.tp-megamenu-list ul li a.aq-mega-feature:hover {
  background-color: var(--aqp-baby);
  text-decoration-color: var(--aqp-ink);
}

/* Offcanvas: the template already stacks .col-xl-4 to full width, this just
   keeps the group headings legible at mobile sizes. */
.tp-offcanvas-menu .tp-megamenu-title {
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  opacity: 0.55;
}

/* ==================================================================== *
 * Brand hover — the wordmark goes baby blue
 *
 * moscow does this with `group-hover:text-sky-300` on its footer logo
 * (src/components/Footer.jsx:22). That reads there because moscow is dark:
 * white letterforms -> #7dd3fc is a clean step. Here the canvas is white and
 * the same colour is 1.67:1, so a straight colour swap would look broken
 * rather than deliberate.
 *
 * The fix is motion, not contrast: while hovered the mark also pulses. A
 * moving low-contrast colour reads as intentional where a static one reads
 * as a rendering fault, and the eye tracks the change rather than the value.
 * Off-hover it returns to ink.
 * ==================================================================== */

@keyframes aq-brand-pulse {
  0%,
  100% { opacity: 1; }
  50%  { opacity: 0.45; }
}

/* ---- Header logo ---------------------------------------------------- *
 * logo.svg paints through a single flat <rect fill="#1e1e1e" mask="url(#m)">,
 * so the file's own alpha is exactly the letterforms. That makes it usable
 * as a CSS mask directly: an overlay painted --aqp-baby and masked by the
 * logo is a baby-blue silhouette registered perfectly over the real one.
 *
 * Done as an ::after on the <a> rather than by recolouring the <img> on
 * purpose -- the theme swaps .logo-1/.logo-2 for light and dark headers, and
 * this overlay sits above whichever is showing without touching that logic.
 * -------------------------------------------------------------------- */
.tp-header-logo > a,
.tp-offcanvas-logo > a {
  position: relative;
  display: inline-block;
}
.tp-header-logo > a::after,
.tp-offcanvas-logo > a::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background-color: var(--aqp-baby);
  -webkit-mask: url(../img/logo/logo.svg) left center / contain no-repeat;
  mask: url(../img/logo/logo.svg) left center / contain no-repeat;
  transition: opacity 0.32s ease;
}
.tp-header-logo > a:hover::after,
.tp-header-logo > a:focus-visible::after,
.tp-offcanvas-logo > a:hover::after,
.tp-offcanvas-logo > a:focus-visible::after {
  opacity: 1;
  animation: aq-brand-pulse 1.7s ease-in-out infinite;
}

/* ---- Footer wordmark ------------------------------------------------ *
 * index.html's ca-footer closes on a giant <h2>Automatq</h2>. Plain text, so
 * this is just a colour swap -- no mask needed. It is not a link, so the
 * cursor is set explicitly to signal the mark is reactive. */
.ca-footer-bigtitle-wrap .tp-hero-title {
  cursor: default;
  transition: color 0.32s ease;
}
.ca-footer-bigtitle-wrap .tp-hero-title:hover {
  color: var(--aqp-baby);
  animation: aq-brand-pulse 1.7s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .tp-header-logo > a:hover::after,
  .tp-offcanvas-logo > a:hover::after,
  .ca-footer-bigtitle-wrap .tp-hero-title:hover {
    animation: none;
  }
}

/* ---- Footer CTA big title ------------------------------------------- *
 * "START HERE" always renders at ~5.99x its font-size. The Cunnet ladder
 * in main.css:6329 was tuned for a shorter word, so at most widths the text
 * overflowed the container and wrapped to two lines. That broke the effect:
 * the h2 grew past one line, so `overflow-y: clip` stopped hiding the blue
 * `text-shadow` duplicate that drives the hover reveal, leaving it stranded
 * mid-block next to the wrapped character. Size off the container instead so
 * the word always fits on one line. 16cqw = 1/5.99 with ~4% headroom. */
.tp-title-anim:has(.cs-footer-bigtitle) {
  container-type: inline-size;
}
.cs-footer-bigtitle {
  font-size: min(300px, 16cqw);
  white-space: nowrap;
}

/* ==================================================================== *
 * Cinematic intro — vanilla port of moscow's CinematicIntro.jsx
 *
 * Same seven beats and the same 6s clock as upstream
 * (moscow/src/components/CinematicIntro.jsx + the keyframes at
 * moscow/src/index.css:449-529). Two things are re-skinned for this theme:
 *
 *   - Type. Upstream is Geist; here the thesis words and the wordmark use
 *     the template's own display faces (--tp-ff-sequel-*), so the intro
 *     hands off to a page set in the same voice.
 *   - Accent. The manifesto cursor and the line glow take --aqp-baby. On the
 *     intro's black canvas baby blue is finally the high-contrast choice it
 *     is in moscow, which is the one place on this site that is true.
 *
 * The overlay is position:fixed and JS-injected, so it never enters layout
 * and cannot perturb the ScrollTrigger measurements the phone demos rely on.
 * Everything animates on transform/opacity only.
 * ==================================================================== */

.aq-intro {
  position: fixed;
  inset: 0;
  z-index: 9999;
  overflow: hidden;
  background: #000;
  color: #fff;
}
.aq-intro.is-fading {
  pointer-events: none;
  animation: aq-intro-fade-out 0.3s ease-out forwards;
}
@keyframes aq-intro-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Film grain + vignette: the two things that stop a black rectangle reading
   as a failed page load. Grain steps rather than eases -- it should judder. */
.aq-intro__grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.08;
  mix-blend-mode: overlay;
  background-size: 220px 220px;
  animation: aq-intro-grain 1.2s steps(4) infinite;
}
@keyframes aq-intro-grain {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(-3%, 2%); }
  50%  { transform: translate(2%, -3%); }
  75%  { transform: translate(-2%, -2%); }
  100% { transform: translate(0, 0); }
}
.aq-intro__vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.6) 100%);
}

.aq-intro__stage {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0 24px;
}

/* ---- Manifesto (above the line) ------------------------------------- */

.aq-intro__manifesto {
  display: flex;
  align-items: flex-end;
  height: 24px;
  margin-bottom: 28px;
  visibility: hidden;
  transition: opacity 0.7s ease;
}
[data-aq-beat="beat1"] .aq-intro__manifesto { visibility: hidden; }
[data-aq-beat="beat2"] .aq-intro__manifesto { visibility: visible; opacity: 1; }
[data-aq-beat="beat3a"] .aq-intro__manifesto,
[data-aq-beat="beat3b"] .aq-intro__manifesto,
[data-aq-beat="beat3c"] .aq-intro__manifesto,
[data-aq-beat="beat4"] .aq-intro__manifesto,
[data-aq-beat="beat5"] .aq-intro__manifesto,
[data-aq-beat="fading"] .aq-intro__manifesto { visibility: visible; opacity: 0.2; }

.aq-intro__manifesto p {
  margin: 0;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.32em;
  white-space: nowrap;
  color: rgba(255, 255, 255, 0.7);
}
/* Typewriter: each char carries an inline --i and pops on its own delay. */
.aq-intro__char {
  display: inline-block;
  opacity: 0;
  animation: aq-intro-char-in 0.02s linear forwards;
  animation-delay: calc(var(--i, 0) * 35ms);
}
@keyframes aq-intro-char-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.aq-intro__caret {
  display: inline-block;
  margin-left: 4px;
  color: var(--aqp-baby);
  animation: aq-intro-blink 1s steps(2, start) infinite;
}
@keyframes aq-intro-blink {
  to { visibility: hidden; }
}

/* ---- The line ------------------------------------------------------- */

.aq-intro__line {
  width: 78vw;
  max-width: 640px;
  height: 1px;
  transform: scaleX(0);
  transform-origin: center;
  background: rgba(255, 255, 255, 0.3);
  box-shadow: 0 0 12px rgba(125, 211, 252, 0.25);
  animation: aq-intro-line 0.8s cubic-bezier(0.7, 0, 0.3, 1) forwards;
}
@keyframes aq-intro-line {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* ---- Below the line: thesis words, then the wordmark ---------------- */

.aq-intro__below {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  width: 100%;
  min-height: 10rem;
  margin-top: 56px;
}

.aq-intro__word {
  position: absolute;
  margin: 0;
  font-family: var(--tp-ff-sequel-bold-head);
  font-size: clamp(2.5rem, 8vw, 6rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.03em;
  text-align: center;
  color: #fff;
  opacity: 0;
  animation: aq-intro-word-in 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes aq-intro-word-in {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.aq-intro__lockup {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  will-change: transform;
  animation: aq-intro-fade-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes aq-intro-fade-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}
.aq-intro__lockup img {
  display: block;
  height: 128px;
  width: auto;
}
.aq-intro__kicker {
  margin-top: 18px;
  font-family: var(--tp-ff-sequel-medium);
  font-size: 22px;
  text-transform: uppercase;
  letter-spacing: 0.6em;
  /* letter-spacing pads the right edge; pull it back so the word stays centred
     under the wordmark rather than sitting half a space to the right. */
  text-indent: 0.6em;
  color: rgba(255, 255, 255, 0.85);
}

/* The lockup flies to the real header logo. JS measures both boxes and sets
   --morph-x/y/scale; this only plays the move. */
.aq-intro__lockup.is-morphing {
  transform-origin: top left;
  animation: aq-intro-morph 1s cubic-bezier(0.7, 0, 0.3, 1) forwards;
}
@keyframes aq-intro-morph {
  to {
    transform: translate(var(--morph-x, 0), var(--morph-y, 0)) scale(var(--morph-scale, 1));
    opacity: 0.85;
  }
}

/* ---- Skip ----------------------------------------------------------- */

.aq-intro__skip {
  position: fixed;
  top: 24px;
  right: 24px;
  z-index: 1;
  padding: 8px 12px;
  border: 0;
  background: transparent;
  font-family: var(--tp-ff-inter, inherit);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.3em;
  color: rgba(255, 255, 255, 0.4);
  cursor: pointer;
  opacity: 0;
  transition: color 0.25s ease;
  animation: aq-intro-fade-up 0.5s ease 1s forwards;
}
.aq-intro__skip:hover,
.aq-intro__skip:focus-visible {
  color: var(--aqp-baby);
}

@media (max-width: 1023px) {
  /* The gate in automatq.js already refuses to build the overlay below this
     width -- the 6s hold is a straight LCP tax on the devices least able to
     afford it. This is belt and braces if the markup ever ships server-side. */
  .aq-intro { display: none; }
}

/* ==================================================================== *
 * Print layer
 *
 * The site shipped with zero @media print and zero @page. Two jobs here.
 *
 * A. Make Cmd+P on ANY page produce a document. main.js runs three motion
 *    systems that each empty a printed page:
 *      1. ScrollSmoother (main.js:154) turns #smooth-wrapper into a fixed,
 *         overflow:hidden viewport and transforms #smooth-content, so the
 *         print box contains only the visible screenful — the rest is
 *         clipped, not merely faded.
 *      2. .tp_fade_anim (main.js:1261) is gsap.from({opacity:0}) with a
 *         ScrollTrigger; immediateRender leaves everything below the fold at
 *         inline opacity:0 until it is scrolled to.
 *      3. SplitText .line/.word/.char tween from opacity 0 the same way, and
 *         main.css:1871 greys every .reveal-text .char until it is revealed.
 *    GSAP writes PLAIN inline styles, so !important outranks all of it
 *    without touching a line of main.js.
 *
 * B. Lay out brochure/index.html for downloads/automatq-brochure.pdf. That
 *    page loads no JS at all, so (A) is belt and braces there.
 *
 * WIDTH WARNING: Chromium lays print out at (paper - margins) x 96dpi.
 * Letter with 18mm side margins is ~680 CSS px — BELOW Bootstrap's md
 * breakpoint (768). Every .col-md-*/.col-xl-* collapses to full width in
 * print, and every `@media (max-width: 767px)` block in this file fires.
 * The brochure therefore uses .aqb-grid (CSS grid) and never loads
 * bootstrap.min.css at all, so the columns cannot be reached for.
 *
 * @page cannot be selector-scoped and this file loads on all 22 pages, so
 * these are the site-wide print defaults. That is deliberate: Cmd+P is
 * broken everywhere today, and this fixes it. It does not affect the
 * brochure PDF — that render passes geometry on the browse command line and
 * deliberately omits --prefer-css-page-size, so the CLI stays the single
 * authority and these values are only the Cmd+P fallback.
 * ==================================================================== */

@page {
  size: Letter;
  margin: 16mm 18mm 24mm;
}

@media print {
  /* ---- A1 · undo ScrollSmoother ------------------------------------ */
  html,
  body {
    height: auto !important;
    min-height: 0 !important;
    overflow: visible !important;
    background: #fff !important;
  }
  #smooth-wrapper,
  #smooth-content {
    position: static !important;
    height: auto !important;
    min-height: 0 !important;
    overflow: visible !important;
    transform: none !important;
    will-change: auto !important;
  }
  .pin-spacer {
    height: auto !important;
    padding: 0 !important;
  }

  /* ---- A2 · undo the fade and split tweens ------------------------- *
   * GSAP's inline styles are plain, so !important wins outright. */
  .tp_fade_anim,
  .tp_fade_anim *,
  .tp-char-animation,
  .tp-char-animation *,
  .reveal-text,
  .reveal-text *,
  .tp_text_anim,
  .tp_text_anim *,
  .tp-title-anim,
  .tp-title-anim * {
    opacity: 1 !important;
    visibility: visible !important;
    transform: none !important;
    clip-path: none !important;
    filter: none !important;
    animation: none !important;
    transition: none !important;
  }
  /* main.css:1871 greys every char until .revealed lands. */
  .reveal-text .char {
    color: var(--tp-common-black) !important;
  }

  /* Expand Bootstrap collapses. pricing.html hides five website tiers, three
     reputation tiers and the care plan behind "See all plans and website
     tiers"; the FAQ answers are collapsed the same way. Somebody printing
     the pricing page wants the prices, not the summary. */
  .collapse,
  .accordion-collapse {
    display: block !important;
    height: auto !important;
    visibility: visible !important;
  }
  .accordion-button {
    /* Printed collapsed-state chevrons point the wrong way once the panel
       above is forced open. */
    background: transparent !important;
    color: var(--tp-common-black) !important;
  }
  .accordion-button::after { display: none !important; }

  /* ---- A3 · chrome that must never print --------------------------- *
   * display:none rather than visibility:hidden throughout: .aq-intro is
   * position:fixed inset:0 and would otherwise reserve a whole page. */
  #preloader,
  .preloader,
  #magic-cursor,
  #ball,
  .tp-cursor-point-area,
  .back-to-top-wrapper,
  header,
  .tp-header-area,
  .tp-header-height,
  .tp-offcanvas-area,
  .tp-offcanvas-overlay,
  .tp-search-form-toggle,
  .tp-search-body-overlay,
  footer,
  .tp-footer-area,
  .cs-footer-area,
  .aq-intro,
  #aq-consent,
  .subscribe-popup,
  .aqp-browser,
  .aq-mac,
  .aq-phone,
  video,
  iframe {
    display: none !important;
  }
}

/* ---- B · the brochure document -------------------------------------- *
 * Namespace .aqb-* (Automatq Brochure). Screen and print share one measure
 * so the localhost preview is an honest proof of the PDF.
 * -------------------------------------------------------------------- */

.aqb-doc {
  --aqb-ink: #14171a;
  --aqb-muted: #5c6469;
  --aqb-line: #d9dee1;
  --aqb-accent: var(--tp-theme-primary);
  max-width: 680px;
  margin: 0 auto;
  padding: 40px 20px;
  font-family: var(--tp-ff-body);
  font-size: 10pt;
  line-height: 1.5;
  color: var(--aqb-ink);
}
.aqb-doc p {
  margin: 0 0 3mm;
  orphans: 3;
  widows: 3;
}

/* Each section is one page. */
.aqb-page {
  break-before: page;
  page-break-before: always;
}
.aqb-page:first-child {
  break-before: auto;
  page-break-before: auto;
}

.aqb-doc h1,
.aqb-doc h2,
.aqb-doc h3 {
  font-family: var(--tp-ff-sequel-medium);
  letter-spacing: -0.02em;
  break-after: avoid;
  page-break-after: avoid;
}
.aqb-doc h1 { font-size: 30pt; line-height: 1.05; margin: 0 0 4mm; }
.aqb-doc h2 { font-size: 20pt; line-height: 1.15; margin: 0 0 2mm; }
.aqb-doc h3 { font-size: 12pt; margin: 0 0 1mm; }

.aqb-eyebrow {
  display: block;
  margin-bottom: 2mm;
  font-size: 8pt;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--aqb-muted);
}
.aqb-lede {
  font-size: 11pt;
  line-height: 1.45;
  color: var(--aqb-muted);
  margin-bottom: 6mm;
}
.aqb-rule {
  height: 0;
  margin: 5mm 0;
  border: 0;
  border-top: 0.5pt solid var(--aqb-line);
}

.aqb-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 5mm;
}
.aqb-grid--3 { grid-template-columns: repeat(3, 1fr); }
.aqb-grid--1 { grid-template-columns: 1fr; }

.aqb-offer {
  break-inside: avoid;
  page-break-inside: avoid;
  padding: 5mm;
  border: 0.5pt solid var(--aqb-line);
  border-radius: 3mm;
  background: #fff;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.aqb-offer__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 3mm;
}
.aqb-price {
  font-family: var(--tp-ff-sequel-semi-bold);
  font-size: 15pt;
  white-space: nowrap;
}
.aqb-unit {
  font-size: 8pt;
  font-weight: 400;
  color: var(--aqb-muted);
}
/* staffing-saas is the only tier with a one-time setup on top of the
   monthly. Showing the monthly alone would be a misleading price in a
   document a prospect keeps. */
.aqb-setup {
  display: block;
  margin-top: 1mm;
  font-size: 8.5pt;
  color: var(--aqb-muted);
}
.aqb-tagline {
  margin: 1mm 0 0;
  font-size: 9.5pt;
  color: var(--aqb-muted);
}

.aqb-features {
  margin: 3mm 0 0;
  padding: 0;
  list-style: none;
}
.aqb-features li {
  position: relative;
  padding: 1.6mm 0 1.6mm 5mm;
  border-bottom: 0.4pt solid var(--aqb-line);
  font-size: 9pt;
  break-inside: avoid;
}
.aqb-features li:last-child { border-bottom: 0; }
/* Flat dot, not .aqp-features' data:-URL SVG check — a background image is a
   larger failure surface when --print-background is the only thing keeping
   it alive. */
.aqb-features li::before {
  content: "";
  position: absolute;
  top: 2.6mm;
  left: 0;
  width: 2mm;
  height: 2mm;
  border-radius: 50%;
  background: var(--aqb-accent);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.aqb-note,
.aqb-disclaimer {
  font-size: 8.5pt;
  line-height: 1.45;
  color: var(--aqb-muted);
}
.aqb-contact {
  margin-top: 5mm;
  padding: 5mm;
  border: 0.5pt solid var(--aqb-line);
  border-radius: 3mm;
  break-inside: avoid;
  page-break-inside: avoid;
}
.aqb-contact a { color: var(--aqb-ink); text-decoration: underline; }

@media print {
  .aqb-screen-only { display: none !important; }
  .aqb-doc { max-width: none; padding: 0; }
}

/* Truncation notice on a capped feature list — see SECTIONS in
   .context/scripts/content-brochure.py. Reads as a note, not a feature. */
.aqb-features li.aqb-more {
  font-style: italic;
  color: var(--aqb-muted);
}
.aqb-features li.aqb-more::before { background: transparent; }

/* ---- Cal.com inline booking embed ---------------------------------- *
 *
 * The static site has no React, so we load Cal's own embed.js and mount
 * the same `automatq/discovery` event the app uses (see moscow's
 * index.html). Cal injects an iframe that sizes itself, but it reports
 * height only after the month view paints — without a min-height the
 * container collapses to 0 and the calendar looks like a missing block
 * on first paint. The <noscript>/fallback link inside the container is
 * what shows if embed.js never loads.
 * -------------------------------------------------------------------- */
.aq-cal {
  width: 100%;
  min-height: 640px;
  overflow: hidden;
  border: 1px solid var(--aqp-line);
  border-radius: var(--aqp-radius);
  background: var(--tp-common-white);
  box-shadow: var(--aqp-shadow-soft);
}
.aq-cal iframe { width: 100% !important; border: 0; }

/* Shown until Cal mounts, and left in place for good if scripts are
   blocked. Cal *appends* its iframe rather than replacing the container's
   children, so the fallback has to be hidden explicitly or it stacks
   above the calendar. The linkReady callback removes it outright; this
   rule is the earlier, no-JS-timing-dependent half of the same job. */
.aq-cal:has(iframe) .aq-cal__fallback { display: none; }
.aq-cal__fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 640px;
  padding: 40px 24px;
  text-align: center;
  color: var(--aqp-muted);
}
.aq-cal__fallback a { color: var(--aqp-ink); text-decoration: underline; }

/* ==================================================================== *
 * Mobile pass
 *
 * Measured at a real 390px viewport, not inferred from breakpoints.
 * Layout was already sound -- zero horizontal overflow on every page --
 * so this is only about legibility and touch.
 * ==================================================================== */

@media (max-width: 767px) {
  /* These render at 10px and 11px on a phone. 26 eyebrows on products.html
     alone, carrying the product category and price. */
  .aqp-card-eyebrow { font-size: 12px; letter-spacing: 0.14em; }
  .aqp-label { font-size: 12px; }

  /* The hamburger is the primary mobile control and measured 36x36 on
     index. 44px is the smallest reliable touch target. */
  .tp-menu-bar,
  .tp-header-sidebar-btn,
  .hamburger-open-btn {
    min-width: 44px;
    min-height: 44px;
  }

  /* The search overlay's form is <form action="#"> -- template residue that
     does nothing. It was a 20x28 target sitting between the login link and
     the hamburger, so on a phone it only competed with controls that work. */
  .tp-header-search-btn { display: none; }
}

/* ==================================================================== *
 * Font Awesome replacements for glyphs no markup swap can reach
 *
 * Two icons are not in the HTML: main.css injects the FAQ accordion's
 * plus/minus via `content: "\f067"/"\f068"`, and main.js:168 appends
 * `<i class="fa-solid fa-plus">` into the mobile menu toggle at runtime.
 * Both files are pristine vendor, so these are drawn in CSS instead --
 * two bars each, no font. Everything else became inline SVG in
 * rewrite-chrome.py.
 * ==================================================================== */

/* ---- FAQ accordion: minus when open, plus when collapsed ----
   main.css paints the circular plate with the `background` SHORTHAND, at
   .tp-faq-button.collapsed::after -- specificity (0,2,1) and, being a
   shorthand, it resets background-image and background-size to initial. Any
   plain longhand here loses to it, so the bars need !important. main.css is
   vendor and cannot be edited, which is exactly when !important is the right
   tool. The plate's own colour is left to the template. */
.tp-faq-button::before,
.tp-faq-button::after {
  content: "" !important;
  font-family: inherit !important;
}
/* ::before and ::after both draw the same plate in the template, and only
   ::after carried the glyph -- so ::before is redundant once the glyph is a
   drawn shape, and stacking two plates would double the border. */
.tp-faq-button::before { display: none !important; }

/* The class is doubled to reach specificity (0,2,1). Two separate main.css
   rules sit at that level and both use the `background` SHORTHAND, which
   resets background-image: .tp-faq-button.collapsed::after, and
   .tp-service-details-faq-two .tp-faq-button:after -- the latter with
   !important, so a plain longhand loses to it however loud. Matching
   specificity and coming later is what actually wins.
   currentColor carries the right ink either way: white on the plated variant,
   black on the -two variant which deliberately has no plate. */
.tp-faq-button.tp-faq-button::after {
  background-image:
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor) !important;
  background-size: 13px 1.5px, 1.5px 13px !important;
  background-position: center center, center center !important;
  background-repeat: no-repeat !important;
}
/* Open state drops the vertical bar, so the control reads as a real toggle. */
.tp-faq-button.tp-faq-button:not(.collapsed)::after {
  background-size: 13px 1.5px, 0 0 !important;
}

/* ---- Mobile menu toggle: the runtime-injected plus ---- */
.tp-menu-close i {
  position: relative;
  display: inline-block;
  width: 18px;
  height: 18px;
  font-size: 0 !important;
}
.tp-menu-close i::before,
.tp-menu-close i::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  background: currentColor;
  transform: translate(-50%, -50%);
}
.tp-menu-close i::before { width: 13px; height: 1.5px; }
.tp-menu-close i::after  { width: 1.5px; height: 13px; }
/* Open rows lose the vertical bar, so the control reads as a real toggle
   rather than a plus that never changes. */
.tp-offcanvas-menu ul li.active > .tp-menu-close i::after { height: 0; }
