/* ==========================================================================
   AGRIYANA — Motion system

   Rules this file follows, so the whole site stays at 60fps:

   1. Only `transform`, `opacity`, `clip-path` and `filter` are animated.
      Never width, height, top, left, margin or background-position.
   2. Anything that moves gets a compositor layer only while it is moving.
      `will-change` is added by motion.js on approach and removed on arrival,
      because a permanent `will-change` on 40 cards costs more than it saves.
   3. Every duration reads a `--dur-*` token, so `prefers-reduced-motion`
      collapses the entire system from one place in tokens.css.
   4. Reveals must be able to fail open. If an element is still at opacity 0
      when motion.js gives up, `.no-js`/`.m-ready` fallbacks put it back.

   Naming: `data-*` attributes drive behaviour, `.m-*` classes carry state.
   ========================================================================== */

/* Until motion.js boots, nothing is hidden. If the script never runs, the page
   is still fully readable — the reveal system can never eat the content. */
[data-rise],
[data-mask] > *,
[data-stagger] > * { opacity: 1; }

html.m-ready [data-rise],
html.m-ready [data-stagger] > * {
  opacity: 0;
  transform: translate3d(0, var(--rise-y, 22px), 0) scale(var(--rise-s, 1));
  transition:
    opacity var(--dur-3) var(--ease-soil),
    transform var(--dur-3) var(--ease-soil);
  transition-delay: var(--rise-d, 0ms);
}

/* Directional variants are declared BEFORE the arrival rule on purpose.
   `html.m-ready [data-rise='left']` and `html.m-ready [data-rise].is-in` have
   identical specificity (0,3,1), so whichever is written last wins. With the
   variants last, every directional element kept its offset transform forever
   and simply never arrived. Order is load-bearing here — do not move these
   below the `.is-in` block. */
html.m-ready [data-rise='left']  { transform: translate3d(-28px, 0, 0); }
html.m-ready [data-rise='right'] { transform: translate3d(28px, 0, 0); }
html.m-ready [data-rise='in']    { transform: scale(.965); }
html.m-ready [data-rise='fade']  { transform: none; }

html.m-ready [data-rise].is-in,
html.m-ready [data-stagger] > .is-in {
  opacity: 1;
  transform: none;
}

/* ==================================================================== mask
   The signature reveal. Text sits inside a clipped box and slides up from its
   own baseline, so a headline surfaces the way a layer of soil is exposed
   rather than simply fading in. Pure transform on the inner element — the
   clip is static, so this composites cleanly. */
[data-mask] { display: block; }
[data-mask] > * { display: block; }

html.m-ready [data-mask] {
  overflow: hidden;
  /* a little breathing room so descenders and italic overhangs are not shaved */
  padding-bottom: .12em;
  margin-bottom: -.12em;
}
html.m-ready [data-mask] > * {
  transform: translate3d(0, 105%, 0);
  transition: transform var(--dur-3) var(--ease-soil);
  transition-delay: var(--rise-d, 0ms);
}
html.m-ready [data-mask].is-in > * { transform: none; }

/* ==================================================================== draw
   SVG line work that draws itself. motion.js measures each path and sets
   --len, so this works on any path without hand-tuned dash values. */
html.m-ready [data-draw] path,
html.m-ready [data-draw] line,
html.m-ready [data-draw] circle,
html.m-ready [data-draw] polyline {
  stroke-dasharray: var(--len, 1000);
  stroke-dashoffset: var(--len, 1000);
  transition: stroke-dashoffset var(--dur-4) var(--ease-out);
  transition-delay: var(--rise-d, 0ms);
}
html.m-ready [data-draw].is-in path,
html.m-ready [data-draw].is-in line,
html.m-ready [data-draw].is-in circle,
html.m-ready [data-draw].is-in polyline { stroke-dashoffset: 0; }

/* ==================================================================== parallax
   motion.js writes --pfx/--pfy as the element crosses the viewport. Keeping the
   transform in CSS means the JS only ever touches a custom property, which
   never triggers layout. */
[data-parallax] {
  transform: translate3d(var(--pfx, 0px), var(--pfy, 0px), 0) scale(var(--pfs, 1));
}
@media (prefers-reduced-motion: reduce) {
  [data-parallax] { transform: none !important; }
}

