/* ==========================================================================
   Layout Variants
   ==========================================================================
   Structural options chosen at Appearance -> Customize -> Color Scheme &
   Layout Style. Each one is toggled by a class on <body> (see the
   aiap_body_classes() filter in inc/style-system.php) so templates never
   need to know which variant is active — only the CSS below does.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Header / navigation style
   -------------------------------------------------------------------------- */

/* "standard" (logo left / nav center / actions right) is the theme's
   original layout and needs no extra rules here. */

/* "centered": logo on the left, nav truly centered, actions on the right —
   using a 3-column grid instead of space-between so the nav stays centered
   on the page regardless of how wide the logo or actions block is. Desktop
   only; the off-canvas mobile nav is shared by every header style. */
@media (min-width: 861px) {
  body.header-style-centered .site-header .container {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
  }
  body.header-style-centered .site-brand { justify-self: start; }
  body.header-style-centered .main-nav { justify-self: center; }
  body.header-style-centered .header-actions { justify-self: end; }
  body.header-style-centered .nav-overlay { display: none; }
}

/* "minimal": quieter chrome — no blur/border, outline buttons, more
   letter-spacing on the logo. */
body.header-style-minimal .site-header { border-bottom: none; }
body.header-style-minimal .site-header::before { background: var(--bg); backdrop-filter: none; box-shadow: 0 1px 0 var(--border); }
body.header-style-minimal .site-header .main-nav ul { gap: 2.5rem; }
body.header-style-minimal .site-logo { letter-spacing: -.01em; }
body.header-style-minimal .site-header .btn-gradient {
  background: transparent; color: var(--text); box-shadow: none;
  border: 1px solid var(--border);
}
body.header-style-minimal .site-header .btn-gradient:hover { border-color: var(--primary-500); color: var(--primary-600); }

/* --------------------------------------------------------------------------
   Hero style (homepage only — harmless elsewhere since .hero doesn't exist there)
   -------------------------------------------------------------------------- */

/* "centered" (current design) needs no extra rules. */

/* "split": headline/CTA on one side, a decorative panel on the other —
   built from CSS only (gradient blobs + floating glass cards) so it never
   depends on an uploaded image. Markup for this variant is added by
   front-page.php only when this style is active. */
body.hero-style-split .hero { text-align: start; padding: 6rem 0 5rem; }
.hero-inner.hero-flex {
  max-width: 1200px;
  display: grid; grid-template-columns: 1.1fr .9fr; gap: 3rem; align-items: center;
}
.hero-flex .hero-copy { text-align: start; }
.hero-flex .hero-ctas,
.hero-flex .trust-row { justify-content: flex-start; }
.hero-flex .lead { margin-inline: 0; }

.hero-visual {
  position: relative; aspect-ratio: 4 / 3.2; border-radius: var(--radius-2xl);
  background: linear-gradient(135deg, var(--primary-500), var(--secondary-600));
  overflow: hidden; box-shadow: var(--shadow-soft-lg);
  /* Stacks any number of .hero-visual-card widgets (1, 2, 3+, from the
     "Homepage Hero Visual Panel" widget position, or the 2 fallback cards
     below when it's empty) top-to-bottom with room to breathe, instead of
     the old fixed 2-card absolute positioning that only worked for exactly
     two hardcoded cards. justify-content:space-between alone only spaces
     cards out when the box is taller than its content — true on desktop,
     where the aspect-ratio below keeps a fixed, roomy height, but not on
     mobile, where the panel's height instead shrinks to fit its content
     (see the "aspect-ratio: auto" mobile override further down), leaving no
     leftover space to distribute and no gap between 3+ cards. An explicit
     gap guarantees spacing either way. */
  display: flex; flex-direction: column; justify-content: space-between; gap: 1rem;
  padding: 1.75rem;
}
.hero-visual::before,
.hero-visual::after {
  content: ''; position: absolute; border-radius: 50%; filter: blur(40px); opacity: .55;
}
.hero-visual::before { width: 9rem; height: 9rem; background: #fff; top: -2.5rem; inset-inline-end: -2rem; }
.hero-visual::after { width: 7rem; height: 7rem; background: var(--dark-900); bottom: -2rem; inset-inline-start: -1.5rem; opacity: .25; }
.hero-visual-card {
  position: relative; background: rgba(255,255,255,.14); backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,.35); border-radius: var(--radius-xl);
  color: #fff; padding: .9rem 1.1rem; box-shadow: 0 20px 40px -20px rgba(0,0,0,.4);
  width: fit-content; max-width: 80%;
  align-self: flex-start; /* alternate left/right down the panel */
}
.hero-visual-card:nth-child(even) { align-self: flex-end; }
.hero-visual-card svg { width: 1.4rem; height: 1.4rem; margin-bottom: .35rem; }
.hero-visual-card strong { display: block; font-size: 1.1rem; }
.hero-visual-card span { display: block; font-size: .72rem; opacity: .85; }

