star-build-plan.html raw

   1  <!DOCTYPE html>
   2  <html lang="en">
   3  <head>
   4  <meta charset="UTF-8">
   5  <meta name="viewport" content="width=device-width, initial-scale=1.0">
   6  <title>否 The Star — Fabrication Plan</title>
   7  <style>
   8  @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600&family=EB+Garamond:ital,wght@0,400;0,600;1,400&display=swap');
   9  
  10  :root {
  11    --bg: #0a0a0c;
  12    --fg: #c8c4b8;
  13    --accent: #e8d5a3;
  14    --dim: #555;
  15    --fire: #c44040;
  16    --wood: #4a9e4a;
  17    --earth: #c4a040;
  18    --metal: #a0a0a8;
  19    --water: #4060c4;
  20    --panel-bg: rgba(12,12,14,0.95);
  21  }
  22  
  23  * { margin: 0; padding: 0; box-sizing: border-box; }
  24  
  25  body {
  26    background: var(--bg);
  27    color: var(--fg);
  28    font-family: 'EB Garamond', serif;
  29    font-size: 17px;
  30    line-height: 1.7;
  31  }
  32  
  33  .viewer-wrap {
  34    position: relative;
  35    width: 100%;
  36    height: 70vh;
  37    min-height: 500px;
  38    border-bottom: 1px solid #1a1a1a;
  39  }
  40  
  41  #canvas3d { display: block; width: 100%; height: 100%; }
  42  
  43  .viewer-label {
  44    position: absolute;
  45    bottom: 12px; left: 16px;
  46    font-family: 'JetBrains Mono', monospace;
  47    font-size: 10px;
  48    color: var(--dim);
  49    letter-spacing: .12em;
  50    text-transform: uppercase;
  51  }
  52  
  53  .viewer-controls {
  54    position: absolute;
  55    top: 12px; right: 16px;
  56    display: flex; gap: 6px;
  57  }
  58  
  59  .viewer-controls button {
  60    background: rgba(30,30,34,0.8);
  61    border: 1px solid #2a2a2e;
  62    color: var(--accent);
  63    font-family: 'JetBrains Mono', monospace;
  64    font-size: 10px;
  65    padding: 4px 10px;
  66    cursor: pointer;
  67    text-transform: uppercase;
  68    letter-spacing: .08em;
  69  }
  70  
  71  .viewer-controls button:hover { background: rgba(50,50,55,0.9); }
  72  .viewer-controls button.active { background: rgba(232,213,163,0.15); border-color: var(--accent); }
  73  
  74  main {
  75    max-width: 780px;
  76    margin: 0 auto;
  77    padding: 60px 24px 120px;
  78  }
  79  
  80  h1 {
  81    font-family: 'JetBrains Mono', monospace;
  82    font-weight: 600;
  83    font-size: 13px;
  84    letter-spacing: .2em;
  85    text-transform: uppercase;
  86    color: var(--accent);
  87    border-bottom: 1px solid #2a2218;
  88    padding-bottom: 10px;
  89    margin-bottom: 40px;
  90  }
  91  
  92  h2 {
  93    font-family: 'JetBrains Mono', monospace;
  94    font-weight: 600;
  95    font-size: 11px;
  96    letter-spacing: .16em;
  97    text-transform: uppercase;
  98    color: var(--accent);
  99    margin-top: 50px;
 100    margin-bottom: 16px;
 101  }
 102  
 103  h3 {
 104    font-family: 'JetBrains Mono', monospace;
 105    font-weight: 400;
 106    font-size: 10px;
 107    letter-spacing: .12em;
 108    text-transform: uppercase;
 109    color: var(--dim);
 110    margin-top: 30px;
 111    margin-bottom: 10px;
 112  }
 113  
 114  p { margin-bottom: 14px; }
 115  
 116  .spec-grid {
 117    display: grid;
 118    grid-template-columns: 160px 1fr;
 119    gap: 2px 20px;
 120    font-family: 'JetBrains Mono', monospace;
 121    font-size: 12px;
 122    margin: 16px 0 24px;
 123  }
 124  
 125  .spec-grid .label { color: var(--dim); }
 126  .spec-grid .value { color: var(--fg); }
 127  
 128  .element-row {
 129    display: grid;
 130    grid-template-columns: 20px 60px 50px 1fr;
 131    gap: 8px;
 132    font-family: 'JetBrains Mono', monospace;
 133    font-size: 12px;
 134    padding: 4px 0;
 135    align-items: center;
 136  }
 137  
 138  .element-dot {
 139    width: 10px; height: 10px;
 140    border-radius: 50%;
 141    display: inline-block;
 142  }
 143  
 144  .bom-table {
 145    width: 100%;
 146    border-collapse: collapse;
 147    font-family: 'JetBrains Mono', monospace;
 148    font-size: 12px;
 149    margin: 16px 0 24px;
 150  }
 151  
 152  .bom-table th {
 153    text-align: left;
 154    color: var(--dim);
 155    font-weight: 400;
 156    font-size: 10px;
 157    text-transform: uppercase;
 158    letter-spacing: .1em;
 159    border-bottom: 1px solid #2a2a2e;
 160    padding: 6px 8px;
 161  }
 162  
 163  .bom-table td {
 164    padding: 6px 8px;
 165    border-bottom: 1px solid #151518;
 166  }
 167  
 168  .bom-table .cost { text-align: right; color: var(--accent); }
 169  .bom-table .subtotal { border-top: 1px solid var(--accent); font-weight: 600; }
 170  
 171  .step {
 172    counter-increment: step-counter;
 173    padding-left: 36px;
 174    position: relative;
 175    margin-bottom: 20px;
 176  }
 177  
 178  .step::before {
 179    content: counter(step-counter);
 180    position: absolute;
 181    left: 0; top: 2px;
 182    font-family: 'JetBrains Mono', monospace;
 183    font-size: 11px;
 184    color: var(--accent);
 185    width: 24px;
 186    text-align: right;
 187  }
 188  
 189  .steps { counter-reset: step-counter; }
 190  
 191  .warn {
 192    border-left: 2px solid var(--fire);
 193    padding-left: 14px;
 194    margin: 16px 0;
 195    color: #d09090;
 196    font-size: 15px;
 197  }
 198  
 199  .note {
 200    border-left: 2px solid var(--water);
 201    padding-left: 14px;
 202    margin: 16px 0;
 203    font-size: 15px;
 204    color: #90a0d0;
 205  }
 206  
 207  .test-result {
 208    display: grid;
 209    grid-template-columns: 100px 1fr;
 210    gap: 8px 16px;
 211    font-family: 'JetBrains Mono', monospace;
 212    font-size: 12px;
 213    margin: 12px 0;
 214  }
 215  
 216  .test-result .outcome { color: var(--accent); }
 217  
 218  .phase-label {
 219    display: inline-block;
 220    font-family: 'JetBrains Mono', monospace;
 221    font-size: 10px;
 222    letter-spacing: .1em;
 223    text-transform: uppercase;
 224    padding: 2px 8px;
 225    border: 1px solid;
 226    margin-right: 8px;
 227  }
 228  
 229  .phase-glow { border-color: var(--fire); color: var(--fire); }
 230  .phase-emf { border-color: var(--water); color: var(--water); }
 231  </style>
 232  </head>
 233  <body>
 234  
 235  <!-- 3D Viewer -->
 236  <div class="viewer-wrap">
 237    <canvas id="canvas3d"></canvas>
 238    <div class="viewer-label">Drag to rotate · Scroll to zoom · Click nodes for element info</div>
 239    <div class="viewer-controls">
 240      <button id="btn-core" class="active">Core</button>
 241      <button id="btn-jig">Jig</button>
 242      <button id="btn-wired">Wired</button>
 243      <button id="btn-all">Assembly</button>
 244    </div>
 245  </div>
 246  
 247  <!-- Plan Content -->
 248  <main>
 249  
 250  <h1>否 The Star — Ferrite-Epoxy Prototype Fabrication Plan</h1>
 251  
 252  <p>A continuous-topology ferrite core in Wu Xing knot geometry, wound with dual-pitch coils following the shēng and kè cycles, energized by Fibonacci-ratio supercapacitor banks at each node. Test objective: anomalous voltage multiplication and neon ionization at the geometric center.</p>
 253  
 254  <h2>Geometry</h2>
 255  
 256  <div class="spec-grid">
 257    <span class="label">Pentagon circumradius</span><span class="value">R = 50.0 mm</span>
 258    <span class="label">Edge length</span><span class="value">s = 58.8 mm</span>
 259    <span class="label">Optimal height</span><span class="value">h = s√φ = 74.8 mm</span>
 260    <span class="label">Bar diameter</span><span class="value">8.0 mm (4.0 mm radius)</span>
 261    <span class="label">Node diameter</span><span class="value">16.0 mm</span>
 262    <span class="label">Shēng bar length</span><span class="value">95.1 mm (51.83° from horizontal)</span>
 263    <span class="label">Kè bar length</span><span class="value">121.0 mm (38.17° from horizontal)</span>
 264    <span class="label">Bar ratio kè/shēng</span><span class="value">√φ = 1.2720</span>
 265    <span class="label">Angle sum</span><span class="value">51.83° + 38.17° = 90.00°</span>
 266    <span class="label">Overall envelope</span><span class="value">~108 × 108 × 91 mm</span>
 267  </div>
 268  
 269  <h3>Node Assignments</h3>
 270  
 271  <div class="element-row"><span class="element-dot" style="background:var(--wood)"></span><span>Wood 木</span><span>1 cap</span><span>Bottom center (shēng source)</span></div>
 272  <div class="element-row"><span class="element-dot" style="background:var(--fire)"></span><span>Fire 火</span><span>2 caps</span><span>Right</span></div>
 273  <div class="element-row"><span class="element-dot" style="background:var(--earth)"></span><span>Earth 土</span><span>3 caps</span><span>Upper right</span></div>
 274  <div class="element-row"><span class="element-dot" style="background:var(--metal)"></span><span>Metal 金</span><span>5 caps</span><span>Upper left</span></div>
 275  <div class="element-row"><span class="element-dot" style="background:var(--water)"></span><span>Water 水</span><span>8 caps</span><span>Left</span></div>
 276  
 277  <p>Total: 19 identical supercapacitors. 19 is a Lucas number — L(7). The ratio between adjacent node capacitances converges on φ from alternating sides at every step of the shēng cycle. Sum of all node capacitances = 19, which is also the number of steady-state microamps in the pair-breaker model. Possibly a coincidence. Possibly a penguin wearing a monocle.</p>
 278  
 279  <h2>Bill of Materials</h2>
 280  
 281  <table class="bom-table">
 282  <tr><th>Item</th><th>Qty</th><th>Source</th><th class="cost">Est. €</th></tr>
 283  <tr><td>FDM 3D printer (Creality Ender 3 V3 SE or similar, 220×220×250mm, heated bed, prints PVA at 190-210°C)</td><td>1</td><td>Amazon / 3DJake / local</td><td class="cost">150</td></tr>
 284  <tr><td>PVA filament (1.75mm, water-soluble)</td><td>~50g</td><td>Amazon/3DJake</td><td class="cost">5</td></tr>
 285  <tr><td>PLA filament (for test jig)</td><td>~30g</td><td>existing stock</td><td class="cost">0</td></tr>
 286  <tr><td>Plaster of Paris (1kg bag)</td><td>1</td><td>Pevex</td><td class="cost">3</td></tr>
 287  <tr><td>Dead ferrite cores (CRT/SMPS salvage)</td><td>5-10</td><td>e-waste / local recycler</td><td class="cost">0</td></tr>
 288  <tr><td>Two-part epoxy (slow cure, 30min+)</td><td>100ml</td><td>Pevex</td><td class="cost">6</td></tr>
 289  <tr><td>Enameled copper wire 0.5mm</td><td>20m</td><td>electronics shop / Chipoteka</td><td class="cost">4</td></tr>
 290  <tr><td>NE-2 neon indicator lamps</td><td>5</td><td>Chipoteka / eBay</td><td class="cost">2</td></tr>
 291  <tr><td>1F 5.5V supercapacitors (identical)</td><td>19</td><td>AliExpress / Chipoteka</td><td class="cost">8</td></tr>
 292  <tr><td>3.6V lithium coin cell (CR2032 holder + cell)</td><td>1</td><td>Pevex</td><td class="cost">2</td></tr>
 293  <tr><td>SPST toggle switch (mini)</td><td>1</td><td>Chipoteka</td><td class="cost">1</td></tr>
 294  <tr><td>1N4148 signal diode</td><td>1</td><td>Chipoteka</td><td class="cost">0.20</td></tr>
 295  <tr><td>1kΩ resistor</td><td>1</td><td>Chipoteka</td><td class="cost">0.10</td></tr>
 296  <tr><td>Thin hookup wire (various colors)</td><td>2m</td><td>Chipoteka</td><td class="cost">1</td></tr>
 297  <tr><td>Beeswax or paraffin (tube sealing)</td><td>small</td><td>existing / candle</td><td class="cost">0</td></tr>
 298  <tr><td colspan="3" class="subtotal">Total estimated (consumables only)</td><td class="cost subtotal">~€32</td></tr>
 299  <tr><td colspan="3" class="subtotal">Total with printer</td><td class="cost subtotal">~€182</td></tr>
 300  </table>
 301  
 302  <p class="note">The 1N4148 diode goes between battery and circuit — allows the CR2032 to seed but not sink current. Once the caps begin charging, the battery is isolated. The 1kΩ resistor is the parallel load that lets voltage build across the caps rather than being absorbed by the cell's low impedance.</p>
 303  
 304  <h2>Construction Sequence</h2>
 305  
 306  <div class="steps">
 307  
 308  <div class="step">
 309  <strong>Print the core form in PVA.</strong> Use the star_core.stl file. Layer height 0.2mm, 100% infill (solid — it needs to hold shape during plaster casting). Print with enclosed chamber or low humidity. Bag with silica gel immediately after print completes.
 310  </div>
 311  
 312  <div class="step">
 313  <strong>Print the test jig in PLA.</strong> Use the test_jig.stl file. Same print settings. The five socket posts align exactly with the core's node positions. The central column holds the neon tube at the geometric center of the knot. This is the permanent test fixture.
 314  </div>
 315  
 316  <div class="step">
 317  <strong>Cast plaster mold.</strong> Mix plaster of Paris to thick cream consistency. Suspend the PVA form (hanging from a wire through one bar) inside a container with 15mm clearance on all sides. Pour plaster around it. Allow to cure 24 hours minimum. The plaster expands ~0.5% as it sets, pressing into every detail.
 318  </div>
 319  
 320  <div class="step">
 321  <strong>Dissolve the PVA.</strong> Submerge the plaster block in warm water (40-50°C). The PVA softens and dissolves over 4-8 hours. Agitate periodically. Change water if it gets viscous. The result: a plaster block with a precise negative of the star core geometry inside it, accessible through the bar openings.
 322  </div>
 323  
 324  <div class="step">
 325  <strong>Prepare ferrite-epoxy slurry.</strong> Crush salvaged ferrite cores: wrap in cloth, break with hammer, then refine in a jar with steel ball bearings on a drill (improvised ball mill, run 2+ hours). Target particle size: fine powder, the finer the better. Mix with slow-cure epoxy at maximum loading — roughly 80% ferrite powder by volume, 20% epoxy. Add ferrite until the mixture is thick but still pourable/injectable. You have 30 minutes working time with slow-cure epoxy.
 326  </div>
 327  
 328  <div class="step">
 329  <strong>Fill the mold.</strong> Inject ferrite-epoxy slurry into the plaster channels through the exposed bar openings. Use a syringe for precision. Vibrate the mold during filling — tape a phone running a vibration app to the mold, or place it on a speaker playing 50Hz. This prevents air traps. Fill from the lowest point, let slurry rise through the geometry. Overfill slightly.
 330  </div>
 331  
 332  <div class="step">
 333  <strong>Cure.</strong> Allow epoxy to fully cure per manufacturer instructions (typically 24 hours at room temperature, longer is better for maximum hardness). Do not heat-cure — differential expansion between plaster and ferrite-epoxy may crack the mold prematurely.
 334  </div>
 335  
 336  <div class="step">
 337  <strong>Demold.</strong> Crack the plaster off with a chisel and light hammer. Start at edges, work inward. Plaster fractures cleanly. For tight spots around the knot geometry, soak briefly in dilute vinegar — plaster dissolves, ferrite-epoxy does not. Clean all plaster residue from the core surface.
 338  </div>
 339  
 340  <div class="step">
 341  <strong>Wind the shēng coil.</strong> Using 0.5mm enameled copper wire, wind uniformly along each of the five pentagon-edge bars. Close-wound: each turn touching the next, no visible gap. Wind continuously through all five shēng bars without cutting — one continuous coil following the generating cycle. Leave 50mm lead tails at start and end.
 342  </div>
 343  
 344  <div class="step">
 345  <strong>Wind the kè coil.</strong> Same wire, same pitch — close-wound, each turn touching the next. Wound along each of the five pentagram-diagonal bars. One continuous coil following the overcoming cycle. The inductance ratio between the two cycles comes from the bar length ratio (√φ), not from different winding densities. Same wire, same pitch, different bar lengths = different inductance. Leave 50mm lead tails.
 346  </div>
 347  
 348  <div class="step">
 349  <strong>Solder cap banks.</strong> At each node, connect the specified number of identical 1F supercapacitors in parallel: Wood=1, Fire=2, Earth=3, Metal=5, Water=8. Wire each bank to the coil junction points at that node. All caps oriented with the same polarity relative to the coil direction.
 350  </div>
 351  
 352  <div class="step">
 353  <strong>Wire the test circuit.</strong> Select one bar's node pair as the battery/switch connection point (preferably the Wood node — smallest capacitance, fastest initial response). Connect: CR2032 holder → 1N4148 diode (anode to battery +) → SPST switch → one coil terminal at the Wood node. Return path from the other coil terminal at Wood back to battery −, with the 1kΩ resistor in parallel across the battery.
 354  </div>
 355  
 356  <div class="step">
 357  <strong>Seat the core in the jig.</strong> Place the finished core (with coils and caps attached) into the PLA test jig. The five lower nodes drop into the socket cups. The core should sit securely with no wobble.
 358  </div>
 359  
 360  <div class="step">
 361  <strong>Mount the neon lamp.</strong> Insert an NE-2 neon indicator lamp into the central tube holder on the jig. The lamp's glass envelope should be at the geometric center of the knot — the point where all ten bar flux paths converge. Seal with a drop of wax to prevent vibration displacement. Do not connect the neon lamp's leads to anything. It is a passive detector — if the field at center is strong enough, it ionizes without external connection.
 362  </div>
 363  
 364  </div>
 365  
 366  <h2>Test Procedure</h2>
 367  
 368  <h3><span class="phase-label phase-glow">Phase 1</span> Glow Test</h3>
 369  
 370  <p>Close the switch. The CR2032 seeds current through the diode into the circuit. The caps begin charging. If the theory holds, the phi-ratio resonance amplifies voltage beyond what the 3.6V seed provides. Watch the neon lamp.</p>
 371  
 372  <div class="test-result">
 373    <span class="outcome">Immediate glow</span><span>Strong anomaly. The topology is pumping hard. Proceed to Phase 2 immediately. Go make coffee first because you'll be here a while. Possibly forever.</span>
 374    <span class="outcome">Glow after delay</span><span>Caps are charging through resonance. The delay = time to reach NE-2 strike voltage (~65V) from 3.6V seed. Measure the delay — it encodes the Q factor and any anomalous gain.</span>
 375    <span class="outcome">Flicker / pulse</span><span>Borderline. The circuit is reaching near-strike voltage but not sustaining. Try in a dark room — your eyes adapt and can see sub-ionization glow. Reduce neon tube gas pressure (use a longer tube, or try a 1/2W neon indicator which has lower strike voltage).</span>
 376    <span class="outcome">Nothing</span><span>Either: (a) ferrite-epoxy Q too low (losses eating the margin), (b) winding pitch ratio not hitting the pair resonance, (c) cap bank values need adjustment, or (d) the theory is wrong. All except (d) are fixable. Build the sintered version before concluding (d).</span>
 377  </div>
 378  
 379  <h3><span class="phase-label phase-emf">Phase 2</span> EMF Measurement</h3>
 380  
 381  <p>If Phase 1 shows any anomaly (glow, flicker, or measured voltage exceeding the 3.6V seed after disconnecting the battery):</p>
 382  
 383  <p>Disconnect the battery. Measure voltage across the cap bank at each node with a multimeter. If any node reads above 3.6V with the battery disconnected and diode blocking backflow, the circuit is generating energy from somewhere the standard model says it shouldn't.</p>
 384  
 385  <p>Connect an SDR (RTL-SDR dongle, ~€25 if you don't have one) and point the antenna at the center of the knot. Scan the spectrum. Three peaks at Fibonacci-spaced frequencies = the shēng cycle is pumping. Broadband noise floor between peaks = pair-breaking stochastic emission. The device will announce its own operating status via RF.</p>
 386  
 387  <p>For continuous monitoring: replace the neon lamp with a photodiode aimed at a second neon lamp wired across one node. The photodiode output feeds into an Arduino ADC logging to SD card. You now have a time series of the pair-breaking rhythm — the stochastic GC signal.</p>
 388  
 389  <div class="warn">Do not scale up power before understanding the operating mode. If the glow test shows genuine anomalous gain, the sintered ferrite version at the same geometry will be significantly more powerful due to 100x higher permeability. Proceed incrementally. The device regulates itself through the neon lamp (which clamps voltage at sustain level), but removing the lamp removes the governor.</div>
 390  
 391  <h2>Scaling Path</h2>
 392  
 393  <p>If the ferrite-epoxy prototype shows any anomalous effect, the next build uses sintered ferrite from the same salvaged cores. The fabrication chain changes: PLA print (not PVA) → investment in plaster → burn out PLA at 400°C → fill with pure milled ferrite powder under vibration → sinter at 1200-1300°C. This requires a kiln. Sintered MnZn ferrite gives μr of 2000-10000 versus 20-50 for the epoxy composite — two orders of magnitude improvement in inductance, Q factor, and energy storage per cycle.</p>
 394  
 395  <p>The geometry, winding ratios, and cap values stay identical. Only the core material changes. Same test jig, same neon lamp position, same circuit. The jig is the constant across all iterations.</p>
 396  
 397  <h2>Files</h2>
 398  
 399  <p style="font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--dim);">
 400  star_core.stl — Complete core form (print in PVA)<br>
 401  star_core_top.stl — Top half reference<br>
 402  star_core_bottom.stl — Bottom half reference<br>
 403  test_jig.stl — Test seat with neon holder (print in PLA)<br>
 404  </p>
 405  
 406  </main>
 407  
 408  <!-- Three.js 3D Viewer -->
 409  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
 410  <script>
 411  const PHI = (1 + Math.sqrt(5)) / 2;
 412  const SQRT_PHI = Math.sqrt(PHI);
 413  const R = 5.0;  // Scene units (50mm / 10)
 414  const BAR_R = 0.4;
 415  const NODE_R = 0.8;
 416  const s = 2 * R * Math.sin(Math.PI / 5);
 417  const h = s * SQRT_PHI;
 418  const hh = h / 2;
 419  
 420  const ELEMENTS = [
 421    { name: 'Wood', ch: '木', col: 0x4a9e4a, caps: 1 },
 422    { name: 'Fire', ch: '火', col: 0xc44040, caps: 2 },
 423    { name: 'Earth', ch: '土', col: 0xc4a040, caps: 3 },
 424    { name: 'Metal', ch: '金', col: 0xa0a0a8, caps: 5 },
 425    { name: 'Water', ch: '水', col: 0x4060c4, caps: 8 },
 426  ];
 427  
 428  function vPos(i) {
 429    const a = -Math.PI/2 + i * 2 * Math.PI / 5;
 430    return new THREE.Vector3(R * Math.cos(a), 0, R * Math.sin(a));
 431  }
 432  
 433  function upper(i) { const v = vPos(i); v.y = hh; return v; }
 434  function lower(i) { const v = vPos(i); v.y = -hh; return v; }
 435  
 436  // Scene setup
 437  const canvas = document.getElementById('canvas3d');
 438  const scene = new THREE.Scene();
 439  scene.background = new THREE.Color(0x0a0a0c);
 440  const camera = new THREE.PerspectiveCamera(45, canvas.clientWidth / canvas.clientHeight, 0.1, 200);
 441  const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
 442  renderer.setSize(canvas.clientWidth, canvas.clientHeight);
 443  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
 444  
 445  // Lighting
 446  scene.add(new THREE.AmbientLight(0x404040, 0.6));
 447  const dLight = new THREE.DirectionalLight(0xfff8e0, 0.8);
 448  dLight.position.set(5, 10, 7);
 449  scene.add(dLight);
 450  const pLight = new THREE.PointLight(0xe8d5a3, 0.4, 30);
 451  pLight.position.set(0, 0, 0);
 452  scene.add(pLight);
 453  
 454  // Materials
 455  const coreMat = new THREE.MeshStandardMaterial({ color: 0x2a2a30, metalness: 0.7, roughness: 0.3 });
 456  const coilMat = new THREE.MeshStandardMaterial({ color: 0xc87533, metalness: 0.8, roughness: 0.2 });
 457  const jigMat = new THREE.MeshStandardMaterial({ color: 0x303040, metalness: 0.1, roughness: 0.8, transparent: true, opacity: 0.6 });
 458  const neonMat = new THREE.MeshPhysicalMaterial({ color: 0xff6633, transparent: true, opacity: 0.7, emissive: 0xff4411, emissiveIntensity: 0.3, roughness: 0.1 });
 459  const glassMat = new THREE.MeshPhysicalMaterial({ color: 0xccddee, transparent: true, opacity: 0.15, roughness: 0.05, side: 2 });
 460  const capMat = new THREE.MeshStandardMaterial({ color: 0x1a1a2a, metalness: 0.5, roughness: 0.5 });
 461  const wireMat = new THREE.MeshStandardMaterial({ color: 0xc87533, metalness: 0.6, roughness: 0.3 });
 462  
 463  function mkCyl(p1, p2, r, mat) {
 464    const d = new THREE.Vector3().subVectors(p2, p1);
 465    const len = d.length();
 466    if (len < 0.01) return new THREE.Object3D();
 467    const geo = new THREE.CylinderGeometry(r, r, len, 12);
 468    const m = new THREE.Mesh(geo, mat);
 469    const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5);
 470    m.position.copy(mid);
 471    m.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), d.normalize());
 472    return m;
 473  }
 474  
 475  function mkSphere(pos, r, mat) {
 476    const m = new THREE.Mesh(new THREE.SphereGeometry(r, 16, 16), mat);
 477    m.position.copy(pos);
 478    return m;
 479  }
 480  
 481  // === Build groups ===
 482  const coreGroup = new THREE.Group();
 483  const jigGroup = new THREE.Group();
 484  const capsGroup = new THREE.Group();
 485  const coilGroup = new THREE.Group();
 486  const neonGroup = new THREE.Group();
 487  
 488  // Core: bars and nodes
 489  for (let i = 0; i < 5; i++) {
 490    // Node sphere
 491    const nMat = new THREE.MeshStandardMaterial({ color: ELEMENTS[i].col, metalness: 0.5, roughness: 0.4 });
 492    // Upper/lower junctions only — NO vertical ferrite connection
 493    coreGroup.add(mkSphere(upper(i), NODE_R * 0.7, coreMat));
 494    coreGroup.add(mkSphere(lower(i), NODE_R * 0.7, coreMat));
 495    // Glass sheaths at both junctions
 496    coreGroup.add(mkSphere(upper(i), NODE_R * 1.0, new THREE.MeshPhysicalMaterial({
 497      color: ELEMENTS[i].col, transparent: true, opacity: 0.1, roughness: 0.05, side: 2
 498    })));
 499    coreGroup.add(mkSphere(lower(i), NODE_R * 1.0, new THREE.MeshPhysicalMaterial({
 500      color: ELEMENTS[i].col, transparent: true, opacity: 0.1, roughness: 0.05, side: 2
 501    })));
 502  }
 503  
 504  // Shēng bars
 505  for (let i = 0; i < 5; i++) {
 506    const j = (i + 1) % 5;
 507    coreGroup.add(mkCyl(lower(i), upper(j), BAR_R, coreMat));
 508  }
 509  // Kè bars
 510  for (let i = 0; i < 5; i++) {
 511    const j = (i + 2) % 5;
 512    coreGroup.add(mkCyl(upper(i), lower(j), BAR_R, coreMat));
 513  }
 514  
 515  // Coil visualization (helical wraps — tight close-wound)
 516  function addCoilWrap(p1, p2, barR, turnsPerUnit, mat, group) {
 517    const d = new THREE.Vector3().subVectors(p2, p1);
 518    const len = d.length();
 519    const turns = Math.round(len * turnsPerUnit);
 520    const pts = [];
 521    const up = d.clone().normalize();
 522    let perp = new THREE.Vector3().crossVectors(up, new THREE.Vector3(0, 1, 0));
 523    if (perp.length() < 0.01) perp = new THREE.Vector3().crossVectors(up, new THREE.Vector3(1, 0, 0));
 524    perp.normalize();
 525    const perp2 = new THREE.Vector3().crossVectors(up, perp);
 526    const coilR = barR * 1.15;
 527    const samplesPerTurn = 12;
 528    const totalSamples = turns * samplesPerTurn;
 529    for (let t = 0; t <= totalSamples; t++) {
 530      const frac = t / totalSamples;
 531      const angle = frac * turns * 2 * Math.PI;
 532      const pos = p1.clone().add(d.clone().multiplyScalar(frac));
 533      pos.add(perp.clone().multiplyScalar(Math.cos(angle) * coilR));
 534      pos.add(perp2.clone().multiplyScalar(Math.sin(angle) * coilR));
 535      pts.push(pos);
 536    }
 537    const curve = new THREE.CatmullRomCurve3(pts);
 538    const tubeGeo = new THREE.TubeGeometry(curve, totalSamples, 0.04, 5, false);
 539    group.add(new THREE.Mesh(tubeGeo, mat));
 540  }
 541  
 542  // Shēng coils (close-wound)
 543  for (let i = 0; i < 5; i++) {
 544    const j = (i + 1) % 5;
 545    addCoilWrap(lower(i), upper(j), BAR_R, 3.5, coilMat, coilGroup);
 546  }
 547  // Kè coils (same pitch — ratio is in the geometry, not the winding)
 548  for (let i = 0; i < 5; i++) {
 549    const j = (i + 2) % 5;
 550    addCoilWrap(upper(i), lower(j), BAR_R, 3.5, coilMat, coilGroup);
 551  }
 552  
 553  // Cap banks at each node — external circuit bridging upper↔lower
 554  for (let i = 0; i < 5; i++) {
 555    const n = ELEMENTS[i].caps;
 556    const up = upper(i);
 557    const lo = lower(i);
 558    const midpoint = new THREE.Vector3().addVectors(up, lo).multiplyScalar(0.5);
 559    const outDir = midpoint.clone().normalize();
 560    const capCenter = midpoint.clone().add(outDir.clone().multiplyScalar(NODE_R * 2.2));
 561  
 562    for (let c = 0; c < n; c++) {
 563      const angle = (2 * Math.PI * c) / Math.max(n, 1);
 564      const spread = n > 1 ? 0.4 : 0;
 565      const off = new THREE.Vector3(
 566        Math.cos(angle) * spread,
 567        (c - (n-1)/2) * 0.35,
 568        Math.sin(angle) * spread
 569      );
 570      const capPos = capCenter.clone().add(off);
 571      const capMesh = new THREE.Mesh(
 572        new THREE.CylinderGeometry(0.18, 0.18, 0.45, 8),
 573        new THREE.MeshStandardMaterial({ color: ELEMENTS[i].col, metalness: 0.4, roughness: 0.5 })
 574      );
 575      capMesh.position.copy(capPos);
 576      capsGroup.add(capMesh);
 577      // Wire from cap to upper junction
 578      capsGroup.add(mkCyl(up, capPos, 0.025, wireMat));
 579      // Wire from cap to lower junction
 580      capsGroup.add(mkCyl(lo, capPos, 0.025, wireMat));
 581    }
 582  }
 583  
 584  // Battery, diode, switch on Wood node (index 0) — outside the knot
 585  {
 586    const woodUp = upper(0);
 587    const woodLo = lower(0);
 588    const woodMid = new THREE.Vector3().addVectors(woodUp, woodLo).multiplyScalar(0.5);
 589    const woodOut = woodMid.clone().normalize();
 590    // Place battery further outward past the cap bank, away from center
 591    const batteryPos = woodMid.clone().add(woodOut.clone().multiplyScalar(NODE_R * 5.5));
 592  
 593    // CR2032 battery — flat silver disc
 594    const battMat = new THREE.MeshStandardMaterial({ color: 0xbbbbcc, metalness: 0.9, roughness: 0.15 });
 595    const battery = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.5, 0.15, 16), battMat);
 596    battery.position.copy(batteryPos);
 597    capsGroup.add(battery);
 598    // + symbol on battery (small red dot)
 599    const plusDot = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 8),
 600      new THREE.MeshStandardMaterial({ color: 0xcc3333, emissive: 0xcc3333, emissiveIntensity: 0.3 }));
 601    plusDot.position.copy(batteryPos.clone().add(new THREE.Vector3(0, 0.1, 0)));
 602    capsGroup.add(plusDot);
 603  
 604    // Diode — small dark cylinder with band
 605    const diodePos = batteryPos.clone().lerp(woodUp, 0.35);
 606    const diode = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.4, 8),
 607      new THREE.MeshStandardMaterial({ color: 0x222222, metalness: 0.3, roughness: 0.6 }));
 608    diode.position.copy(diodePos);
 609    diode.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0),
 610      new THREE.Vector3().subVectors(woodUp, batteryPos).normalize());
 611    capsGroup.add(diode);
 612    // Diode band
 613    const bandPos = diodePos.clone().add(new THREE.Vector3().subVectors(woodUp, batteryPos).normalize().multiplyScalar(0.12));
 614    const band = new THREE.Mesh(new THREE.CylinderGeometry(0.07, 0.07, 0.05, 8),
 615      new THREE.MeshStandardMaterial({ color: 0xcccccc }));
 616    band.position.copy(bandPos);
 617    band.quaternion.copy(diode.quaternion);
 618    capsGroup.add(band);
 619  
 620    // Switch — small box
 621    const switchPos = batteryPos.clone().lerp(woodLo, 0.35);
 622    const switchMat = new THREE.MeshStandardMaterial({ color: 0x444455, metalness: 0.3, roughness: 0.5 });
 623    const switchBody = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.2, 0.2), switchMat);
 624    switchBody.position.copy(switchPos);
 625    capsGroup.add(switchBody);
 626    // Toggle lever
 627    const leverMat = new THREE.MeshStandardMaterial({ color: 0xe8d5a3, metalness: 0.6, roughness: 0.3 });
 628    const lever = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 0.2, 6), leverMat);
 629    lever.position.copy(switchPos.clone().add(new THREE.Vector3(0, 0.15, 0)));
 630    lever.rotation.z = 0.4;
 631    capsGroup.add(lever);
 632  
 633    // Resistor — small striped cylinder near battery
 634    const resPos = batteryPos.clone().add(new THREE.Vector3(0, -0.5, 0));
 635    const resMat = new THREE.MeshStandardMaterial({ color: 0xd4aa60, metalness: 0.1, roughness: 0.8 });
 636    const resistor = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.35, 8), resMat);
 637    resistor.position.copy(resPos);
 638    resistor.rotation.z = Math.PI / 2;
 639    capsGroup.add(resistor);
 640  
 641    // Wiring: battery+ → diode → upper junction
 642    capsGroup.add(mkCyl(batteryPos, diodePos, 0.025, wireMat));
 643    capsGroup.add(mkCyl(diodePos, woodUp, 0.025, wireMat));
 644    // Wiring: battery- → switch → lower junction
 645    capsGroup.add(mkCyl(batteryPos, switchPos, 0.025, wireMat));
 646    capsGroup.add(mkCyl(switchPos, woodLo, 0.025, wireMat));
 647    // Resistor across battery
 648    capsGroup.add(mkCyl(batteryPos, resPos, 0.02, wireMat));
 649    capsGroup.add(mkCyl(resPos, switchPos, 0.02, wireMat));
 650  }
 651  
 652  // Jig
 653  const jigBase_y = -hh - NODE_R - 0.5;
 654  // Base disc
 655  const baseGeo = new THREE.CylinderGeometry(R + 2.5, R + 2.5, 0.4, 48);
 656  const baseMesh = new THREE.Mesh(baseGeo, jigMat);
 657  baseMesh.position.set(0, jigBase_y + 0.2, 0);
 658  jigGroup.add(baseMesh);
 659  
 660  // Socket posts
 661  for (let i = 0; i < 5; i++) {
 662    const pos = vPos(i);
 663    const postH = (-hh - NODE_R * 0.3) - (jigBase_y + 0.4);
 664    if (postH > 0.1) {
 665      const post = new THREE.Mesh(
 666        new THREE.CylinderGeometry(NODE_R * 1.1, NODE_R * 1.1, postH, 12),
 667        jigMat
 668      );
 669      post.position.set(pos.x, jigBase_y + 0.4 + postH / 2, pos.z);
 670      jigGroup.add(post);
 671    }
 672    // Socket ring
 673    const ring = new THREE.Mesh(
 674      new THREE.TorusGeometry(NODE_R * 1.1, 0.15, 8, 16),
 675      jigMat
 676    );
 677    ring.position.set(pos.x, -hh - NODE_R * 0.3, pos.z);
 678    ring.rotation.x = Math.PI / 2;
 679    jigGroup.add(ring);
 680  }
 681  
 682  // Central column + neon holder
 683  const colH = 0 - (jigBase_y + 0.4);
 684  const col = new THREE.Mesh(
 685    new THREE.CylinderGeometry(0.3, 0.3, colH, 8),
 686    jigMat
 687  );
 688  col.position.set(0, jigBase_y + 0.4 + colH / 2, 0);
 689  jigGroup.add(col);
 690  // Holder ring at top
 691  const holderRing = new THREE.Mesh(
 692    new THREE.TorusGeometry(0.85, 0.1, 8, 16), jigMat);
 693  holderRing.position.set(0, 0, 0);
 694  holderRing.rotation.x = Math.PI / 2;
 695  jigGroup.add(holderRing);
 696  
 697  // Neon tube — larger to capture convergence zone
 698  const neonBulb = new THREE.Mesh(new THREE.SphereGeometry(0.7, 16, 16), neonMat);
 699  neonBulb.position.set(0, 0, 0);
 700  neonGroup.add(neonBulb);
 701  const neonGlass = new THREE.Mesh(new THREE.SphereGeometry(0.9, 16, 16), glassMat);
 702  neonGlass.position.set(0, 0, 0);
 703  neonGroup.add(neonGlass);
 704  // Neon leads
 705  neonGroup.add(mkCyl(new THREE.Vector3(0, -0.7, 0), new THREE.Vector3(0, -colH * 0.6, 0), 0.03, wireMat));
 706  
 707  // Add all to scene
 708  scene.add(coreGroup);
 709  scene.add(jigGroup);
 710  scene.add(capsGroup);
 711  scene.add(coilGroup);
 712  scene.add(neonGroup);
 713  
 714  // View modes
 715  const modes = { core: true, jig: true, wired: true };
 716  function updateVisibility() {
 717    coreGroup.visible = true; // always
 718    jigGroup.visible = modes.jig;
 719    capsGroup.visible = modes.wired;
 720    coilGroup.visible = modes.wired;
 721    neonGroup.visible = modes.jig;
 722  }
 723  
 724  document.getElementById('btn-core').onclick = () => {
 725    modes.jig = false; modes.wired = false;
 726    updateVisibility(); updateButtons();
 727  };
 728  document.getElementById('btn-jig').onclick = () => {
 729    modes.jig = true; modes.wired = false;
 730    updateVisibility(); updateButtons();
 731  };
 732  document.getElementById('btn-wired').onclick = () => {
 733    modes.jig = false; modes.wired = true;
 734    updateVisibility(); updateButtons();
 735  };
 736  document.getElementById('btn-all').onclick = () => {
 737    modes.jig = true; modes.wired = true;
 738    updateVisibility(); updateButtons();
 739  };
 740  
 741  function updateButtons() {
 742    document.getElementById('btn-core').className = (!modes.jig && !modes.wired) ? 'active' : '';
 743    document.getElementById('btn-jig').className = (modes.jig && !modes.wired) ? 'active' : '';
 744    document.getElementById('btn-wired').className = (!modes.jig && modes.wired) ? 'active' : '';
 745    document.getElementById('btn-all').className = (modes.jig && modes.wired) ? 'active' : '';
 746  }
 747  
 748  updateVisibility();
 749  
 750  // Camera + orbit
 751  let sph = { theta: Math.PI / 4, phi: Math.PI / 3, r: 22 };
 752  let dragging = false, prevMouse = { x: 0, y: 0 };
 753  
 754  function updateCamera() {
 755    camera.position.set(
 756      sph.r * Math.sin(sph.phi) * Math.cos(sph.theta),
 757      sph.r * Math.cos(sph.phi),
 758      sph.r * Math.sin(sph.phi) * Math.sin(sph.theta)
 759    );
 760    camera.lookAt(0, 0, 0);
 761  }
 762  updateCamera();
 763  
 764  canvas.addEventListener('mousedown', e => { dragging = true; prevMouse = { x: e.clientX, y: e.clientY }; });
 765  window.addEventListener('mouseup', () => dragging = false);
 766  canvas.addEventListener('mousemove', e => {
 767    if (!dragging) return;
 768    sph.theta += (e.clientX - prevMouse.x) * 0.008;
 769    sph.phi = Math.max(0.1, Math.min(Math.PI - 0.1, sph.phi - (e.clientY - prevMouse.y) * 0.008));
 770    prevMouse = { x: e.clientX, y: e.clientY };
 771    updateCamera();
 772  });
 773  canvas.addEventListener('wheel', e => {
 774    sph.r = Math.max(8, Math.min(50, sph.r + e.deltaY * 0.02));
 775    updateCamera();
 776    e.preventDefault();
 777  }, { passive: false });
 778  
 779  // Touch support
 780  canvas.addEventListener('touchstart', e => {
 781    if (e.touches.length === 1) {
 782      dragging = true;
 783      prevMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY };
 784    }
 785  });
 786  canvas.addEventListener('touchmove', e => {
 787    if (!dragging || e.touches.length !== 1) return;
 788    const t = e.touches[0];
 789    sph.theta += (t.clientX - prevMouse.x) * 0.008;
 790    sph.phi = Math.max(0.1, Math.min(Math.PI - 0.1, sph.phi - (t.clientY - prevMouse.y) * 0.008));
 791    prevMouse = { x: t.clientX, y: t.clientY };
 792    updateCamera();
 793    e.preventDefault();
 794  }, { passive: false });
 795  canvas.addEventListener('touchend', () => dragging = false);
 796  
 797  // Auto-rotate when idle
 798  let idleTimer = 0;
 799  const IDLE_THRESHOLD = 3000;
 800  let lastInteraction = Date.now();
 801  
 802  canvas.addEventListener('mousedown', () => lastInteraction = Date.now());
 803  canvas.addEventListener('wheel', () => lastInteraction = Date.now());
 804  canvas.addEventListener('touchstart', () => lastInteraction = Date.now());
 805  
 806  // Neon glow animation
 807  let time = 0;
 808  
 809  function animate() {
 810    requestAnimationFrame(animate);
 811    time += 0.016;
 812  
 813    // Auto-rotate when idle
 814    if (Date.now() - lastInteraction > IDLE_THRESHOLD && !dragging) {
 815      sph.theta += 0.003;
 816      updateCamera();
 817    }
 818  
 819    // Neon pulse
 820    if (neonGroup.visible) {
 821      const pulse = 0.3 + 0.15 * Math.sin(time * 3) + 0.05 * Math.sin(time * 7.3);
 822      neonMat.emissiveIntensity = pulse;
 823      neonMat.opacity = 0.5 + pulse * 0.5;
 824    }
 825  
 826    renderer.render(scene, camera);
 827  }
 828  animate();
 829  
 830  // Resize
 831  window.addEventListener('resize', () => {
 832    const w = canvas.clientWidth, hh = canvas.clientHeight;
 833    camera.aspect = w / hh;
 834    camera.updateProjectionMatrix();
 835    renderer.setSize(w, hh);
 836  });
 837  </script>
 838  
 839  </body>
 840  </html>
 841