/* ==========================================================================
   Shared motion system for every page using base_aziza.html — the stylesheet
   half. Its script half is static/js/aziza-motion.js; neither does anything
   useful without the other, and base_aziza.html loads both together.

   This used to be an inline <style> inside a template included at the end of
   every body, which meant ~15KB of CSS and JS re-sent, uncompressed and
   uncacheable, on every page view of every template. As a static file it goes
   through CompressedManifestStaticFilesStorage: content-hashed, compressed,
   and fetched once per visitor instead of once per page.

   TWO THINGS TO KNOW ABOUT WHERE THIS FILE SITS IN THE CASCADE
   --------------------------------------------------------------------------
   It is linked in <head> BEFORE the shell's own hand-written <style>, which
   is deliberate: the shell owns the tokens this file reads and the
   reduced-motion clamp that overrules it, and a layer that overrules another
   should be able to do it by being later rather than by shouting !important.

   That is also a change from the old end-of-body position, where this
   stylesheet came after every child template's {% block extra_css %} and so
   won every tie against it. Nothing in the repo actually contested one — no
   other template names .aziza-motion-scene, [data-motion-reveal] or
   .aziza-motion-card — but a child template CAN now override these rules at
   equal specificity, and that is the right way round.

   Timing, easing and every colour below come from the token layer at the top
   of base_aziza.html's <style> block.
   ========================================================================== */

/* Timing, easing and every colour in this stylesheet come from the token
   layer at the top of base_aziza.html's <style> block. Two consequences
   worth knowing before editing here:

     * there are no rgba() literals left below — a colour that needs to
       differ between dark and light belongs in that token pair, not in a
       second copy of this rule;
     * anything already in the palette (accent, accent-dark, violet) is read
       straight off it as rgb(var(--aziza-x) / alpha) rather than getting a
       motion token of its own. */
.aziza-motion-scene { position: relative; isolation: isolate; }

.aziza-motion-scene::before {
  content: "";
  position: absolute;
  z-index: 0;
  top: -1px;
  left: 12%;
  width: 76%;
  height: 1px;
  pointer-events: none;
  opacity: 0;
  transform: scaleX(.2);
  background-image: var(--aziza-motion-rule);
  transition: opacity .8s ease, transform 1.25s var(--aziza-ease-out);
}

.aziza-motion-scene.aziza-scene-visible::before {
  opacity: .72;
  transform: scaleX(1);
}

.aziza-motion-scene > * { position: relative; z-index: 1; }

.aziza-motion-scene.aziza-motion-ready [data-motion-reveal] {
  --motion-x: 0px;
  --motion-y: 44px;
  --motion-lift: 0px;
  --motion-rx: 0deg;
  --motion-ry: 0deg;
  opacity: 0;
  filter: blur(11px);
  transform: perspective(1100px)
    translate3d(var(--motion-x),calc(var(--motion-y) + var(--motion-lift)),0)
    rotateX(var(--motion-rx)) rotateY(var(--motion-ry)) scale(.975);
  transform-origin: 50% 60%;
  transition:
    opacity .85s var(--aziza-ease-out),
    filter .85s var(--aziza-ease-out),
    transform .9s var(--aziza-ease-out),
    border-color .3s ease,
    background-color .3s ease,
    box-shadow .35s ease;
  transition-delay: var(--motion-delay,0ms);
  /* will-change is deliberately NOT declared here. As a static rule it held
     every reveal target on the page in its own compositor layer for the
     whole session — on a dashboard that is forty textures in GPU memory,
     thirty-nine of them with nothing animating. The script hands it out to
     the elements within about a viewport and takes it back after. */
}

.aziza-motion-scene.aziza-motion-ready [data-motion-reveal].aziza-motion-visible {
  --motion-x: 0px;
  --motion-y: 0px;
  opacity: 1;
  filter: blur(0);
  transform: perspective(1100px)
    translate3d(0,var(--motion-lift),0)
    rotateX(var(--motion-rx)) rotateY(var(--motion-ry)) scale(1);
}