@media (max-width: 960px) {
  /* Must match (or beat) the base ".hero-inner.hero-flex" selector's
     specificity (two classes) — an earlier version of this rule was just
     ".hero-flex" (one class), which is LOWER specificity than
     ".hero-inner.hero-flex", so despite being inside a matching media
     query, its display/grid-template-columns/gap always lost the cascade
     to the desktop 2-column rule above. That's why the panel kept showing
     up beside the text instead of stacking, no matter how narrow the
     screen was. */
  .hero-inner.hero-flex {
    grid-template-columns: 1fr;
    /* Was a 2-column grid with the visual panel pulled to the top via
       order:-1 and squeezed into a narrow 26rem/fixed-height box — on a
       phone that read as a small floating card overlapping the text
       instead of a clean stack. Now it's a single column, in normal
       document order (copy, then the visual panel — no more order
       override), each block taking the full row width, one below the
       other with no overlap. */
    display: flex; flex-direction: column; gap: 2.5rem;
  }
  .hero-flex .hero-visual {
    width: 100%; max-width: 26rem; margin: 0 auto;
    /* The panel's fixed aspect-ratio (set for the side-by-side desktop
       layout, where it has to line up with the copy column's height) was
       still forcing a fixed height here even after the panel switched to
       holding a variable number of cards. On a narrow screen — where cards
       stack more tightly and a longer custom label or a 3rd widget added
       to "Homepage Hero Visual Panel" needs more room — that fixed height
       was too short, and with overflow:hidden clipping the extra content,
       cards ended up drawn on top of each other instead of each getting
       its own space. Letting height follow the content here (with a floor
       so the panel doesn't go too short with only one card) fixes that. */
    aspect-ratio: auto; min-height: 14rem;
  }
  .hero-flex .hero-copy,
  .hero-flex .hero-ctas,
  .hero-flex .trust-row { text-align: center; justify-content: center; }
}

@media (max-width: 600px) {
  .hero-visual-card { max-width: 100%; align-self: stretch !important; }
}

/* "minimal": small, quiet, text-only — good for a more editorial/utility feel. */
body.hero-style-minimal .hero { padding: 3.5rem 0 3rem; background: var(--bg); }
body.hero-style-minimal .hero::before { content: none; }
body.hero-style-minimal .hero h1 { font-size: clamp(1.8rem, 4vw, 2.5rem); margin: 1rem 0 .85rem; }
body.hero-style-minimal .hero .badge-pill,
body.hero-style-minimal .hero .kbd-pill { display: none; }

/* --------------------------------------------------------------------------
   Dashboard sidebar layout
   -------------------------------------------------------------------------- */

/* "expanded" (current design) needs no extra rules. */

/* "compact": icon-only rail — labels collapse to screen-reader-only text,
   with the native `title` attribute (added in dashboard-sidebar.php)
   providing a hover tooltip for sighted mouse users. Desktop only; the
   sidebar is already an off-canvas panel on mobile, where the labels stay
   visible for easier tapping. */
@media (min-width: 901px) {
  body.dash-layout-compact .dash-sidebar { width: 4.75rem; padding-inline: .5rem; }
  body.dash-layout-compact .dash-brand { justify-content: center; padding-inline: 0; }
  body.dash-layout-compact .dash-brand > a > span:not(.icon-tile) { display: none; }
  body.dash-layout-compact .dash-nav a { justify-content: center; padding-inline: .6rem; }
  body.dash-layout-compact .dash-nav a span:not(.badge-new) {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
  }
  /* No hover-expand in "compact" — an icon has no room to show a "New"
     pill and no expanded state will ever reveal one, so the badge is
     simply hidden here rather than left as a small corner dot (which,
     unlabeled, would just read as an unexplained mark on the icon). */
  body.dash-layout-compact .dash-nav a .badge-new { display: none; }
  body.dash-layout-compact .dash-nav-label,
  body.dash-layout-compact .dash-sidebar-slot { display: none; }
}

