/*
  fx.css -- the spectacle layer.

  Everything here is additive craft on top of styles.css. Deleting this file
  and its <link> returns the site to its previous appearance with no other
  change; that is the reversibility story and it is worth preserving.

  Three constraints from styles.css that every rule below respects:

    1. `* { border-radius: 0 !important }` (styles.css:29) is a blanket
       !important. Nothing here can use radius, and nothing here tries.
    2. The reduced-motion block (styles.css:~58) collapses every animation and
       transition duration to ~0. So no effect here may depend on a transition
       actually running to reach its final appearance -- each must LAND on its
       finished state. Reduced motion gets the composition, minus the movement.
    3. .panel has `overflow: hidden` (required by panelSweep) and motion.js
       writes inline `transform` on it. Nothing here sets transform on .panel
       or expects to paint outside it.
*/

:root {
  /* Not brand-ratified -- following the --state-* precedent in styles.css,
     which documents the same status. These exist only for light effects. */
  --halo: rgba(201, 162, 75, 0.16);   /* pointer-following glow */
  --shaft: rgba(240, 226, 188, 0.07); /* light shafts / sweeps */
  --ember: rgba(201, 162, 75, 0.42);  /* icon + edge accents */
}

/* ===========================================================================
   Icons

   Linear (stroked), not filled or duotone. Two reasons, both load-bearing:

   - The whole site is built from 1px hairlines and flat type. Filled duotone
     glyphs read as blobs against that and fight the design language; solar's
     linear set strokes at 1.5, which is the same visual weight as the rules
     already on the page.
   - Legibility. A filled glyph at 28px on near-black loses its interior
     detail entirely. Stroked shapes keep it.

   Colour is --gold, never --brass: styles.css documents --brass as
   borders-only because it fails contrast at 3.9:1, and an icon carrying
   meaning is content, not a divider.
   =========================================================================== */

.fx-icon {
  width: 30px; height: 30px;
  display: block;
  color: var(--gold);
  flex: 0 0 auto;
  transition: color 160ms ease, transform 220ms cubic-bezier(.2,.7,.3,1);
}
.fx-icon-sm { width: 19px; height: 19px; }

/* ===========================================================================
   Hero light shafts

   Done here rather than in the WebGL scene. Additive geometry on an alpha:true
   canvas composites against the page unpredictably -- the canvas gains alpha
   wherever the glow draws, so a dim beam becomes an opaque patch that hides
   the CSS hero glow behind it. mix-blend-mode: screen cannot darken what is
   underneath it, which is exactly the guarantee a light effect needs.

   The angle matches the CSS hero glow origin already set in styles.css
   (14% -8%, upper left). If the two disagree the hero reads as two pages
   pasted together, so this is not a free choice.
   =========================================================================== */

/*
  Measured 2026-09-30, from composited pixels rather than token values, because
  a light layer changes the ground the text sits on:

    hero, worst case   — bone 12.02:1 · white 14.99:1 · gold 6.25:1  all pass
                         slate would be 4.35:1 — UNDER the 4.5:1 body threshold
    final-cta          — bone 14.66:1 · white 18.28:1 · gold 7.62:1 · slate 5.30:1

  So: do not put --slate text in the hero. The shafts brighten the ground enough
  that slate's usual 5.7:1 stops holding there. Anywhere else on the page it is
  fine, including the final CTA, whose glow is weaker.
*/
.hero { position: relative; }
.hero::after {
  content: "";
  position: absolute;
  inset: -10% -5% 0 -5%;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0.55;
  background:
    linear-gradient(107deg, transparent 38%, rgba(240,226,188,0.055) 42%, transparent 46%),
    linear-gradient(107deg, transparent 52%, rgba(201,162,75,0.07) 57%, transparent 62%),
    linear-gradient(107deg, transparent 66%, rgba(240,226,188,0.04) 69%, transparent 73%);
  animation: fxShaftDrift 14s ease-in-out infinite alternate;
}
@keyframes fxShaftDrift {
  from { opacity: 0.42; transform: translateX(-0.6%); }
  to   { opacity: 0.62; transform: translateX(0.6%); }
}

/* The hero content must sit above the shaft layer. */
.hero-inner { position: relative; z-index: 1; }