/* THE AREA CAP.
   Set by the script on anything whose bounding box is bigger than roughly
   one and a fifth viewports — a results column, a whole dashboard rail.
   Blur is a per-pixel filter and transform forces the element into its own
   texture, so a full-page-height element revealing this way is one enormous
   bitmap for the compositor to build, upload and throw away. Such a thing
   reveals on opacity alone; at that size nobody can see the 44px slide it
   is giving up anyway.

   Two selectors, not one: the second (0,5,0) also outranks the visible rule
   regardless of where either lands in the file. */
.aziza-motion-scene.aziza-motion-ready [data-motion-reveal][data-motion-plain],
.aziza-motion-scene.aziza-motion-ready [data-motion-reveal][data-motion-plain].aziza-motion-visible {
  filter: none;
  transform: none;
}

/* THE FOCUS ESCAPE HATCH.
   A reveal target starts at opacity 0 and can sit there for the better part
   of a second. Tab into it before it reveals — a filter field, a card's
   link — and the focus ring is on something the reader cannot see, which is
   WCAG 2.4.7 and 2.4.11 failing, with or without any of the motion above.
   Focus therefore ends the animation outright rather than shortening it.

   :has() sits in its OWN rule on purpose: a browser that does not know the
   pseudo-class throws away the whole selector list it appears in, and it
   would have taken the plain :focus-visible hatch — the one that matters
   most — down with it. */
.aziza-motion-scene.aziza-motion-ready [data-motion-reveal]:focus-visible {
  opacity: 1;
  filter: none;
  transform: none;
  transition: none;
}

.aziza-motion-scene.aziza-motion-ready [data-motion-reveal]:has(:focus-visible) {
  opacity: 1;
  filter: none;
  transform: none;
  transition: none;
}

/* OPT-IN LISTS — data-motion-list.
   Held here rather than in the reveal rules above because a list item is not
   a card: opacity and 10px of travel, and NEVER blur. Thirty small text nodes
   under a blur are thirty separate filter textures for an effect nobody can
   see at that size.

   Everything below hangs off [data-motion-list-item], which only the script
   writes, and only onto the first eight children. Script blocked and nothing
   is hidden; the ninth item onwards is never hidden either. The delay is
   capped in the script at the same three steps the reveal engine uses, so a
   twelve-line pricing checklist finishes in 210ms rather than running for a
   second. */
[data-motion-list] > [data-motion-list-item] {
  opacity: 0;
  transform: translate3d(0, 10px, 0);
  transition:
    opacity var(--aziza-dur-3) var(--aziza-ease-out),
    transform var(--aziza-dur-3) var(--aziza-ease-out);
  transition-delay: calc(var(--motion-list-index, 0) * var(--aziza-stagger));
}

/* Two ways in: the list sits inside something the observer reveals (a plan
   card, a scorecard panel), or — when nothing above it was a reveal target —
   the list is the reveal target itself. */
.aziza-motion-visible [data-motion-list] > [data-motion-list-item],
[data-motion-list].aziza-motion-visible > [data-motion-list-item] {
  opacity: 1;
  transform: none;
}

/* The same hatch the reveal targets get, for the same reason and with more
   need of it: this is the one pass that multiplies reveal targets containing
   links, and a staggered <li> holding an invisible focused link is WCAG 2.4.7
   failing. :has() stays in its own rule — a browser that does not know the
   pseudo-class discards the entire selector list it appears in, and it would
   take the plain hatch down with it. */
[data-motion-list] > [data-motion-list-item]:focus-visible {
  opacity: 1;
  transform: none;
  transition: none;
}

[data-motion-list] > [data-motion-list-item]:has(:focus-visible) {
  opacity: 1;
  transform: none;
  transition: none;
}

/* THE MEASUREMENT PRIMITIVES.
   Their script half is the second IIFE in static/js/aziza-motion.js; what is
   here is the resting state, which is also the finished, correct value. Both
   of these render right with the script blocked entirely — that is the whole
   contract.

   A bar keeps the width the server stamped on it and is scaled from zero up
   to it — one composited property, no layout, and nothing here owns the
   number. That last part is the point: an earlier draft moved the value into
   a custom property and computed the width from it, which meant a stylesheet
   that failed to load rendered every match bar on the board at 100%. This
   file has one job on a meter, and it is three words long.

   Width itself is never ANIMATED. tailwind.config.js sets important:true, so
   .w-* emits width:… !important and would beat anything this file could say,
   and animating width relayouts the subtree on every frame. */