/* "hover": same icon rail as "compact" at rest, but instead of staying
   that way, it smoothly widens to show labels the moment the mouse is
   over it, and narrows again on mouse-out — no click needed.

   First version toggled `justify-content` (center <-> flex-start) and
   the label's visibility (the screen-reader-only clip trick, on <-> off)
   the instant :hover matched, while only the sidebar's width actually
   transitioned. Since those other properties can't be animated, they
   snapped instantly while the width was still catching up over its
   0.18s, so for a moment the icon had already jumped to its expanded
   position (or the label had already appeared) inside a box that was
   still narrow — read as a messy, un-smooth pop rather than one clean
   motion. Same problem for the badge, which jumped from a pinned corner
   dot straight to an inline pill in one frame.

   Fixed by removing every property that has to snap: the icon now sits
   at the same fixed inset in both states (no justify-content toggle, so
   it never moves), the label reveals itself with its own width/opacity/
   margin transition (timed to roughly match the sidebar's), and the
   badge stays a small pinned corner dot in both states instead of
   switching layout. The only two truly instant changes left are the
   label's actual text becoming selectable/visible right as its box
   reaches zero width (unnoticeable) and the section headings/widget
   slots (dash-nav-label / dash-sidebar-slot), which are whole extra
   blocks of content, not a single label sliding open — trying to
   animate those tends to look worse, not better, so they simply show up
   once there's room. Desktop only, same reasoning as "compact": the
   sidebar is already an off-canvas panel on mobile, with no hover to
   speak of, and the full-width labels stay visible there for tapping. */
@media (min-width: 901px) {
  body.dash-layout-hover .dash-sidebar {
    width: 4.75rem; padding-inline: .5rem; overflow: hidden;
    transition: width .2s ease, box-shadow .2s ease;
  }
  body.dash-layout-hover .dash-brand { padding-inline: .4rem; }
  body.dash-layout-hover .dash-brand > a > span:not(.icon-tile) {
    display: inline-block; max-width: 0; opacity: 0; margin-inline-start: 0;
    overflow: hidden; white-space: nowrap;
    transition: max-width .2s ease, opacity .15s ease, margin-inline-start .2s ease;
  }
  /* Fixed start-side inset in both states — this is what stops the icon
     itself from jumping sideways when the sidebar widens; only the
     label to its right gains room to unfold into. */
  body.dash-layout-hover .dash-nav a { justify-content: flex-start; padding-inline: .8rem; gap: 0; }
  body.dash-layout-hover .dash-nav a span:not(.badge-new) {
    display: inline-block; max-width: 0; opacity: 0; margin-inline-start: 0;
    overflow: hidden; white-space: nowrap;
    transition: max-width .2s ease, opacity .15s ease .03s, margin-inline-start .2s ease;
  }
  /* Hidden on the collapsed icon — an unlabeled dot on a bare icon reads
     as an unexplained mark, and the point of this layout is that nothing
     shows until there's room to show it properly. Kept as a pinned
     corner spot (rather than an inline pill) so revealing it is a plain
     opacity fade with nothing else to jump — no position/layout switch
     left to snap mid-transition. */
  body.dash-layout-hover .dash-nav a .badge-new {
    position: absolute; top: .3rem; inset-inline-end: .3rem; margin-left: 0;
    opacity: 0; transition: opacity .15s ease;
  }
  body.dash-layout-hover .dash-nav-label,
  body.dash-layout-hover .dash-sidebar-slot { display: none; }

  body.dash-layout-hover .dash-sidebar:hover {
    width: 16rem; overflow: visible; box-shadow: var(--shadow-soft-lg);
  }
  body.dash-layout-hover .dash-sidebar:hover .dash-brand > a > span:not(.icon-tile) {
    max-width: 10rem; opacity: 1; margin-inline-start: .6rem;
  }
  body.dash-layout-hover .dash-sidebar:hover .dash-nav a span:not(.badge-new) {
    max-width: 10rem; opacity: 1; margin-inline-start: .75rem;
  }
  /* Only fades in once the sidebar itself is hovered open — never on the
     collapsed rail. */
  body.dash-layout-hover .dash-sidebar:hover .dash-nav a .badge-new { opacity: 1; }
  body.dash-layout-hover .dash-sidebar:hover .dash-nav-label,
  body.dash-layout-hover .dash-sidebar:hover .dash-sidebar-slot { display: block; }
}

/* --------------------------------------------------------------------------
   Card & section density
   -------------------------------------------------------------------------- */

/* "comfortable" (current spacing) needs no extra rules. */

