/* LocalStar Systems: motion.
   The system is a loop and the product is a review, so the page moves like that: things arrive calmly, the gold
   rule draws and catches the light once, the loop draws itself, and five stars light one after another the way a
   Google review lands. Opacity, transform, masks and strokes only. No bounce anywhere, nothing on the logo.

   Everything in here waits for html.ls-motion, which the one-line script in <head> sets when the visitor has not
   asked for reduced motion. Without it (no script, reduced motion, an old browser) every element sits in its final
   state, so nothing is ever hidden from someone who cannot see it move. If site.js fails to load, the head script
   lifts the class again after three seconds. */

:root {
  --ease: cubic-bezier(.2, .7, .2, 1);
  --ease-glide: cubic-bezier(.22, .61, .36, 1);
  --ease-reveal: cubic-bezier(.16, 1, .3, 1);
  --dur-reveal: 900ms;
  --glint: color-mix(in srgb, var(--ivory) 80%, var(--gold));
}

/* ---------- 1. arrivals: rise 14px and fade, staggered by --d (set by site.js among things arriving together).
   An animation rather than a transition, so once it ends each element's own transitions and hovers are back. ---------- */
.ls-motion .rv:not(.is-in) { opacity: 0; transform: translateY(14px); }
.ls-motion .rv.is-in { animation: ls-fade 700ms var(--ease-glide) var(--d, 0ms) backwards, ls-rise var(--dur-reveal) var(--ease-reveal) var(--d, 0ms) backwards; }
@keyframes ls-fade { from { opacity: 0; } }
@keyframes ls-rise { from { transform: translateY(14px); } }

/* ---------- 2. the section rule draws from the eyebrow, then a glint runs along it once (on navy, where light can catch) ---------- */
.ls-sec-head .ls-rule i { position: relative; overflow: visible; }
.ls-sec-head .ls-rule i::after { content: ""; position: absolute; top: -3px; left: 0; width: 100%; height: 7px; background: radial-gradient(13px 3.5px at calc(100% - 13px) 50%, var(--glint), transparent); transform: translateX(-100%); opacity: 0; pointer-events: none; }
[data-theme="ivory"] .ls-sec-head .ls-rule i::after { content: none; }
.ls-motion .ls-sec-head .ls-rule i { transform: scaleX(0); transform-origin: left center; }
.ls-motion .ls-sec-head.is-in .ls-rule i { transform: none; transition: transform 1.4s var(--ease-reveal) calc(var(--d, 0ms) + 150ms); }
.ls-motion .ls-sec-head.is-in .ls-rule i::after { animation: ls-glint 1.8s var(--ease-glide) calc(var(--d, 0ms) + 1.3s); }
@keyframes ls-glint { 0% { transform: translateX(-100%); opacity: 0; } 12% { opacity: 1; } 75% { opacity: .9; } 100% { transform: translateX(calc(-30% + 26px)); opacity: 0; } }

/* ---------- 3. the hero: the ring turns once into place behind the phone, then rests; the phone plays the front desk at work.
   The ring and hint are laid out in site.css; the translate in ls-turn-in must match the ring's transform there. ---------- */
.ls-motion .ls-hero-ring { animation: ls-turn-in 4.8s var(--ease-glide) both; }
@keyframes ls-turn-in { from { transform: translate(-50%, -50%) rotate(-30deg); } to { transform: translate(-50%, -50%) rotate(0deg); } }
.ls-motion .ls-hero-hint i { animation: ls-nudge 2.6s var(--ease-glide) 2.4s 3; }
@keyframes ls-nudge { 0%, 100% { transform: translateY(-2px); opacity: 1; } 50% { transform: translateY(5px); opacity: .55; } }