[data-aziza-meter]:not([data-aziza-meter="ring"]) {
  transform-origin: left center;
}

/* A ring takes the same 0-100 number plus its own circumference, and the arc
   is arithmetic from there. Doing it here rather than in the template is not
   tidiness: every ring on this site currently computes its own dash maths in
   {% widthratio %}, and the legacy one on the job-seeker dashboard got it
   wrong for months — it put the raw percentage into stroke-dasharray against
   a 251 circumference, so a 40%-complete profile drew 16% of the circle.
   There is one place to be wrong now, and it is under test.

   Full dashoffset is an empty ring, which is where the script starts it. */
[data-aziza-meter="ring"] {
  /* px rather than a bare number, and deliberately: inside a viewBox one px
     IS one user unit, and a length is unambiguous to a parser in a way that a
     unitless calc() result is not. A ring whose dashoffset failed to parse
     falls back to 0 — a full circle — which would report every profile as
     100% complete. Not a failure mode worth saving four characters for. */
  stroke-dasharray: calc(var(--aziza-meter-length, 251) * 1px);
  stroke-dashoffset: calc(
    var(--aziza-meter-length, 251) * 1px * (100 - var(--aziza-meter-value, 100)) / 100
  );
}

/* THE SCORE RING — templates/components/aziza/score_ring.html.
   A component rather than motion, and it lives here anyway, because it is a
   meter with a shape: it consumes both primitives above and would otherwise
   need a third stylesheet on the same load path to say twenty lines.

   Everything inside inherits `color` from the wrapper, so the tone is set in
   exactly one place and the component needs no dark variant — the arc is
   currentColor and the track is currentColor at low alpha, which is correct
   on the dark shell and correct again on the employer surface, whose whole
   neutral ramp inverts under .aziza-employer. */
.aziza-score-ring {
  position: relative;
  flex-shrink: 0;
  width: var(--aziza-ring-size, 96px);
  height: var(--aziza-ring-size, 96px);
  color: rgb(var(--aziza-accent));
}

.aziza-score-ring[data-tone="success"] { color: rgb(var(--aziza-success)); }
.aziza-score-ring[data-tone="warn"] { color: rgb(var(--aziza-warn)); }
/* --aziza-danger is declared in base_aziza.html's <style>, not in the palette
   source: see the block there. */
.aziza-score-ring[data-tone="danger"] { color: rgb(var(--aziza-danger)); }

.aziza-score-ring__dial {
  width: 100%;
  height: 100%;
  display: block;
}

.aziza-score-ring__track {
  stroke: currentColor;
  stroke-width: 7;
  opacity: .16;
}

.aziza-score-ring__arc {
  stroke: currentColor;
  stroke-width: 7;
}

/* The numeral inherits the tone too, and safely: tailwind.input.css re-picks
   every semantic token against paper for the light theme — success from
   #22c55e to #15803d, warn from #eab308 to #a16207 — precisely because the
   -500 shades fail AA as text on white. Inheriting is therefore the one
   choice that is correct in both themes without this file naming a colour. */
.aziza-score-ring__value {
  position: absolute;
  inset: 0;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: inherit;
  font-weight: 700;
  font-size: calc(var(--aziza-ring-size, 96px) * .26);
  line-height: 1;
}

/* The counting numeral. The script replaces the element's text with these
   three spans: the real string for screen readers, a hidden copy of it
   holding the layout open, and the ticker absolutely positioned over the top
   so nothing reflows while it runs. */
.aziza-count-live {
  position: relative;
  display: inline-block;
  font-variant-numeric: tabular-nums;
}

.aziza-count-ghost { visibility: hidden; }

.aziza-count-value {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
}

/* Not .sr-only: this file must not depend on a Tailwind utility surviving in
   the hand-committed build artifact. */
.aziza-count-reader {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
}

.aziza-motion-card {
  position: relative;
  transform-style: preserve-3d;
}