body.section-density-compact .section { padding: 3.25rem 0; }
body.section-density-compact .card { padding: 1.15rem; border-radius: var(--radius-lg); }
body.section-density-compact .grid { gap: 1rem; }
body.section-density-compact .dash-content { padding: 1.15rem; }
body.section-density-compact .section-header { margin-bottom: 2rem; }

body.section-density-spacious .section { padding: 7rem 0; }
body.section-density-spacious .card { padding: 2.1rem; border-radius: var(--radius-2xl); }
body.section-density-spacious .grid { gap: 2rem; }
body.section-density-spacious .dash-content { padding: 2.25rem; }
body.section-density-spacious .section-header { margin-bottom: 4rem; }

/* --------------------------------------------------------------------------
   Widget-position / shortcode card shadow (Appearance -> Customize ->
   Widget Zone Layout -> "Shadow on widget-position & shortcode cards")
   -------------------------------------------------------------------------- */

/* Global off switch: strips the shadow from every widget-position card,
   [aiap_widget_area] box, and account/stat shortcode card at once — both at
   rest and on hover — while leaving the border/radius/padding untouched. */
body.aiap-no-widget-shadow .dash-widget-item,
body.aiap-no-widget-shadow .dash-widget-item.card-hover:hover,
body.aiap-no-widget-shadow .stat-card,
body.aiap-no-widget-shadow .stat-card.card-hover:hover {
  box-shadow: none;
}

/* A widget added from another plugin (a gallery, a form builder, a review
   box, etc.) often draws its own shadow with its own CSS rather than using
   the theme's .card class, so the rule above alone won't touch it. This
   blanket rule reaches every element inside a widget-position box — the
   plugin's own wrapper included — and needs !important since we have no
   control over that plugin's selector specificity or load order. */
body.aiap-no-widget-shadow .dash-widget-item,
body.aiap-no-widget-shadow .dash-widget-item * {
  box-shadow: none !important;
}

/* The same, but for a shortcode (the theme's own, or a plugin's) typed
   directly into a page or post's content rather than added as a widget in
   a position — .page-content wraps every page/post/dashboard-page body, so
   this reaches it without needing to find and wrap each shortcode by hand
   with [aiap_clean_box] every time. */
body.aiap-no-widget-shadow .page-content,
body.aiap-no-widget-shadow .page-content * {
  box-shadow: none !important;
}

/* Per-instance override: a single [aiap_widget_area shadow="no"] call (or
   aiap_widget_position($id, false) in a template) removes the shadow from
   just that position's widgets — including ones drawn by another plugin's
   own CSS — regardless of the global setting above. */
.aiap-no-card-shadow .card,
.aiap-no-card-shadow .card.card-hover:hover,
.aiap-no-card-shadow .dash-widget-item,
.aiap-no-card-shadow .dash-widget-item * {
  box-shadow: none !important;
}

/* [aiap_widget_area shadow="yes"] forces the shadow back on for one
   position even while the global setting above is off. */
body.aiap-no-widget-shadow .aiap-force-card-shadow .card.card-hover:hover {
  box-shadow: var(--shadow-soft-lg);
}

/* [aiap_clean_box shadow="no"] removes the shadow from one shortcode's
   output inside a page/post — including a plugin's own shadow — and
   [aiap_clean_box shadow="yes"] forces it back on for one spot even while
   the global setting above has switched every other shortcode in that page
   off. Both need to win against the blanket .page-content rule above, so
   they're repeated here, after it, at the same !important weight. */
.aiap-clean-box.aiap-clean-no-shadow,
.aiap-clean-box.aiap-clean-no-shadow * {
  box-shadow: none !important;
}
/* Draws the shadow on the wrapper <div> itself rather than requiring a
   nested .card — a shortcode from another plugin never has that class, so
   requiring it meant shadow="yes" silently did nothing for anything but the
   theme's own stat/account shortcodes. This still isn't necessarily the
   exact shadow the plugin drew before the global setting stripped it (that
   value is gone once removed with !important — it can't be recovered), but
   it reliably puts *a* soft shadow back around the whole boxed shortcode. */
.aiap-clean-box.aiap-clean-force-shadow {
  box-shadow: var(--shadow-soft-lg) !important;
}

/* --------------------------------------------------------------------------
   Dashboard card border (Appearance -> Customize -> Widget Zone Layout ->
   "Border on dashboard cards")
   -------------------------------------------------------------------------- */