/* the thread: each event arrives at its time (--t, set in the markup); the thread glides up as the job-done messages arrive */
.ls-motion .ls-phone .ls-screen { height: 580px; }   /* fixed while the scene plays, so the thread can scroll; it grows to fit without motion */
.ls-motion .ls-phone .ls-ev { opacity: 0; transform: translateY(8px); }
.ls-motion .ls-phone.is-play .ls-ev { animation: ls-ev-in 600ms var(--ease-reveal) var(--t, 0s) both; }
@keyframes ls-ev-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.ls-motion .ls-phone.is-play .ls-events { animation: ls-thread 800ms cubic-bezier(.45, 0, .2, 1) var(--scroll-at, 4.45s) both; }
@keyframes ls-thread { to { transform: translateY(var(--scroll-by, -104px)); } }
/* the review: five stars light one after another, and the row warms once; the only glow on the page */
.ls-motion .ls-phone .ls-review .ls-stars svg { opacity: .15; transform: scale(.6); }
.ls-motion .ls-phone.is-play .ls-review .ls-stars svg { animation: ls-star-in 700ms var(--ease-reveal) both; }
.ls-motion .ls-phone.is-play .ls-review .ls-stars svg:nth-child(1) { animation-delay: var(--stars-at, 6.8s); }
.ls-motion .ls-phone.is-play .ls-review .ls-stars svg:nth-child(2) { animation-delay: calc(var(--stars-at, 6.8s) + .14s); }
.ls-motion .ls-phone.is-play .ls-review .ls-stars svg:nth-child(3) { animation-delay: calc(var(--stars-at, 6.8s) + .28s); }
.ls-motion .ls-phone.is-play .ls-review .ls-stars svg:nth-child(4) { animation-delay: calc(var(--stars-at, 6.8s) + .42s); }
.ls-motion .ls-phone.is-play .ls-review .ls-stars svg:nth-child(5) { animation-delay: calc(var(--stars-at, 6.8s) + .56s); }
.ls-motion .ls-phone.is-play .ls-review .ls-stars { animation: ls-star-bloom 1.6s var(--ease-glide) calc(var(--stars-at, 6.8s) + .9s); }
@keyframes ls-star-in { from { opacity: .15; transform: scale(.6); } to { opacity: 1; transform: none; } }
@keyframes ls-star-bloom { 40% { filter: drop-shadow(0 0 4px color-mix(in srgb, var(--star) 55%, transparent)); } }

/* ---------- 4. stars elsewhere: the Reviews rung and the closing line light up, one by one, as they arrive ---------- */
.ls-motion .rv:not(.is-in) .ls-stars.ls-lights svg { opacity: .15; transform: scale(.6); }
.ls-motion .rv.is-in .ls-stars.ls-lights svg { animation: ls-star-in 700ms var(--ease-reveal) backwards; }
.ls-motion .rv.is-in .ls-stars.ls-lights svg:nth-child(1) { animation-delay: calc(var(--d, 0ms) + .35s); }
.ls-motion .rv.is-in .ls-stars.ls-lights svg:nth-child(2) { animation-delay: calc(var(--d, 0ms) + .47s); }
.ls-motion .rv.is-in .ls-stars.ls-lights svg:nth-child(3) { animation-delay: calc(var(--d, 0ms) + .59s); }
.ls-motion .rv.is-in .ls-stars.ls-lights svg:nth-child(4) { animation-delay: calc(var(--d, 0ms) + .71s); }
.ls-motion .rv.is-in .ls-stars.ls-lights svg:nth-child(5) { animation-delay: calc(var(--d, 0ms) + .83s); }

/* ---------- 5. the loop: the diagram fades in without rising (the mark at its centre never moves),
   the ring draws itself, then the six steps light in order (each is a <g class="step" style="--i:N"> in the markup) ---------- */