/* ==================================================================== counter */
[data-count] { font-variant-numeric: tabular-nums; }

/* ==================================================================== magnetic
   Buttons lean very slightly toward the cursor. 5px maximum — at 10px it stops
   reading as polish and starts reading as a bug. Pointer-fine only. */
[data-magnet] {
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
  transition: transform var(--dur-2) var(--ease-spring);
}
[data-magnet].is-pulling { transition-duration: 80ms; }

/* ==================================================================== marquee
   Duplicated track, translated by exactly -50%, so the loop is seamless with
   no JS ticking every frame. Pauses on hover and when the tab is hidden. */
.marquee { display: flex; overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); }
.marquee-track {
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--s-6);
  padding-right: var(--s-6);
  min-width: 100%;
  animation: mq-slide var(--mq-dur, 42s) linear infinite;
}
.marquee:hover .marquee-track,
.marquee:focus-within .marquee-track { animation-play-state: paused; }
@keyframes mq-slide { to { transform: translate3d(-100%, 0, 0); } }
@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; }
  .marquee { -webkit-mask-image: none; mask-image: none; overflow-x: auto; }
}

/* ==================================================================== kinetic
   A single line where one word swaps. Used in the hero to carry both halves of
   the proposition — "Fresh from the farm" / "Essential for your farm" — in one
   sentence instead of two competing headlines. */
.kinetic {
  display: inline-grid;
  vertical-align: bottom;
  overflow: hidden;
  text-align: left;
}
.kinetic > * {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translate3d(0, 100%, 0);
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-3) var(--ease-soil);
}
.kinetic > .is-on  { opacity: 1; transform: none; }
.kinetic > .is-out { opacity: 0; transform: translate3d(0, -100%, 0); }

/* ==================================================================== press
   One shared press response. Anything interactive that is not a link gets it,
   so the whole site answers a click the same way. */
.pressable { transition: transform var(--dur-1) var(--ease-out); }
.pressable:active { transform: scale(.972); }
@media (prefers-reduced-motion: reduce) { .pressable:active { transform: none; } }

/* ==================================================================== shimmer
   Skeletons while a grid re-renders. Animating background-position would be a
   paint every frame, so the sheen is a pseudo-element that translates. */
.skel { position: relative; overflow: hidden; background: var(--surface-2); border-radius: var(--r-ctl); }
.skel::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translate3d(-100%, 0, 0);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--surface) 78%, transparent), transparent);
  animation: skel-sweep 1.35s var(--ease-in-out) infinite;
}
@keyframes skel-sweep { to { transform: translate3d(100%, 0, 0); } }
@media (prefers-reduced-motion: reduce) { .skel::after { animation: none; } }

/* ==================================================================== hairline
   Section rules that draw in from the left as they arrive. Scale on a 1px line
   is free; animating width is not. */
.rule { height: 1px; background: var(--border); transform-origin: left center; }
html.m-ready [data-rise] .rule,
html.m-ready .rule[data-rise] { transform: scaleX(0); transition: transform var(--dur-4) var(--ease-out); }
html.m-ready [data-rise].is-in .rule,
html.m-ready .rule[data-rise].is-in { transform: scaleX(1); }

/* ==================================================================== focus
   One focus ring for the whole site, and it never gets removed — only moved
   out of the way of the element's own border radius. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 3px;
  border-radius: var(--r-chip);
}

/* ==================================================================== page in
   Deliberately NOT a fade on <body>.

   The obvious version of this is `body { opacity: 0 }` plus a `page-in`
   keyframe with `animation-fill-mode: both`. It looks good and it is a trap:
   the entire page is then invisible until an animation completes, so any
   environment that does not advance animations — a paused tab on wake, a
   headless renderer, an extension that blocks animation — leaves the visitor
   staring at a blank screen with no way to recover. That failure was reached
   during development here, and the whole document really did stay at opacity 0.

   The arrival polish is carried by the section reveals instead. Those fail
   open by design: worst case they appear instantly, which is just a normal
   page. Nothing above this line is ever allowed to hide <body>. */