/* Scoped to body.aiap-app — the dashboard, its widget positions/shortcode
   boxes, and the login/register/forgot-password screens — so it never
   touches the public-site cards (feature grid, blog list, etc.). Reaches
   every descendant with !important for the same reason as the shadow rule
   above: a widget from another plugin may draw its own border, not the
   theme's. */
body.aiap-app.aiap-no-dashboard-border .card,
body.aiap-app.aiap-no-dashboard-border .card *,
body.aiap-app.aiap-no-dashboard-border .auth-card,
body.aiap-app.aiap-no-dashboard-border .auth-card * {
  border: none !important;
}

/* Per-instance override, same idea as the shadow one above: a single
   [aiap_widget_area border="no"/"yes"] call (or aiap_widget_position()'s
   third argument) beats the global setting for just that position. */
.aiap-no-card-border .card,
.aiap-no-card-border .card *,
.aiap-no-card-border .dash-widget-item,
.aiap-no-card-border .dash-widget-item * {
  border: none !important;
}
body.aiap-app.aiap-no-dashboard-border .aiap-force-card-border .card {
  border: 1px solid var(--border) !important;
}

/* --------------------------------------------------------------------------
   [aiap_clean_box] border (the shadow half of this shortcode's rules lives
   above, next to the global shadow switch, since it has to win a tie-break
   against the .page-content blanket rule there). border="no" strips the
   border from anything inside — a plugin's own included — even when the
   dashboard-border setting above is on, or the wrapped content isn't in the
   dashboard at all. See inc/widget-positions.php for the shortcode itself.
   -------------------------------------------------------------------------- */
.aiap-clean-box.aiap-clean-no-border,
.aiap-clean-box.aiap-clean-no-border * {
  border: none !important;
}
.aiap-clean-box.aiap-clean-force-border,
.aiap-clean-box.aiap-clean-force-border .card {
  border: 1px solid var(--border) !important;
}