.ls-motion .ls-loop-wrap.rv:not(.is-in) { transform: none; }
.ls-motion .ls-loop-wrap.rv.is-in { animation: ls-fade 700ms var(--ease-glide) var(--d, 0ms) backwards; }
.ls-motion .ls-loop-wrap .ring { stroke-dasharray: 0 178; }
.ls-motion .ls-loop-wrap.is-in .ring { stroke-dasharray: 122 56; transition: stroke-dasharray 2s var(--ease-reveal) calc(var(--d, 0ms) + .2s); }
.ls-motion .ls-loop-wrap :is(.head, .node, .num, .name) { opacity: 0; }
.ls-motion .ls-loop-wrap.is-in :is(.head, .node, .num, .name) { opacity: 1; transition: opacity 600ms var(--ease-glide); }
.ls-motion .ls-loop-wrap.is-in .step > * { transition-delay: calc(var(--d, 0ms) + .5s + var(--i, 0) * .25s); }
.ls-motion .ls-loop-wrap.is-in .head { transition-delay: calc(var(--d, 0ms) + 1.9s); }
.ls-motion .ls-loop-list li { opacity: 0; transform: translateY(6px); }
.ls-motion .ls-loop-wrap.is-in .ls-loop-list li { opacity: 1; transform: none; transition: opacity 500ms var(--ease-glide), transform 700ms var(--ease-reveal); }
.ls-motion .ls-loop-wrap.is-in .ls-loop-list li:nth-child(1) { transition-delay: .5s; }
.ls-motion .ls-loop-wrap.is-in .ls-loop-list li:nth-child(2) { transition-delay: .75s; }
.ls-motion .ls-loop-wrap.is-in .ls-loop-list li:nth-child(3) { transition-delay: 1s; }
.ls-motion .ls-loop-wrap.is-in .ls-loop-list li:nth-child(4) { transition-delay: 1.25s; }
.ls-motion .ls-loop-wrap.is-in .ls-loop-list li:nth-child(5) { transition-delay: 1.5s; }
.ls-motion .ls-loop-wrap.is-in .ls-loop-list li:nth-child(6) { transition-delay: 1.75s; }

/* ---------- 6. the timeline: the connector draws across, each step square warms to gold as it arrives ---------- */
.ls-motion .ls-steps::before { transform: scaleX(0); transform-origin: left center; }
.ls-motion .ls-steps.is-in::before { transform: none; transition: transform 1.8s var(--ease-reveal) .2s; }
.ls-motion .ls-step .ls-step-n { transform: scale(.86); border-color: var(--line); }
.ls-motion .ls-step.is-in .ls-step-n { transform: none; border-color: var(--rule); transition: transform 800ms var(--ease-reveal) calc(var(--d, 0ms) + .3s), border-color 800ms var(--ease-glide) calc(var(--d, 0ms) + .3s); }
@media (max-width: 1079px) {
  .ls-motion .ls-steps::before { transform: scaleY(0); transform-origin: center top; }
}

/* ---------- 7. the ladder: the featured rung arrives like the others, then is stamped once ---------- */
.ls-motion .ls-rung.is-featured.is-in { animation: ls-fade 700ms var(--ease-glide) var(--d, 0ms) backwards, ls-rise var(--dur-reveal) var(--ease-reveal) var(--d, 0ms) backwards, ls-stamp 1.1s var(--ease) calc(var(--d, 0ms) + .5s) both; }
@keyframes ls-stamp { from { outline: 1px solid color-mix(in srgb, var(--rule) 90%, transparent); outline-offset: 0; } to { outline: 1px solid transparent; outline-offset: 14px; } }

/* ---------- 8. the hairline rows (the punch list): the line traces in under each row ---------- */
.ls-pains li { position: relative; }
.ls-pains li::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px; background: var(--rule); transform: scaleX(0); transform-origin: left center; opacity: 0; pointer-events: none; }
.ls-motion .ls-pains li.is-in::after { animation: ls-trace 1.5s var(--ease) calc(var(--d, 0ms) + 200ms) both; }
@keyframes ls-trace { 0% { transform: scaleX(0); opacity: 1; } 55% { transform: scaleX(1); opacity: 1; } 100% { transform: scaleX(1); opacity: 0; } }

