MediaWiki:Common.js

From HADES
Jump to navigationJump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* ================================================================
   H A D E S  —  MediaWiki:Common.js
   Ambient visual layer: candles, gothic arches, rose petals,
   sacred hearts, damask motifs, wax drips, dust motes
   ================================================================ */

( function () {
  'use strict';

  /* ── FONT PRELOAD ─────────────────────────────────────────── */
  function preloadFonts() {
    if ( document.getElementById( 'hades-fonts' ) ) { return; }
    var link = document.createElement( 'link' );
    link.id   = 'hades-fonts';
    link.rel  = 'stylesheet';
    link.href = 'https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700;900&family=Cinzel:wght@400;600;700&family=IM+Fell+English:ital@0;1&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,400&family=UnifrakturMaguntia&family=Playfair+Display+SC:wght@400;700&family=Petit+Formal+Script&display=swap';
    document.head.appendChild( link );
  }

  /* ── SVG LIBRARY ──────────────────────────────────────────── */
  var SVG = {

    /* Tall pillar candle with dripping wax */
    candleTall: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 200" width="44" height="146">',
        /* flame */
        '<g style="animation:h-flame-dance 0.8s ease-in-out infinite;transform-origin:30px 22px">',
          '<ellipse cx="30" cy="28" rx="7" ry="14" fill="#ff7010" opacity="0.9"/>',
          '<ellipse cx="30" cy="30" rx="4" ry="10" fill="#f8c840" opacity="0.95"/>',
          '<ellipse cx="30" cy="32" rx="2" ry="5"  fill="#ffffff" opacity="0.7"/>',
          /* glow halo */
          '<ellipse cx="30" cy="28" rx="14" ry="18" fill="#f8c840" opacity="0.08"/>',
        '</g>',
        /* wick */
        '<line x1="30" y1="40" x2="30" y2="50" stroke="#3a2010" stroke-width="1.5"/>',
        /* wax body */
        '<rect x="18" y="48" width="24" height="130" rx="3" fill="#f2ede0"/>',
        '<rect x="20" y="50" width="4" height="128" rx="1" fill="#ffffff" opacity="0.15"/>',
        /* drips */
        '<path d="M22,50 Q20,60 21,75 Q22,82 22,90" stroke="#e8e0cc" stroke-width="3" fill="none" stroke-linecap="round"/>',
        '<path d="M36,50 Q38,65 37,78" stroke="#e8e0cc" stroke-width="2.5" fill="none" stroke-linecap="round"/>',
        '<path d="M28,48 Q27,58 28,68" stroke="#f0e8d0" stroke-width="2" fill="none" stroke-linecap="round"/>',
        '<ellipse cx="21" cy="90" rx="2.5" ry="3.5" fill="#d8d0c0"/>',
        '<ellipse cx="37" cy="78" rx="2" ry="3" fill="#d8d0c0"/>',
        /* base puddle */
        '<ellipse cx="30" cy="180" rx="18" ry="6" fill="#d8d0c0" opacity="0.6"/>',
        '<rect x="16" y="174" width="28" height="8" rx="3" fill="#c8c0b0"/>',
      '</svg>'
    ].join( '' ),

    /* Short stubby candle, more wax pooled */
    candleShort: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 120" width="36" height="86">',
        '<g style="animation:h-flame-dance 1.1s ease-in-out infinite;transform-origin:25px 18px">',
          '<ellipse cx="25" cy="22" rx="5" ry="10" fill="#ff7010" opacity="0.85"/>',
          '<ellipse cx="25" cy="24" rx="3" ry="7"  fill="#f8c840" opacity="0.95"/>',
          '<ellipse cx="25" cy="25" rx="1.5" ry="4" fill="#ffffff" opacity="0.6"/>',
          '<ellipse cx="25" cy="22" rx="10" ry="14" fill="#f8c840" opacity="0.07"/>',
        '</g>',
        '<line x1="25" y1="32" x2="25" y2="40" stroke="#3a2010" stroke-width="1.5"/>',
        '<rect x="14" y="38" width="22" height="65" rx="4" fill="#f0ece0"/>',
        '<rect x="16" y="40" width="3" height="62" rx="1" fill="#ffffff" opacity="0.12"/>',
        '<path d="M18,40 Q16,55 17,70 Q17,78 16,85" stroke="#e0d8c4" stroke-width="3" fill="none" stroke-linecap="round"/>',
        '<ellipse cx="16" cy="85" rx="2.5" ry="3" fill="#ccc4b0"/>',
        /* large wax pool */
        '<ellipse cx="25" cy="106" rx="22" ry="7" fill="#ddd8c8" opacity="0.7"/>',
        '<rect x="10" y="100" width="30" height="10" rx="4" fill="#c8c0b0"/>',
      '</svg>'
    ].join( '' ),

    /* Cluster of 3 candles on a holder */
    candleCluster: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 200" width="100" height="167">',
        /* left candle */
        '<g transform="translate(10,20)">',
          '<g style="animation:h-flame-dance 0.9s 0.2s ease-in-out infinite;transform-origin:20px 16px">',
            '<ellipse cx="20" cy="18" rx="5" ry="11" fill="#ff8020" opacity="0.9"/>',
            '<ellipse cx="20" cy="20" rx="3" ry="7"  fill="#f8c840"/>',
            '<ellipse cx="20" cy="22" rx="1.5" ry="4" fill="#fff" opacity="0.6"/>',
            '<ellipse cx="20" cy="18" rx="10" ry="14" fill="#f8c840" opacity="0.07"/>',
          '</g>',
          '<line x1="20" y1="28" x2="20" y2="35" stroke="#3a2010" stroke-width="1"/>',
          '<rect x="12" y="33" width="16" height="100" rx="3" fill="#f2ede0"/>',
          '<path d="M14,35 Q12,50 13,70" stroke="#e0d8c4" stroke-width="2.5" fill="none" stroke-linecap="round"/>',
        '</g>',
        /* centre candle (taller) */
        '<g transform="translate(40,0)">',
          '<g style="animation:h-flame-dance 0.7s ease-in-out infinite;transform-origin:20px 18px">',
            '<ellipse cx="20" cy="20" rx="6" ry="14" fill="#ff6010" opacity="0.95"/>',
            '<ellipse cx="20" cy="23" rx="4" ry="9"  fill="#f8c840"/>',
            '<ellipse cx="20" cy="25" rx="2" ry="5"  fill="#fff" opacity="0.7"/>',
            '<ellipse cx="20" cy="20" rx="14" ry="18" fill="#f8c840" opacity="0.08"/>',
          '</g>',
          '<line x1="20" y1="32" x2="20" y2="40" stroke="#3a2010" stroke-width="1.5"/>',
          '<rect x="11" y="38" width="18" height="130" rx="3" fill="#f0ece0"/>',
          '<path d="M13,40 Q11,60 12,85" stroke="#e0d8c4" stroke-width="3" fill="none" stroke-linecap="round"/>',
          '<ellipse cx="12" cy="85" rx="2.5" ry="3.5" fill="#ccc4b0"/>',
        '</g>',
        /* right candle */
        '<g transform="translate(76,28)">',
          '<g style="animation:h-flame-dance 1.0s 0.5s ease-in-out infinite;transform-origin:18px 15px">',
            '<ellipse cx="18" cy="16" rx="4" ry="9" fill="#ff7018" opacity="0.85"/>',
            '<ellipse cx="18" cy="18" rx="3" ry="6" fill="#f8c840"/>',
            '<ellipse cx="18" cy="20" rx="1.5" ry="3.5" fill="#fff" opacity="0.55"/>',
            '<ellipse cx="18" cy="16" rx="9" ry="13" fill="#f8c840" opacity="0.06"/>',
          '</g>',
          '<line x1="18" y1="25" x2="18" y2="32" stroke="#3a2010" stroke-width="1"/>',
          '<rect x="10" y="30" width="15" height="90" rx="3" fill="#f2ede0"/>',
          '<path d="M22,32 Q24,48 23,62" stroke="#e0d8c4" stroke-width="2.5" fill="none" stroke-linecap="round"/>',
        '</g>',
        /* shared base plate */
        '<rect x="4" y="170" width="112" height="14" rx="4" fill="#8a6030"/>',
        '<rect x="2" y="183" width="116" height="8" rx="3" fill="#6a4820"/>',
        '<ellipse cx="60" cy="191" rx="58" ry="6" fill="#4a3010" opacity="0.5"/>',
      '</svg>'
    ].join( '' ),

    /* Gothic pointed arch frame */
    gothicArch: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 150" width="90" height="135">',
        '<path d="M10,150 L10,70 Q10,5 50,5 Q90,5 90,70 L90,150" fill="none" stroke="#c8860a" stroke-width="1.5" opacity="0.7"/>',
        /* inner arch */
        '<path d="M18,150 L18,74 Q18,20 50,20 Q82,20 82,74 L82,150" fill="none" stroke="#e8b030" stroke-width="0.8" opacity="0.4"/>',
        /* keystone ornament */
        '<ellipse cx="50" cy="8" rx="6" ry="7" fill="none" stroke="#c8860a" stroke-width="1.2" opacity="0.7"/>',
        '<circle cx="50" cy="8" r="2.5" fill="#c8860a" opacity="0.6"/>',
        /* column capitals */
        '<rect x="4" y="140" width="14" height="10" rx="1" fill="#8a5a18" opacity="0.5"/>',
        '<rect x="82" y="140" width="14" height="10" rx="1" fill="#8a5a18" opacity="0.5"/>',
        /* tracery at top */
        '<path d="M35,35 Q50,18 65,35" fill="none" stroke="#c8860a" stroke-width="0.8" opacity="0.35"/>',
        '<circle cx="50" cy="30" r="4" fill="none" stroke="#e8b030" stroke-width="0.8" opacity="0.4"/>',
      '</svg>'
    ].join( '' ),

    /* Sacred heart */
    sacredHeart: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 90" width="70" height="79">',
        /* heart body */
        '<path d="M40,70 C20,55 5,45 5,28 C5,16 14,8 25,8 C31,8 37,11 40,16 C43,11 49,8 55,8 C66,8 75,16 75,28 C75,45 60,55 40,70Z" fill="#a84858" opacity="0.8"/>',
        /* light overlay */
        '<path d="M25,14 Q30,10 35,16" stroke="#f0b0c0" stroke-width="2" fill="none" opacity="0.5"/>',
        /* flames on top */
        '<g transform="translate(33,-2)">',
          '<path d="M7,10 Q5,4 7,0 Q9,4 7,10Z" fill="#f8c840" opacity="0.9"/>',
          '<path d="M14,12 Q12,5 14,1 Q16,5 14,12Z" fill="#ff7010" opacity="0.85"/>',
          '<path d="M21,10 Q19,4 21,0 Q23,4 21,10Z" fill="#f8c840" opacity="0.9"/>',
        '</g>',
        /* crown */
        '<g transform="translate(22,-5)">',
          '<path d="M0,12 L0,0 L9,5 L18,0 L27,5 L36,0 L36,12Z" fill="none" stroke="#e8b030" stroke-width="1" opacity="0.7"/>',
          '<circle cx="9"  cy="5" r="2" fill="#e8b030" opacity="0.7"/>',
          '<circle cx="18" cy="0" r="2.5" fill="#f8d060" opacity="0.8"/>',
          '<circle cx="27" cy="5" r="2" fill="#e8b030" opacity="0.7"/>',
        '</g>',
        /* thorns / ring */
        '<circle cx="40" cy="35" r="18" fill="none" stroke="#3a1e0c" stroke-width="1.5" stroke-dasharray="3,4" opacity="0.6"/>',
      '</svg>'
    ].join( '' ),

    /* Baroque damask ornament */
    damask: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="90" height="90">',
        '<g fill="none" stroke="#c8860a" stroke-width="0.6" opacity="0.8">',
          '<ellipse cx="50" cy="50" rx="8" ry="8"/>',
          '<ellipse cx="50" cy="22" rx="5" ry="16"/>',
          '<ellipse cx="50" cy="78" rx="5" ry="16"/>',
          '<ellipse cx="22" cy="50" rx="16" ry="5"/>',
          '<ellipse cx="78" cy="50" rx="16" ry="5"/>',
          '<path d="M50,34 Q60,42 50,50 Q40,42 50,34Z"/>',
          '<path d="M50,66 Q60,58 50,50 Q40,58 50,66Z"/>',
          '<path d="M34,50 Q42,60 50,50 Q42,40 34,50Z"/>',
          '<path d="M66,50 Q58,60 50,50 Q58,40 66,50Z"/>',
          '<circle cx="50" cy="10"  r="3"/>',
          '<circle cx="50" cy="90"  r="3"/>',
          '<circle cx="10" cy="50"  r="3"/>',
          '<circle cx="90" cy="50"  r="3"/>',
          '<path d="M38,38 Q50,30 62,38 Q70,50 62,62 Q50,70 38,62 Q30,50 38,38Z"/>',
        '</g>',
      '</svg>'
    ].join( '' ),

    /* Rose petal */
    petal: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 36" width="18" height="27">',
        '<path d="M12,2 Q20,8 20,20 Q20,32 12,34 Q4,32 4,20 Q4,8 12,2Z" fill="#c06070" opacity="0.7"/>',
        '<path d="M12,6 Q16,12 15,22" stroke="#e090a0" stroke-width="0.8" fill="none" opacity="0.5"/>',
      '</svg>'
    ].join( '' ),

    /* Ornate candelabra */
    candelabra: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 260" width="130" height="211">',
        /* left arm candle */
        '<g transform="translate(4,30)">',
          '<g style="animation:h-flame-dance 0.85s 0.1s ease-in-out infinite;transform-origin:18px 16px">',
            '<ellipse cx="18" cy="18" rx="5" ry="12" fill="#ff7010" opacity="0.9"/>',
            '<ellipse cx="18" cy="20" rx="3" ry="8"  fill="#f8c840"/>',
            '<ellipse cx="18" cy="22" rx="2" ry="4"  fill="#fff" opacity="0.6"/>',
            '<ellipse cx="18" cy="18" rx="12" ry="16" fill="#f8c840" opacity="0.06"/>',
          '</g>',
          '<line x1="18" y1="30" x2="18" y2="36" stroke="#3a2010" stroke-width="1.2"/>',
          '<rect x="11" y="34" width="14" height="80" rx="3" fill="#f0ece0"/>',
          '<path d="M12,36 Q10,55 11,72" stroke="#e0d8c4" stroke-width="2.5" fill="none" stroke-linecap="round"/>',
        '</g>',
        /* right arm candle */
        '<g transform="translate(134,30)">',
          '<g style="animation:h-flame-dance 1.05s 0.3s ease-in-out infinite;transform-origin:18px 16px">',
            '<ellipse cx="18" cy="18" rx="5" ry="12" fill="#ff7010" opacity="0.9"/>',
            '<ellipse cx="18" cy="20" rx="3" ry="8"  fill="#f8c840"/>',
            '<ellipse cx="18" cy="22" rx="2" ry="4"  fill="#fff" opacity="0.6"/>',
            '<ellipse cx="18" cy="18" rx="12" ry="16" fill="#f8c840" opacity="0.06"/>',
          '</g>',
          '<line x1="18" y1="30" x2="18" y2="36" stroke="#3a2010" stroke-width="1.2"/>',
          '<rect x="11" y="34" width="14" height="80" rx="3" fill="#f0ece0"/>',
          '<path d="M24,36 Q26,55 25,72" stroke="#e0d8c4" stroke-width="2.5" fill="none" stroke-linecap="round"/>',
        '</g>',
        /* centre tall candle */
        '<g transform="translate(62,0)">',
          '<g style="animation:h-flame-dance 0.7s ease-in-out infinite;transform-origin:18px 20px">',
            '<ellipse cx="18" cy="20" rx="7" ry="16" fill="#ff6010" opacity="0.95"/>',
            '<ellipse cx="18" cy="23" rx="4" ry="10" fill="#f8c840"/>',
            '<ellipse cx="18" cy="26" rx="2" ry="6"  fill="#fff" opacity="0.7"/>',
            '<ellipse cx="18" cy="20" rx="16" ry="22" fill="#f8c840" opacity="0.08"/>',
          '</g>',
          '<line x1="18" y1="36" x2="18" y2="44" stroke="#3a2010" stroke-width="1.5"/>',
          '<rect x="10" y="42" width="16" height="120" rx="3" fill="#f2ede0"/>',
          '<path d="M11,44 Q9,68 10,95" stroke="#e0d8c4" stroke-width="3" fill="none" stroke-linecap="round"/>',
          '<ellipse cx="10" cy="95" rx="2.5" ry="3.5" fill="#ccc4b0"/>',
        '</g>',
        /* arms */
        '<path d="M80,90 Q30,80 22,60" fill="none" stroke="#8a6030" stroke-width="3" stroke-linecap="round"/>',
        '<path d="M80,90 Q130,80 152,60" fill="none" stroke="#8a6030" stroke-width="3" stroke-linecap="round"/>',
        /* stem */
        '<rect x="74" y="160" width="12" height="70" rx="4" fill="#7a5020"/>',
        '<ellipse cx="80" cy="162" rx="12" ry="6" fill="#8a6030"/>',
        /* base */
        '<ellipse cx="80" cy="236" rx="45" ry="12" fill="#6a4020" opacity="0.7"/>',
        '<rect x="50" y="228" width="60" height="10" rx="4" fill="#8a5820"/>',
      '</svg>'
    ].join( '' ),

    /* Mirror silhouette (album cover gothic arch mirror) */
    mirrorSilhouette: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 160" width="80" height="128">',
        /* outer frame */
        '<path d="M8,160 L8,80 Q8,5 50,5 Q92,5 92,80 L92,160" fill="none" stroke="#8a5a18" stroke-width="3" opacity="0.6"/>',
        /* inner mirror */
        '<path d="M16,158 L16,82 Q16,18 50,18 Q84,18 84,82 L84,158" fill="rgba(10,5,2,0.5)" stroke="#c8860a" stroke-width="1" opacity="0.5"/>',
        /* glow inside mirror */
        '<path d="M16,158 L16,82 Q16,18 50,18 Q84,18 84,82 L84,158 Z" fill="url(#mirrorGlow)" opacity="0.3"/>',
        '<defs><radialGradient id="mirrorGlow" cx="50%" cy="60%"><stop offset="0%" stop-color="#f8c840" stop-opacity="0.15"/><stop offset="100%" stop-color="#f8c840" stop-opacity="0"/></radialGradient></defs>',
        /* spire ornaments */
        '<path d="M8,80 L8,50 L14,30 L20,50 L20,80" fill="none" stroke="#8a5a18" stroke-width="1.5" opacity="0.5"/>',
        '<path d="M80,80 L80,50 L86,30 L92,50 L92,80" fill="none" stroke="#8a5a18" stroke-width="1.5" opacity="0.5"/>',
        /* keystone */
        '<ellipse cx="50" cy="8" rx="7" ry="8" fill="none" stroke="#c8860a" stroke-width="1.5" opacity="0.6"/>',
        '<circle cx="50" cy="8" r="3" fill="#c8860a" opacity="0.5"/>',
        /* base shelf */
        '<rect x="2" y="152" width="96" height="10" rx="2" fill="#6a4820" opacity="0.5"/>',
        /* sacred heart base motif */
        '<path d="M50,148 C44,143 38,140 38,134 C38,130 41,127 45,127 C47,127 49,128 50,130 C51,128 53,127 55,127 C59,127 62,130 62,134 C62,140 56,143 50,148Z" fill="#a84858" opacity="0.4"/>',
      '</svg>'
    ].join( '' ),

    /* Wax drip trail */
    waxDrip: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 120" width="24" height="96">',
        '<path d="M15,0 Q18,20 17,35 Q16,55 18,70 Q20,85 15,100 Q10,85 12,70 Q14,55 13,35 Q12,20 15,0Z" fill="#f2ede0" opacity="0.7"/>',
        '<ellipse cx="15" cy="100" rx="8" ry="6" fill="#e8e0cc" opacity="0.6"/>',
      '</svg>'
    ].join( '' ),

    /* Ornate key */
    key: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 120" width="30" height="90">',
        '<circle cx="20" cy="20" r="14" fill="none" stroke="#c8860a" stroke-width="2" opacity="0.8"/>',
        '<circle cx="20" cy="20" r="7"  fill="none" stroke="#e8b030" stroke-width="1" opacity="0.6"/>',
        '<circle cx="20" cy="20" r="3"  fill="#c8860a" opacity="0.7"/>',
        '<rect x="18" y="34" width="4" height="80" rx="1" fill="#c8860a" opacity="0.7"/>',
        '<rect x="22" y="80" width="12" height="4" rx="1" fill="#c8860a" opacity="0.7"/>',
        '<rect x="22" y="94" width="9"  height="4" rx="1" fill="#c8860a" opacity="0.7"/>',
      '</svg>'
    ].join( '' ),

    /* Crown */
    crown: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 80" width="100" height="67">',
        '<path d="M5,75 L5,35 L25,10 L40,35 L60,5 L80,35 L95,10 L115,35 L115,75 Z" fill="none" stroke="#c8860a" stroke-width="2" stroke-linejoin="round" opacity="0.7"/>',
        '<path d="M5,60 L115,60" stroke="#e8b030" stroke-width="1" opacity="0.5"/>',
        '<circle cx="25"  cy="13" r="4"  fill="#e8b030" opacity="0.7"/>',
        '<circle cx="60"  cy="7"  r="5"  fill="#f8d060" opacity="0.8"/>',
        '<circle cx="95"  cy="13" r="4"  fill="#e8b030" opacity="0.7"/>',
        '<circle cx="40"  cy="36" r="3"  fill="#d08090" opacity="0.6"/>',
        '<circle cx="80"  cy="36" r="3"  fill="#d08090" opacity="0.6"/>',
        '<rect x="5" y="68" width="110" height="8" rx="2" fill="#8a5a18" opacity="0.4"/>',
      '</svg>'
    ].join( '' ),

    /* Floating rose */
    rose: [
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" width="48" height="48">',
        '<circle cx="30" cy="30" r="10" fill="#a84858" opacity="0.8"/>',
        '<path d="M30,20 Q38,24 36,32 Q30,38 22,32 Q20,24 30,20Z" fill="#c06070" opacity="0.7"/>',
        '<path d="M30,20 Q22,24 24,32 Q30,38 38,32 Q40,24 30,20Z" fill="#b05060" opacity="0.7"/>',
        '<path d="M30,14 Q40,18 40,28 Q40,38 30,42 Q20,38 20,28 Q20,18 30,14Z" fill="none" stroke="#d08090" stroke-width="0.8" opacity="0.5"/>',
        '<path d="M30,8  Q44,14 44,30 Q44,46 30,50 Q16,46 16,30 Q16,14 30,8Z"  fill="none" stroke="#c06070" stroke-width="0.6" opacity="0.4"/>',
        '<path d="M30,50 L30,60" stroke="#3a1e0c" stroke-width="1.5"/>',
        '<path d="M24,56 Q30,52 36,56" stroke="#2a1808" stroke-width="1" fill="none"/>',
      '</svg>'
    ].join( '' )
  };

  /* ── HELPERS ──────────────────────────────────────────────── */
  function rand( a, b ) { return Math.random() * ( b - a ) + a; }
  function randInt( a, b ) { return Math.floor( rand( a, b + 1 ) ); }

  /* ── BUILD LAYER ──────────────────────────────────────────── */
  function createLayer() {
    var div = document.createElement( 'div' );
    div.className = 'h-ambient';
    div.id = 'hades-ambient';
    document.body.insertBefore( div, document.body.firstChild );
    return div;
  }

  /* ── SPAWN AMBIENT ELEMENT ───────────────────────────────── */
  function spawn( layer, svgKey, cssClass, opts ) {
    opts = opts || {};
    var el = document.createElement( 'div' );
    el.className = cssClass;
    el.innerHTML = SVG[ svgKey ];

    var scale = rand( opts.minScale || 0.5, opts.maxScale || 1.4 );
    el.style.transformOrigin = 'center center';
    el.style.transform = 'scale(' + scale + ')';

    /* Position */
    var edged = ( opts.edged !== false ) && Math.random() < 0.35;
    var top, left;
    if ( edged ) {
      var side = randInt( 0, 3 );
      if ( side === 0 )      { top = rand( 0, 8 );   left = rand( 5, 90 ); }
      else if ( side === 1 ) { top = rand( 85, 97 ); left = rand( 5, 90 ); }
      else if ( side === 2 ) { top = rand( 5, 90 );  left = rand( 0, 6 );  }
      else                   { top = rand( 5, 90 );  left = rand( 88, 97 );}
    } else {
      top  = rand( opts.topMin  || 5, opts.topMax  || 90 );
      left = rand( opts.leftMin || 5, opts.leftMax || 90 );
    }
    el.style.top  = top  + 'vh';
    el.style.left = left + 'vw';

    /* Animation stagger */
    var delay = rand( 0, 18 );
    var dur   = rand( opts.durMin || 5, opts.durMax || 14 );
    el.style.animationDelay    = delay + 's';
    el.style.animationDuration = dur + 's';
    if ( opts.opacity ) { el.style.opacity = opts.opacity; }

    layer.appendChild( el );
    return el;
  }

  /* ── ROSE PETAL FALL ─────────────────────────────────────── */
  function spawnPetal( layer ) {
    var el = document.createElement( 'div' );
    el.className = 'h-petal';
    el.innerHTML = SVG.petal;
    el.style.left  = rand( 5, 95 ) + 'vw';
    el.style.top   = '-30px';
    el.style.setProperty( '--drift', rand( -40, 60 ) + 'px' );
    el.style.setProperty( '--rot',   rand( -180, 360 ) + 'deg' );
    var dur = rand( 8, 18 );
    el.style.animationDuration = dur + 's';
    el.style.animationDelay    = rand( 0, 20 ) + 's';
    el.style.opacity = rand( 0.25, 0.55 );
    layer.appendChild( el );
  }

  /* ── DUST MOTES ──────────────────────────────────────────── */
  function spawnMote( layer ) {
    var el = document.createElement( 'div' );
    el.className = 'h-mote';
    el.style.left = rand( 10, 90 ) + 'vw';
    el.style.top  = rand( 20, 90 ) + 'vh';
    el.style.setProperty( '--mx', rand( -30, 30 ) + 'px' );
    el.style.setProperty( '--my', rand( -80, -120 ) + 'px' );
    var dur = rand( 5, 12 );
    el.style.animationDuration = dur + 's';
    el.style.animationDelay    = rand( 0, 15 ) + 's';
    /* Vary colour: gold, rose, cream */
    var colours = [ '#f8d060', '#e8b030', '#d08090', '#f0e8d0' ];
    el.style.background = colours[ randInt( 0, colours.length - 1 ) ];
    el.style.boxShadow  = '0 0 ' + rand( 2, 6 ) + 'px currentColor';
    layer.appendChild( el );
  }

  /* ── WAX DRIP TRAILS ON CONTENT EDGE ────────────────────── */
  function injectWaxDrips() {
    var content = document.getElementById( 'content' )
                || document.getElementById( 'mw-content-text' )
                || document.querySelector( '.mw-body' );
    if ( !content ) { return; }

    /* Top drip row — candle wax flowing down */
    var topBar = document.createElement( 'div' );
    topBar.style.cssText = [
      'position:absolute',
      'top:-1px',
      'left:0',
      'width:100%',
      'height:40px',
      'pointer-events:none',
      'overflow:hidden',
      'z-index:10'
    ].join( ';' );

    for ( var i = 0; i < 12; i++ ) {
      var drip = document.createElement( 'div' );
      drip.innerHTML = SVG.waxDrip;
      drip.style.cssText = [
        'position:absolute',
        'top:0',
        'left:' + ( i * 8.5 + rand( 0, 4 ) ) + '%',
        'opacity:' + rand( 0.2, 0.45 ),
        'transform:scaleY(0)',
        'transform-origin:top center',
        'animation:h-wax-drip ' + rand( 2, 5 ) + 's ' + rand( 0, 8 ) + 's ease-in forwards'
      ].join( ';' );
      topBar.appendChild( drip );
    }

    /* Make content position:relative so absolute children work */
    if ( getComputedStyle( content ).position === 'static' ) {
      content.style.position = 'relative';
    }
    content.appendChild( topBar );
  }

  /* ── MOUSE PARALLAX ──────────────────────────────────────── */
  function initParallax( layer ) {
    var cx = window.innerWidth / 2, cy = window.innerHeight / 2;
    var tx = 0, ty = 0, cx2 = 0, cy2 = 0;
    document.addEventListener( 'mousemove', function ( e ) {
      tx = ( e.clientX - cx ) / cx * 12;
      ty = ( e.clientY - cy ) / cy * 8;
    } );
    ( function tick() {
      cx2 += ( tx - cx2 ) * 0.03;
      cy2 += ( ty - cy2 ) * 0.03;
      layer.style.transform = 'translate(' + cx2 + 'px,' + cy2 + 'px)';
      requestAnimationFrame( tick );
    }() );
  }

  /* ── CANDLE CLICK SPARK ──────────────────────────────────── */
  function initClickSpark() {
    document.addEventListener( 'click', function ( e ) {
      var sparks = 6;
      for ( var s = 0; s < sparks; s++ ) {
        ( function ( i ) {
          var sp = document.createElement( 'div' );
          var angle = ( i / sparks ) * Math.PI * 2 + rand( -0.3, 0.3 );
          var dist  = rand( 20, 55 );
          sp.style.cssText = [
            'position:fixed',
            'pointer-events:none',
            'z-index:9994',
            'width:' + rand( 3, 6 ) + 'px',
            'height:' + rand( 3, 6 ) + 'px',
            'border-radius:50%',
            'left:' + e.clientX + 'px',
            'top:'  + e.clientY + 'px',
            'background:' + ( Math.random() < 0.5 ? '#f8c840' : '#ff7010' ),
            'box-shadow:0 0 6px #f8c840',
            'transition:transform 0.5s ease-out,opacity 0.5s ease-out',
            'opacity:0.9'
          ].join( ';' );
          document.body.appendChild( sp );
          requestAnimationFrame( function () {
            requestAnimationFrame( function () {
              sp.style.transform = 'translate(' + Math.cos( angle ) * dist + 'px,' + Math.sin( angle ) * dist + 'px)';
              sp.style.opacity   = '0';
              setTimeout( function () { if ( sp.parentNode ) { sp.parentNode.removeChild( sp ); } }, 550 );
            } );
          } );
        }( s ) );
      }
    } );
  }

  /* ── GOLD CURSOR TRAIL ────────────────────────────────────── */
  function initCursorTrail() {
    var LEN = 8;
    var dots = [], pos = [];
    var mx = 0, my = 0;
    for ( var i = 0; i < LEN; i++ ) {
      var d = document.createElement( 'div' );
      var sz = ( 6 - i * 0.5 );
      d.style.cssText = [
        'position:fixed',
        'pointer-events:none',
        'z-index:9996',
        'width:'  + sz + 'px',
        'height:' + sz + 'px',
        'border-radius:50%',
        'opacity:' + ( ( LEN - i ) / LEN * 0.7 ),
        'background:' + ( i % 3 === 0 ? '#f8c840' : i % 3 === 1 ? '#c8860a' : '#d08090' ),
        'box-shadow:0 0 ' + sz + 'px ' + ( i % 3 === 0 ? '#f8c840' : '#c8860a' ),
        'mix-blend-mode:screen'
      ].join( ';' );
      document.body.appendChild( d );
      dots.push( d );
      pos.push( { x: 0, y: 0 } );
    }
    document.addEventListener( 'mousemove', function ( e ) { mx = e.clientX; my = e.clientY; } );
    ( function tick() {
      pos.unshift( { x: mx, y: my } );
      pos.length = LEN;
      for ( var k = 0; k < LEN; k++ ) {
        dots[ k ].style.left = ( pos[ k ].x - 3 ) + 'px';
        dots[ k ].style.top  = ( pos[ k ].y - 3 ) + 'px';
      }
      requestAnimationFrame( tick );
    }() );
  }

  /* ── CANDLE GLOW ON HEADERS ──────────────────────────────── */
  function animateHeaders() {
    var heads = document.querySelectorAll( '.mw-headline, h2, h3' );
    heads.forEach( function ( h, i ) {
      h.style.animationDelay = ( i * 0.4 ) + 's';
    } );
  }

  /* ── AMBIENT HAZE ────────────────────────────────────────── */
  function createHaze() {
    var hz = document.createElement( 'div' );
    hz.id = 'hades-haze';
    hz.style.cssText = [
      'position:fixed',
      'inset:0',
      'pointer-events:none',
      'z-index:1',
      'background:' +
        'radial-gradient(ellipse 70% 50% at 50% 10%,rgba(200,134,10,0.06) 0%,transparent 70%),' +
        'radial-gradient(ellipse 50% 40% at 50% 95%,rgba(168,72,88,0.04) 0%,transparent 65%),' +
        'radial-gradient(ellipse 30% 30% at 80% 30%,rgba(248,200,64,0.03) 0%,transparent 60%)',
      'animation:h-breathe 10s ease-in-out infinite alternate'
    ].join( ';' );
    document.body.insertBefore( hz, document.body.firstChild );
  }

  /* ── MAIN ─────────────────────────────────────────────────── */
  function init() {
    if ( typeof mw !== 'undefined' ) {
      var action = mw.config.get( 'wgAction' );
      if ( action === 'edit' || action === 'submit' ) { return; }
    }

    preloadFonts();
    createHaze();

    var layer = createLayer();

    /* ── Candles ── */
    for ( var i = 0; i < 5; i++ )  { spawn( layer, 'candleTall',    'h-candle-el', { minScale: 0.4, maxScale: 0.9, edged: true  } ); }
    for ( var j = 0; j < 5; j++ )  { spawn( layer, 'candleShort',   'h-candle-el', { minScale: 0.5, maxScale: 1.0, edged: true  } ); }
    for ( var k = 0; k < 3; k++ )  { spawn( layer, 'candleCluster', 'h-candle-el', { minScale: 0.4, maxScale: 0.7, edged: false } ); }
    for ( var ci = 0; ci < 2; ci++ ) { spawn( layer, 'candelabra',   'h-candle-el', { minScale: 0.35, maxScale: 0.6, edged: true, opacity: '0.12' } ); }

    /* ── Gothic arches ── */
    for ( var a = 0; a < 5; a++ )  { spawn( layer, 'gothicArch',        'h-arch-el', { minScale: 0.5, maxScale: 1.2, opacity: '0.1' } ); }

    /* ── Mirror silhouettes ── */
    for ( var m = 0; m < 3; m++ )  { spawn( layer, 'mirrorSilhouette',  'h-arch-el', { minScale: 0.4, maxScale: 0.9, opacity: '0.08' } ); }

    /* ── Sacred hearts ── */
    for ( var sh = 0; sh < 4; sh++ ) { spawn( layer, 'sacredHeart',     'h-arch-el', { minScale: 0.4, maxScale: 0.9, opacity: '0.1' } ); }

    /* ── Crowns ── */
    for ( var cr = 0; cr < 3; cr++ ) { spawn( layer, 'crown',           'h-candle-el', { minScale: 0.5, maxScale: 0.9, opacity: '0.09' } ); }

    /* ── Damask ornaments ── */
    for ( var d = 0; d < 6; d++ )  { spawn( layer, 'damask',   'h-damask-el', { minScale: 0.6, maxScale: 1.4, opacity: '0.07' } ); }

    /* ── Roses ── */
    for ( var r = 0; r < 5; r++ )  { spawn( layer, 'rose',     'h-candle-el', { minScale: 0.5, maxScale: 1.0, opacity: '0.1' } ); }

    /* ── Keys ── */
    for ( var kk = 0; kk < 4; kk++ ) { spawn( layer, 'key',    'h-arch-el', { minScale: 0.4, maxScale: 0.9, opacity: '0.09' } ); }

    /* ── Wax drips (scattered) ── */
    for ( var w = 0; w < 8; w++ )  { spawn( layer, 'waxDrip',  'h-candle-el', { minScale: 0.3, maxScale: 0.8, opacity: '0.12' } ); }

    /* ── Rose petals falling ── */
    for ( var p = 0; p < 14; p++ ) { spawnPetal( layer ); }

    /* ── Dust motes ── */
    for ( var mo = 0; mo < 20; mo++ ) { spawnMote( layer ); }

    /* ── Effects ── */
    initParallax( layer );
    initClickSpark();
    initCursorTrail();
    animateHeaders();
    injectWaxDrips();

    /* Slowly add new petals over time */
    setInterval( function () {
      if ( layer.querySelectorAll( '.h-petal' ).length < 25 ) { spawnPetal( layer ); }
    }, 8000 );

    /* Replenish motes */
    setInterval( function () {
      if ( layer.querySelectorAll( '.h-mote' ).length < 30 ) { spawnMote( layer ); }
    }, 4000 );
  }

  /* ── BOOT ─────────────────────────────────────────────────── */
  if ( typeof mw !== 'undefined' ) {
    mw.loader.using( 'mediawiki.util' ).then( function () {
      $( document ).ready( init );
    } );
  } else {
    document.addEventListener( 'DOMContentLoaded', init );
  }

}() );