/* ==========================================================================
   Fraktalicious Cycle Dial — presentation layer
   Include via: <link rel="stylesheet" href="/dial.css">
                <script src="/dial.js"></script>

   ⛔ LOCKED BY THE OWNER, 2026-08-21. DO NOT REMOVE OR REDESIGN.

      "the cycle position circle graphic with the brand asset in the center
      is AWESOME, dont lose that."  — the owner, 2026-08-21

   The six-phase ring with the brand primitive at its centre is the product —
   a six-phase navigator drawn as a live instrument — and it is set in stone
   by owner decision. The full lock note lives in public/index.html above its
   copy of the markup; read that before changing anything here.

      Allowed:     swapping `.core`'s src for a new brand primitive (that is
                   what the slot is for), retuning colour/scale/motion.
      Not allowed: replacing it with an icon, a progress bar, a horizontal
                   stepper or a chart; dropping it from any screen it is on.

   ── Two copies exist, deliberately ─────────────────────────────────────────
   public/index.html carries its OWN inline copy of this CSS and of the SVG.
   That copy is server-rendered (`data-ssr-set`, lit arc chosen from a
   server-set `data-phase-index`) so the marketing page's ring is correct with
   JavaScript disabled, for crawlers, and in a social-card screenshot. A
   client-side renderer cannot satisfy that, which is why index.html was not
   migrated onto this file. See dial.js's header for the same note and for the
   geometry that must be kept in sync between the two.

   ── Tokens ─────────────────────────────────────────────────────────────────
   The dial's own rule colours are defined on `.dialwrap` below and default to
   LITERAL values, not to the host page's hairline token. That is deliberate and
   was a measured bug fix — see the note there. A host may override by defining
   `--rule` / `--rule-dim`, but it never has to, and the instrument looks the
   same on every page whether it does or not.
   The one token worth wiring per-page is `--live`: set it to the current
   phase's colour on the mount element and the whole instrument follows.
   ========================================================================== */

/* The mount. `position: relative` is load-bearing — `.core` is absolutely
   positioned against it. The padding keeps the ring's outermost legends off the
   container edge on wide screens — and must stay symmetric, see the note inside. */
