/*
 * Homepage entrance animations (Notion "Website 4"), driven by assets/js/anim.js.
 * Effects and timings follow the frame-by-frame cuts of the GIFs (.playwright-mcp/sb-*.png) 1:1.
 *
 * Everything is scoped to html.anim, set only when the visitor allows motion, so without JS
 * or with "reduce motion" every block is simply visible. Only opacity, transform, clip-path
 * and border-radius move.
 *
 * Delays: --d fixed delay; --cd card delay set by JS within a batch;
 * words: --wb first word, --ws step between words, --wi word index (set by JS).
 */

.anim {
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --pop: cubic-bezier(0.34, 1.3, 0.64, 1);
}

/* ---------- words ---------- */

.anim .a-w {
  display: inline-block;
  transition:
    opacity 0.35s ease,
    transform 0.25s var(--ease);
  transition-delay: calc(var(--cd, 0ms) + var(--wb, 0ms) + var(--wi) * var(--ws, 60ms));
}

/* Hidden until the section (heads) or the card (card text) is in view. Default, as in the GIF:
   each word appears half a line low and pale, rises into place and darkens. */
.anim :is(.hero, .adv, .svc, .steps, .cta):not(.is-in) .a-w,
.anim :is(.adv__card, .steps__item):not(.is-in) .a-w {
  opacity: 0;
  transform: translateY(0.5em);
}

/* Title words (slide from the right) and steps words (fade only) move slower, as in the GIF. */
.anim :is(.adv__title, .svc .sect-title) .a-w {
  transition:
    opacity 0.3s ease,
    transform 0.3s var(--ease);
  transition-delay: calc(var(--cd, 0ms) + var(--wb, 0ms) + var(--wi) * var(--ws, 60ms));
}

.anim :is(.steps__title, .steps__lead, .steps__text) .a-w {
  transition: opacity 0.45s ease;
  transition-delay: calc(var(--cd, 0ms) + var(--wb, 0ms) + var(--wi) * var(--ws, 60ms));
}

/* Advantages and services titles: each word slides in from the right. */
.anim :is(.adv, .svc):not(.is-in) :is(.adv__title, .sect-title) .a-w {
  transform: translateX(0.6em);
}

/* Steps: words only fade in, no motion. */
.anim .steps:not(.is-in) :is(.steps__title, .steps__lead) .a-w,
.anim .steps__item:not(.is-in) .steps__text .a-w {
  transform: none;
}

/* ---------- shared pieces ---------- */

.anim .hero__badge,
.anim .steps__intro > .badge,
.anim .cta__head > .badge {
  transform-origin: 50% 50%;
  transition:
    opacity 0.2s ease var(--d, 0ms),
    transform 0.36s var(--ease) var(--d, 0ms);
}

/* The hero, steps and banner badges fly in tiny and tilted from below left, then straighten. */
.anim .hero:not(.is-in) .hero__badge,
.anim .steps:not(.is-in) .steps__intro > .badge,
.anim .cta:not(.is-in) .cta__head > .badge {
  opacity: 0;
  transform: translate(-50%, 60%) rotate(-25deg) scale(0);
}

/* The advantages and services badges grow from half size. */
.anim .sect-head > .badge {
  --d: 160ms;
  transition:
    opacity 0.2s ease var(--d, 0ms),
    transform 0.2s var(--ease) var(--d, 0ms);
}

.anim :is(.adv, .svc):not(.is-in) .sect-head > .badge {
  opacity: 0;
  transform: scale(0.5);
}

/* Big titles grow from half size (hero H1, banner title). */
.anim .hero__title,
.anim .cta__title {
  transition:
    opacity 0.3s ease var(--d, 0ms),
    transform 0.4s var(--ease) var(--d, 0ms);
}

.anim .hero:not(.is-in) .hero__title,
.anim .cta:not(.is-in) .cta__title {
  opacity: 0;
  transform: scale(0.5);
}

/* Buttons only fade in. */
.anim .hero__actions,
.anim .cta__actions,
.anim .svc__call {
  transition: opacity 0.5s ease var(--d, 0ms);
}

.anim .hero:not(.is-in) .hero__actions,
.anim .cta:not(.is-in) .cta__actions,
.anim .svc:not(.is-in) .svc__call {
  opacity: 0;
}

/* ---------- 1. hero ---------- */

/* The card is rounded and in place from the first frame; the photo fades in from black.
   (The GIF starts full width and shrinks into the card; dropped on request, 2026-10-09.) */
.anim .hero__card {
  transition: background-color 0.4s ease;
}

