/* ============================================================================
   JT GOLDEN BALANCE MODULE — v1.0.0

   Figma component "hero · golden balance / motion layer" (492:23711), 666×551.
   Read from the node tree, variant by variant — not from a screenshot.

   MEASURED
     component        666×551
     phi rings        (83, 2)     480×480
     paired axes      (83, 2)     480×480
     equilibrium core (281, 200)   84×84    — concentric with the two above
     drawing centre   (323, 242)
     captions         living equilibrium  y 512–528  amber #f0a416 at 96%
                      6 forces · 3 dims   y 535–551  cyan  #02ccf2 at 92%
     axis labels      11px, +9% tracking, #a8cde0 at 90%

   So the drawing occupies 666×485 and the captions are ordinary flow beneath
   it. That is why --jt-balance-stage-ratio is 666/485 and not 666/551.

   THE SPIRAL IS GONE — removed from the component in Figma. With it went its
   layer, its art file, its slow fade-in, its counter-rotation, and the
   hand-computed off-centre transform-origin that its blur-expanded export
   bounds had forced. Nothing here replaces it.

   SELF-CONTAINED, per MODULES.md. Every colour, size and font this figure needs
   is declared here with a default; the module never reads a token from the host
   page. That is a change from when it lived in landing.css and borrowed --steel,
   --amber, --cyan-art and --display from it — a module that needs the page to
   declare things for it is not a module.

   NAMING
   Everything is --jt-balance- prefixed and .jt-balance scoped. This file styles
   nothing outside .jt-balance, and its keyframes are prefixed too: bare names
   like `jt-spin` would collide with the host page's own.
   ========================================================================== */

.jt-balance {
  /* --- knobs ---------------------------------------------------------------
     How wide the figure is. The comp draws it at 666. A host page that wants it
     to share a row with copy sets this — the landing page uses min(666px, 42vw)
     — but the module works with no page CSS at all. */
  --jt-balance-width: min(666px, 100%);

  /* Pulls the labels in or pushes them out without touching any of the six
     measured radii. 1 is the comp. Below 1 the ring of labels tightens, which
     is what a narrow column needs before the labels start colliding with the
     drawing they annotate. */
  --jt-balance-label-r: 1;

  /* Colours, straight from the component. Amber #f0a416 is the FIGURE's amber
     and is a shade off the brand's #eb8b2d — that difference is in the design,
     not a mistake, so it is kept and named here rather than rounded to the
     nearer token. */
  --jt-balance-cyan:  #02ccf2;
  --jt-balance-amber: #f0a416;
  --jt-balance-steel: #a8cde0;

  --jt-balance-font: "Tektur", ui-monospace, "SFMono-Regular", Menlo, monospace;

  /* Ambient loop periods. Long on purpose: this reads as drift, not rotation.
     Set any of them to 0s to stop that group. */
  --jt-balance-spin-a: 90s;   /* outer circle, inner circle — clockwise      */
  --jt-balance-spin-b: 90s;   /* middle circle, middle hexagon — counter     */
  --jt-balance-spin-c: 75s;   /* inner hexagon — counter, 20% faster         */
  --jt-balance-pulse:   4s;   /* the core                                    */

  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Comp: 27px between the drawing's bottom edge and the first caption. */
  gap: 27px;
  width: var(--jt-balance-width);
  margin: 0;
}

/* The drawing, without the captions. */
.jt-balance__stage {
  position: relative;
  width: 100%;
  aspect-ratio: 666 / 485;
}

/* rings and axes: (83, 2), 480×480, as percentages of the 666×485 stage so the
   figure scales as one piece. */
.jt-balance__layer {
  position: absolute;
  left: 12.4625%;               /*  83/666 */
  top: 0.4124%;                 /*   2/485 */
  width: 72.0721%;              /* 480/666 */
  aspect-ratio: 1;
}

/* The core is concentric with them: 84 wide, centred on (323, 242). */
.jt-balance__layer--core {
  left: 42.1922%;               /* 281/666 */
  top: 41.2371%;                /* 200/485 */
  width: 12.6126%;              /*  84/666 */
}

.jt-balance__svg { display: block; width: 100%; height: 100%; overflow: visible; }

/* --- the six shapes of the rings export, each turning on its own -----------
   transform-box: view-box makes the origin resolve against the 480×480 viewBox
   rather than each path's own tight bounding box — without it every shape would
   spin about its own centre, which for the hexagons is not the figure's. */
