/* ============================================================
   HELIOCASTLE — FEATURE ICONS, ALIVE
   Fourteen icons, each with a movement of its own, all running at
   once and none of them ever stopping.

   css/feature-grid.css draws the tiles. This file is only movement.
   The @keyframes `draw-line` it leans on is the shared one defined in
   css/plan-motion.css — do not redefine it here, a second block under
   the same name would replace the first for the whole page.

   ------------------------------------------------------------------
   HOW A PART MOVES
   Every stroke carries pathLength="1", so one dash length draws any
   of them. A part that also loops needs BOTH animations in one
   shorthand — a second `animation` declaration would replace the
   first, not add to it — so they are written together:

     animation: draw-line .9s var(--ease) forwards var(--s2),
                open 3.2s ease-in-out infinite var(--life);

   The first <time> in a shorthand is the duration and the second is
   the delay, which is what --s1..--s4 and --life are.

     --s1..--s4  when the 1st..4th stroke of this icon draws in
     --life      when this icon starts living

   --life is only a small offset per tile — just enough that fourteen
   icons are not beating in lockstep. It is NOT a queue: they all move
   all of the time, each at its own tempo.

   The +1.6s in it clears the drawing-in. A loop starting under a
   stroke that is still being drawn wins the property, and the draw
   is never seen.

   --i is written on the tile by js/stagger.js and inherits down.
   ------------------------------------------------------------------ */

.feat-icon {
  --s1: calc(var(--i, 0) * 0.055s + 0.3s);
  --s2: calc(var(--i, 0) * 0.055s + 0.45s);
  --s3: calc(var(--i, 0) * 0.055s + 0.58s);
  --s4: calc(var(--i, 0) * 0.055s + 0.7s);
  --life: calc(var(--i, 0) * 0.14s + 1.6s);
}

/* every moving part turns about the icon's own 24x24 grid, not about
   its rendered box — without this each origin would need measuring */
.feat-icon * { transform-box: view-box; }

/* ------------------------------------------------------------------
   THE ONE KEYFRAME EVERY DRAWN ICON NEEDS

   Defined here, in the same file as the icons, on purpose. It is also
   defined in the site's plan-motion.css — an identical duplicate is
   harmless, and without it here this file draws nothing at all when
   copied into another project: every stroke sits at its full dash
   offset and renders as a dot. That has bitten this project once.
   ------------------------------------------------------------------ */

@keyframes draw-line { to { stroke-dashoffset: 0; } }

/* ============================================================
   ARRIVAL — the tiles land, then the strokes draw
   ============================================================ */

.feat-grid.in-view .feat {
  animation: feat-in 0.6s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 0.055s);
}
@keyframes feat-in {
  from { opacity: 0; transform: translateY(18px) scale(0.95); }
  to   { opacity: 1; transform: none; }
}