.aziza-motion-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background-image: radial-gradient(260px circle at var(--motion-glow-x,50%) var(--motion-glow-y,50%),var(--aziza-motion-glow-a),var(--aziza-motion-glow-b) 36%,transparent 72%);
  /* The blend has to follow the theme, not just the colours. screen against
     a #ffffff card returns #ffffff for every input, so on paper this glow —
     the signature of the whole hover — was not faint, it was absent. The
     token is `screen` in dark and `multiply` in light. */
  mix-blend-mode: var(--aziza-motion-blend);
  transition: opacity .3s ease;
}

.aziza-motion-card:hover {
  --motion-lift: -7px;
  box-shadow: 0 24px 68px var(--aziza-motion-shadow), 0 0 0 1px var(--aziza-motion-ring);
}

.aziza-motion-card:hover::after { opacity: 1; }

[data-motion-section="process"] .aziza-step-node {
  position: relative;
  overflow: visible;
}

[data-motion-section="process"] .aziza-step-node::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  border: 1px solid rgb(var(--aziza-accent) / .52);
  opacity: 0;
}

/* Gated on the scene, not just on the card: an infinite animation on a
   section the reader scrolled past ten screens ago still costs a frame
   every frame. The observer that owns .aziza-scene-visible is already
   running, so this is free. The pulse rests at opacity 0, which is also
   where its keyframes start and end, so stopping and restarting it is
   invisible. */
[data-motion-section="process"].aziza-scene-visible .aziza-motion-visible .aziza-step-node::after {
  animation: azizaStepPulse 2.8s ease-out calc(var(--step-index,0) * .22s) infinite;
}

/* The featured plan's breathing glow.

   This used to animate box-shadow on the card itself. A 64px shadow is not
   a compositable property: every frame re-rastered the card's entire
   bounding box, forever, on the main thread. The shadow is now static and
   sits on a pseudo-element whose OPACITY animates instead — that the
   compositor can run on its own. Same picture, none of the raster.

   ::before and not ::after, because .aziza-motion-card already owns ::after
   for the pointer glow and the featured card is always both (it matches
   /border/, so the engine gives it the card class too). */
[data-motion-section="pricing"] .aziza-featured-card { position: relative; }

[data-motion-section="pricing"] .aziza-featured-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  box-shadow: 0 18px 64px rgb(var(--aziza-accent-dark) / .20), 0 0 34px rgb(var(--aziza-accent) / .10);
}

[data-motion-section="pricing"].aziza-scene-visible .aziza-featured-card::before {
  animation: azizaFeaturedPlan 4s ease-in-out infinite;
}

[data-motion-section="testimonials"] .aziza-motion-card blockquote::before {
  content: "\201C";
  display: block;
  height: 28px;
  margin-bottom: 10px;
  font: 700 42px/1 Georgia,serif;
  color: var(--aziza-motion-quote);
  transition: color .3s ease, transform .3s ease;
}

[data-motion-section="testimonials"] .aziza-motion-card:hover blockquote::before {
  color: var(--aziza-motion-quote-hi);
  transform: translateY(-3px) scale(1.08);
}

[data-motion-section="final"]::after {
  content: "";
  position: absolute;
  z-index: 0;
  left: 50%;
  top: 50%;
  width: min(760px,90vw);
  height: 300px;
  pointer-events: none;
  /* Rest state is deliberately the animation's own 0%/100% frame, so gating
     the animation below starts and stops it without a step. */
  opacity: .55;
  transform: translate(-50%,-50%) scale(.92);
  background-image: radial-gradient(ellipse,rgb(var(--aziza-accent) / .12),rgb(var(--aziza-violet) / .045) 45%,transparent 72%);
}

[data-motion-section="final"].aziza-scene-visible::after {
  animation: azizaFinalBreathe 5s ease-in-out infinite;
}

@keyframes azizaStepPulse {
  0% { opacity: .55; transform: scale(1); }
  72%,100% { opacity: 0; transform: scale(1.48); }
}

/* Opacity only — the shadow it fades in is declared once, statically, on
   the pseudo-element above. */
@keyframes azizaFeaturedPlan {
  0%,100% { opacity: 0; }
  50% { opacity: 1; }
}