.jt-balance__ring {
  transform-box: view-box;
  transform-origin: 50% 50%;
}

/* ---------------------------------------------------------------------------
   Axis labels

   Placed by polar coordinates and orbited as one set. The wrapper is a SQUARE
   centred on the DRAWING's centre — not the stage's, which is 666×485 and would
   send the labels round an ellipse and off their line endings.

   The comp's centre is (323, 242): 48.4985% across and 49.8969% down. Both are
   nearly but not exactly 50%, and "nearly" is the whole reason they are written
   out rather than rounded.
   ------------------------------------------------------------------------- */
.jt-balance__orbit {
  position: absolute;
  left: 48.4985%;               /* 323/666 */
  top: 49.8969%;                /* 242/485 */
  width: 100%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* A full-size box rotated to the label's angle and pushed out along it. The
   text then sits at this box's centre, so no per-label offsets are needed. */
.jt-balance__axis {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  transform: rotate(var(--jt-a))
             translateX(calc(var(--jt-r) * var(--jt-balance-label-r)));
}

/* Comp: 11px, +9% tracking, #a8cde0 at 90%. The counter-rotation cancels the
   parent's angle so the text reads level at every position. */
/* THE LABEL IS TWO WEIGHTS, and the REGULAR one is the default here.
   The component sets each axis as two runs — the force in Tektur Bold, then
   everything from the slash on in Regular — so the base weight is 400 and the
   force opts up to 700. It was 500 across the whole string, which read as one
   compound name rather than a force and the aspect it carries. */
.jt-balance__axis-label {
  transform: rotate(calc(-1 * var(--jt-a)));
  color: var(--jt-balance-steel);
  opacity: 0.9;
  font-family: var(--jt-balance-font);
  font-size: clamp(8px, 0.55vw + 4px, 11px);
  font-weight: 400;
  letter-spacing: 0.09em;
  line-height: 1.35;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Tektur Bold, the component's first run. `<b>` rather than `<strong>`: the
   force is not more IMPORTANT than the aspect, it is typographically set
   apart — which is exactly the distinction the two elements draw. */
.jt-balance__axis-force { font-weight: 700; }


/* ===========================================================================
   THE DUALITY HOVER — opt-in, and entirely CSS

   Figma "[Website Page] Triunity of Dualities" (492:23712) draws this state
   into the frame: hovering EASTERN / FLOW turns that label, its opposite
   WESTERN / STRUCTURE, the line joining them and the ring at each end amber
   (#eb8b2d), leaving the other two axes cold.

   WHY THERE IS NO JAVASCRIPT HERE.
   Every part of the interaction is something the platform already does:

     • the pairing        `data-pair` is on the two labels AND on the five
                          paths that draw their axis, so ONE selector reaches
                          all seven elements.
     • the trigger        :has() lets an ancestor react to a descendant's
                          :hover / :focus-visible. Baseline in every engine
                          since Firefox 121 (Dec 2023).
     • the fade           a transition on stroke / fill / color.
     • the scroll         the labels are real <a href="#…">. The browser
                          scrolls, smoothly, honouring scroll-padding for the
                          fixed menu — and it works with scripting off.

   So the figure gains a behaviour and the module gains no runtime. The one
   thing this costs is that the state cannot be driven programmatically; if a
   future page needs to highlight an axis without a pointer, that is when a
   script earns its place.

   HOW THE COLOUR TRAVELS.
   The blocks below do one thing each: set `--jt-axis-ink` on everything
   carrying the hovered pair's `data-pair`. The generic rules under them read
   that property with the resting colour as the fallback, so the default state
   is written once and the active state is a single custom property.
   ========================================================================= */

.jt-balance {
  /* The hover colour, from the comp. NOT --jt-balance-amber (#f0a416): that is
     the FIGURE's amber, used by the core and the equilibrium caption. This is
     the brand's #eb8b2d, which is what the Triunity frame paints these with.
     Two ambers, two jobs, both named. */
  --jt-balance-hot: #eb8b2d;

  /* How long the pair takes to warm and to cool. One value so the two
     directions match; the comp holds states, not durations, so this is a
     judgement — slow enough to read as a fade rather than a flick. */
  --jt-balance-hover-t: 220ms;
}

/* The orbit is pointer-transparent so the figure never eats clicks meant for
   the page. The LINKS have to opt back in, or nothing could be hovered at
   all — this is the one hole punched in that rule, and only for the labels
   that actually lead somewhere. */
.jt-balance--interactive .jt-balance__axis-link {
  pointer-events: auto;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

/* Resting colours, now expressed as fallbacks. The artwork's own presentation
   attributes (stroke="#02CCF2" and so on) still sit on the paths; any CSS rule
   beats a presentation attribute, so these win and the export stays untouched
   and re-exportable. */
.jt-balance__stage [data-role="line"] { stroke: var(--jt-axis-ink, #02ccf2); }
.jt-balance__stage [data-role="ring"] { stroke: var(--jt-axis-ink, var(--jt-balance-steel)); }
.jt-balance__stage [data-role="dot"]  { fill:   var(--jt-axis-ink, #02ccf2); }

.jt-balance__axis[data-pair] .jt-balance__axis-label {
  color: var(--jt-axis-ink, var(--jt-balance-steel));
}

.jt-balance--interactive .jt-balance__stage [data-pair],
.jt-balance--interactive .jt-balance__axis[data-pair] .jt-balance__axis-label {
  transition: stroke var(--jt-balance-hover-t) ease-out,
              fill   var(--jt-balance-hover-t) ease-out,
              color  var(--jt-balance-hover-t) ease-out,
              opacity var(--jt-balance-hover-t) ease-out;
}

/* One block per duality. They are written out rather than generated because
   :has() cannot be parameterised — and three explicit rules are easier to
   check against the comp than a clever one would be.

   :focus-visible is listed beside :hover so the keyboard sees what the mouse
   sees. A reader tabbing to WESTERN gets the same pair lit before they press
   Enter, which is the only way to know where Enter will take them. */
.jt-balance--interactive:has(.jt-balance__axis--spiritual .jt-balance__axis-link:is(:hover, :focus-visible)) [data-pair="spiritual-material"],
.jt-balance--interactive:has(.jt-balance__axis--material  .jt-balance__axis-link:is(:hover, :focus-visible)) [data-pair="spiritual-material"],
.jt-balance--interactive:has(.jt-balance__axis--heritage  .jt-balance__axis-link:is(:hover, :focus-visible)) [data-pair="heritage-futurism"],
.jt-balance--interactive:has(.jt-balance__axis--futurism  .jt-balance__axis-link:is(:hover, :focus-visible)) [data-pair="heritage-futurism"],
.jt-balance--interactive:has(.jt-balance__axis--eastern   .jt-balance__axis-link:is(:hover, :focus-visible)) [data-pair="eastern-western"],
.jt-balance--interactive:has(.jt-balance__axis--western   .jt-balance__axis-link:is(:hover, :focus-visible)) [data-pair="eastern-western"] {
  --jt-axis-ink: var(--jt-balance-hot);
}

/* The labels rest at 90% opacity; a lit pair comes fully forward. Written as
   a separate rule from the ink so the two can be tuned apart. */
.jt-balance--interactive:has(.jt-balance__axis--spiritual .jt-balance__axis-link:is(:hover, :focus-visible)) .jt-balance__axis[data-pair="spiritual-material"] .jt-balance__axis-label,
.jt-balance--interactive:has(.jt-balance__axis--material  .jt-balance__axis-link:is(:hover, :focus-visible)) .jt-balance__axis[data-pair="spiritual-material"] .jt-balance__axis-label,
.jt-balance--interactive:has(.jt-balance__axis--heritage  .jt-balance__axis-link:is(:hover, :focus-visible)) .jt-balance__axis[data-pair="heritage-futurism"] .jt-balance__axis-label,
.jt-balance--interactive:has(.jt-balance__axis--futurism  .jt-balance__axis-link:is(:hover, :focus-visible)) .jt-balance__axis[data-pair="heritage-futurism"] .jt-balance__axis-label,
.jt-balance--interactive:has(.jt-balance__axis--eastern   .jt-balance__axis-link:is(:hover, :focus-visible)) .jt-balance__axis[data-pair="eastern-western"] .jt-balance__axis-label,
.jt-balance--interactive:has(.jt-balance__axis--western   .jt-balance__axis-link:is(:hover, :focus-visible)) .jt-balance__axis[data-pair="eastern-western"] .jt-balance__axis-label {
  opacity: 1;
}

.jt-balance__axis-link:focus-visible {
  outline: 2px solid var(--jt-balance-hot);
  outline-offset: 4px;
  border-radius: 2px;
}

/* The fade is decoration; the colour change is the information. Reduced motion
   drops the transition and keeps the state, which is the right half to lose. */
@media (prefers-reduced-motion: reduce) {
  .jt-balance--interactive .jt-balance__stage [data-pair],
  .jt-balance--interactive .jt-balance__axis[data-pair] .jt-balance__axis-label {
    transition: none;
  }
}


/* ---------------------------------------------------------------------------
   Captions
   Comp: LIVING EQUILIBRIUM (amber) above SIX FORCES · THREE DIMENSIONS (cyan),
   at y 512 and 535 — so 7px between the two boxes.
   ------------------------------------------------------------------------- */
.jt-balance__captions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  margin: 0;
  text-align: center;
}

.jt-balance__equilibrium,
.jt-balance__title {
  font-family: var(--jt-balance-font);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 1.7px;
  text-transform: uppercase;
}

.jt-balance__equilibrium { color: var(--jt-balance-amber); opacity: 0.96; }
.jt-balance__title       { color: var(--jt-balance-cyan);  opacity: 0.92; }

/* The figure's real description, for anyone who cannot see it. The drawing is
   aria-hidden and the six axis labels alone read as a list of words, so without
   this the <figure> announces nothing useful. Not `display: none`, which would
   take it out of the accessibility tree along with the layout. */
.jt-balance__description {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Captions off — the Triunity page embeds the figure mid-article with both of
   them switched off in Figma. VISUALLY hidden, never removed: the <figure> is
   labelled by its title caption and described by the hidden paragraph, so
   deleting them would leave an unlabelled figure with no description. Same
   clip-path treatment the description already uses.

   The figure's own gap goes with them, so the drawing is the whole block. */
.jt-balance--no-captions { gap: 0; }

.jt-balance--no-captions .jt-balance__captions {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------------------
   The ambient loop

   Applied by balance.js once every reveal has landed, so a loop can never fight
   a draw. Keyframe names are module-prefixed: a bare `jt-spin` would collide
   with the host page's own, and the loser is whichever stylesheet loads second.
   ------------------------------------------------------------------------- */
@keyframes jt-balance-spin     { to { transform: rotate(360deg); } }
@keyframes jt-balance-spin-ccw { to { transform: rotate(-360deg); } }
@keyframes jt-balance-pulse {
  0%, 100% { transform: scale(1);    opacity: 0.85; }
  50%      { transform: scale(1.06); opacity: 1; }
}

.jt-balance.is-loop-active .jt-balance__layer--rings.is-looping .jt-balance__ring[data-spin="a"] {
  animation: jt-balance-spin var(--jt-balance-spin-a) linear infinite;
}
.jt-balance.is-loop-active .jt-balance__layer--rings.is-looping .jt-balance__ring[data-spin="b"] {
  animation: jt-balance-spin-ccw var(--jt-balance-spin-b) linear infinite;
}
.jt-balance.is-loop-active .jt-balance__layer--rings.is-looping .jt-balance__ring[data-spin="c"] {
  animation: jt-balance-spin-ccw var(--jt-balance-spin-c) linear infinite;
}
.jt-balance.is-loop-active .jt-balance__layer--core.is-looping .jt-balance__svg {
  animation: jt-balance-pulse var(--jt-balance-pulse) ease-in-out infinite;
}

/* ---------------------------------------------------------------------------
   Reduced motion

   balance.js does not arm the figure at all under this preference — nothing is
   hidden, so nothing has to be revealed. This is the belt to that braces: it
   stops the loops for anyone whose preference changes after load, which the
   script cannot retroactively undo.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .jt-balance__layer.is-looping .jt-balance__svg,
  .jt-balance__ring {
    animation: none !important;
  }
}

/* ---------------------------------------------------------------------------
   Narrow viewports

   The comp draws one width; both breakpoints below are derived and are
   candidates for a mobile frame.

     ≤600   the labels are set smaller and pulled in — at the comp's radii they
            start touching the line endings they annotate
     ≤480   they are hidden. Overlapping labels are worse than no labels: the
            drawing still reads, and the two captions still say what it is.
   ------------------------------------------------------------------------- */
@media (max-width: 600px) {
  .jt-balance {
    --jt-balance-label-r: 0.94;
  }
  .jt-balance__axis-label { font-size: 8px; letter-spacing: 0.06em; }
}

@media (max-width: 480px) {
  .jt-balance__axis { display: none; }
}