.feat-icon > * {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
.feat-grid.in-view .feat-icon > *                { animation: draw-line 0.9s var(--ease) forwards var(--s1); }
.feat-grid.in-view .feat-icon > *:nth-child(2)   { animation: draw-line 0.9s var(--ease) forwards var(--s2); }
.feat-grid.in-view .feat-icon > *:nth-child(3)   { animation: draw-line 0.9s var(--ease) forwards var(--s3); }
.feat-grid.in-view .feat-icon > *:nth-child(4)   { animation: draw-line 0.9s var(--ease) forwards var(--s4); }

/* ============================================================
   THE FIVE WITH A STORY
   These do something specific rather than just breathing. Each has
   extra parts in the markup — a spark, two letters, a blade — that
   are NOT drawn in with the rest, so each one turns the inherited
   dash off for itself.
   ============================================================ */

/* ------------------------------------------------------------------
   TABLE RESERVATIONS — they swing together, strike, and throw a spark
   The fork pivots at its handle and the knife at its own, so they
   meet high up where the tines and the blade cross. The spark lives
   at that crossing and only exists for a sixth of the cycle.
   ------------------------------------------------------------------ */

.feat-grid.in-view .feat-icon.i-cutlery .fork {
  transform-origin: 6.5px 22px;
  animation: draw-line 0.9s var(--ease) forwards var(--s1),
             strike-left 2.6s cubic-bezier(0.5, 0, 0.35, 1) infinite var(--life);
}
.feat-grid.in-view .feat-icon.i-cutlery .knife {
  transform-origin: 19px 22px;
  animation: draw-line 0.9s var(--ease) forwards var(--s2),
             strike-right 2.6s cubic-bezier(0.5, 0, 0.35, 1) infinite var(--life);
}
/* fast in to the clash, slower away from it — the easing does the
   work of making it read as a hit rather than a sway */
@keyframes strike-left {
  0%, 100% { transform: rotate(-4deg); }
  34%      { transform: rotate(6.5deg); }
  41%      { transform: rotate(4.5deg); }
}
@keyframes strike-right {
  0%, 100% { transform: rotate(4deg); }
  34%      { transform: rotate(-6.5deg); }
  41%      { transform: rotate(-4.5deg); }
}

.feat-icon.i-cutlery .spark {
  stroke-dasharray: none;
  stroke-dashoffset: 0;
  opacity: 0;
  transform-origin: 13px 5px;
}
.feat-icon.i-cutlery .spark-core {
  fill: var(--gold-light);
  stroke: none;
}
.feat-icon.i-cutlery .spark-rays {
  stroke: var(--gold-light);
  stroke-width: 1.1;
}
.feat-grid.in-view .feat-icon.i-cutlery .spark {
  animation: spark 2.6s linear infinite var(--life);
}
@keyframes spark {
  0%, 31%   { opacity: 0; transform: scale(0.2) rotate(0deg); }
  35%       { opacity: 1; transform: scale(1.15) rotate(12deg); }
  46%       { opacity: 0; transform: scale(1.7) rotate(26deg); }
  100%      { opacity: 0; transform: scale(0.2) rotate(0deg); }
}

/* ------------------------------------------------------------------
   CUSTOMER ACCOUNTS - the person waves and says hi
   They stay where they are. Only the arm moves, pivoting at the
   shoulder where it joins the body, and "Hi" pops in beside them on
   the upswing.
   ------------------------------------------------------------------ */

.feat-grid.in-view .feat-icon.i-user .arm {
  transform-origin: 16px 17px;          /* the shoulder joint */
  animation: draw-line 0.9s var(--ease) forwards var(--s2),
             wave 2.6s ease-in-out infinite var(--life);
}
@keyframes wave {
  0%, 8%, 68%, 100% { transform: rotate(0deg); }
  18%               { transform: rotate(-30deg); }
  30%               { transform: rotate(12deg); }
  42%               { transform: rotate(-28deg); }
  54%               { transform: rotate(10deg); }
}

.feat-icon.i-user .hi {
  fill: currentColor;
  stroke: none;                      /* the icons are stroked, text is not */
  stroke-dasharray: none;
  font-family: system-ui, "Segoe UI", sans-serif;
  font-size: 6.4px;
  font-weight: 600;
  text-anchor: middle;
  opacity: 0;
  transform-origin: 21.5px 7.5px;
}
.feat-grid.in-view .feat-icon.i-user .hi {
  animation: say-hi 2.6s ease-out infinite var(--life);
}
@keyframes say-hi {
  0%, 12%   { opacity: 0; transform: translateY(2.5px) scale(0.5); }
  24%       { opacity: 1; transform: translateY(0)     scale(1); }
  58%       { opacity: 1; transform: translateY(0)     scale(1); }
  72%, 100% { opacity: 0; transform: translateY(-2px)  scale(0.9); }
}

/* the ring answers the greeting */
.feat-grid.in-view .feat-icon.i-user .ring {
  transform-origin: 12px 12px;
  animation: draw-line 0.9s var(--ease) forwards var(--s1),
             welcome 2.6s ease-out infinite var(--life);
}
@keyframes welcome {
  0%, 14%, 52%, 100% { transform: scale(1);    opacity: 1; }
  26%                { transform: scale(1.07); opacity: 0.55; }
}

/* ------------------------------------------------------------------
   TWO LANGUAGES — it turns, and throws a letter out of itself
   Two letters, one per language, taking it in turns so one is always
   in the air. Change the characters in the markup if the second
   language is not Arabic — they are ordinary text, not paths.
   ------------------------------------------------------------------ */

.feat-icon.i-globe,
.feat-icon.i-user,
.feat-icon.i-pin { overflow: visible; }   /* letters fly out, the pin bolts out */

.feat-icon.i-globe .say {
  fill: currentColor;
  stroke: none;                      /* the icons are stroked, text is not */
  stroke-dasharray: none;
  font-family: system-ui, "Segoe UI", sans-serif;
  font-size: 8.5px;
  font-weight: 600;
  text-anchor: middle;
  opacity: 0;
  transform-origin: 12px 12px;
}
.feat-grid.in-view .feat-icon.i-globe .s1 { animation: throw-a 3.6s ease-out infinite var(--life); }
.feat-grid.in-view .feat-icon.i-globe .s2 { animation: throw-b 3.6s ease-out infinite calc(var(--life) + 1.8s); }

/* They stay invisible while they are still inside the globe and only
   appear once they are past its edge, so a letter is never seen sitting
   on top of the meridian. They finish outside the icon's own box, which
   is why .i-globe lets its overflow show. */
@keyframes throw-a {
  0%,  12% { opacity: 0; transform: translate(2px, -3px)    scale(0.6); }
  26%      { opacity: 1; transform: translate(8.5px, -9.5px) scale(1); }
  58%      { opacity: 0; transform: translate(16px, -18px)  scale(0.7); }
  100%     { opacity: 0; transform: translate(2px, -3px)    scale(0.6); }
}
@keyframes throw-b {
  0%,  12% { opacity: 0; transform: translate(-2px, -3px)    scale(0.6); }
  26%      { opacity: 1; transform: translate(-8.5px, -9px)  scale(1); }
  58%      { opacity: 0; transform: translate(-16px, -17px)  scale(0.7); }
  100%     { opacity: 0; transform: translate(-2px, -3px)    scale(0.6); }
}

.feat-grid.in-view .feat-icon.i-globe .meridian {
  transform-origin: 12px 12px;
  animation: draw-line 0.9s var(--ease) forwards var(--s3),
             turn 3.6s linear infinite var(--life);
}
@keyframes turn {
  0%   { transform: scaleX(1); }
  25%  { transform: scaleX(0.06); }
  50%  { transform: scaleX(-1); }
  75%  { transform: scaleX(0.06); }
  100% { transform: scaleX(1); }
}

/* ------------------------------------------------------------------
   DELIVERY ZONES - it charges, then bolts off to the right
   Three parts, and they have to be three elements: `animation` is one
   shorthand per element, so the shiver could not share an element with
   the run. .pin is the direct child and takes the run; .pin-in is
   nested inside it and takes the charge, inheriting the dash from its
   parent so the entrance draw still works.

   The cycle: it shakes harder and harder and brightens, launches right
   and stretches with the speed, drops the bolt behind it, and comes
   back round from the left while nobody can see it.
   ------------------------------------------------------------------ */

.feat-grid.in-view .feat-icon.i-pin .pin {
  transform-origin: 12px 12px;
  animation: draw-line 0.9s var(--ease) forwards var(--s1),
             bolt-away 3s cubic-bezier(0.6, 0, 0.85, 0.2) infinite var(--life);
}
/* The stretch is deliberately small. A pin pulled out to 2.4x stops being
   a pin and becomes a blob — the speed is carried by the bolt and by how
   fast it leaves, not by squashing the shape. */
@keyframes bolt-away {
  0%, 44%   { transform: translateX(0)      scaleX(1);    opacity: 1; }
  48%       { transform: translateX(5px)    scaleX(1.2);  opacity: 1; }
  53%       { transform: translateX(20px)   scaleX(1.35); opacity: 0.3; }
  58%       { transform: translateX(34px)   scaleX(1.4);  opacity: 0; }
  64%       { transform: translateX(-30px)  scaleX(1.3);  opacity: 0; }
  73%       { transform: translateX(-5px)   scaleX(1.06); opacity: 0.9; }
  80%, 100% { transform: translateX(0)      scaleX(1);    opacity: 1; }
}

/* the charge: a shiver that grows, and a glow that comes up with it */
.feat-grid.in-view .feat-icon.i-pin .pin-in {
  transform-origin: 12px 12px;
  animation: charge 3s linear infinite var(--life);
}
@keyframes charge {
  0%, 6%    { transform: translate(0, 0);            filter: none; }
  11%       { transform: translate(-0.3px, 0.2px); }
  16%       { transform: translate(0.3px, -0.2px); }
  21%       { transform: translate(-0.45px, -0.3px); }
  26%       { transform: translate(0.45px, 0.3px); }
  31%       { transform: translate(-0.6px, 0.35px); }
  36%       { transform: translate(0.6px, -0.35px); }
  40%       { transform: translate(-0.8px, -0.45px);
              filter: drop-shadow(0 0 3px rgb(var(--accent-light-rgb) / 0.8)); }
  44%       { transform: translate(0, 0) scale(1.12);
              filter: drop-shadow(0 0 7px rgb(var(--accent-light-rgb) / 1)); }
  48%, 100% { transform: translate(0, 0) scale(1);   filter: none; }
}

/* the lightning it leaves behind, drawn left to right as it goes */
.feat-icon.i-pin .bolt {
  stroke: var(--gold-light);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  opacity: 0;
  filter: drop-shadow(0 0 5px rgb(var(--accent-light-rgb) / 0.95));
}
.feat-grid.in-view .feat-icon.i-pin .bolt {
  animation: leave-bolt 3s linear infinite var(--life);
}
@keyframes leave-bolt {
  0%, 45%   { stroke-dashoffset: 1; opacity: 0; }
  47%       { opacity: 1; }
  55%       { stroke-dashoffset: 0; opacity: 1; }
  68%       { stroke-dashoffset: 0; opacity: 0; }
  100%      { stroke-dashoffset: 1; opacity: 0; }
}

/* ------------------------------------------------------------------
   DISCOUNT CODES - the blade goes through and it comes apart
   The face is drawn twice in the markup, each copy clipped to one side
   of the diagonal by a <clipPath>. That is what gives the two pieces a
   real cut edge to separate along - a single shape cannot be broken in
   two by CSS.

   Both halves move square to the cut, in opposite directions, on the
   frame the blade reaches the middle.
   ------------------------------------------------------------------ */

.feat-grid.in-view .feat-icon.i-percent .ha {
  transform-origin: 9px 9px;
  animation: draw-line 0.9s var(--ease) forwards var(--s1),
             fall-apart-a 2.8s cubic-bezier(0.25, 1.4, 0.5, 1) infinite var(--life);
}
.feat-grid.in-view .feat-icon.i-percent .hb {
  transform-origin: 15px 15px;
  animation: draw-line 0.9s var(--ease) forwards var(--s2),
             fall-apart-b 2.8s cubic-bezier(0.25, 1.4, 0.5, 1) infinite var(--life);
}
@keyframes fall-apart-a {
  0%, 15%, 62%, 100% { transform: translate(0, 0)          rotate(0deg); }
  27%                { transform: translate(-2.2px, -2.2px) rotate(-4deg); }
  40%                { transform: translate(-2.4px, -2.4px) rotate(-4.5deg); }
}
@keyframes fall-apart-b {
  0%, 15%, 62%, 100% { transform: translate(0, 0)          rotate(0deg); }
  27%                { transform: translate(2.2px, 2.2px)   rotate(4deg); }
  40%                { transform: translate(2.4px, 2.4px)   rotate(4.5deg); }
}

.feat-icon.i-percent .blade {
  stroke: var(--gold-light);
  stroke-width: 1.2;
  stroke-linecap: round;
  /* pathLength="100" in the markup, so a 16-long dash is 16% of the
     line however long the line really is */
  stroke-dasharray: 16 100;
  stroke-dashoffset: 116;
  opacity: 0;
  filter: drop-shadow(0 0 3px rgb(var(--accent-light-rgb) / 0.9));
}
.feat-grid.in-view .feat-icon.i-percent .blade {
  animation: slice 2.8s linear infinite var(--life);
}
@keyframes slice {
  0%        { stroke-dashoffset: 116; opacity: 0; }
  6%        { opacity: 1; }
  30%       { stroke-dashoffset: -16; opacity: 1; }
  34%, 100% { stroke-dashoffset: -16; opacity: 0; }
}

/* NEVER hide the <defs> with display:none. It picks up the dash and the
   draw animation from the generic rule, which looks like something worth
   suppressing - but a clipPath inside a display:none ancestor stops being
   resolved, both halves render whole, and the icon reads as two squares
   sitting on top of each other. A <defs> is not painted anyway. */

/* ============================================================
   THE REST
   Simpler movements, but the same rule holds: none of them rests.
   Each runs its own duration so the grid never falls into step.
   ============================================================ */

/* --- booking: the appointment is ticked off, wiped, and ticked again --- */
.feat-grid.in-view .feat-icon.i-cal .tick {
  animation: draw-line 0.9s var(--ease) forwards var(--s3),
             tick-again 3.4s ease-in-out infinite var(--life);
}
@keyframes tick-again {
  0%        { stroke-dashoffset: 0; }
  12%       { stroke-dashoffset: 1; }
  38%, 100% { stroke-dashoffset: 0; }
}

/* --- search: the glass never stops looking --- */
.feat-grid.in-view .feat-icon.i-search .glass {
  animation: draw-line 0.9s var(--ease) forwards var(--s1),
             hunt 3s ease-in-out infinite var(--life);
}
@keyframes hunt {
  0%, 100% { transform: translate(-1.8px, 0.6px) rotate(-4deg); }
  50%      { transform: translate(1.8px, -0.6px) rotate(4deg); }
}

/* --- gallery: the sun crosses the sky --- */
.feat-grid.in-view .feat-icon.i-image .sun {
  transform-origin: 8.6px 8.6px;
  animation: draw-line 0.9s var(--ease) forwards var(--s2),
             cross-sky 4.2s ease-in-out infinite var(--life);
}
@keyframes cross-sky {
  0%, 100% { transform: translate(0, 0)          scale(1); }
  50%      { transform: translate(5.5px, 1.4px)  scale(1.12); }
}
.feat-grid.in-view .feat-icon.i-image .hill {
  animation: draw-line 0.9s var(--ease) forwards var(--s3),
             shift 4.2s ease-in-out infinite var(--life);
}
@keyframes shift {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(-1.2px); }
}

