/* ============================================================
   Textures
   ------------------------------------------------------------
   Two kinds of texture, split on one question: does it have to
   tile?

   The fine grain that covers the whole page does, at every
   viewport size, so it is procedural — `feTurbulence` with
   `stitchTiles`, inlined as a data URI. It is seamless by
   construction, costs no request, and cannot break if an asset
   host does. A photograph of paper cannot do that job: it
   repeats visibly at any size that matters.

   The large panel surfaces do not tile — each one covers a
   single block — so those are photographs, commissioned for
   this palette. PRODUCT.md asks that nature-derived colour and
   real imagery carry the warmth, and warns specifically against
   "over-blurred nature imagery that loses the recognizable place
   and light". A procedural field cannot satisfy that: it has no
   place and no light. These do, and they were briefed to stay
   sharp enough that you can tell what you are looking at.

   Every photographic layer is blended into its panel's brand
   colour rather than laid over it, so the panel keeps its hue
   and its text contrast, and the photograph only contributes
   structure.
   ============================================================ */

:root {
  /* Fine monochrome grain. The base layer under everything else. */
  --tex-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");



  /* Warp and weft. Two gradients crossing at right angles. */
  --tex-weave:
    repeating-linear-gradient(90deg, rgba(16, 37, 25, 0.05) 0 1px, transparent 1px 7px),
    repeating-linear-gradient(0deg, rgba(16, 37, 25, 0.05) 0 1px, transparent 1px 7px);

  /* Diagonal hatch at the 45 degrees the leaf mark is rotated to. */
  --tex-hatch: repeating-linear-gradient(
    135deg,
    rgba(16, 37, 25, 0.06) 0 1px,
    transparent 1px 9px
  );
}

/* --- The page-wide grain ------------------------------------
   One fixed overlay rather than a background on each section, so
   the grain does not slide relative to the content as you scroll
   — it behaves like the surface the page is printed on, which is
   the entire point of it. `mix-blend-mode: multiply` keeps it
   reading as texture in the paper rather than a grey film over
   the top; the `@supports` fallback drops the opacity for the
   handful of engines that would otherwise render it as haze. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  background-image: var(--tex-grain);
  opacity: 0.05;
  mix-blend-mode: multiply;
}

@supports not (mix-blend-mode: multiply) {
  body::after { opacity: 0.025; }
}

/* --- Textured surfaces --------------------------------------
   Each of these sets `isolation: isolate` so its own texture
   pseudo-element composites against the section, not the page. */

.hero,
.page-hero,
.account-story,
.ownership,
.plain-version,
.draft-banner {
  position: relative;
  isolation: isolate;
}

.hero::before,
.page-hero::before,
.hero::after,
.page-hero::after,
.account-story::after,
.ownership::after,
.plain-version::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
}

/* --- The photographic layer ---------------------------------
   Four commissioned textures sit under the four large panels.
   They are photographs rather than more turbulence for the
   reason PRODUCT.md gives: a recognizable place with real light
   carries warmth that no procedural field does, and the brief
   for each was to stay sharp enough that you can tell what you
   are looking at.

   Each is blended rather than laid down flat, so the panel's
   brand colour still drives the surface and the photograph only
   supplies structure. That is what keeps text contrast where it
   was — every panel here is darkening or lightening its own
   background by a bounded amount, not replacing it.
   ------------------------------------------------------------ */

/* Hero: backlit canopy, multiplied into the flat leaf ground at
   low strength. The leaf colour still reads as the hero's
   colour; the photograph only adds leaf shadow. */
.hero::before,
.page-hero::before {
  z-index: -3;
  background: url("textures/canopy.webp") center / cover no-repeat;
  mix-blend-mode: multiply;
  opacity: 0.26;
}

/* A whisper of hatch over the top, so the hero reads as printed
   rather than photographic. */
.hero::after,
.page-hero::after {
  z-index: -1;
  background-image: var(--tex-hatch);
  opacity: 0.5;
}

/* The dark account panel: wet moss on stone. Kept under 0.4 so
   the bright fronds never come up far enough to threaten the
   off-white text sitting on them. */
.account-story::after {
  z-index: -1;
  background: url("textures/moss.webp") center / cover no-repeat;
  opacity: 0.38;
}