/*
  No-3D hero: a static composition, not an empty column.

  gl-runtime.js sets .no-gl on <html> whenever the capability gate declines
  (reduced motion, no WebGL, or the download failed). Without this the right
  half of the hero is simply blank for those visitors — measurably worse than
  what everyone else sees. This draws the mark's own 5-bar rhythm in CSS: the
  same five heights the WebGL scene derives from the logo SVG (60/84/108/84/60),
  with the centre member in leaf, lit from the upper left to agree with the
  page's light source.

  Scoped above 860px because styles.css already hides .hero-3d below that.
*/
@media (min-width: 861px) {
  :root.no-gl .hero-3d canvas { display: none; }
  :root.no-gl .hero-3d {
    opacity: 1;
    /* Five bars as five gradient layers, painted bottom-aligned. Widths and the
       60/84/108/84/60 height ratio come from the same logo geometry hero3d.js
       uses; the centre member is leaf, as in the mark. A sixth layer is the
       base plate. Gradients run 160deg so the highlight falls upper-left,
       matching the page's light source. */
    background-image:
      linear-gradient(160deg, #F0E2BC 0%, #C9A24B 100%),
      linear-gradient(160deg, #C9A24B 0%, #8A6A24 100%),
      linear-gradient(160deg, #C9A24B 0%, #8A6A24 100%),
      linear-gradient(160deg, #C9A24B 0%, #8A6A24 100%),
      linear-gradient(160deg, #C9A24B 0%, #8A6A24 100%),
      linear-gradient(#8A6A24, #8A6A24);
    background-repeat: no-repeat;
    background-size:
      11% 46%,
      11% 36%, 11% 36%,
      11% 26%, 11% 26%,
      63% 3px;
    background-position:
      50% 82%,
      35.5% 82%, 64.5% 82%,
      21% 82%, 79% 82%,
      50% 82%;
  }
}

/* ===========================================================================
   Trust strip -- icon + label, and a single gold sweep as it enters
   =========================================================================== */

.trust-list li {
  display: flex;
  align-items: center;
  gap: 10px;
}
.trust-list .fx-icon { color: var(--gold); opacity: 0.85; }

/* The sweep is a ::after on the section so it cannot disturb list layout.
   `forwards` + a one-shot animation means the end state is "invisible", which
   is also the reduced-motion state -- nothing is left half-lit. */
.trust-strip { position: relative; overflow: hidden; }
.trust-strip::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 35%, var(--shaft) 50%, transparent 65%);
  transform: translateX(-100%);
  pointer-events: none;
  opacity: 0;
}
.trust-strip.reveal-in::after {
  animation: fxSweep 1400ms cubic-bezier(.3,.7,.4,1) 200ms 1 both;
}
@keyframes fxSweep {
  0%   { transform: translateX(-100%); opacity: 0; }
  20%  { opacity: 1; }
  80%  { opacity: 1; }
  100% { transform: translateX(100%); opacity: 0; }
}

/* ===========================================================================
   Problem tiles -- pointer-tracked spotlight
   Coordinates come from motion.js as --mx/--my. Defaults are set here so a
   tile that has never been hovered (or a keyboard-only session) renders
   correctly rather than with an unresolved var.
   =========================================================================== */

.problem-tile {
  position: relative;
  --mx: 50%; --my: 0%;
}
.problem-tile::before {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(240px circle at var(--mx) var(--my), var(--halo), transparent 70%);
  opacity: 0;
  transition: opacity 260ms ease;
  pointer-events: none;
}
.problem-tile:hover::before,
.problem-tile:focus-within::before { opacity: 1; }

.problem-tile .fx-icon { margin-bottom: 16px; color: var(--gold); opacity: 0.8; }
.problem-tile:hover .fx-icon,
.problem-tile:focus-within .fx-icon { opacity: 1; }

/* Tile content must sit above the spotlight layer. */
.problem-tile h3, .problem-tile p, .problem-tile .fx-icon { position: relative; z-index: 1; }

/* ===========================================================================
   Solution cards -- animated gradient edge
   .panel::before is taken by panelSweep in styles.css, so this uses ::after.
   =========================================================================== */

@property --fx-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.solution-card { position: relative; }
.solution-card::after {
  content: "";
  position: absolute; inset: 0;
  padding: 1px;
  background: conic-gradient(from var(--fx-angle),
              transparent 0deg, var(--ember) 40deg, transparent 110deg, transparent 360deg);
  /* Mask to an outline: the gradient paints only the 1px border band. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  opacity: 0;
  transition: opacity 240ms ease;
  pointer-events: none;
}
.solution-card:hover::after,
.solution-card:focus-within::after {
  opacity: 1;
  animation: fxRotate 4s linear infinite;
}
@keyframes fxRotate { to { --fx-angle: 360deg; } }

.solution-card .fx-icon { margin-bottom: 16px; }
.solution-card:hover .fx-icon,
.solution-card:focus-within .fx-icon { transform: translateY(-2px); }

/* ===========================================================================
   FAQ -- native <details>, smoothed
   The markup keeps <details>/<summary> exactly so keyboard and screen-reader
   behaviour stay free. Only presentation changes.
   =========================================================================== */

.faq-list summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  cursor: pointer;
  list-style: none;
}
.faq-list summary::-webkit-details-marker { display: none; }
.faq-list .fx-q { flex: 1 1 auto; }

.fx-chev {
  width: 20px; height: 20px;
  flex: 0 0 auto;
  color: var(--gold);
  opacity: 0.7;
  transition: transform 220ms cubic-bezier(.2,.7,.3,1), color 160ms ease;
}
.faq-list details[open] .fx-chev { transform: rotate(180deg); opacity: 1; }
.faq-list summary:hover .fx-chev { opacity: 1; }

/* 0fr -> 1fr is the only way to animate to an unknown content height.
   details[open] sets the end state directly, so with transitions disabled the
   panel is simply open -- never stuck at zero height. */
.faq-list .details-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 260ms cubic-bezier(.2,.7,.3,1);
}
.faq-list details[open] .details-body { grid-template-rows: 1fr; }
.faq-list .details-inner { overflow: hidden; }

.faq-list details[open] { border-left: 2px solid var(--gold); padding-left: 18px; }

/* ===========================================================================
   Buttons -- a moving specular band, the CSS stand-in for a shader.
   Deliberately not WebGL: a GL context per button would blow the context cap
   for no visual gain at this size.
   =========================================================================== */

.btn { position: relative; overflow: hidden; }
.btn::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 40%, rgba(255,255,255,0.18) 50%, transparent 60%);
  transform: translateX(-120%);
  transition: transform 620ms cubic-bezier(.2,.7,.3,1);
  pointer-events: none;
}
.btn:hover::after, .btn:focus-visible::after { transform: translateX(120%); }

/* ===========================================================================
   Final CTA — the page's last frame, and it was its weakest

   Scored 4/10 on composition in the Phase 7 pass: a flat band with a small
   heading and three equal buttons. It is the last thing anyone sees, so it
   gets the strongest treatment on the page after the hero.

   Same mix-blend-mode: screen shafts as the hero, for the same reason — they
   cannot darken what is beneath them, so there is no way to produce the
   opaque-patch artifact that sank the WebGL version.
   =========================================================================== */

.final-cta { position: relative; overflow: hidden; }
.final-cta::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: screen;
  background:
    radial-gradient(1100px 380px at 50% 118%, rgba(201,162,75,0.20), transparent 70%),
    linear-gradient(103deg, transparent 40%, rgba(240,226,188,0.05) 44%, transparent 48%),
    linear-gradient(103deg, transparent 58%, rgba(201,162,75,0.06) 62%, transparent 66%);
  animation: fxShaftDrift 16s ease-in-out infinite alternate;
}
.final-cta > * { position: relative; z-index: 1; }