/* --- reviews: it keeps being given the star --- */
.feat-icon.i-star .star { fill: rgb(var(--accent-rgb) / 0); }
.feat-grid.in-view .feat-icon.i-star .star {
  transform-origin: 12px 12px;
  animation: draw-line 0.9s var(--ease) forwards var(--s1),
             rate 3.2s ease-in-out infinite var(--life);
}
@keyframes rate {
  0%, 100% { fill: rgb(var(--accent-rgb) / 0);    transform: scale(1) rotate(0deg); }
  30%      { fill: rgb(var(--accent-rgb) / 0.9);  transform: scale(1.2) rotate(-10deg); }
  60%      { fill: rgb(var(--accent-rgb) / 0.35); transform: scale(1.02) rotate(3deg); }
}

/* --- newsletter: the flap opens and closes --- */
.feat-grid.in-view .feat-icon.i-mail .flap {
  transform-origin: 12px 6.6px;      /* the top edge it folds on */
  animation: draw-line 0.9s var(--ease) forwards var(--s2),
             open 3s ease-in-out infinite var(--life);
}
@keyframes open {
  0%, 100% { transform: scaleY(1); }
  45%      { transform: scaleY(0.12); }
}

/* --- ordering: somebody is always typing --- */
.feat-grid.in-view .feat-icon.i-chat .t1 {
  animation: draw-line 0.9s var(--ease) forwards var(--s2),
             type 1.3s ease-in-out infinite var(--life);
}
.feat-grid.in-view .feat-icon.i-chat .t2 {
  animation: draw-line 0.9s var(--ease) forwards var(--s3),
             type 1.3s ease-in-out infinite calc(var(--life) + 0.14s);
}
.feat-grid.in-view .feat-icon.i-chat .t3 {
  animation: draw-line 0.9s var(--ease) forwards var(--s4),
             type 1.3s ease-in-out infinite calc(var(--life) + 0.28s);
}
@keyframes type {
  0%, 55%, 100% { transform: translateY(0);      opacity: 0.5; }
  25%           { transform: translateY(-2.4px); opacity: 1; }
}