@keyframes azizaFinalBreathe {
  0%,100% { opacity: .55; transform: translate(-50%,-50%) scale(.92); }
  50% { opacity: 1; transform: translate(-50%,-50%) scale(1.08); }
}

@media (max-width:700px) {
  .aziza-motion-scene.aziza-motion-ready [data-motion-reveal],
  .aziza-motion-scene.aziza-motion-ready [data-motion-reveal].aziza-motion-visible {
    --motion-x: 0px;
    --motion-lift: 0px;
    --motion-rx: 0deg;
    --motion-ry: 0deg;
  }
}

@media (prefers-reduced-motion:reduce) {
  .aziza-motion-scene.aziza-motion-ready [data-motion-reveal] {
    opacity: 1;
    filter: none;
    transform: none;
    transition: none;
    /* Nothing here moves, so nothing here needs a compositor layer. Without
       this the reader who asked for less motion was the one paying for the
       most GPU memory: every reveal target promoted, none of them animating. */
    will-change: auto;
  }
  /* The list items' hidden state comes from a rule keyed on an attribute the
     script writes, and the script writes it whether or not motion is reduced
     — the tagging pass runs before that check, because the reveal engine's
     own reduced path still needs the elements marked. So the items must be
     released here explicitly rather than left to the class that releases
     them. */
  [data-motion-list] > [data-motion-list-item] {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* These selectors match the gated ones above character for character on
     purpose. They used to be shorter — and therefore less specific than the
     rules they were meant to cancel, so they lost, and only the shell's
     !important clamp was actually stopping these animations. Equal
     specificity, later in the file, wins on its own. */
  .aziza-motion-scene::before,
  [data-motion-section="process"].aziza-scene-visible .aziza-motion-visible .aziza-step-node::after,
  [data-motion-section="pricing"].aziza-scene-visible .aziza-featured-card::before,
  [data-motion-section="final"].aziza-scene-visible::after { animation: none; }
  .aziza-motion-card:hover { box-shadow: none; }
}

/* ==========================================================================
   Aziza Motion — Extra Audio Wave, Floating, and Pulsing Motion Classes
   ========================================================================== */

/* Audio Wave Visualizer Animation */
.aziza-wave-container {
  display: inline-flex;
  align-items: flex-end;
  gap: 3px;
  height: 24px;
}

.aziza-wave-bar {
  width: 3px;
  height: 100%;
  background: rgb(var(--aziza-accent));
  border-radius: 99px;
  transform-origin: bottom;
  animation: azizaWavePulse 1.2s ease-in-out infinite;
}

.aziza-wave-bar:nth-child(1) { animation-delay: 0.1s; }
.aziza-wave-bar:nth-child(2) { animation-delay: 0.3s; }
.aziza-wave-bar:nth-child(3) { animation-delay: 0.2s; }
.aziza-wave-bar:nth-child(4) { animation-delay: 0.4s; }
.aziza-wave-bar:nth-child(5) { animation-delay: 0.15s; }

@keyframes azizaWavePulse {
  0%, 100% { transform: scaleY(0.25); }
  50% { transform: scaleY(1); }
}

/* Float Motion */
.aziza-float {
  animation: azizaFloat 4s ease-in-out infinite;
}

@keyframes azizaFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8px); }
}

/* Pulsing Radial Aura Glow */
.aziza-pulse-glow {
  position: relative;
}

.aziza-pulse-glow::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: inherit;
  background: rgb(var(--aziza-accent) / 0.25);
  z-index: -1;
  animation: azizaAuraPulse 2s cubic-bezier(0, 0, 0.2, 1) infinite;
}

@keyframes azizaAuraPulse {
  0% { transform: scale(1); opacity: 0.8; }
  100% { transform: scale(1.35); opacity: 0; }
}

/* Interactive Card Motion Elevation */
.aziza-motion-lift {
  transition: transform 0.3s var(--aziza-ease-out), box-shadow 0.3s var(--aziza-ease-out), border-color 0.3s ease;
}

.aziza-motion-lift:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 32px -8px rgba(0, 0, 0, 0.3), 0 0 0 1px rgb(var(--aziza-accent) / 0.2);
}