/* ---------- 9. buttons: a sheen crosses the hero's primary once as it arrives; hover is a quiet lift, press is quick ---------- */
.ls-btn { transition: background-color 180ms var(--ease), border-color 180ms var(--ease), color 180ms var(--ease), transform 140ms var(--ease); }
.ls-hero .ls-btn-primary { position: relative; overflow: hidden; }
.ls-hero .ls-btn-primary::after { content: ""; position: absolute; top: -20%; bottom: -20%; left: 0; width: 36%; background: linear-gradient(100deg, transparent, color-mix(in srgb, var(--ivory) 28%, transparent) 50%, transparent); transform: translateX(-140%) skewX(-18deg); pointer-events: none; }
.ls-motion .ls-hero .rv.is-in .ls-btn-primary::after { animation: ls-sheen 1.1s var(--ease) calc(var(--d, 0ms) + 900ms) both; }
@keyframes ls-sheen { from { transform: translateX(-140%) skewX(-18deg); } to { transform: translateX(380%) skewX(-18deg); } }
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .ls-btn:hover { transform: translateY(-1px); }
}
.ls-btn:active { transform: translateY(1px); }

/* ---------- 10. cards, screens and rows answer the pointer: the border for everyone, the lift only with motion ---------- */
.ls-card, .ls-rung, .ls-aside, .ls-job .ls-shot { transition: transform 420ms var(--ease-glide), border-color 320ms var(--ease-glide), box-shadow 420ms var(--ease-glide); }
.ls-job .ls-shot img.ls-fill { transition: transform 1.1s var(--ease-glide); }
@media (hover: hover) and (pointer: fine) {
  .ls-card:hover, .ls-rung:hover { border-color: var(--line-strong); }
  .ls-rung.is-featured:hover { border-color: var(--rule); }
  .ls-faq summary { transition: color 180ms var(--ease), text-decoration-color 220ms var(--ease); }
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .ls-card:hover, .ls-rung:hover, .ls-job:hover .ls-shot { transform: translateY(-3px); }
  .ls-job:hover .ls-shot img.ls-fill { transform: scale(1.035); }
}
.ls-faq summary::after { transition: transform 260ms var(--ease-glide), opacity 180ms; }
.ls-faq details[open] > p { animation: ls-faq-in 380ms var(--ease-reveal) both; }
@keyframes ls-faq-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* ---------- 11. the header: the current section's underline slides in ---------- */
.ls-header nav > a:not(.ls-btn)::after { content: ""; position: absolute; left: 0; right: 0; bottom: -8px; height: 2px; background: var(--rule); transform: scaleX(0); transform-origin: left center; transition: transform 320ms var(--ease-glide); }
.ls-header nav > a.is-active::after { transform: none; }
.ls-header { transition: box-shadow 320ms var(--ease-glide); }
.ls-lang > a, .ls-lang > span { transition: color 180ms var(--ease), background-color 180ms var(--ease); }

/* ---------- 12. the form: fields answer focus, the sent tick draws ---------- */
.ls-input, .ls-select { transition: border-color 220ms var(--ease), box-shadow 220ms var(--ease); }
.ls-input:focus, .ls-select:focus { border-color: var(--accent-text); }
.ls-form-state.is-success .ls-h3 svg path:last-child { stroke-dasharray: 14; stroke-dashoffset: 0; }
.ls-motion .ls-form-state.is-success.is-shown .ls-h3 svg path:last-child { animation: ls-tick 700ms var(--ease-reveal) 200ms both; }
@keyframes ls-tick { from { stroke-dashoffset: 14; } to { stroke-dashoffset: 0; } }

/* ---------- reduced motion: the head script never sets ls-motion, so this only mops up the always-on bits ---------- */
@media (prefers-reduced-motion: reduce) {
  .ls-hero-ring, .ls-hero-hint i, .ls-btn-primary::after { animation: none !important; }
  .ls-faq details[open] > p { animation: none; }
}