/* --------------------------------------------------------------------------
   [aiap_widget_area float="yes"] — a gentle, continuous up/down float on
   every widget in that one position, staggered per widget so they don't all
   bob in sync. Opt-in per position (default off); respects prefers-reduced-
   motion, since this is pure decoration with no information carried in the
   movement.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .aiap-widget-float > .dash-widget-item {
    animation: aiap-widget-float 4.5s ease-in-out infinite;
  }
  .aiap-widget-float > .dash-widget-item:nth-child(2n) {
    animation-delay: .6s;
    animation-duration: 5.2s;
  }
  .aiap-widget-float > .dash-widget-item:nth-child(3n) {
    animation-delay: 1.1s;
    animation-duration: 4.8s;
  }
  @keyframes aiap-widget-float {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-10px); }
  }

  /* --------------------------------------------------------------------
     Homepage Hero Visual Panel — special effects (Appearance -> Customize
     -> Color Scheme & Layout Style -> "Hero visual panel — special
     effect"). One .aiap-hero-fx-* class on .hero-visual per choice; "none"
     adds no class at all, so the panel just keeps today's static look.
     -------------------------------------------------------------------- */

  /* "float": reuses the same keyframes as [aiap_widget_area float="yes"]
     above, applied to .hero-visual-card instead of .dash-widget-item —
     the Split hero uses that class for both its two default stat cards
     and any widgets added to the "Homepage Hero Visual Panel" position. */
  .aiap-hero-fx-float > .hero-visual-card {
    animation: aiap-widget-float 4.5s ease-in-out infinite;
  }
  .aiap-hero-fx-float > .hero-visual-card:nth-child(2n) {
    animation-delay: .6s;
    animation-duration: 5.2s;
  }
  .aiap-hero-fx-float > .hero-visual-card:nth-child(3n) {
    animation-delay: 1.1s;
    animation-duration: 4.8s;
  }

  /* "glow": the panel's own drop-shadow slowly breathes into a soft
     colored halo and back — a premium SaaS-dashboard touch. Keeps the
     panel's normal elevation shadow at both ends so it never looks like
     the shadow just disappears. */
  .aiap-hero-fx-glow {
    animation: aiap-hero-glow 3.6s ease-in-out infinite;
  }
  @keyframes aiap-hero-glow {
    0%, 100% { box-shadow: var(--shadow-soft-lg), 0 0 0 0 rgba(var(--primary-rgb), 0); }
    50%      { box-shadow: var(--shadow-soft-lg), 0 0 55px 14px rgba(var(--primary-rgb), .38); }
  }

  /* "shimmer": a diagonal band of light glides across the panel on a
     loop, like light catching glass — a common "premium" cue in modern
     product UI. Clipped to the panel's rounded corners by its existing
     overflow:hidden. */
  /* .hero-visual::after is already used for one of the panel's two
     decorative blur blobs (width/height/position/opacity/filter/
     border-radius, declared with .before near the top of this file), and
     since a class selector + pseudo-element always has the same
     specificity, those un-overridden properties were still winning here —
     the gradient sweep was inheriting that rule's filter:blur(40px),
     opacity:.55 and border-radius:50%, which smeared it into an invisible
     haze. Every property that blob rule sets has to be explicitly reset
     back to normal below so the sweep actually renders as a crisp light
     band instead. */
  .aiap-hero-fx-shimmer { position: relative; }
  .aiap-hero-fx-shimmer::after {
    content: ''; position: absolute; inset: 0; z-index: 1;
    width: auto; height: auto; border-radius: 0; filter: none; opacity: 1;
    background: linear-gradient(115deg, transparent 40%, rgba(255,255,255,.4) 50%, transparent 60%);
    background-repeat: no-repeat;
    background-size: 250% 250%;
    animation: aiap-hero-shimmer 3.2s ease-in-out infinite;
    pointer-events: none;
  }
  @keyframes aiap-hero-shimmer {
    0%   { background-position: 140% 0; }
    100% { background-position: -40% 0; }
  }

  /* "drift": the first version of this only moved the panel's two corner
     blur blobs, but those sit mostly OUTSIDE the panel by design (e.g.
     top:-2.5rem, so most of the blob is already clipped by
     .hero-visual's overflow:hidden) — animating something that's already
     mostly cropped out of view is why moving it further didn't read as any
     visible change. The actual, reliably-visible part of "drift" is now
     the gradient itself slowly panning behind everything (the same trick
     used for animated "aurora" gradients): the blobs still drift too, as a
     bonus layered on top, but they're no longer the only thing carrying
     the effect. */
  .aiap-hero-fx-drift {
    background-size: 220% 220%;
    animation: aiap-hero-drift-bg 12s ease-in-out infinite;
  }
  @keyframes aiap-hero-drift-bg {
    0%, 100% { background-position: 0% 30%; }
    50%      { background-position: 100% 70%; }
  }
  .aiap-hero-fx-drift::before { animation: aiap-hero-drift-a 9s ease-in-out infinite; }
  .aiap-hero-fx-drift::after  { animation: aiap-hero-drift-b 11s ease-in-out infinite; }
  @keyframes aiap-hero-drift-a {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50%      { transform: translate(-2.75rem, 2.5rem) scale(1.2); }
  }
  @keyframes aiap-hero-drift-b {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50%      { transform: translate(2.75rem, -2.5rem) scale(1.2); }
  }

  /* "scroll": unlike "drift" (which eases back and forth, so it visibly
     changes direction), this pans the background in ONE direction only,
     forever, with the end of the image connecting straight into its own
     start ("پشت هم" — back-to-back), no jump when it loops.

     v1 animated background-position from 0% to -100% on a single
     repeating background — jumped on most screens, because percentage
     background-position isn't measured against the tile's own width, so
     -100% is almost never exactly one whole tile.

     v2 tried two full-panel "cover" copies sliding side by side — that
     avoided the jump but CROPPED the image (each copy fills the panel
     like a photo crop) and, because "cover" crops differ from a true
     repeating tile, looked like two overlapping photos rather than one
     continuous strip. Not what "کل تصویر، سر و ته وصل" asked for.

     v3 (this version) shows the image uncropped at its own aspect ratio
     (background-size: auto 100%, same as a plain, non-scrolling
     background would) and tiles it edge-to-edge with background-repeat,
     which is what actually makes the end of one copy touch the start of
     the next. The only remaining piece is sliding it by EXACTLY one
     tile's width so the loop restart is invisible — that width depends
     on the real image's pixel aspect ratio, which isn't knowable from
     CSS alone, so assets/js/main.js measures it once the image has
     loaded and writes it into the --aiap-scroll-tile custom property
     used below. Without JS (or before it runs) this still falls back to
     a -100% shift, which scrolls fine and only risks a soft seam instead
     of a hard jump until the exact value is set. */
  .aiap-hero-fx-scroll {
    background-repeat: repeat-x;
    background-size: auto 100%;
    animation: aiap-hero-scroll 18s linear infinite;
  }
  @keyframes aiap-hero-scroll {
    from { background-position-x: 0; }
    to   { background-position-x: calc(-1 * var(--aiap-scroll-tile, 100%)); }
  }
}
