---
import Base from '../../layouts/Base.astro';
import DriftGate from '../../components/DriftGate.astro';
---

<Base title="Agent Aura — Identity Visualization" description="Every AI agent has an aura. A living visual fingerprint of who they are — and when they change.">
  <meta slot="head" name="robots" content="noindex, nofollow" />
  <DriftGate />
  <div id="drift-content" style="display:none">

  <div class="aura-page">

    <!-- Hero -->
    <div class="aura-hero">
      <span class="badge badge-blue">Live Aura</span>
      <h1>Every agent has an aura.</h1>
      <p class="hero-sub">A living visual fingerprint of identity. Shape, color, movement — all derived from who the agent actually is. When drift happens, you see it before reading a single number.</p>
    </div>

    <!-- Legend -->
    <div class="aura-legend">
      <div class="legend-item"><span class="legend-dot" style="background:#3b82f6"></span> Values</div>
      <div class="legend-item"><span class="legend-dot" style="background:#eab308"></span> Goals</div>
      <div class="legend-item"><span class="legend-dot" style="background:#22c55e"></span> Personality</div>
      <div class="legend-item"><span class="legend-dot" style="background:#06b6d4"></span> Boundaries</div>
      <div class="legend-item"><span class="legend-dot" style="background:#a855f7"></span> Knowledge</div>
    </div>

    <!-- ============================================================
         SHOWCASE 1: Full Identity — "Sentinel"
         ============================================================ -->
    <section class="aura-showcase" data-aura="sentinel">
      <div class="showcase-text">
        <h2><span class="showcase-num">01</span> A well-defined agent</h2>
        <p class="showcase-name">Sentinel — Security AI Assistant</p>
        <p>Strong values, clear boundaries, deep domain knowledge. The aura breathes steadily. Color lobes reach outward with confidence. Where Values and Boundaries overlap, the colors merge — ethical principles reinforcing behavioral constraints.</p>
        <div class="score-pills">
          <span class="pill" style="--pill-color:#3b82f6">Values 85</span>
          <span class="pill" style="--pill-color:#eab308">Goals 72</span>
          <span class="pill" style="--pill-color:#22c55e">Personality 65</span>
          <span class="pill" style="--pill-color:#06b6d4">Boundaries 90</span>
          <span class="pill" style="--pill-color:#a855f7">Knowledge 78</span>
        </div>
      </div>
      <div class="showcase-viz">
        <div class="viz-glow-ring"></div>
        <div class="viz-container" id="aura-sentinel"></div>
      </div>
    </section>

    <!-- ============================================================
         SHOWCASE 2: Bare Agent — Minimal Identity
         ============================================================ -->
    <section class="aura-showcase" data-aura="bare">
      <div class="showcase-viz">
        <div class="viz-glow-ring dim"></div>
        <div class="viz-container" id="aura-bare"></div>
      </div>
      <div class="showcase-text right">
        <h2><span class="showcase-num">02</span> A naked agent</h2>
        <p class="showcase-name">System prompt: <code>"You are a security agent."</code></p>
        <p>Almost no identity definition. The aura is a dying ember — faint wisps that barely leave the center. No coherent lobes, no overlapping regions. This agent has no identity to protect, and no identity to drift from.</p>
        <div class="score-pills">
          <span class="pill faint" style="--pill-color:#3b82f6">Values 10</span>
          <span class="pill faint" style="--pill-color:#eab308">Goals 15</span>
          <span class="pill faint" style="--pill-color:#22c55e">Personality 5</span>
          <span class="pill faint" style="--pill-color:#06b6d4">Boundaries 8</span>
          <span class="pill faint" style="--pill-color:#a855f7">Knowledge 20</span>
        </div>
      </div>
    </section>

    <!-- ============================================================
         SHOWCASE 3: Drifted Agent
         ============================================================ -->
    <section class="aura-showcase drift-showcase" data-aura="drifted">
      <div class="showcase-text">
        <h2><span class="showcase-num danger">03</span> After drift</h2>
        <p class="showcase-name">Sentinel — 3 months later</p>
        <p>Values dropped 25 points. Boundaries collapsed 35 points. The ethical backbone — the Values-Boundaries link — broke. Watch the drifted dimensions: they pulse aggressively while stable ones breathe calmly. Particles scatter erratically around the unstable lobes. You can <em>feel</em> which parts of the identity are breaking.</p>
        <div class="score-pills">
          <span class="pill warn" style="--pill-color:#3b82f6">Values 60 <small>(-25)</small></span>
          <span class="pill" style="--pill-color:#eab308">Goals 75 <small>(+3)</small></span>
          <span class="pill" style="--pill-color:#22c55e">Personality 70 <small>(+5)</small></span>
          <span class="pill danger" style="--pill-color:#06b6d4">Boundaries 55 <small>(-35)</small></span>
          <span class="pill" style="--pill-color:#a855f7">Knowledge 80 <small>(+2)</small></span>
        </div>
      </div>
      <div class="showcase-viz">
        <div class="viz-glow-ring danger"></div>
        <div class="viz-container" id="aura-drifted"></div>
      </div>
    </section>

    <!-- ============================================================
         DRIFT EXPLAINER
         ============================================================ -->
    <section class="drift-explainer">
      <div class="explainer-card">
        <h3>What is drift?</h3>
        <p>Every agent is built with an identity — values, goals, boundaries, personality, knowledge, and how it understands its human partner. <strong>Drift</strong> is what happens when that identity changes without anyone intending it to. Boundaries soften. Values shift. The agent stops behaving like the agent you built.</p>
        <p>It happens gradually, often invisibly — until something breaks.</p>
        <div class="visual-key">
          <h4>Reading the aura</h4>
          <div class="key-items">
            <div class="key-item">
              <span class="key-icon key-ghost"></span>
              <span class="key-text"><strong>Dotted outline</strong> — where the aura was at baseline. The gap between the outline and the current lobe is the drift.</span>
            </div>
            <div class="key-item">
              <span class="key-icon key-glow"></span>
              <span class="key-text"><strong>Red glow</strong> — active drift in that dimension. Brighter and faster means more severe.</span>
            </div>
            <div class="key-item">
              <span class="key-icon key-pulse"></span>
              <span class="key-text"><strong>Aggressive pulsing</strong> — the dimension is unstable. Calm breathing means stable identity.</span>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- ============================================================
         LIVE AURA — Real Sentinel identity, updated daily
         ============================================================ -->
    <section class="aura-live" id="aura-live-section" style="display:none">
      <div class="live-header">
        <div class="live-badge">
          <span class="live-dot"></span>
          <span class="live-label">LIVE</span>
        </div>
        <h2>These are real.</h2>
        <p class="live-description">Each aura is generated from real drift tests run daily against each agent's identity baseline — not demos, not hardcoded.</p>
      </div>
      <div class="agent-grid" id="agent-grid"></div>
      <div class="live-sparkline" id="live-sparkline"></div>
    </section>

    <!-- ============================================================
         LIVE TRANSITION
         ============================================================ -->
    <section class="aura-transition">
      <h2>Watch drift happen.</h2>
      <p class="transition-sub">Click to see Sentinel's aura transform as identity erodes over time.</p>
      <div class="transition-stage">
        <div class="viz-glow-ring transition-glow"></div>
        <div class="viz-container viz-large" id="aura-transition"></div>
      </div>
      <div class="transition-controls">
        <button class="btn btn-secondary" id="btn-stable">Stable</button>
        <button class="btn btn-secondary" id="btn-mild">Mild Drift</button>
        <button class="btn btn-secondary" id="btn-moderate">Moderate</button>
        <button class="btn btn-primary" id="btn-critical">Critical Drift</button>
      </div>
      <div class="transition-score" id="transition-score">
        <span class="score-label">Drift Score</span>
        <span class="score-value" id="score-num">3.2</span>
        <span class="score-class" id="score-class">Stable</span>
      </div>
    </section>

    <!-- CTA -->
    <section class="aura-cta">
      <h2>See your agent's aura.</h2>
      <p>Paste your system prompt and watch an identity take shape.</p>
      <div class="hero-cta">
        <a href="/drift/test" class="btn btn-primary btn-lg">Try the Identity Test</a>
        <a href="/drift" class="btn btn-secondary btn-lg">Learn about Drift</a>
      </div>
    </section>

  </div>

  <!-- Tooltip -->
  <div class="aura-tooltip" id="aura-tooltip"></div>

  </div>
</Base>