/* Petal panels: handmade rag paper, multiplied. The pink flecks
   in the sheet happen to sit in the same family as --petal,
   which is why this pairing works at all. */
.ownership::after,
.plain-version::after {
  z-index: -1;
  background:
    var(--tex-weave),
    url("textures/fiber.webp") center / cover no-repeat;
  mix-blend-mode: multiply;
  opacity: 0.45;
}

/* Cards in the principle and exit grids get the faintest hatch,
   enough to separate them from the page without a heavier rule. */
.principles article,
.exits article,
.offer-grid article {
  position: relative;
  background-image: var(--tex-hatch);
  background-blend-mode: multiply;
}

/* Orbit tiles: each product blob carries the weave, so the four
   of them read as the same material in different colours. */
.orbit-product,
.orbit-center {
  background-image: var(--tex-weave);
  background-blend-mode: multiply;
}

/* --- Reduced transparency ----------------------------------
   Anyone who has asked the OS for less visual noise gets the
   flat colour. Texture is decoration; it is never load-bearing. */
@media (prefers-reduced-transparency: reduce) {
  body::after,
  .hero::before,
  .page-hero::before,
  .hero::after,
  .page-hero::after,
  .account-story::after,
  .ownership::after,
  .plain-version::after { display: none; }

  .principles article,
  .exits article,
  .offer-grid article,
  .orbit-product,
  .orbit-center { background-image: none; }
}

/* --- Motion primitives -------------------------------------
   juice.js sets these classes. Keeping the transitions in CSS
   rather than in the animation loop means the compositor owns
   them and the main thread stays free for the shader.

   `--ease-overshoot` is the spring curve used for anything that
   arrives; `--ease-settle` is for anything that returns to rest.
   Arrivals overshoot because that is what reads as alive. Things
   returning to rest do not, because a control that wobbles after
   you let go feels broken rather than lively. */
:root {
  --ease-overshoot: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-settle: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur-quick: 140ms;
  --dur-base: 320ms;
  --dur-slow: 720ms;
}

/* Scroll reveal. Elements start displaced and land with the
   overshoot curve; the stagger index is set as a custom property
   by the observer so siblings arrive in sequence rather than as
   one block. */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition:
    opacity var(--dur-slow) var(--ease-settle),
    transform var(--dur-slow) var(--ease-overshoot);
  transition-delay: calc(var(--reveal-index, 0) * 65ms);
  will-change: opacity, transform;
}
.js-reveal [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* A reveal is a response to scrolling, and printing does not
   scroll. Without this, everything below the first page comes
   out of the printer blank — the effect would be quietly
   destroying the document. */
@media print {
  .js-reveal [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* The feedback ladder, in three states.

   Hover lifts and brightens: the element acknowledges the cursor
   before it is committed to. Press drops it below its resting
   position and shrinks it slightly — the recoil is what makes a
   click feel like it landed on something physical rather than
   flipping a state. Release returns on the overshoot curve, so
   the button rebounds past rest for a few frames.

   The press transition is deliberately much faster than the
   release. A slow press feels like lag; a slow release feels
   like weight. */
.account-link,
.button,
.text-link,
.product-link {
  transition:
    transform var(--dur-base) var(--ease-overshoot),
    box-shadow var(--dur-base) var(--ease-settle),
    filter var(--dur-quick) linear;
}
.account-link:active,
.button:active {
  transform: translateY(1px) scale(0.965);
  transition-duration: var(--dur-quick);
  transition-timing-function: linear;
}
.account-link:hover,
.button:hover {
  filter: brightness(1.08);
  box-shadow: 0 8px 20px rgba(8, 39, 24, 0.22);
}

/* Magnetic pull. juice.js writes --mx/--my in pixels; the
   element does the moving itself so the compositor handles it. */
[data-magnetic] {
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
}
[data-magnetic]:active {
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0) scale(0.965);
}

/* Product rows: the verb pill leans out and the row shifts on
   hover, so the whole row reads as one target rather than the
   link inside it being the only live part. */
.product-list article {
  transition:
    transform var(--dur-base) var(--ease-overshoot),
    background-color var(--dur-base) var(--ease-settle);
}
.product-list article:hover {
  transform: translateX(6px);
  background-color: rgba(185, 219, 67, 0.13);
}
.product-verb {
  transition:
    transform var(--dur-base) var(--ease-overshoot),
    background-color var(--dur-base) var(--ease-settle),
    color var(--dur-base) var(--ease-settle);
}
.product-list article:hover .product-verb {
  transform: rotate(-3deg) scale(1.07);
  color: var(--off-white);
  background-color: var(--forest);
}

/* Cards tilt toward the cursor. juice.js supplies the angles. */
[data-tilt] {
  transform: perspective(760px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg))
    translate3d(0, var(--lift, 0px), 0);
  transition: transform var(--dur-base) var(--ease-settle);
}

/* Click ripple. Spawned as a child, removed on animation end. */
.ripple {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%) scale(0);
  background: radial-gradient(circle, rgba(247, 248, 240, 0.55), transparent 68%);
  animation: ripple-out 620ms var(--ease-settle) forwards;
}
@keyframes ripple-out {
  to { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}

/* The orbit's continuous drift. Rings counter-rotate so the two
   of them read as separate planes rather than one turning disc. */
.orbit-ring-one { animation: orbit-drift 64s linear infinite; }
.orbit-ring-two { animation: orbit-drift 92s linear infinite reverse; }
@keyframes orbit-drift {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* Each product blob breathes on its own period, so they never
   sync up into a single pulse. juice.js adds the pointer repulsion
   on top of this via --px/--py. */
.orbit-product {
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0) rotate(var(--spin, 0deg));
  animation: blob-breathe var(--breathe, 7s) ease-in-out infinite;
  transition: transform var(--dur-base) var(--ease-settle);
}
.orbit-product:hover {
  animation-play-state: paused;
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0) rotate(var(--spin, 0deg)) scale(1.09);
}
@keyframes blob-breathe {
  0%, 100% { translate: 0 0; }
  50% { translate: 0 -9px; }
}