/* --- loyalty: the lid keeps lifting --- */
.feat-grid.in-view .feat-icon.i-gift .lid {
  transform-origin: 12px 12px;
  animation: draw-line 0.9s var(--ease) forwards var(--s1),
             lift 3.2s ease-in-out infinite var(--life);
}
@keyframes lift {
  0%, 100% { transform: translateY(0)      rotate(0deg); }
  45%      { transform: translateY(-2.4px) rotate(-5deg); }
}

/* --- management: the handles ride up and down their slots --- */
.feat-grid.in-view .feat-icon.i-sliders .h1 {
  animation: draw-line 0.9s var(--ease) forwards var(--s2),
             slide-up 3.6s ease-in-out infinite var(--life);
}
.feat-grid.in-view .feat-icon.i-sliders .h2 {
  animation: draw-line 0.9s var(--ease) forwards var(--s3),
             slide-down 3.6s ease-in-out infinite calc(var(--life) + 0.4s);
}
.feat-grid.in-view .feat-icon.i-sliders .h3 {
  animation: draw-line 0.9s var(--ease) forwards var(--s4),
             slide-up 3.6s ease-in-out infinite calc(var(--life) + 0.8s);
}
/* the travel stays inside the gap the rails leave, or a handle would
   float over a line instead of riding in its slot */