.anim .hero:not(.is-in) .hero__card {
  background-color: #000;
}

.anim .hero:not(.is-in) .hero__media {
  opacity: 0;
}

.anim .hero__badge {
  --d: 520ms;
}

.anim .hero__title {
  --d: 840ms;
}

.anim .hero__lead {
  --wb: 1160ms;
  --ws: 22ms;
}

.anim .hero__actions {
  --d: 1600ms;
}

/*
 * The rating slides in from the bottom right corner and pushes the cut-out into the photo.
 * The L-shaped photo mask starts shifted right and down by --s (the cut-out size, so the cut is
 * outside the card); two solid layers fill the top and left strips it leaves (2px overlap hides
 * the anti-aliased seam), and a small square
 * fills the shifted mask corner. The mask and the rating move back together.
 */
.anim .hero__media,
.anim .hero__score {
  --s: calc((100cqi - 2 * clamp(8px, 5.56cqi, 80px)) * 0.16);
}

.anim .hero__media {
  -webkit-mask-image: url("/assets/img/hero-mask.svg"), linear-gradient(#000, #000), linear-gradient(#000, #000), linear-gradient(#000, #000);
  mask-image: url("/assets/img/hero-mask.svg"), linear-gradient(#000, #000), linear-gradient(#000, #000), linear-gradient(#000, #000);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: 0 0, 0 0, 0 0, 0 0;
  mask-position: 0 0, 0 0, 0 0, 0 0;
  -webkit-mask-size: 100% 100%, 100% 0px, 0px 100%, 48px 48px;
  mask-size: 100% 100%, 100% 0px, 0px 100%, 48px 48px;
  transition:
    opacity 0.4s ease,
    -webkit-mask-position 0.4s var(--ease) 1800ms,
    -webkit-mask-size 0.4s var(--ease) 1800ms,
    mask-position 0.4s var(--ease) 1800ms,
    mask-size 0.4s var(--ease) 1800ms;
}

.anim .hero:not(.is-in) .hero__media {
  -webkit-mask-position: var(--s) var(--s), 0 0, 0 0, var(--s) var(--s);
  mask-position: var(--s) var(--s), 0 0, 0 0, var(--s) var(--s);
  -webkit-mask-size: 100% 100%, 100% calc(var(--s) + 2px), calc(var(--s) + 2px) 100%, 48px 48px;
  mask-size: 100% 100%, 100% calc(var(--s) + 2px), calc(var(--s) + 2px) 100%, 48px 48px;
}

.anim .hero__score {
  transition:
    opacity 0.2s ease 1800ms,
    transform 0.4s var(--ease) 1800ms;
}

.anim .hero:not(.is-in) .hero__score {
  opacity: 0;
  transform: translate(var(--s), var(--s));
}

@media (max-width: 900px) {
  /* In one column the card itself is the container; the cut-out is 40% of its width. */
  .anim .hero__media,
  .anim .hero__score {
    --s: calc(100cqi * 0.4);
  }

  .anim .hero__media {
    -webkit-mask-image: url("/assets/img/hero-mask-m.svg"), linear-gradient(#000, #000), linear-gradient(#000, #000), linear-gradient(#000, #000);
    mask-image: url("/assets/img/hero-mask-m.svg"), linear-gradient(#000, #000), linear-gradient(#000, #000), linear-gradient(#000, #000);
  }
}

/* Shown without motion when the page opens scrolled down. */
.anim .anim-skip,
.anim .anim-skip * {
  transition: none !important;
}

/* ---------- 3. advantages ---------- */

.anim .adv__title {
  --wb: 0ms;
  --ws: 110ms;
}

.anim .adv__lead {
  --wb: 200ms;
  --ws: 28ms;
}

.anim .adv__card {
  transition: opacity 0.36s ease var(--cd, 0ms);
}

.anim .adv__card:not(.is-in) {
  opacity: 0;
}

.anim .adv__icon {
  transition:
    opacity 0.2s ease calc(var(--cd, 0ms) + 280ms),
    transform 0.4s var(--pop) calc(var(--cd, 0ms) + 280ms);
}

.anim .adv__card:not(.is-in) .adv__icon {
  opacity: 0;
  transform: scale(0.5);
}

.anim .adv__name {
  --wb: 480ms;
  --ws: 40ms;
}

/*
 * Card words, compared frame by frame with the GIF: a word shows up at once at full colour,
 * 0.6em below the line, and then rises into place (text 0.12s, title 0.2s). One word per 40ms.
 */
.anim :is(.adv__name, .adv__text) .a-w {
  transition:
    opacity 0.04s linear,
    transform 0.12s ease-in-out;
  transition-delay: calc(var(--cd, 0ms) + var(--wb, 0ms) + var(--wi) * var(--ws, 60ms));
}

.anim .adv__name .a-w {
  transition-duration: 0.04s, 0.2s;
}

.anim .adv__card:not(.is-in) :is(.adv__name, .adv__text) .a-w {
  transform: translateY(0.6em);
}

/* Eased index (see anim.js): the whole text runs over about 470ms, gaps growing towards the end. */
.anim .adv__text {
  --wb: 480ms;
  --ws: 36ms;
}

/* ---------- 4. services ---------- */

.anim .svc .sect-title {
  --wb: 0ms;
  --ws: 110ms;
}

.anim .svc__lead {
  --wb: 200ms;
  --ws: 28ms;
}

.anim .svc__call {
  --d: 900ms;
}

.anim .svc__card {
  transition: opacity 0.2s ease var(--cd, 0ms);
}

.anim .svc__grid > li:not(.is-in) .svc__card {
  opacity: 0;
}

/* The photo flies in small and turned, then straightens, once the text is in. */
.anim .svc__slot {
  transition:
    opacity 0.2s ease calc(var(--cd, 0ms) + 920ms),
    transform 0.4s var(--pop) calc(var(--cd, 0ms) + 920ms);
}

.anim .svc__grid > li:not(.is-in) .svc__slot {
  opacity: 0;
  transform: scale(0.15) rotate(-20deg);
}

/* Price and arrow fade in slowly and evenly after the photo, the same 560ms behind it as in the GIF. */
.anim .svc__price,
.anim .svc__go {
  transition: opacity 0.56s linear calc(var(--cd, 0ms) + 1480ms);
}

.anim .svc__grid > li:not(.is-in) :is(.svc__price, .svc__go) {
  opacity: 0;
}

/* Card title and text: every line rises quickly inside its own clipped line box. They come first
   (before the photo, on request 2026-10-20): the text starts at 360ms, the title 80ms later,
   lines of a block 120ms apart (line boxes are built by anim.js and removed once the text is in). */
.anim .a-line {
  display: block;
  overflow: hidden;
}

.anim .a-line__in {
  display: block;
  transition: transform 0.15s var(--ease) calc(var(--cd, 0ms) + var(--lb, 0ms) + var(--li, 0) * 120ms);
}

.anim .svc__text {
  --lb: 360ms;
}

.anim .svc__name {
  --lb: 440ms;
}

.anim .svc__grid > li:not(.is-in) .a-line__in {
  transform: translateY(100%);
}

/* Until anim.js has built the line boxes the card text stays hidden. */
.anim :is(.svc__name, .svc__text):not(.a-lined) {
  visibility: hidden;
}

/* ---------- 5. steps ---------- */

.anim .steps__intro > .badge {
  --d: 640ms;
}

.anim .steps__title {
  --wb: 80ms;
  --ws: 90ms;
}

.anim .steps__lead {
  --wb: 1040ms;
  --ws: 25ms;
}

.anim .steps__icon {
  transition:
    opacity 0.2s ease var(--cd, 0ms),
    transform 0.2s var(--ease) var(--cd, 0ms);
}

.anim .steps__item:not(.is-in) .steps__icon {
  opacity: 0;
  transform: scale(0.8);
}

.anim .steps__num,
.anim .steps__name {
  transition: opacity 0.96s ease calc(var(--cd, 0ms) + 120ms);
}

.anim .steps__item:not(.is-in) :is(.steps__num, .steps__name) {
  opacity: 0;
}

.anim .steps__text {
  --wb: 440ms;
  --ws: 48ms;
}

/* ---------- 6. banner: same as the hero ---------- */

.anim .cta__card {
  transition:
    transform 0.16s var(--ease),
    border-radius 0.16s var(--ease);
}

.anim .cta__bg img {
  transition: opacity 0.4s ease;
}

.anim .cta:not(.is-in) .cta__card {
  border-radius: 0;
  transform: scale(1.04);
}

.anim .cta:not(.is-in) .cta__bg img {
  opacity: 0;
}

.anim .cta__head > .badge {
  --d: 520ms;
}

.anim .cta__title {
  --d: 840ms;
}

.anim .cta__lead {
  --wb: 1160ms;
  --ws: 22ms;
}

.anim .cta__actions {
  --d: 1600ms;
}

/* Counters keep a steady width while they run. */
.anim .stats__value {
  font-variant-numeric: tabular-nums;
}

/* Hero and banner buttons: while fading in, the phone pill slides in from the left and
   "Наші послуги" from the right, about 12px each, towards the centre. Hover colour keeps its transition. */
.anim :is(.hero__btn, .cta__btn) {
  transition:
    transform 0.5s var(--ease) var(--d, 0ms),
    background-color 0.2s;
}

.anim .hero__btn,
.anim .hero__actions {
  --d: 1600ms;
}

.anim .cta__btn,
.anim .cta__actions {
  --d: 1600ms;
}

.anim .hero:not(.is-in) .hero__btn--phone,
.anim .cta:not(.is-in) .cta__btn--phone {
  transform: translateX(-12px);
}

.anim .hero:not(.is-in) .hero__btn--ghost,
.anim .cta:not(.is-in) .cta__btn--ghost {
  transform: translateX(12px);
}

/* ---------- service page hero: homepage hero timings, but the card stays put ----------
   The card is rounded from the first frame (no full-bleed start); only the photo fades in,
   and the rating cut-out slides in later. */

.anim .shero__photo {
  transition: opacity 0.4s ease;
}

.anim .shero:not(.is-in) .shero__photo {
  opacity: 0;
}

/* Breadcrumbs fly in tiny and tilted from below left, like the homepage badge. */
.anim .crumbs {
  transform-origin: 0 50%;
  transition:
    opacity 0.2s ease 520ms,
    transform 0.36s var(--ease) 520ms;
}

.anim .shero:not(.is-in) .crumbs {
  opacity: 0;
  transform: translate(-20%, 60%) rotate(-25deg) scale(0);
}

/* H1 grows from half size, anchored at its left edge. */
.anim .shero__title {
  transform-origin: 0 50%;
  transition:
    opacity 0.3s ease 840ms,
    transform 0.4s var(--ease) 840ms;
}

.anim .shero:not(.is-in) .shero__title {
  opacity: 0;
  transform: scale(0.5);
}

.anim .shero__lead {
  --wb: 1160ms;
  --ws: 22ms;
}

.anim .shero:not(.is-in) .a-w {
  opacity: 0;
  transform: translateY(0.5em);
}

/* Buttons fade in while sliding towards each other. */
.anim .shero__actions {
  --d: 1600ms;
  transition: opacity 0.5s ease 1600ms;
}

.anim .shero:not(.is-in) .shero__actions {
  opacity: 0;
}

.anim .shero:not(.is-in) .shero__actions .cta__btn--phone {
  transform: translateX(-12px);
}

.anim .shero:not(.is-in) .shero__actions .cta__btn--ghost {
  transform: translateX(12px);
}

/* The rating slides in from the corner with its white cut-out. */
.anim .shero__tab {
  transition:
    opacity 0.2s ease 1800ms,
    transform 0.4s var(--ease) 1800ms;
}

.anim .shero:not(.is-in) .shero__tab {
  opacity: 0;
  transform: translate(100%, 100%);
}

/* ---------- contact hero: service hero timings ----------
   The framed card stays put; crumbs and H1 come in like on a service page, then the details,
   the button and the map fade up one after another. */

.anim .chero:not(.is-in) .crumbs {
  opacity: 0;
  transform: translate(-20%, 60%) rotate(-25deg) scale(0);
}

.anim .chero__title {
  transform-origin: 0 50%;
  transition:
    opacity 0.3s ease 840ms,
    transform 0.4s var(--ease) 840ms;
}

.anim .chero:not(.is-in) .chero__title {
  opacity: 0;
  transform: scale(0.5);
}

.anim .chero__about,
.anim .chero__rows,
.anim .chero__call,
.anim .chero__map {
  transition:
    opacity 0.5s ease var(--cd),
    transform 0.5s var(--ease) var(--cd);
}

.anim .chero__about {
  --cd: 1160ms;
}

.anim .chero__rows {
  --cd: 1320ms;
}

.anim .chero__call {
  --cd: 1600ms;
}

.anim .chero__map {
  --cd: 400ms;
}

.anim .chero:not(.is-in) :is(.chero__about, .chero__rows, .chero__call) {
  opacity: 0;
  transform: translateY(16px);
}

.anim .chero:not(.is-in) .chero__map {
  opacity: 0;
}

/* ---------- service article: blocks fade up as they come into view ---------- */

.anim .article__body > * {
  transition:
    opacity 0.5s ease var(--cd, 0ms),
    transform 0.5s var(--ease) var(--cd, 0ms);
}

.anim .article__body > :not(.is-in) {
  opacity: 0;
  transform: translateY(16px);
}