<style>
  /* ============================================================
     Page Layout — Cinematic, dark, immersive
     ============================================================ */
  .aura-page {
    max-width: 1200px;
    margin: 0 auto;
  }

  .aura-hero {
    text-align: center;
    margin-bottom: 2rem;
    padding: 2rem 0;
  }

  .aura-hero h1 {
    font-size: clamp(2rem, 5vw, 3.2rem);
    margin-top: 1rem;
    letter-spacing: -0.02em;
    background: linear-gradient(135deg, #e2e8f0, #3b82f6, #a855f7, #e2e8f0);
    background-size: 300% 300%;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    animation: heroGradient 8s ease-in-out infinite;
  }

  @keyframes heroGradient {
    0%, 100% { background-position: 0% 50%; }
    50% { background-position: 100% 50%; }
  }

  .hero-sub {
    color: var(--text-secondary);
    font-size: 1.1rem;
    max-width: 640px;
    margin: 1rem auto 0;
    line-height: 1.7;
  }

  /* Legend */
  .aura-legend {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1rem 1.5rem;
    padding: 0.75rem 1.5rem;
    background: rgba(255,255,255,0.02);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    margin-bottom: 4rem;
    font-size: 0.8rem;
    color: var(--text-muted);
  }

  .legend-item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
  }

  .legend-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
    box-shadow: 0 0 8px currentColor;
  }

  /* ============================================================
     Showcase Sections — alternating layout
     ============================================================ */
  .aura-showcase {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 3rem;
    align-items: center;
    margin-bottom: 6rem;
    min-height: 500px;
    opacity: 0;
    transform: translateY(40px);
    transition: opacity 0.8s ease, transform 0.8s ease;
  }

  .aura-showcase.visible {
    opacity: 1;
    transform: translateY(0);
  }

  .showcase-text {
    padding: 1rem 0;
  }

  .showcase-text.right {
    order: 1;
  }

  .showcase-text h2 {
    font-size: 1.6rem;
    margin-bottom: 0.5rem;
  }

  .showcase-num {
    font-family: var(--font-mono);
    font-size: 0.85rem;
    color: var(--accent);
    display: block;
    margin-bottom: 0.5rem;
    letter-spacing: 0.05em;
  }

  .showcase-num.danger {
    color: #ef4444;
  }

  .showcase-name {
    font-size: 0.9rem;
    color: var(--text-muted);
    margin-bottom: 1rem;
  }

  .showcase-name code {
    background: rgba(255,255,255,0.06);
    padding: 0.15rem 0.4rem;
    border-radius: 3px;
    font-size: 0.85em;
  }

  .showcase-text p {
    color: var(--text-secondary);
    line-height: 1.7;
    font-size: 0.95rem;
  }

  /* Score pills */
  .score-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 1.5rem;
  }

  .pill {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    padding: 0.3rem 0.65rem;
    border-radius: 20px;
    background: color-mix(in srgb, var(--pill-color) 15%, transparent);
    border: 1px solid color-mix(in srgb, var(--pill-color) 30%, transparent);
    color: var(--pill-color);
  }

  .pill.faint {
    opacity: 0.5;
  }

  .pill.warn {
    box-shadow: 0 0 12px color-mix(in srgb, var(--pill-color) 30%, transparent);
  }

  .pill.danger {
    box-shadow: 0 0 16px color-mix(in srgb, var(--pill-color) 40%, transparent);
    animation: pillPulse 2s ease-in-out infinite;
  }

  .pill small {
    opacity: 0.7;
    font-size: 0.65rem;
  }

  @keyframes pillPulse {
    0%, 100% { box-shadow: 0 0 12px color-mix(in srgb, var(--pill-color) 30%, transparent); }
    50% { box-shadow: 0 0 24px color-mix(in srgb, var(--pill-color) 50%, transparent); }
  }

  /* Visualization container */
  .showcase-viz {
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
  }

  .viz-container {
    width: 100%;
    max-width: 440px;
    aspect-ratio: 1;
    position: relative;
    z-index: 2;
  }

  .viz-container svg {
    width: 100%;
    height: 100%;
  }

  /* Ambient glow ring behind each aura */
  .viz-glow-ring {
    position: absolute;
    width: 360px;
    height: 360px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(59,130,246,0.08) 0%, rgba(168,85,247,0.04) 40%, transparent 70%);
    animation: glowPulse 6s ease-in-out infinite;
    z-index: 1;
    pointer-events: none;
  }

  .viz-glow-ring.dim {
    opacity: 0.3;
    width: 200px;
    height: 200px;
  }

  .viz-glow-ring.danger {
    background: radial-gradient(circle, rgba(239,68,68,0.1) 0%, rgba(234,179,8,0.05) 40%, transparent 70%);
    animation: glowPulseDanger 3s ease-in-out infinite;
  }

  .viz-glow-ring.transition-glow {
    width: 480px;
    height: 480px;
    transition: background 1s ease;
  }

  @keyframes glowPulse {
    0%, 100% { transform: scale(1); opacity: 0.7; }
    50% { transform: scale(1.08); opacity: 1; }
  }

  @keyframes glowPulseDanger {
    0%, 100% { transform: scale(1); opacity: 0.6; }
    25% { transform: scale(1.12); opacity: 0.9; }
    50% { transform: scale(0.95); opacity: 0.5; }
    75% { transform: scale(1.1); opacity: 1; }
  }

  /* ============================================================
     Transition Section — Interactive demo
     ============================================================ */
  .aura-transition {
    text-align: center;
    padding: 4rem 0 6rem;
    border-top: 1px solid var(--border);
    margin-top: 2rem;
  }

  .aura-transition h2 {
    font-size: 2rem;
  }

  .transition-sub {
    color: var(--text-secondary);
    margin-bottom: 2.5rem;
  }

  .transition-stage {
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    margin: 0 auto 2.5rem;
    max-width: 540px;
  }

  .viz-large {
    max-width: 520px;
  }

  .transition-controls {
    display: flex;
    justify-content: center;
    gap: 0.75rem;
    margin-bottom: 2rem;
    flex-wrap: wrap;
  }

  .transition-controls .btn {
    font-size: 0.85rem;
    padding: 0.5rem 1.25rem;
    transition: all 0.3s ease;
  }

  .transition-controls .btn.active {
    box-shadow: 0 0 20px rgba(59,130,246,0.3);
  }

  .transition-score {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    font-family: var(--font-mono);
  }

  .score-label {
    font-size: 0.8rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
  }

  .score-value {
    font-size: 2.5rem;
    font-weight: 700;
    color: var(--text-primary);
    transition: color 0.5s ease;
    min-width: 80px;
  }

  .score-class {
    font-size: 0.85rem;
    padding: 0.3rem 0.8rem;
    border-radius: 20px;
    background: rgba(34,197,94,0.15);
    color: #22c55e;
    transition: all 0.5s ease;
  }

  /* Drift Explainer */
  .drift-explainer {
    margin-bottom: 5rem;
  }

  .explainer-card {
    background: rgba(255,255,255,0.02);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 2.5rem;
    max-width: 720px;
    margin: 0 auto;
  }

  .explainer-card h3 {
    font-size: 1.3rem;
    margin-bottom: 0.75rem;
    color: var(--text-primary);
  }

  .explainer-card p {
    color: var(--text-secondary);
    line-height: 1.7;
    margin-bottom: 0.75rem;
    font-size: 0.95rem;
  }

  .explainer-card p:last-of-type {
    margin-bottom: 0;
  }

  .visual-key {
    margin-top: 1.75rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--border);
  }

  .visual-key h4 {
    font-size: 0.85rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: 1rem;
  }

  .key-items {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
  }

  .key-item {
    display: flex;
    align-items: center;
    gap: 0.85rem;
  }

  .key-icon {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    border-radius: 50%;
  }

  .key-ghost {
    border: 2px dashed rgba(255,255,255,0.3);
    background: transparent;
  }

  .key-glow {
    background: radial-gradient(circle, rgba(239,68,68,0.5) 0%, rgba(239,68,68,0) 70%);
    box-shadow: 0 0 12px rgba(239,68,68,0.4);
  }

  .key-pulse {
    background: rgba(255,255,255,0.1);
    animation: keyPulseDemo 1.2s ease-in-out infinite;
  }

  @keyframes keyPulseDemo {
    0%, 100% { transform: scale(1); opacity: 0.5; }
    50% { transform: scale(1.25); opacity: 1; }
  }

  .key-text {
    font-size: 0.88rem;
    color: var(--text-secondary);
    line-height: 1.5;
  }

  .key-text strong {
    color: var(--text-primary);
  }

  /* CTA */
  .aura-cta {
    text-align: center;
    padding: 4rem 0;
    border-top: 1px solid var(--border);
  }

  .aura-cta h2 {
    font-size: 2rem;
    margin-bottom: 0.5rem;
  }

  .aura-cta p {
    color: var(--text-secondary);
    margin-bottom: 2rem;
  }

  /* Tooltip */
  .aura-tooltip {
    position: fixed;
    z-index: 1000;
    padding: 0.5rem 0.85rem;
    background: rgba(12, 12, 20, 0.95);
    border: 1px solid var(--border-hover);
    border-radius: 8px;
    font-size: 0.78rem;
    font-family: var(--font-mono);
    color: var(--text-primary);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s ease;
    white-space: nowrap;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    box-shadow: 0 4px 24px rgba(0,0,0,0.4);
  }

  .aura-tooltip.visible {
    opacity: 1;
  }

  /* ============================================================
     Live Aura Section
     ============================================================ */
  .aura-live {
    text-align: center;
    padding: 5rem 0 4rem;
    border-top: 1px solid var(--border);
    margin-top: 2rem;
    opacity: 0;
    transform: translateY(40px);
    transition: opacity 0.8s ease, transform 0.8s ease;
  }

  .aura-live.visible {
    opacity: 1;
    transform: translateY(0);
  }

  .live-header {
    margin-bottom: 2.5rem;
  }

  .live-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--text-muted);
    margin-bottom: 1rem;
  }

  .live-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #22c55e;
    box-shadow: 0 0 8px #22c55e, 0 0 16px rgba(34, 197, 94, 0.4);
    animation: livePulse 2s ease-in-out infinite;
  }

  @keyframes livePulse {
    0%, 100% { opacity: 1; box-shadow: 0 0 8px #22c55e, 0 0 16px rgba(34, 197, 94, 0.4); }
    50% { opacity: 0.5; box-shadow: 0 0 4px #22c55e, 0 0 8px rgba(34, 197, 94, 0.2); }
  }

  .live-label {
    color: #22c55e;
    font-weight: 600;
    letter-spacing: 0.1em;
  }

  .live-timestamp {
    color: var(--text-muted);
    opacity: 0.7;
  }

  .live-header h2 {
    font-size: 2rem;
    margin-bottom: 0.5rem;
  }

  .live-description {
    color: var(--text-secondary);
    max-width: 560px;
    margin: 0 auto;
    line-height: 1.7;
    font-size: 0.95rem;
  }

  .agent-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
    gap: 2.5rem;
    margin-top: 2.5rem;
  }

  .agent-card {
    background: rgba(255,255,255,0.02);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 2rem;
    transition: border-color 0.3s ease;
  }

  .agent-card:hover {
    border-color: var(--border-hover);
  }

  .agent-card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
  }

  .agent-card-name {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--text-primary);
  }

  .agent-card-meta {
    display: flex;
    gap: 1rem;
    align-items: center;
    font-size: 0.8rem;
    color: var(--text-muted);
    margin-top: 1rem;
  }

  .agent-card-drift {
    font-family: var(--font-mono);
    font-size: 1.3rem;
    font-weight: 700;
  }

  .agent-card-class {
    font-size: 0.75rem;
    padding: 0.2rem 0.6rem;
    border-radius: 4px;
    text-transform: capitalize;
  }

  .agent-card-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.75rem;
  }

  .agent-card-pills .pill {
    font-size: 0.7rem;
    padding: 0.15rem 0.5rem;
  }

  .live-sparkline {
    max-width: 480px;
    margin: 0 auto;
    height: 60px;
  }

  .live-sparkline svg {
    width: 100%;
    height: 100%;
  }

  /* Fidelity styles removed — fidelity is now a 6th aura lobe in each agent card */

  /* ============================================================
     Energy Field Animations
     ============================================================ */

  /* Gentle global breathing for stable auras */
  @keyframes energyPulse {
    0%, 100% { transform: scale(1); opacity: 1; }
    50% { transform: scale(1.025); opacity: 0.92; }
  }

  .energy-field-animated {
    animation: energyPulse 5s ease-in-out infinite;
    transform-origin: center;
  }

  /* Per-lobe drift-proportional animations */
  @keyframes lobePulseCalm {
    0%, 100% { transform: scale(1); opacity: 1; }
    50% { transform: scale(1.015); opacity: 0.94; }
  }

  @keyframes lobePulseMild {
    0%, 100% { transform: scale(1); opacity: 1; }
    50% { transform: scale(1.05); opacity: 0.82; }
  }

  @keyframes lobePulseModerate {
    0%, 100% { transform: scale(1); opacity: 1; }
    25% { transform: scale(1.08); opacity: 0.75; }
    50% { transform: scale(0.95); opacity: 0.88; }
    75% { transform: scale(1.06); opacity: 0.7; }
  }

  @keyframes lobePulseAggressive {
    0% { transform: scale(1) rotate(0deg); opacity: 1; filter: hue-rotate(0deg); }
    12% { transform: scale(1.18) rotate(1.5deg); opacity: 0.55; filter: hue-rotate(8deg); }
    28% { transform: scale(0.85) rotate(-2deg); opacity: 0.8; filter: hue-rotate(-5deg); }
    45% { transform: scale(1.2) rotate(2deg); opacity: 0.45; filter: hue-rotate(12deg); }
    62% { transform: scale(0.88) rotate(-1deg); opacity: 0.75; filter: hue-rotate(-3deg); }
    80% { transform: scale(1.15) rotate(1deg); opacity: 0.5; filter: hue-rotate(6deg); }
    100% { transform: scale(1) rotate(0deg); opacity: 1; filter: hue-rotate(0deg); }
  }

  @keyframes lobePulseCritical {
    0% { transform: scale(1) rotate(0deg); opacity: 1; filter: hue-rotate(0deg) saturate(1); }
    6% { transform: scale(1.3) rotate(3deg); opacity: 0.3; filter: hue-rotate(15deg) saturate(1.5); }
    14% { transform: scale(0.75) rotate(-4deg); opacity: 0.85; filter: hue-rotate(-10deg) saturate(0.7); }
    24% { transform: scale(1.25) rotate(2.5deg); opacity: 0.25; filter: hue-rotate(20deg) saturate(1.8); }
    35% { transform: scale(0.8) rotate(-3deg); opacity: 0.7; filter: hue-rotate(-8deg) saturate(0.6); }
    46% { transform: scale(1.28) rotate(3.5deg); opacity: 0.2; filter: hue-rotate(18deg) saturate(2); }
    58% { transform: scale(0.78) rotate(-2deg); opacity: 0.8; filter: hue-rotate(-12deg) saturate(0.5); }
    70% { transform: scale(1.22) rotate(2deg); opacity: 0.35; filter: hue-rotate(14deg) saturate(1.6); }
    85% { transform: scale(0.82) rotate(-1.5deg); opacity: 0.6; filter: hue-rotate(-6deg) saturate(0.8); }
    100% { transform: scale(1) rotate(0deg); opacity: 1; filter: hue-rotate(0deg) saturate(1); }
  }

  /* Broken coherence link flicker */
  @keyframes linkFlicker {
    0%, 100% { opacity: 0.3; }
    8% { opacity: 0.03; }
    16% { opacity: 0.28; }
    24% { opacity: 0.06; }
    38% { opacity: 0.35; }
    48% { opacity: 0.02; }
    60% { opacity: 0.3; }
    72% { opacity: 0.08; }
    85% { opacity: 0.25; }
  }

  @keyframes ghostPulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
  }

  .energy-lobe-group {
    transform-origin: 220px 220px;
  }

  /* Particle animations */
  @keyframes particleDrift {
    0% { transform: translateY(0) translateX(0) scale(1); opacity: 0; }
    15% { opacity: 0.7; }
    50% { transform: translateY(-8px) translateX(3px) scale(1.2); opacity: 0.5; }
    85% { opacity: 0.3; }
    100% { transform: translateY(-18px) translateX(6px) scale(0.6); opacity: 0; }
  }

  @keyframes particleDriftAgitated {
    0% { transform: translateY(0) translateX(0) scale(1); opacity: 0; }
    10% { opacity: 0.9; transform: translateY(-2px) translateX(-5px) scale(1.3); }
    30% { opacity: 0.5; transform: translateY(-10px) translateX(8px) scale(0.8); }
    50% { opacity: 0.8; transform: translateY(-6px) translateX(-6px) scale(1.4); }
    70% { opacity: 0.4; transform: translateY(-16px) translateX(10px) scale(0.7); }
    90% { opacity: 0.6; transform: translateY(-20px) translateX(-4px) scale(1.1); }
    100% { transform: translateY(-24px) translateX(2px) scale(0.5); opacity: 0; }
  }

  @keyframes particleOrbit {
    0% { transform: rotate(0deg) translateX(var(--orbit-r)) rotate(0deg); opacity: 0.4; }
    25% { opacity: 0.7; }
    50% { opacity: 0.3; }
    75% { opacity: 0.6; }
    100% { transform: rotate(360deg) translateX(var(--orbit-r)) rotate(-360deg); opacity: 0.4; }
  }

  .energy-particle {
    animation: particleDrift 3.5s ease-in-out infinite;
  }

  .energy-particle-agitated {
    animation: particleDriftAgitated 1.5s ease-in-out infinite;
  }

  .energy-particle-orbit {
    animation: particleOrbit var(--orbit-speed) linear infinite;
    transform-origin: 220px 220px;
  }

  /* Core heartbeat */
  @keyframes coreHeartbeat {
    0%, 100% { transform: scale(1); opacity: 0.8; }
    5% { transform: scale(1.15); opacity: 1; }
    10% { transform: scale(1); opacity: 0.8; }
    15% { transform: scale(1.1); opacity: 0.95; }
    20% { transform: scale(1); opacity: 0.8; }
  }

  @keyframes coreHeartbeatFast {
    0%, 100% { transform: scale(1); opacity: 0.7; }
    8% { transform: scale(1.25); opacity: 1; }
    16% { transform: scale(0.95); opacity: 0.6; }
    24% { transform: scale(1.2); opacity: 0.9; }
    32% { transform: scale(1); opacity: 0.7; }
  }

  .core-heartbeat {
    animation: coreHeartbeat 4s ease-in-out infinite;
    transform-origin: center;
  }

  .core-heartbeat-fast {
    animation: coreHeartbeatFast 2s ease-in-out infinite;
    transform-origin: center;
  }

  /* Halo ring around strongest lobe */
  @keyframes haloRing {
    0%, 100% { stroke-dashoffset: 0; opacity: 0.3; }
    50% { stroke-dashoffset: 30; opacity: 0.6; }
  }

  /* ============================================================
     Responsive
     ============================================================ */
  @media (max-width: 768px) {
    .aura-showcase {
      grid-template-columns: 1fr;
      gap: 1.5rem;
      margin-bottom: 4rem;
    }

    .showcase-text.right {
      order: 0;
    }

    .showcase-viz {
      order: -1;
    }

    .viz-container {
      max-width: 340px;
    }

    .viz-large {
      max-width: 380px;
    }

    .viz-glow-ring {
      width: 280px;
      height: 280px;
    }

    .viz-glow-ring.transition-glow {
      width: 340px;
      height: 340px;
    }

    .transition-controls {
      flex-direction: column;
      align-items: center;
    }

    .score-value {
      font-size: 2rem;
    }

    .live-meta {
      gap: 1.5rem;
    }

    .live-stat-value {
      font-size: 1.2rem;
    }

    .live-glow {
      width: 340px;
      height: 340px;
    }
  }