@keyframes slide-up   { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3.4px); } }
@keyframes slide-down { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(3.4px); } }

/* --- reports: the numbers keep moving ---
   Scaled from the axis, not redrawn from nothing. A loop that hides its
   own artwork to start again left the bars gone for part of every cycle,
   and at a glance the tile read as an empty box. */
.feat-grid.in-view .feat-icon.i-chart .r1 {
  transform-origin: 7.5px 17px;          /* each bar stands on the axis */
  animation: draw-line 0.9s var(--ease) forwards var(--s2),
             bar 3.4s ease-in-out infinite var(--life);
}
.feat-grid.in-view .feat-icon.i-chart .r2 {
  transform-origin: 12px 17px;
  animation: draw-line 0.9s var(--ease) forwards var(--s3),
             bar 3.4s ease-in-out infinite calc(var(--life) + 0.4s);
}
.feat-grid.in-view .feat-icon.i-chart .r3 {
  transform-origin: 16.5px 17px;
  animation: draw-line 0.9s var(--ease) forwards var(--s4),
             bar 3.4s ease-in-out infinite calc(var(--life) + 0.8s);
}
@keyframes bar {
  0%, 100% { transform: scaleY(1); }
  50%      { transform: scaleY(0.38); }
}

/* ============================================================
   HOVER
   The cursor lights the icon. It does NOT pause it — these are meant
   to run without stopping.
   ============================================================ */

.feat:hover .feat-icon { filter: drop-shadow(0 0 12px rgb(var(--accent-rgb) / 0.8)); }

/* ============================================================
   OFF SCREEN
   js/when-visible.js keeps .is-live on the grid only while it is in
   view, and stamps .has-live on <html> to say it is doing so. Fourteen
   icons animating behind the fold is work nobody can see, and on a
   phone it is battery.

   Paused, not stopped: every icon holds its frame and picks up where
   it left off, so a draw caught half-way finishes rather than snapping.

   The rule hangs off .has-live so that if the script never runs,
   nothing is ever paused — the icons keep moving instead of freezing
   part-drawn, which is the right way round to fail.
   ============================================================ */

.has-live .feat-grid:not(.is-live) .feat-icon * { animation-play-state: paused; }