/* A single hairline above the band, so the section reads as a deliberate
   close rather than as one more row that happens to be last. */
.final-cta::after {
  content: "";
  position: absolute;
  top: 0; left: 50%;
  transform: translateX(-50%);
  width: 220px;
  border-top: 1px solid var(--gold);
  pointer-events: none;
}

/* ===========================================================================
   Model section — make four boxes read as one sequence

   Scored 5/10: correct, but the four steps sat as equal blocks with nothing
   carrying the eye across them. A rule through the step numbers turns a row
   of cards into a progression, which is what the content actually is.
   =========================================================================== */

/* Only while the grid is one row. styles.css reflows .model-grid to two
   columns at 860px and one at 480px, where a horizontal connector would run
   between unrelated steps and read as a mistake. */
@media (min-width: 861px) {
  .model-section .model-step { position: relative; }
  .model-section .model-step::before {
    content: "";
    position: absolute;
    top: 34px;
    left: 0;
    right: 0;
    border-top: 1px solid var(--brass);
    opacity: 0.55;
    pointer-events: none;
  }
  /* The sequence starts at the first number and ends at the last, rather than
     running off both edges of the section. */
  .model-section .model-step:first-child::before { left: 50%; }
  .model-section .model-step:last-child::before { right: 50%; }
  .model-section .model-number {
    position: relative;
    display: inline-block;
    padding-right: 14px;
    background: var(--graphite);
  }
}

/* ===========================================================================
   Below-the-fold rendering cost
   Skips layout and paint for offscreen sections. contain-intrinsic-size keeps
   the scrollbar honest so this cannot introduce CLS.
   =========================================================================== */

.problem-section, .solutions-section, .proof-section, .faq-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 600px;
}

/* ===========================================================================
   Reduced motion
   Mirror of everything above. styles.css already zeroes durations globally;
   this block makes sure each effect's RESTING state is the correct one rather
   than a frozen mid-animation frame.
   =========================================================================== */

@media (prefers-reduced-motion: reduce) {
  /* Shafts stay -- they are composition, not movement. Only the drift stops,
     landing at a fixed mid opacity rather than a frozen keyframe. */
  .hero::after { animation: none !important; opacity: 0.5; transform: none; }
  .trust-strip::after { animation: none !important; opacity: 0 !important; }
  .solution-card::after { animation: none !important; }
  .solution-card:hover::after,
  .solution-card:focus-within::after { opacity: 1; }
  .btn::after { display: none; }
  .problem-tile::before { transition: none; }
  .fx-icon, .fx-chev { transition: none; }
  .faq-list .details-body { transition: none; }
  .solution-card:hover .fx-icon,
  .solution-card:focus-within .fx-icon { transform: none; }
}