.orbit-center {
  animation: center-pulse 9s ease-in-out infinite;
}
@keyframes center-pulse {
  0%, 100% { scale: 1; }
  50% { scale: 1.028; }
}

/* The hero shader canvas.

   It sits between the canopy photograph and the hatch, and it is
   composited with `soft-light`. That is the whole trick: the
   shader emits a grey luminance field with no colour of its own,
   so what the eye reads is the real photograph being lit and
   unlit as the light moves, rather than a shader doing an
   impression of foliage. Neutral grey (0.5) is a no-op under
   soft-light, which is why the field is built around 0.5.

   It never intercepts a pointer event, and it is absent entirely
   under reduced motion. */
.hero-canvas {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  pointer-events: none;
  mix-blend-mode: soft-light;
  opacity: 0;
  transition: opacity 1200ms var(--ease-settle);
}
.hero-canvas.is-ready { opacity: 0.62; }

/* Without soft-light the field would sit as a grey sheet over
   the hero, which is worse than no field at all. */
@supports not (mix-blend-mode: soft-light) {
  .hero-canvas { display: none; }
}

/* Each blob's resting rotation, moved off `transform` so the
   pointer repulsion in juice.js can own that property. These
   match the angles the original layout set. */
.orbit-twyne { --spin: 5deg; }
.orbit-keating { --spin: -6deg; }
.orbit-interleave { --spin: 8deg; }
.orbit-stich { --spin: -8deg; }

/* The idle drifter: a single seed that crosses the hero after a
   long pause, then removes itself. One at a time, never
   repeating within a session more than a few times. */
.drifter {
  position: absolute;
  z-index: 0;
  width: 13px;
  height: 13px;
  pointer-events: none;
  background: var(--forest);
  border-radius: 50% 50% 12% 50%;
  opacity: 0.5;
}

/* --- Reduced motion -----------------------------------------
   The full stop. Textures stay, because a texture is not motion.
   Everything that moves on its own is switched off, and the
   reveal state is forced to its resting value so nothing is left
   invisible on a page that never animates. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .orbit-ring-one,
  .orbit-ring-two,
  .orbit-product,
  .orbit-center {
    animation: none;
  }
  .product-list article:hover,
  .product-list article:hover .product-verb,
  [data-tilt] {
    transform: none;
  }
  .hero-canvas,
  .drifter,
  .ripple {
    display: none;
  }
}