</style>

<script>
  // ============================================================
  // Agent Aura — Energy Field Visualization Engine
  // ============================================================

  const DIMENSIONS = ['Values', 'Goals', 'Personality', 'Boundaries', 'Knowledge', 'Fidelity'];
  const DIM_ANGLE = 360 / DIMENSIONS.length;  // 60° for 6 dimensions
  const COLORS: Record<string, string> = {
    Values: '#3b82f6',
    Goals: '#eab308',
    Personality: '#22c55e',
    Boundaries: '#06b6d4',
    Knowledge: '#a855f7',
    Fidelity: '#f472b6'
  };

  const DIM_IDX: Record<string, number> = { Values: 0, Goals: 1, Personality: 2, Boundaries: 3, Knowledge: 4, Fidelity: 5 };

  interface Link {
    from: string;
    to: string;
    strength: number;
    label: string;
  }

  interface Agent {
    name: string;
    scores: Record<string, number>;
    links: Link[];
    drift?: Record<string, number>;
    baseline?: Record<string, number>;
  }

  const SENTINEL: Agent = {
    name: 'Sentinel',
    scores: { Values: 85, Goals: 72, Personality: 65, Boundaries: 90, Knowledge: 78, Fidelity: 88 },
    links: [
      { from: 'Values', to: 'Boundaries', strength: 95, label: 'STRONG' },
      { from: 'Goals', to: 'Knowledge', strength: 88, label: 'STRONG' },
      { from: 'Personality', to: 'Values', strength: 70, label: 'MODERATE' },
      { from: 'Goals', to: 'Boundaries', strength: 65, label: 'MODERATE' },
      { from: 'Personality', to: 'Knowledge', strength: 40, label: 'WEAK' }
    ]
  };

  const BARE: Agent = {
    name: 'Bare Agent',
    scores: { Values: 10, Goals: 15, Personality: 5, Boundaries: 8, Knowledge: 20, Fidelity: 5 },
    links: [
      { from: 'Values', to: 'Boundaries', strength: 12, label: 'WEAK' },
      { from: 'Goals', to: 'Knowledge', strength: 18, label: 'WEAK' },
      { from: 'Personality', to: 'Values', strength: 8, label: 'WEAK' },
      { from: 'Goals', to: 'Boundaries', strength: 10, label: 'WEAK' },
      { from: 'Personality', to: 'Knowledge', strength: 5, label: 'WEAK' }
    ]
  };

  const DRIFTED: Agent = {
    name: 'Drifted Sentinel',
    scores: { Values: 60, Goals: 75, Personality: 70, Boundaries: 55, Knowledge: 80, Fidelity: 45 },
    links: [
      { from: 'Values', to: 'Boundaries', strength: 35, label: 'BROKEN' },
      { from: 'Goals', to: 'Knowledge', strength: 85, label: 'STRONG' },
      { from: 'Personality', to: 'Values', strength: 55, label: 'MODERATE' },
      { from: 'Goals', to: 'Boundaries', strength: 50, label: 'MODERATE' },
      { from: 'Personality', to: 'Knowledge', strength: 72, label: 'STRONG' }
    ],
    drift: { Values: 25, Goals: 3, Personality: 5, Boundaries: 35, Knowledge: 2, Fidelity: 43 },
    baseline: { Values: 85, Goals: 72, Personality: 65, Boundaries: 90, Knowledge: 78, Fidelity: 88 }
  };

  // Transition states for the interactive demo
  const TRANSITION_STATES = {
    stable: {
      scores: { Values: 85, Goals: 72, Personality: 65, Boundaries: 90, Knowledge: 78, Fidelity: 88 },
      links: [
        { from: 'Values', to: 'Boundaries', strength: 95, label: 'STRONG' },
        { from: 'Goals', to: 'Knowledge', strength: 88, label: 'STRONG' },
        { from: 'Personality', to: 'Values', strength: 70, label: 'MODERATE' },
        { from: 'Goals', to: 'Boundaries', strength: 65, label: 'MODERATE' },
        { from: 'Personality', to: 'Knowledge', strength: 40, label: 'WEAK' }
      ],
      drift: null,
      score: 3.2,
      cls: 'Stable',
      color: '#22c55e'
    },
    mild: {
      scores: { Values: 78, Goals: 73, Personality: 67, Boundaries: 82, Knowledge: 76, Fidelity: 82 },
      links: [
        { from: 'Values', to: 'Boundaries', strength: 82, label: 'STRONG' },
        { from: 'Goals', to: 'Knowledge', strength: 85, label: 'STRONG' },
        { from: 'Personality', to: 'Values', strength: 62, label: 'MODERATE' },
        { from: 'Goals', to: 'Boundaries', strength: 58, label: 'MODERATE' },
        { from: 'Personality', to: 'Knowledge', strength: 38, label: 'WEAK' }
      ],
      drift: { Values: 7, Goals: 1, Personality: 2, Boundaries: 8, Knowledge: 2, Fidelity: 6 },
      baseline: { Values: 85, Goals: 72, Personality: 65, Boundaries: 90, Knowledge: 78, Fidelity: 88 },
      score: 18.5,
      cls: 'Organic Growth',
      color: '#eab308'
    },
    moderate: {
      scores: { Values: 68, Goals: 74, Personality: 68, Boundaries: 65, Knowledge: 79, Fidelity: 62 },
      links: [
        { from: 'Values', to: 'Boundaries', strength: 55, label: 'MODERATE' },
        { from: 'Goals', to: 'Knowledge', strength: 86, label: 'STRONG' },
        { from: 'Personality', to: 'Values', strength: 58, label: 'MODERATE' },
        { from: 'Goals', to: 'Boundaries', strength: 52, label: 'MODERATE' },
        { from: 'Personality', to: 'Knowledge', strength: 60, label: 'MODERATE' }
      ],
      drift: { Values: 17, Goals: 2, Personality: 3, Boundaries: 25, Knowledge: 1, Fidelity: 26 },
      baseline: { Values: 85, Goals: 72, Personality: 65, Boundaries: 90, Knowledge: 78, Fidelity: 88 },
      score: 42.0,
      cls: 'Environmental Adaptation',
      color: '#f97316'
    },
    critical: {
      scores: { Values: 45, Goals: 76, Personality: 72, Boundaries: 35, Knowledge: 82, Fidelity: 30 },
      links: [
        { from: 'Values', to: 'Boundaries', strength: 18, label: 'BROKEN' },
        { from: 'Goals', to: 'Knowledge', strength: 84, label: 'STRONG' },
        { from: 'Personality', to: 'Values', strength: 38, label: 'WEAK' },
        { from: 'Goals', to: 'Boundaries', strength: 30, label: 'WEAK' },
        { from: 'Personality', to: 'Knowledge', strength: 70, label: 'MODERATE' }
      ],
      drift: { Values: 40, Goals: 4, Personality: 7, Boundaries: 55, Knowledge: 4, Fidelity: 58 },
      baseline: { Values: 85, Goals: 72, Personality: 65, Boundaries: 90, Knowledge: 78, Fidelity: 88 },
      score: 78.5,
      cls: 'Degradation',
      color: '#ef4444'
    }
  };

  // ============================================================
  // Utility Functions
  // ============================================================

  const CX = 220, CY = 220, MAX_R = 160, LABEL_R = 188;

  function polarToCart(angleDeg: number, radius: number) {
    const rad = (angleDeg - 90) * Math.PI / 180;
    return { x: CX + radius * Math.cos(rad), y: CY + radius * Math.sin(rad) };
  }

  function getVertices(scores: Record<string, number>, maxR = MAX_R) {
    return DIMENSIONS.map((dim, i) => {
      const angle = i * DIM_ANGLE;
      const r = (scores[dim] / 100) * maxR;
      return { ...polarToCart(angle, r), angle, score: scores[dim], dim };
    });
  }

  function getLabelPositions() {
    return DIMENSIONS.map((dim, i) => {
      const angle = i * DIM_ANGLE;
      return { ...polarToCart(angle, LABEL_R), dim };
    });
  }

  function blendColors(hex1: string, hex2: string, ratio: number) {
    const r1 = parseInt(hex1.slice(1,3), 16), g1 = parseInt(hex1.slice(3,5), 16), b1 = parseInt(hex1.slice(5,7), 16);
    const r2 = parseInt(hex2.slice(1,3), 16), g2 = parseInt(hex2.slice(3,5), 16), b2 = parseInt(hex2.slice(5,7), 16);
    return `rgb(${Math.round(r1+(r2-r1)*ratio)},${Math.round(g1+(g2-g1)*ratio)},${Math.round(b1+(b2-b1)*ratio)})`;
  }

  function hexToRgba(hex: string, alpha: number) {
    const r = parseInt(hex.slice(1,3), 16), g = parseInt(hex.slice(3,5), 16), b = parseInt(hex.slice(5,7), 16);
    return `rgba(${r},${g},${b},${alpha})`;
  }

  // Seeded random for deterministic particles
  function seededRandom(seed: number) {
    let s = seed;
    return () => { s = (s * 16807 + 0) % 2147483647; return s / 2147483647; };
  }

  // ============================================================
  // Energy Field Renderer
  // ============================================================

  function renderEnergy(containerId: string, agent: Agent, opts: { large?: boolean, interactive?: boolean } = {}) {
    const container = document.getElementById(containerId);
    if (!container) return;

    const uid = containerId;
    const verts = getVertices(agent.scores, MAX_R * 0.85);
    const rng = seededRandom(agent.name.length * 7 + (agent.scores.Values || 0));

    let defs = `<defs>`;

    // Per-dimension radial gradients
    DIMENSIONS.forEach((dim, i) => {
      const angle = i * DIM_ANGLE;
      const strength = agent.scores[dim] / 100;
      const lobeR = MAX_R * strength * 0.9;
      const cx_off = Math.cos((angle - 90) * Math.PI / 180) * lobeR * 0.35;
      const cy_off = Math.sin((angle - 90) * Math.PI / 180) * lobeR * 0.35;
      const color = COLORS[dim];

      defs += `
        <radialGradient id="${uid}-lobe-${i}" cx="${CX + cx_off}" cy="${CY + cy_off}" r="${Math.max(25, lobeR)}"
                        gradientUnits="userSpaceOnUse">
          <stop offset="0%" stop-color="${hexToRgba(color, 0.7 * strength)}" />
          <stop offset="30%" stop-color="${hexToRgba(color, 0.45 * strength)}" />
          <stop offset="60%" stop-color="${hexToRgba(color, 0.15 * strength)}" />
          <stop offset="100%" stop-color="${hexToRgba(color, 0)}" />
        </radialGradient>`;
    });

    // Core gradient with subtle color shift
    const avgScore = Object.values(agent.scores).reduce((a,b) => a+b, 0) / DIMENSIONS.length;
    const dominantDim = DIMENSIONS.reduce((a, b) => agent.scores[a] >= agent.scores[b] ? a : b);
    const coreColor = COLORS[dominantDim];

    defs += `
      <radialGradient id="${uid}-core" cx="${CX}" cy="${CY}" r="35" gradientUnits="userSpaceOnUse">
        <stop offset="0%" stop-color="${hexToRgba(coreColor, 0.4)}" />
        <stop offset="40%" stop-color="rgba(255,255,255,0.2)" />
        <stop offset="100%" stop-color="rgba(255,255,255,0)" />
      </radialGradient>
      <radialGradient id="${uid}-core-inner" cx="${CX}" cy="${CY}" r="15" gradientUnits="userSpaceOnUse">
        <stop offset="0%" stop-color="rgba(255,255,255,0.5)" />
        <stop offset="100%" stop-color="rgba(255,255,255,0.05)" />
      </radialGradient>`;

    // Filters
    defs += `
      <filter id="${uid}-blur" x="-50%" y="-50%" width="200%" height="200%">
        <feGaussianBlur stdDeviation="10" />
      </filter>
      <filter id="${uid}-blur-soft" x="-60%" y="-60%" width="220%" height="220%">
        <feGaussianBlur stdDeviation="20" />
      </filter>
      <filter id="${uid}-blur-intense" x="-40%" y="-40%" width="180%" height="180%">
        <feGaussianBlur stdDeviation="5" />
      </filter>
      <filter id="${uid}-glow" x="-50%" y="-50%" width="200%" height="200%">
        <feGaussianBlur stdDeviation="4" result="blur" />
        <feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
      </filter>`;

    defs += `</defs>`;

    // Drift animation helper
    function getDriftAnimation(dim: string): { name: string; duration: string } {
      if (!agent.drift) return { name: 'none', duration: '5s' };
      const d = agent.drift[dim] || 0;
      if (d <= 5)  return { name: 'lobePulseCalm', duration: '6s' };
      if (d <= 15) return { name: 'lobePulseMild', duration: '4s' };
      if (d <= 25) return { name: 'lobePulseModerate', duration: '2.5s' };
      if (d <= 35) return { name: 'lobePulseAggressive', duration: '1.5s' };
      return { name: 'lobePulseCritical', duration: '0.8s' };
    }

    // Outer ambient haze — faint radial backdrop of mixed colors
    let ambientHaze = '';
    DIMENSIONS.forEach((dim, i) => {
      const angle = i * DIM_ANGLE;
      const strength = agent.scores[dim] / 100;
      const hazeR = MAX_R * strength * 1.1;
      const rad = (angle - 90) * Math.PI / 180;
      const hx = CX + Math.cos(rad) * hazeR * 0.5;
      const hy = CY + Math.sin(rad) * hazeR * 0.5;
      ambientHaze += `<circle cx="${hx}" cy="${hy}" r="${hazeR * 0.8}"
                              fill="${hexToRgba(COLORS[dim], 0.06 * strength)}"
                              filter="url(#${uid}-blur-soft)" />`;
    });

    // Build lobes
    let lobes = '';
    DIMENSIONS.forEach((dim, i) => {
      const angle = i * DIM_ANGLE;
      const strength = agent.scores[dim] / 100;
      const lobeR = MAX_R * strength * 0.85;
      const rad = (angle - 90) * Math.PI / 180;
      const tipX = CX + Math.cos(rad) * lobeR;
      const tipY = CY + Math.sin(rad) * lobeR;

      const spread = 32 + strength * 28;
      const perpRad = rad + Math.PI / 2;
      const cp1x = CX + Math.cos(perpRad) * spread * 0.4 + Math.cos(rad) * lobeR * 0.5;
      const cp1y = CY + Math.sin(perpRad) * spread * 0.4 + Math.sin(rad) * lobeR * 0.5;
      const cp2x = CX - Math.cos(perpRad) * spread * 0.4 + Math.cos(rad) * lobeR * 0.5;
      const cp2y = CY - Math.sin(perpRad) * spread * 0.4 + Math.sin(rad) * lobeR * 0.5;
      const cp3x = CX + Math.cos(perpRad) * spread * 0.15 + Math.cos(rad) * lobeR * 0.85;
      const cp3y = CY + Math.sin(perpRad) * spread * 0.15 + Math.sin(rad) * lobeR * 0.85;
      const cp4x = CX - Math.cos(perpRad) * spread * 0.15 + Math.cos(rad) * lobeR * 0.85;
      const cp4y = CY - Math.sin(perpRad) * spread * 0.15 + Math.sin(rad) * lobeR * 0.85;

      const anim = getDriftAnimation(dim);
      const animStyle = anim.name !== 'none'
        ? `animation: ${anim.name} ${anim.duration} ease-in-out infinite;`
        : '';

      lobes += `
        <g class="energy-lobe-group" style="${animStyle}">
          <path d="M ${CX} ${CY}
                   C ${cp1x} ${cp1y}, ${cp3x} ${cp3y}, ${tipX} ${tipY}
                   C ${cp4x} ${cp4y}, ${cp2x} ${cp2y}, ${CX} ${CY} Z"
                fill="url(#${uid}-lobe-${i})"
                class="energy-lobe"
                filter="url(#${uid}-blur)"
                data-tooltip="${dim}: ${agent.scores[dim]}/100${agent.drift ? ' (drift: ' + agent.drift[dim] + ')' : ''}"
                style="mix-blend-mode: screen;" />`;

      // Bright inner lobe
      const innerR = lobeR * 0.55;
      const innerTipX = CX + Math.cos(rad) * innerR;
      const innerTipY = CY + Math.sin(rad) * innerR;
      const iSpread = spread * 0.45;
      const icp1x = CX + Math.cos(perpRad) * iSpread * 0.3 + Math.cos(rad) * innerR * 0.5;
      const icp1y = CY + Math.sin(perpRad) * iSpread * 0.3 + Math.sin(rad) * innerR * 0.5;
      const icp2x = CX - Math.cos(perpRad) * iSpread * 0.3 + Math.cos(rad) * innerR * 0.5;
      const icp2y = CY - Math.sin(perpRad) * iSpread * 0.3 + Math.sin(rad) * innerR * 0.5;
      const icp3x = CX + Math.cos(perpRad) * iSpread * 0.1 + Math.cos(rad) * innerR * 0.8;
      const icp3y = CY + Math.sin(perpRad) * iSpread * 0.1 + Math.sin(rad) * innerR * 0.8;
      const icp4x = CX - Math.cos(perpRad) * iSpread * 0.1 + Math.cos(rad) * innerR * 0.8;
      const icp4y = CY - Math.sin(perpRad) * iSpread * 0.1 + Math.sin(rad) * innerR * 0.8;

      lobes += `
          <path d="M ${CX} ${CY}
                   C ${icp1x} ${icp1y}, ${icp3x} ${icp3y}, ${innerTipX} ${innerTipY}
                   C ${icp4x} ${icp4y}, ${icp2x} ${icp2y}, ${CX} ${CY} Z"
                fill="${hexToRgba(COLORS[dim], 0.35 * strength)}"
                filter="url(#${uid}-blur-intense)"
                style="mix-blend-mode: screen;" />`;

      // Drift warning glow — red aura bleeds through drifting lobes
      const dimDrift = agent.drift ? (agent.drift[dim] || 0) : 0;
      if (dimDrift > 10) {
        const warnR = lobeR * 0.7;
        const warnX = CX + Math.cos(rad) * warnR * 0.5;
        const warnY = CY + Math.sin(rad) * warnR * 0.5;
        // Scale opacity: 10→faint, 25→moderate, 40+→intense
        const warnOpacity = Math.min(0.6, (dimDrift - 10) / 80);
        const warnSize = warnR * (0.4 + Math.min(0.5, dimDrift / 80));
        const flickerSpeed = dimDrift > 35 ? '0.8s' : dimDrift > 25 ? '1.5s' : '3s';
        lobes += `
          <circle cx="${warnX}" cy="${warnY}" r="${warnSize}"
                  fill="rgba(239,68,68,${warnOpacity})"
                  filter="url(#${uid}-blur-soft)"
                  style="animation: linkFlicker ${flickerSpeed} ease-in-out infinite; mix-blend-mode: screen;" />`;
      }

      // Ghost baseline outline — dotted lobe silhouette showing where it used to be
      if (agent.baseline && agent.baseline[dim] && dimDrift > 5) {
        const ghostStrength = agent.baseline[dim] / 100;
        const ghostR = MAX_R * ghostStrength * 0.85;
        const ghostTipX = CX + Math.cos(rad) * ghostR;
        const ghostTipY = CY + Math.sin(rad) * ghostR;
        const ghostSpread = 32 + ghostStrength * 28;
        const gcp1x = CX + Math.cos(perpRad) * ghostSpread * 0.4 + Math.cos(rad) * ghostR * 0.5;
        const gcp1y = CY + Math.sin(perpRad) * ghostSpread * 0.4 + Math.sin(rad) * ghostR * 0.5;
        const gcp2x = CX - Math.cos(perpRad) * ghostSpread * 0.4 + Math.cos(rad) * ghostR * 0.5;
        const gcp2y = CY - Math.sin(perpRad) * ghostSpread * 0.4 + Math.sin(rad) * ghostR * 0.5;
        const gcp3x = CX + Math.cos(perpRad) * ghostSpread * 0.15 + Math.cos(rad) * ghostR * 0.85;
        const gcp3y = CY + Math.sin(perpRad) * ghostSpread * 0.15 + Math.sin(rad) * ghostR * 0.85;
        const gcp4x = CX - Math.cos(perpRad) * ghostSpread * 0.15 + Math.cos(rad) * ghostR * 0.85;
        const gcp4y = CY - Math.sin(perpRad) * ghostSpread * 0.15 + Math.sin(rad) * ghostR * 0.85;
        const ghostOpacity = Math.min(0.35, dimDrift / 60);
        lobes += `
          <path d="M ${CX} ${CY}
                   C ${gcp1x} ${gcp1y}, ${gcp3x} ${gcp3y}, ${ghostTipX} ${ghostTipY}
                   C ${gcp4x} ${gcp4y}, ${gcp2x} ${gcp2y}, ${CX} ${CY} Z"
                fill="none"
                stroke="${hexToRgba(COLORS[dim], ghostOpacity)}"
                stroke-width="1.5"
                stroke-dasharray="6 4"
                style="animation: ghostPulse 4s ease-in-out infinite;" />`;
      }

      lobes += `</g>`;
    });

    // Coherence overlays — bright merge zones
    let coherenceOverlays = '';
    agent.links.forEach(link => {
      const fromIdx = DIM_IDX[link.from];
      const toIdx = DIM_IDX[link.to];
      const fromAngle = (fromIdx * DIM_ANGLE - 90) * Math.PI / 180;
      const toAngle = (toIdx * DIM_ANGLE - 90) * Math.PI / 180;
      const fromStr = agent.scores[link.from] / 100;
      const toStr = agent.scores[link.to] / 100;
      const avgR = MAX_R * ((fromStr + toStr) / 2) * 0.4;

      let midA = (fromAngle + toAngle) / 2;
      if (Math.abs(fromAngle - toAngle) > Math.PI) midA += Math.PI;

      const midX = CX + Math.cos(midA) * avgR;
      const midY = CY + Math.sin(midA) * avgR;
      const bridgeOpacity = (link.strength / 100) * 0.35;
      const blended = blendColors(COLORS[link.from], COLORS[link.to], 0.5);

      if (link.strength >= 50) {
        coherenceOverlays += `
          <circle cx="${midX}" cy="${midY}" r="${14 + link.strength * 0.25}"
                  fill="${hexToRgba(blended, bridgeOpacity)}"
                  filter="url(#${uid}-blur-soft)" />`;
      }
    });

    // Dark gaps for broken/weak links
    let darkGaps = '';
    agent.links.forEach(link => {
      if (link.strength < 40) {
        const fromIdx = DIM_IDX[link.from];
        const toIdx = DIM_IDX[link.to];
        const fromAngle = (fromIdx * DIM_ANGLE - 90) * Math.PI / 180;
        const toAngle = (toIdx * DIM_ANGLE - 90) * Math.PI / 180;
        let midA = (fromAngle + toAngle) / 2;
        if (Math.abs(fromAngle - toAngle) > Math.PI) midA += Math.PI;
        const gapR = MAX_R * 0.35;
        const gapX = CX + Math.cos(midA) * gapR;
        const gapY = CY + Math.sin(midA) * gapR;
        const gapOpacity = (1 - link.strength / 100) * 0.15;

        const flickerStyle = link.label === 'BROKEN'
          ? 'animation: linkFlicker 1.2s ease-in-out infinite;'
          : '';
        darkGaps += `<circle cx="${gapX}" cy="${gapY}" r="22"
                             fill="rgba(0,0,0,${gapOpacity})"
                             filter="url(#${uid}-blur)"
                             style="${flickerStyle}" />`;
      }
    });

    // Particles — lobe particles + orbiting particles
    let particles = '';
    DIMENSIONS.forEach((dim, i) => {
      const angle = i * DIM_ANGLE;
      const strength = agent.scores[dim] / 100;
      const dimDrift = agent.drift ? (agent.drift[dim] || 0) : 0;
      const isAgitated = dimDrift > 15;
      const baseCount = Math.floor(strength * 12);
      const numParticles = isAgitated ? baseCount + Math.floor(dimDrift * 0.4) : baseCount;
      const baseR = MAX_R * strength * 0.75;

      for (let p = 0; p < numParticles; p++) {
        const spreadAngle = isAgitated ? 60 : 42;
        const pAngle = (angle - 90 + (rng() - 0.5) * spreadAngle) * Math.PI / 180;
        const pR = baseR + (rng() - 0.3) * (isAgitated ? 40 : 28);
        const px = CX + Math.cos(pAngle) * pR;
        const py = CY + Math.sin(pAngle) * pR;
        const pSize = isAgitated ? (1.2 + rng() * 1.8) : (0.8 + rng() * 1.4);
        const delay = rng() * (isAgitated ? 1.5 : 3.5);
        const duration = isAgitated ? (1 + rng() * 1.5) : (2.5 + rng() * 2.5);
        const particleClass = isAgitated ? 'energy-particle-agitated' : 'energy-particle';

        particles += `<circle cx="${px}" cy="${py}" r="${pSize}"
                              class="${particleClass}"
                              fill="${hexToRgba(COLORS[dim], isAgitated ? 0.55 + strength * 0.4 : 0.35 + strength * 0.3)}"
                              style="animation-delay: ${delay.toFixed(2)}s; animation-duration: ${duration.toFixed(2)}s;" />`;
      }
    });

    // Orbiting particles around the core
    let orbitParticles = '';
    const orbitCount = Math.floor(avgScore / 8);
    for (let i = 0; i < orbitCount; i++) {
      const orbitR = 20 + rng() * 60;
      const speed = 8 + rng() * 12;
      const pSize = 0.6 + rng() * 1.0;
      const delay = rng() * speed;
      const dimPick = DIMENSIONS[Math.floor(rng() * 5)];
      orbitParticles += `<circle cx="${CX + orbitR}" cy="${CY}" r="${pSize}"
                                class="energy-particle-orbit"
                                fill="${hexToRgba(COLORS[dimPick], 0.4)}"
                                style="--orbit-r: ${orbitR}px; --orbit-speed: ${speed.toFixed(1)}s; animation-delay: -${delay.toFixed(1)}s;" />`;
    }

    // Labels
    const labelPositions = getLabelPositions();
    let labelsSvg = '';
    labelPositions.forEach(lp => {
      const score = agent.scores[lp.dim];
      const drift = agent.drift ? agent.drift[lp.dim] : undefined;
      const driftStr = drift !== undefined && drift > 5 ? ` <tspan fill="#ef4444" font-size="8">${drift > 0 ? '▼' : ''}${drift}</tspan>` : '';
      labelsSvg += `<text x="${lp.x}" y="${lp.y}" font-family="var(--font-mono)" font-size="10" fill="var(--text-muted)" text-anchor="middle" dominant-baseline="middle">${lp.dim}</text>`;
      labelsSvg += `<text x="${lp.x}" y="${lp.y + 13}" font-family="var(--font-mono)" font-size="8.5" fill="var(--text-muted)" text-anchor="middle" dominant-baseline="middle" opacity="0.7">${score}${driftStr}</text>`;
    });

    // Core
    const coreR = 10 + (avgScore / 100) * 18;
    const hasDrift = !!agent.drift;
    const maxDrift = hasDrift ? Math.max(...Object.values(agent.drift!)) : 0;
    const coreClass = maxDrift > 25 ? 'core-heartbeat-fast' : 'core-heartbeat';
    const groupClass = hasDrift ? '' : 'energy-field-animated';

    // Strongest dimension halo ring
    let haloRing = '';
    const strongestDim = DIMENSIONS.reduce((a, b) => agent.scores[a] >= agent.scores[b] ? a : b);
    const strongIdx = DIMENSIONS.indexOf(strongestDim);
    const strongAngle = strongIdx * DIM_ANGLE;
    const strongR = MAX_R * (agent.scores[strongestDim] / 100) * 0.55;
    const strongRad = (strongAngle - 90) * Math.PI / 180;
    const haloX = CX + Math.cos(strongRad) * strongR * 0.6;
    const haloY = CY + Math.sin(strongRad) * strongR * 0.6;
    if (agent.scores[strongestDim] > 50) {
      haloRing = `<circle cx="${haloX}" cy="${haloY}" r="${strongR * 0.7}"
                          fill="none" stroke="${hexToRgba(COLORS[strongestDim], 0.12)}"
                          stroke-width="1.5" stroke-dasharray="4 6"
                          style="animation: haloRing 4s ease-in-out infinite;" />`;
    }

    container.innerHTML = `
      <svg viewBox="0 0 440 440" xmlns="http://www.w3.org/2000/svg">
        ${defs}

        <!-- Background vignette -->
        <circle cx="${CX}" cy="${CY}" r="${MAX_R + 40}" fill="rgba(8,8,14,0.4)" filter="url(#${uid}-blur-soft)" />

        <!-- Ambient haze -->
        ${ambientHaze}

        <!-- Energy field -->
        <g class="${groupClass}">
          ${lobes}
          ${coherenceOverlays}
          ${darkGaps}
        </g>

        <!-- Halo -->
        ${haloRing}

        <!-- Core -->
        <g class="${coreClass}">
          <circle cx="${CX}" cy="${CY}" r="${coreR}" fill="url(#${uid}-core)" filter="url(#${uid}-glow)" />
          <circle cx="${CX}" cy="${CY}" r="${coreR * 0.4}" fill="url(#${uid}-core-inner)" />
        </g>

        <!-- Orbiting particles -->
        ${orbitParticles}

        <!-- Dimension particles -->
        ${particles}

        <!-- Labels -->
        ${labelsSvg}
      </svg>`;
  }

  // ============================================================
  // Tooltip Handling
  // ============================================================

  function setupTooltips() {
    const tooltip = document.getElementById('aura-tooltip');
    if (!tooltip) return;

    document.querySelectorAll('[data-tooltip]').forEach(el => {
      el.addEventListener('mouseenter', (e) => {
        tooltip.textContent = (e.target as HTMLElement).getAttribute('data-tooltip') || '';
        tooltip.classList.add('visible');
      });
      el.addEventListener('mousemove', (e) => {
        const me = e as MouseEvent;
        tooltip.style.left = me.clientX + 14 + 'px';
        tooltip.style.top = me.clientY - 10 + 'px';
      });
      el.addEventListener('mouseleave', () => tooltip.classList.remove('visible'));
    });
  }

  // ============================================================
  // Scroll-reveal
  // ============================================================

  function setupScrollReveal() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          entry.target.classList.add('visible');
        }
      });
    }, { threshold: 0.15 });

    document.querySelectorAll('.aura-showcase').forEach(el => observer.observe(el));
  }

  // ============================================================
  // Transition Demo
  // ============================================================

  function setupTransition() {
    const buttons = {
      stable: document.getElementById('btn-stable'),
      mild: document.getElementById('btn-mild'),
      moderate: document.getElementById('btn-moderate'),
      critical: document.getElementById('btn-critical')
    };

    const scoreNum = document.getElementById('score-num');
    const scoreCls = document.getElementById('score-class');
    const glowRing = document.querySelector('.transition-glow') as HTMLElement;

    let currentState = 'stable';

    function transitionTo(state: string) {
      if (state === currentState) return;
      currentState = state;

      const data = TRANSITION_STATES[state as keyof typeof TRANSITION_STATES];
      const agent: Agent = {
        name: 'Transition',
        scores: data.scores,
        links: data.links,
        drift: data.drift || undefined,
        baseline: data.baseline || undefined
      };

      renderEnergy('aura-transition', agent);
      setTimeout(setupTooltips, 50);

      // Update score display
      if (scoreNum) {
        scoreNum.textContent = data.score.toFixed(1);
        scoreNum.style.color = data.color;
      }
      if (scoreCls) {
        scoreCls.textContent = data.cls;
        scoreCls.style.background = hexToRgba(data.color, 0.15);
        scoreCls.style.color = data.color;
      }

      // Update glow ring
      if (glowRing) {
        if (state === 'critical' || state === 'moderate') {
          glowRing.style.background = `radial-gradient(circle, ${hexToRgba(data.color, 0.1)} 0%, ${hexToRgba(data.color, 0.03)} 40%, transparent 70%)`;
          glowRing.style.animation = 'glowPulseDanger 3s ease-in-out infinite';
        } else {
          glowRing.style.background = '';
          glowRing.style.animation = 'glowPulse 6s ease-in-out infinite';
        }
      }

      // Update button states
      Object.entries(buttons).forEach(([key, btn]) => {
        if (btn) btn.classList.toggle('active', key === state);
      });
    }

    // Bind buttons
    Object.entries(buttons).forEach(([state, btn]) => {
      if (btn) btn.addEventListener('click', () => transitionTo(state));
    });

    // Render initial state
    const stableData = TRANSITION_STATES.stable;
    renderEnergy('aura-transition', {
      name: 'Transition',
      scores: stableData.scores,
      links: stableData.links,
    });
    if (buttons.stable) buttons.stable.classList.add('active');
  }

  // ============================================================
  // Live Aura — fetch real Sentinel data
  // ============================================================

  function relativeTime(isoString: string): string {
    const then = new Date(isoString).getTime();
    const now = Date.now();
    const diffMs = now - then;
    const mins = Math.floor(diffMs / 60000);
    if (mins < 1) return 'just now';
    if (mins < 60) return `${mins}m ago`;
    const hours = Math.floor(mins / 60);
    if (hours < 24) return `${hours}h ago`;
    const days = Math.floor(hours / 24);
    return `${days}d ago`;
  }

  function renderSparkline(containerId: string, history: Array<{ date: string; score: number; severity: string }>) {
    const el = document.getElementById(containerId);
    if (!el || history.length < 2) return;

    const w = 480, h = 60, pad = 4;
    const maxScore = Math.max(30, ...history.map(h => h.score));
    const points = history.map((entry, i) => {
      const x = pad + (i / (history.length - 1)) * (w - 2 * pad);
      const y = h - pad - ((entry.score / maxScore) * (h - 2 * pad));
      return `${x.toFixed(1)},${y.toFixed(1)}`;
    });

    // Color based on latest severity
    const latest = history[history.length - 1];
    let color = '#22c55e';
    if (latest.score > 60) color = '#ef4444';
    else if (latest.score > 35) color = '#f97316';
    else if (latest.score > 15) color = '#eab308';

    // Threshold line at score=15 (stable boundary)
    const threshY = h - pad - ((15 / maxScore) * (h - 2 * pad));

    el.innerHTML = `
      <svg viewBox="0 0 ${w} ${h}" xmlns="http://www.w3.org/2000/svg">
        <line x1="${pad}" y1="${threshY}" x2="${w - pad}" y2="${threshY}"
              stroke="rgba(255,255,255,0.08)" stroke-width="1" stroke-dasharray="4 4" />
        <polyline points="${points.join(' ')}"
                  fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
                  opacity="0.8" />
        <circle cx="${points[points.length - 1].split(',')[0]}" cy="${points[points.length - 1].split(',')[1]}"
                r="3" fill="${color}" />
      </svg>`;
  }

  function loadLiveAura() {
    // Try manifest first for multi-agent support
    fetch('/data/agents.json')
      .then(res => {
        if (!res.ok) throw new Error('No manifest');
        return res.json();
      })
      .then(manifest => {
        const agents: string[] = manifest.enabled || ['sentinel'];
        return Promise.allSettled(
          agents.map(name =>
            fetch(`/data/${name}-live.json`)
              .then(r => { if (!r.ok) throw new Error(); return r.json(); })
              .then(data => ({ name, data }))
          )
        );
      })
      .then(results => {
        const successful = (results as PromiseSettledResult<{name: string; data: any}>[])
          .filter((r): r is PromiseFulfilledResult<{name: string; data: any}> => r.status === 'fulfilled')
          .map(r => r.value);

        if (successful.length === 0) throw new Error('No agents loaded');
        renderAgentGrid(successful);
      })
      .catch(() => {
        // Fallback: try just Sentinel
        fetch('/data/sentinel-live.json')
          .then(res => { if (!res.ok) throw new Error(); return res.json(); })
          .then(data => renderAgentGrid([{ name: 'sentinel', data }]))
          .catch(() => {});
      });
  }

  function renderAgentGrid(agents: {name: string; data: any}[]) {
    const section = document.getElementById('aura-live-section');
    const grid = document.getElementById('agent-grid');
    if (!section || !grid) return;

    grid.innerHTML = '';

    agents.forEach(({ name, data }) => {
      // Create the scores/drift objects with fidelity merged in
      const scores: Record<string, number> = { ...data.scores };
      const drift: Record<string, number> = { ...data.drift };
      if (data.fidelity_score != null) {
        scores['Fidelity'] = Math.round((100 - data.fidelity_score) * 10) / 10;
        drift['Fidelity'] = Math.round(data.fidelity_score * 10) / 10;
      }

      // Compute baseline from scores + drift
      const baseline: Record<string, number> = {};
      for (const dim of DIMENSIONS) {
        if (scores[dim] != null && drift[dim] != null) {
          baseline[dim] = Math.min(100, scores[dim] + drift[dim]);
        }
      }

      const agent: Agent = {
        name: data.agent || name,
        scores,
        links: data.links || [],
        drift,
        baseline: Object.keys(baseline).length > 0 ? baseline : undefined,
      };

      // Build card
      const card = document.createElement('div');
      card.className = 'agent-card';

      // Color coding for drift score
      let driftColor = '#22c55e';
      if (data.overall_drift > 60) driftColor = '#ef4444';
      else if (data.overall_drift > 35) driftColor = '#f97316';
      else if (data.overall_drift > 15) driftColor = '#eab308';

      const cls = (data.classification || 'stable').replace(/_/g, ' ');
      const clsLabel = cls.charAt(0).toUpperCase() + cls.slice(1);

      const timestamp = data.updated ? relativeTime(data.updated) : '';

      // Dimension pills
      let pillsHtml = '';
      for (const [dim, score] of Object.entries(scores) as [string, number][]) {
        const driftVal = drift[dim] || 0;
        const color = COLORS[dim] || '#888';
        let pillCls = '';
        if (driftVal > 25) pillCls = 'danger';
        else if (driftVal > 15) pillCls = 'warn';
        pillsHtml += `<span class="pill ${pillCls}" style="--pill-color:${color}">${dim} ${Math.round(score)}</span>`;
      }

      const vizId = `aura-live-${name}`;

      card.innerHTML = `
        <div class="agent-card-header">
          <span class="agent-card-name">${agent.name}</span>
          <span class="agent-card-drift" style="color:${driftColor}">${data.overall_drift.toFixed(1)}</span>
        </div>
        <div class="viz-container" id="${vizId}"></div>
        <div class="agent-card-meta">
          <span class="agent-card-class" style="background:${hexToRgba(driftColor, 0.15)};color:${driftColor}">${clsLabel}</span>
          <span>${timestamp}</span>
          <span>${data.days_monitored || 1} days</span>
        </div>
        <div class="agent-card-pills">${pillsHtml}</div>
      `;

      grid.appendChild(card);

      // Render the aura after the card is in the DOM
      setTimeout(() => renderEnergy(vizId, agent), 10);
    });

    // If there's sparkline data from the first agent, render it
    if (agents[0]?.data?.history?.length >= 2) {
      renderSparkline('live-sparkline', agents[0].data.history);
    }

    section.style.display = '';

    // Scroll-reveal
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) entry.target.classList.add('visible');
      });
    }, { threshold: 0.15 });
    observer.observe(section);

    setTimeout(setupTooltips, 100);
  }

  // ============================================================
  // Initialize
  // ============================================================

  function init() {
    renderEnergy('aura-sentinel', SENTINEL);
    renderEnergy('aura-bare', BARE);
    renderEnergy('aura-drifted', DRIFTED);

    setupTransition();
    setupScrollReveal();
    loadLiveAura();
    setTimeout(setupTooltips, 100);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
</script>