.dialwrap {
  position: relative;
  display: grid;
  place-items: center;
  /* ⚠ KEEP THIS PADDING SYMMETRIC. It was `18px 18px 18px 0` and that silently
     decentred the flower by 9px — measured 2026-08-24 after the owner spotted it.
     `.core` is position:absolute, so its containing block is this element's PADDING
     box, while the <svg> is an in-flow grid item centred in the CONTENT box. Any
     left/right asymmetry makes those two boxes disagree by half the difference, and
     the core drifts off the ring by exactly that much. The vertical axis was already
     symmetric, which is why the drift was purely horizontal.
     The offset predates the v2 artwork — the old asset carried ~30% transparent
     padding, so its visible flower was smaller and the same 9px read as less. */
  padding: 18px;

  /* THE DIAL CARRIES ITS OWN RULE COLOURS — do not chain these to the host page's
     hairline token. Measured 2026-08-23: chaining to `--line` made the dial render
     DIFFERENTLY on the portal than on the homepage. index.html defines `--rule` as
     rgba(237,234,228,0.13) — a translucent warm white that reads as a soft hairline
     over the dark ground — but overview.html has no `--rule` and its `--line` is
     #1c1c26, an OPAQUE near-black. The unlit arcs fell through to it and became
     invisible, which destroys the whole point of the element: you cannot read "six
     positions, one of them lit" if you cannot see the other five.
     A host page MAY still override by defining `--rule`/`--rule-dim`, but the
     fallback is now the literal value, not another token that might be anything. */
  --dial-rule:     var(--rule, rgba(237, 234, 228, 0.13));
  --dial-rule-dim: var(--rule-dim, rgba(237, 234, 228, 0.06));
  --dial-legend:   var(--ink-3, #6e6a63);
}

.dial {
  width: 100%;
  max-width: 600px;
  height: auto;
  display: block;
  overflow: visible;
}

.bezel { fill: none; stroke: var(--dial-rule-dim); stroke-width: 1; }
.ticks {
  fill: none;
  stroke: var(--dial-rule);
  stroke-width: 1;
  stroke-dasharray: 1.7 16.2;
}
.arc {
  fill: none;
  stroke: var(--dial-rule);
  stroke-width: 1.5;
  stroke-linecap: butt;
}
.arc.lit {
  stroke: var(--live, #22d3ee);
  stroke-width: 7;
  filter: drop-shadow(0 0 14px color-mix(in oklab, var(--live, #22d3ee) 55%, transparent));
}
.spoke { stroke: var(--dial-rule-dim); stroke-width: 1; }

/* These legends are the only thing telling a first-time reader what the six
   phases ARE. They are content, not chrome — at 9.4px in the faintest ink they
   were invisible, so they sit at 11.4px in the mid-ink. */
.plabel {
  font-family: var(--mono, 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace);
  font-size: 11.4px;
  font-weight: 500;
  letter-spacing: 0.15em;
  fill: var(--dial-legend);
  text-transform: uppercase;
}
.plabel.on { fill: var(--live, #22d3ee); font-weight: 700; }

.marker { fill: var(--live, #22d3ee); }
.ping { fill: none; stroke: var(--live, #22d3ee); stroke-width: 1.4; opacity: 0; }

/* The lit arc + lit label are chosen by an attribute selector on
   `data-phase-index`, never by a JS-applied class. On index.html that index is
   SERVER-set, so the ring is right with JavaScript disabled and in any static
   screenshot; dial.js sets the same attribute so both copies light identically. */
.dial[data-phase-index="0"] .arc[data-arc="0"], .dial[data-phase-index="1"] .arc[data-arc="1"],
.dial[data-phase-index="2"] .arc[data-arc="2"], .dial[data-phase-index="3"] .arc[data-arc="3"],
.dial[data-phase-index="4"] .arc[data-arc="4"], .dial[data-phase-index="5"] .arc[data-arc="5"] {
  stroke: var(--live, #22d3ee);
  stroke-width: 7;
  filter: drop-shadow(0 0 14px color-mix(in oklab, var(--live, #22d3ee) 55%, transparent));
}
.dial[data-phase-index="0"] .plabel[data-lbl="0"], .dial[data-phase-index="1"] .plabel[data-lbl="1"],
.dial[data-phase-index="2"] .plabel[data-lbl="2"], .dial[data-phase-index="3"] .plabel[data-lbl="3"],
.dial[data-phase-index="4"] .plabel[data-lbl="4"], .dial[data-phase-index="5"] .plabel[data-lbl="5"] {
  fill: var(--live, #22d3ee);
  font-weight: 700;
}

/* NO PHASE KNOWN → the ring renders with every arc unlit and no marker at all.
   This product never renders a guessed phase, so "we don't know yet" has to be
   visibly different from "phase 0", not merely a different highlight. Purely
   declarative: dial.js just removes the attribute. */
.dial:not([data-phase-index]) .marker,
.dial:not([data-phase-index]) .ping { display: none; }

@keyframes ping { 0% { r: 9; opacity: .85; } 100% { r: 34; opacity: 0; } }
.ping { animation: ping 2.9s cubic-bezier(.2, .6, .3, 1) infinite; }
@keyframes drift { to { transform: rotate(360deg); } }
.bezel-spin { transform-origin: 200px 200px; animation: drift 240s linear infinite; }

/* The brand primitive, doing real work as the dial's core — not a watermark.
   This is the SWAPPABLE slot the owner's lock note allows ("swapping .core's src for
   a new brand primitive ... retuning colour/scale/motion").

   ── PAINTED THROUGH A MASK, NOT AN <img> (2026-08-24, fraktal-flower-v2) ───────────
   The v2 artwork is pure-white RGB with the whole flower carried in the ALPHA channel,
   so it is a stencil rather than a picture. That is strictly better than the cyan PNG
   it replaces, which had its colour BAKED IN: on a green ACCUMULATE phase the ring,
   marker, labels and glow all turned green while the flower stayed cyan. The core was
   the one part of the instrument that did not obey --live.
   Masking fixes that by construction — the gradient below is painted in the live phase
   colour and the PNG only decides WHERE it lands, so the core can never drift from the
   rest of the dial again, on any phase, with no second asset to maintain.
   `mix-blend-mode: screen` is gone with it: it existed to stop the baked cyan reading
   as a flat sticker, and over a real gradient it only washes the colour out.
   aspect-ratio is the artwork's true ratio (1016x1135, trimmed to its bounding box). */
.core {
  position: absolute;
  /* 38%, not the old 44%. MEASURED: the inner bezel circle is r=112 in a 504-unit-wide
     viewBox, i.e. 44.4% of the rendered SVG — so 44% put the petal tips exactly ON the
     ring. The old asset got away with it because ~30% of its canvas was transparent
     padding; this one is trimmed to its bounding box, so the same percentage renders
     about 1.4x larger. 38% restores the intended inset (146px inside a 170px bezel). */
  width: 38%;
  max-width: 216px;
  aspect-ratio: 1016 / 1135;
  pointer-events: none;
  user-select: none;
  /* Lit from the upper left: a light tint, the phase colour, then a deepened edge. */
  /* ⚠ FALLBACK FIRST — LOAD-BEARING, not a duplicate. This element is painted THROUGH
     A MASK, so if the color-mix() gradient below is rejected the whole `background`
     declaration is invalid and the artwork vanishes ENTIRELY rather than degrading.
     That is what happened: the dial's flower was missing on iOS Safari (owner,
     2026-08-27) while the SVG around it rendered. Flat phase colour is the floor. */
  background: var(--live, #22d3ee);
  background: linear-gradient(155deg,
    color-mix(in oklab, var(--live, #22d3ee) 44%, #fff) 0%,
    var(--live, #22d3ee) 46%,
    color-mix(in oklab, var(--live, #22d3ee) 66%, #000) 100%);
  -webkit-mask: url("/fraktal-flower-v2.png") center / contain no-repeat;
          mask: url("/fraktal-flower-v2.png") center / contain no-repeat;
  filter: drop-shadow(0 0 38px color-mix(in oklab, var(--live, #22d3ee) 34%, transparent));
}

@media (prefers-reduced-motion: reduce) {
  .ping, .bezel-spin { animation: none; }
  .ping { opacity: .3; }
}
