electro/gru-wiring.html
2026-08-24 14:09:36 -04:00

368 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<title>House Lighting, Gru & the Minions (interactive)</title>
<style>
:root{ --bg:#141019; --ink:#f3eeff; --muted:#a99fc4; --rule:#3a3350;
--card:#1c1728; --red:#ff4436; --amber:#ffc14d; --white:#fff6e6; --slate:#9aa6c8;
--cyan:#7fe0ff; --grn:#48c78e; --yellow:#f7d33e; --yellow2:#caa62a;
--denim:#2f5b8c; --denim2:#1f3f63; --skin:#d7b89a; }
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1200px 600px at 50% -10%,#241b3a,#120e1c 70%);
color:var(--ink);font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;padding:22px;}
.wrap{max-width:1100px;margin:0 auto;}
h1{font-size:22px;margin:0 0 2px;letter-spacing:.2px;}
.sub{color:var(--muted);font-size:13px;margin:0 0 16px;}
.card{background:var(--card);border:1px solid var(--rule);border-radius:14px;padding:16px;margin-bottom:18px;}
.scroll{overflow-x:auto;}
svg{display:block;margin:0 auto;max-width:100%;height:auto;min-width:940px;}
text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;fill:var(--ink);}
.lbl{font-size:12.5px;} .sm{font-size:10.5px;fill:var(--muted);} .tag{font-size:11px;font-weight:bold;}
.wire{stroke:#b8b0d0;stroke-width:2;fill:none;}
.node{fill:#b8b0d0;}
table{border-collapse:collapse;width:100%;font-size:13px;margin-top:6px;}
th,td{border:1px solid var(--rule);padding:7px 10px;text-align:left;}
th{background:rgba(255,255,255,.05);}
code{font-family:ui-monospace,Menlo,monospace;background:rgba(255,255,255,.08);padding:1px 5px;border-radius:4px;font-size:.9em;}
.key{display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--muted);margin-top:10px;}
.cmds{display:flex;gap:10px;flex-wrap:wrap;margin-top:2px;}
.cmds .grp{display:flex;gap:10px;flex-wrap:wrap;}
button{font:600 13.5px/1 inherit;color:#1a1205;background:var(--amber);border:0;
padding:11px 15px;border-radius:10px;cursor:pointer;transition:.12s;}
button.zone{background:transparent;color:var(--ink);border:1px solid var(--rule);}
button.zone.on{background:#2c2410;color:var(--amber);border-color:#5a4a1e;}
button.zone.on::before{content:"● ";color:var(--grn);}
button.zone::before{content:"○ ";color:var(--muted);}
button:active{transform:translateY(1px);}
.spacer{flex:1}
</style>
<div class="wrap">
<h1> House Lighting, Gru gives the orders</h1>
<p class="sub">Click a command. Gru shouts it, the right minion (a ULN2803A channel) yanks its zone to ground, and the lights come on. Same wiring as the build.</p>
<!-- ============ reusable characters ============ -->
<svg width="0" height="0" style="position:absolute">
<defs>
<!-- shared minion body pieces via nested reuse would need more markup; each variant is self-contained -->
<!-- A: one eye, spiky hair -->
<symbol id="minionA" viewBox="0 0 60 150">
<path d="M12 70 L6 20" stroke="var(--yellow2)" stroke-width="5" fill="none"/>
<path d="M48 70 L54 20" stroke="var(--yellow2)" stroke-width="5" fill="none"/>
<circle cx="6" cy="18" r="5" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="1.5"/>
<circle cx="54" cy="18" r="5" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="1.5"/>
<rect x="8" y="22" width="44" height="108" rx="22" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="2"/>
<path d="M24 22 l-2 -8 M30 21 l0 -9 M36 22 l2 -8" stroke="#2a2416" stroke-width="1.6" fill="none"/>
<rect x="6" y="44" width="48" height="13" fill="#7d828c"/>
<rect x="6" y="44" width="48" height="13" fill="none" stroke="#5a5f68" stroke-width="1"/>
<circle cx="30" cy="50.5" r="11" fill="#cfd3da" stroke="#63687040" stroke-width="3"/>
<circle cx="30" cy="50.5" r="7" fill="#fff"/>
<circle cx="31" cy="50.5" r="3.2" fill="#3a2b1a"/>
<path d="M22 72 q8 7 16 0" stroke="#7a5b2a" stroke-width="1.6" fill="none"/>
<path d="M8 98 h44 v10 a22 22 0 0 1 -44 0 z" fill="var(--denim)"/>
<rect x="23" y="86" width="14" height="14" fill="var(--denim)"/>
<path d="M24 86 l-4 -12 M36 86 l4 -12" stroke="var(--denim)" stroke-width="3" fill="none"/>
<rect x="25" y="100" width="10" height="9" fill="var(--denim2)" stroke="#16304d" stroke-width="1"/>
<text x="27.5" y="107.5" font-size="7" fill="#cfe0f5" font-family="monospace">G</text>
<rect x="14" y="128" width="14" height="12" rx="3" fill="#2a2a33" stroke="#9a9ab0" stroke-width="1.3"/>
<rect x="32" y="128" width="14" height="12" rx="3" fill="#2a2a33" stroke="#9a9ab0" stroke-width="1.3"/>
</symbol>
<!-- B: two eyes, bald -->
<symbol id="minionB" viewBox="0 0 60 150">
<path d="M12 70 L6 20" stroke="var(--yellow2)" stroke-width="5" fill="none"/>
<path d="M48 70 L54 20" stroke="var(--yellow2)" stroke-width="5" fill="none"/>
<circle cx="6" cy="18" r="5" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="1.5"/>
<circle cx="54" cy="18" r="5" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="1.5"/>
<rect x="8" y="22" width="44" height="108" rx="22" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="2"/>
<rect x="6" y="44" width="48" height="13" fill="#7d828c"/>
<rect x="6" y="44" width="48" height="13" fill="none" stroke="#5a5f68" stroke-width="1"/>
<line x1="30" y1="46" x2="30" y2="55" stroke="#5a5f68" stroke-width="1.4"/>
<circle cx="21" cy="50.5" r="8.5" fill="#cfd3da" stroke="#63687040" stroke-width="2.5"/>
<circle cx="39" cy="50.5" r="8.5" fill="#cfd3da" stroke="#63687040" stroke-width="2.5"/>
<circle cx="21" cy="50.5" r="5.2" fill="#fff"/><circle cx="39" cy="50.5" r="5.2" fill="#fff"/>
<circle cx="22" cy="50.5" r="2.4" fill="#3a2b1a"/><circle cx="40" cy="50.5" r="2.4" fill="#3a2b1a"/>
<path d="M22 72 q8 6 16 0" stroke="#7a5b2a" stroke-width="1.6" fill="none"/>
<path d="M8 98 h44 v10 a22 22 0 0 1 -44 0 z" fill="var(--denim)"/>
<rect x="23" y="86" width="14" height="14" fill="var(--denim)"/>
<path d="M24 86 l-4 -12 M36 86 l4 -12" stroke="var(--denim)" stroke-width="3" fill="none"/>
<rect x="25" y="100" width="10" height="9" fill="var(--denim2)" stroke="#16304d" stroke-width="1"/>
<text x="27.5" y="107.5" font-size="7" fill="#cfe0f5" font-family="monospace">G</text>
<rect x="14" y="128" width="14" height="12" rx="3" fill="#2a2a33" stroke="#9a9ab0" stroke-width="1.3"/>
<rect x="32" y="128" width="14" height="12" rx="3" fill="#2a2a33" stroke="#9a9ab0" stroke-width="1.3"/>
</symbol>
<!-- C: two eyes, side tuft of hair -->
<symbol id="minionC" viewBox="0 0 60 150">
<path d="M12 70 L6 20" stroke="var(--yellow2)" stroke-width="5" fill="none"/>
<path d="M48 70 L54 20" stroke="var(--yellow2)" stroke-width="5" fill="none"/>
<circle cx="6" cy="18" r="5" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="1.5"/>
<circle cx="54" cy="18" r="5" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="1.5"/>
<rect x="8" y="22" width="44" height="108" rx="22" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="2"/>
<path d="M20 23 c 1 -7 7 -9 12 -6 M26 22 c 1 -6 6 -7 10 -4" stroke="#2a2416" stroke-width="1.5" fill="none"/>
<rect x="6" y="44" width="48" height="13" fill="#7d828c"/>
<rect x="6" y="44" width="48" height="13" fill="none" stroke="#5a5f68" stroke-width="1"/>
<line x1="30" y1="46" x2="30" y2="55" stroke="#5a5f68" stroke-width="1.4"/>
<circle cx="21" cy="50.5" r="8.5" fill="#cfd3da" stroke="#63687040" stroke-width="2.5"/>
<circle cx="39" cy="50.5" r="8.5" fill="#cfd3da" stroke="#63687040" stroke-width="2.5"/>
<circle cx="21" cy="50.5" r="5.2" fill="#fff"/><circle cx="39" cy="50.5" r="5.2" fill="#fff"/>
<circle cx="20" cy="50.5" r="2.4" fill="#3a2b1a"/><circle cx="38" cy="50.5" r="2.4" fill="#3a2b1a"/>
<path d="M22 73 q8 5 16 0" stroke="#7a5b2a" stroke-width="1.6" fill="none"/>
<path d="M8 98 h44 v10 a22 22 0 0 1 -44 0 z" fill="var(--denim)"/>
<rect x="23" y="86" width="14" height="14" fill="var(--denim)"/>
<path d="M24 86 l-4 -12 M36 86 l4 -12" stroke="var(--denim)" stroke-width="3" fill="none"/>
<rect x="25" y="100" width="10" height="9" fill="var(--denim2)" stroke="#16304d" stroke-width="1"/>
<text x="27.5" y="107.5" font-size="7" fill="#cfe0f5" font-family="monospace">G</text>
<rect x="14" y="128" width="14" height="12" rx="3" fill="#2a2a33" stroke="#9a9ab0" stroke-width="1.3"/>
<rect x="32" y="128" width="14" height="12" rx="3" fill="#2a2a33" stroke="#9a9ab0" stroke-width="1.3"/>
</symbol>
<!-- D: one eye, bald, wide grin -->
<symbol id="minionD" viewBox="0 0 60 150">
<path d="M12 70 L6 20" stroke="var(--yellow2)" stroke-width="5" fill="none"/>
<path d="M48 70 L54 20" stroke="var(--yellow2)" stroke-width="5" fill="none"/>
<circle cx="6" cy="18" r="5" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="1.5"/>
<circle cx="54" cy="18" r="5" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="1.5"/>
<rect x="8" y="22" width="44" height="108" rx="22" fill="var(--yellow)" stroke="var(--yellow2)" stroke-width="2"/>
<rect x="6" y="44" width="48" height="13" fill="#7d828c"/>
<rect x="6" y="44" width="48" height="13" fill="none" stroke="#5a5f68" stroke-width="1"/>
<circle cx="30" cy="50.5" r="11" fill="#cfd3da" stroke="#63687040" stroke-width="3"/>
<circle cx="30" cy="50.5" r="7" fill="#fff"/>
<circle cx="29" cy="50.5" r="3.2" fill="#3a2b1a"/>
<path d="M20 71 q10 10 20 0" stroke="#7a5b2a" stroke-width="1.8" fill="none"/>
<path d="M8 98 h44 v10 a22 22 0 0 1 -44 0 z" fill="var(--denim)"/>
<rect x="23" y="86" width="14" height="14" fill="var(--denim)"/>
<path d="M24 86 l-4 -12 M36 86 l4 -12" stroke="var(--denim)" stroke-width="3" fill="none"/>
<rect x="25" y="100" width="10" height="9" fill="var(--denim2)" stroke="#16304d" stroke-width="1"/>
<text x="27.5" y="107.5" font-size="7" fill="#cfe0f5" font-family="monospace">G</text>
<rect x="14" y="128" width="14" height="12" rx="3" fill="#2a2a33" stroke="#9a9ab0" stroke-width="1.3"/>
<rect x="32" y="128" width="14" height="12" rx="3" fill="#2a2a33" stroke="#9a9ab0" stroke-width="1.3"/>
</symbol>
<symbol id="gru" viewBox="0 0 130 210">
<path d="M42 92 Q39 98 34 122 L12 205 L118 205 L96 122 Q91 98 88 92 Z" fill="#26282e" stroke="#141518" stroke-width="2"/>
<rect x="36" y="107" width="58" height="18" rx="9" fill="#31333b" stroke="#141518" stroke-width="1.5" transform="rotate(23 65 118)"/>
<rect x="36" y="107" width="58" height="18" rx="9" fill="#3a3d46" stroke="#141518" stroke-width="1.5" transform="rotate(-23 65 118)"/>
<circle cx="91" cy="108" r="6.5" fill="#d9bb9d"/>
<circle cx="39" cy="108" r="6.5" fill="#d9bb9d"/>
<path d="M39 72 Q65 87 91 72 L93 93 Q65 106 37 93 Z" fill="#8f949c" stroke="#585d66" stroke-width="1.5"/>
<path d="M38 78 Q65 92 92 78 M37 85 Q65 99 93 85 M38 91 Q65 104 92 91" stroke="#2f3238" stroke-width="2.6" fill="none"/>
<path d="M80 93 l11 30 l-14 -3 l-1 -25 z" fill="#82878f" stroke="#585d66" stroke-width="1"/>
<path d="M78 101 l12 3 M77 110 l13 3 M77 118 l12 2" stroke="#2f3238" stroke-width="2.4"/>
<ellipse cx="64" cy="45" rx="29" ry="31" fill="#d9bb9d"/>
<ellipse cx="64" cy="30" rx="24" ry="16" fill="#e0c4a8"/>
<circle cx="35" cy="49" r="5" fill="#d9bb9d"/><circle cx="93" cy="49" r="5" fill="#d3b494"/>
<path d="M61 42 Q58 54 61 60 Q66 64 72 59 Q74 51 69 43 Z" fill="#d0ae8f" stroke="#b78f6e" stroke-width="0.8"/>
<path d="M62 59 Q66 62 71 58" stroke="#ad8459" stroke-width="1.1" fill="none"/>
<circle cx="64" cy="59" r="0.9" fill="#a67f57"/><circle cx="69" cy="58" r="0.9" fill="#a67f57"/>
<path d="M47 33 Q55 30 63 35 M82 37 Q75 33 68 36" stroke="#4a3b2a" stroke-width="2.6" fill="none" stroke-linecap="round"/>
<ellipse cx="57" cy="43" rx="5" ry="6.2" fill="#fff" stroke="#3a3a3a" stroke-width="1"/>
<ellipse cx="71" cy="43" rx="5" ry="6.2" fill="#fff" stroke="#3a3a3a" stroke-width="1"/>
<circle cx="58" cy="44" r="2.5" fill="#221"/><circle cx="71" cy="44" r="2.5" fill="#221"/>
<path d="M53 65 Q64 61 75 66" stroke="#7a5b48" stroke-width="2" fill="none"/>
</symbol>
</defs>
</svg>
<!-- ============ command buttons ============ -->
<div class="card">
<div class="cmds">
<button class="zone" data-cmd="all">All lights</button>
<span class="spacer"></span>
<div class="grp">
<button class="zone" data-cmd="gf">Ground floor</button>
<button class="zone" data-cmd="up">Upstairs</button>
<button class="zone" data-cmd="ext">Exterior lights</button>
<button class="zone" data-cmd="fire">Fireplace</button>
</div>
</div>
</div>
<!-- ============ main scene ============ -->
<div class="card scroll">
<svg viewBox="0 0 1180 940" role="img" aria-label="Gru commands the minions to light the house">
<defs>
<marker id="arw" markerWidth="9" markerHeight="9" refX="6" refY="3" orient="auto">
<path d="M0,0 L6,3 L0,6 z" fill="currentColor"/>
</marker>
<filter id="soft" x="-60%" y="-60%" width="220%" height="220%"><feGaussianBlur stdDeviation="9"/></filter>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#33406a"/>
<stop offset="1" stop-color="#1a2138"/>
</linearGradient>
</defs>
<!-- scene backdrop for contrast -->
<rect x="0" y="0" width="1180" height="940" rx="12" fill="url(#sky)"/>
<!-- ===== 4 ZONE COLUMNS ; centers 240 500 760 1020 ===== -->
<!-- GF -->
<g>
<rect x="202" y="96" width="76" height="84" rx="6" fill="#241d33" stroke="var(--rule)"/>
<circle class="led" data-zone="gf" cx="224" cy="130" r="8" fill="var(--white)"/>
<circle class="led" data-zone="gf" cx="256" cy="130" r="8" fill="var(--white)"/>
<text class="sm" x="210" y="170">GF · ×2 white</text>
<rect x="226" y="186" width="28" height="18" rx="3" fill="#2a2340" stroke="var(--rule)"/><text class="sm" x="229" y="199">220</text>
<line class="wire" x1="240" y1="204" x2="240" y2="300"/>
<text class="sm" x="248" y="256">OUT1 · pin18</text>
<ellipse class="glow" data-zone="gf" cx="240" cy="366" rx="34" ry="67" fill="var(--white)" filter="url(#soft)" opacity="0"/>
<use href="#minionA" x="213" y="300" width="54" height="135"/>
<rect id="pill-gf" x="184" y="450" width="112" height="22" rx="11" fill="#241d33" stroke="var(--rule)"/>
<text id="pilltxt-gf" x="240" y="465" font-size="10.5" text-anchor="middle" fill="var(--muted)">ground floor · off</text>
</g>
<!-- UP -->
<g>
<rect x="454" y="96" width="92" height="84" rx="6" fill="#241d33" stroke="var(--rule)"/>
<circle class="led" data-zone="up" cx="474" cy="130" r="7" fill="var(--white)"/>
<circle class="led" data-zone="up" cx="500" cy="130" r="7" fill="var(--white)"/>
<circle class="led" data-zone="up" cx="526" cy="130" r="7" fill="var(--white)"/>
<text class="sm" x="462" y="170">UP · ×3 white</text>
<rect x="486" y="186" width="28" height="18" rx="3" fill="#2a2340" stroke="var(--rule)"/><text class="sm" x="489" y="199">220</text>
<line class="wire" x1="500" y1="204" x2="500" y2="300"/>
<text class="sm" x="508" y="256">OUT2 · pin17</text>
<ellipse class="glow" data-zone="up" cx="500" cy="366" rx="34" ry="67" fill="var(--white)" filter="url(#soft)" opacity="0"/>
<use href="#minionB" x="473" y="300" width="54" height="135"/>
<rect id="pill-up" x="446" y="450" width="108" height="22" rx="11" fill="#241d33" stroke="var(--rule)"/>
<text id="pilltxt-up" x="500" y="465" font-size="10.5" text-anchor="middle" fill="var(--muted)">upstairs · off</text>
</g>
<!-- EXT -->
<g>
<rect x="708" y="96" width="104" height="84" rx="6" fill="#241d33" stroke="var(--rule)"/>
<circle class="led" data-zone="ext" cx="726" cy="122" r="6" fill="var(--white)"/>
<circle class="led" data-zone="ext" cx="746" cy="122" r="6" fill="var(--white)"/>
<circle class="led" data-zone="ext" cx="766" cy="122" r="6" fill="var(--white)"/>
<circle class="led" data-zone="ext" cx="736" cy="146" r="6" fill="var(--white)"/>
<circle class="led" data-zone="ext" cx="756" cy="146" r="6" fill="var(--white)"/>
<text class="sm" x="716" y="172">EXT · ×5 white</text>
<rect x="746" y="186" width="28" height="18" rx="3" fill="#2a2340" stroke="var(--rule)"/><text class="sm" x="749" y="199">220</text>
<line class="wire" x1="760" y1="204" x2="760" y2="300"/>
<text class="sm" x="768" y="256">OUT3 · pin16</text>
<ellipse class="glow" data-zone="ext" cx="760" cy="366" rx="34" ry="67" fill="var(--white)" filter="url(#soft)" opacity="0"/>
<use href="#minionC" x="733" y="300" width="54" height="135"/>
<rect id="pill-ext" x="686" y="450" width="148" height="22" rx="11" fill="#241d33" stroke="var(--rule)"/>
<text id="pilltxt-ext" x="760" y="465" font-size="10.5" text-anchor="middle" fill="var(--muted)">exterior lights · off</text>
</g>
<!-- FIRE -->
<g>
<rect x="984" y="96" width="72" height="84" rx="6" fill="#2a1720" stroke="#5a2530"/>
<circle class="led" data-zone="fire" cx="1020" cy="130" r="9" fill="var(--red)"/>
<text class="sm" x="990" y="172" fill="var(--red)">FIRE · ×1 RED</text>
<rect x="1006" y="186" width="28" height="18" rx="3" fill="#2a2340" stroke="var(--rule)"/><text class="sm" x="1009" y="199">220</text>
<line class="wire" x1="1020" y1="204" x2="1020" y2="300"/>
<text class="sm" x="1028" y="256">OUT4 · pin15</text>
<ellipse class="glow" data-zone="fire" cx="1020" cy="366" rx="34" ry="67" fill="var(--red)" filter="url(#soft)" opacity="0"/>
<use href="#minionD" x="993" y="300" width="54" height="135"/>
<path id="torch" d="M1046 330 q7 -14 0 -26 q12 9 5 26 z" fill="var(--red)" filter="drop-shadow(0 0 6px var(--red))" opacity="0"/>
<rect id="pill-fire" x="948" y="450" width="144" height="22" rx="11" fill="#2a1720" stroke="var(--rule)"/>
<text id="pilltxt-fire" x="1020" y="465" font-size="10.5" text-anchor="middle" fill="var(--muted)">fireplace · off</text>
</g>
<!-- ===== GRU below, shouting a command up each wire ===== -->
<use href="#gru" x="543" y="633" width="175" height="283"/>
<text class="lbl" x="452" y="770">GRU</text>
<text class="sm" x="428" y="786">= Arduino · the boss</text>
<text class="sm" x="452" y="810">shouts PWM on</text>
<text class="sm" x="452" y="824">D3 · D5 · D6 · D9</text>
<!-- command origin ABOVE Gru's head; wires fan up, never crossing his face -->
<line x1="630" y1="652" x2="630" y2="646" stroke="var(--slate)" stroke-width="2.2"/>
<circle cx="630" cy="644" r="4" fill="var(--slate)"/>
<line class="cmdwire" id="wire-gf" x1="630" y1="644" x2="240" y2="426" marker-end="url(#arw)"/>
<line class="cmdwire" id="wire-up" x1="630" y1="644" x2="500" y2="426" marker-end="url(#arw)"/>
<line class="cmdwire" id="wire-ext" x1="630" y1="644" x2="760" y2="426" marker-end="url(#arw)"/>
<line class="cmdwire" id="wire-fire" x1="630" y1="644" x2="1020" y2="426" marker-end="url(#arw)"/>
<text class="tag" x="418" y="544" fill="var(--slate)">D3→IN1</text>
<text class="tag" x="556" y="552" fill="var(--slate)">D5→IN2</text>
<text class="tag" x="690" y="552" fill="var(--slate)">D6→IN3</text>
<text class="tag" x="838" y="544" fill="var(--slate)">D9→IN4</text>
<!-- Gru speech bubble (dynamic) -->
<g>
<rect x="726" y="676" width="210" height="60" rx="12" fill="#241d33" stroke="var(--amber)"/>
<path d="M726 712 l-16 -6 l14 -12 z" fill="#241d33" stroke="var(--amber)" stroke-width="1"/>
<text x="831" y="700" font-size="13" font-weight="bold" text-anchor="middle" fill="var(--amber)">MINIONS!</text>
<text id="speech" x="831" y="720" font-size="12" text-anchor="middle" fill="var(--ink)">Await orders…</text>
</g>
</svg>
<div class="key">
<span><span style="color:#b8b0d0"></span> zone wire (LED → OUT)</span>
<span><span style="color:var(--cyan)">▬→</span> active order · <span style="color:var(--slate)">▬ ▬</span> idle</span>
<span>· each minion sinks its zone to ground (ULN pin 9), see the pin map below</span>
</div>
</div>
<!-- ============ real pin map ============ -->
<div class="card">
<strong> The actual pin map</strong>
<table>
<tr><th>Zone</th><th>Gru says (Arduino)</th><th>Minion ear (ULN in)</th><th>Minion hand (ULN out)</th><th>Lights</th></tr>
<tr><td>GF_INT</td><td><code>D3</code></td><td>IN1 · pin 1</td><td>OUT1 · pin 18</td><td>2 white</td></tr>
<tr><td>UP_INT</td><td><code>D5</code></td><td>IN2 · pin 2</td><td>OUT2 · pin 17</td><td>3 white</td></tr>
<tr><td>EXT</td><td><code>D6</code></td><td>IN3 · pin 3</td><td>OUT3 · pin 16</td><td>5 white</td></tr>
<tr><td>FIRE</td><td><code>D9</code></td><td>IN4 · pin 4</td><td>OUT4 · pin 15</td><td>1 <b style="color:var(--red)">red</b></td></tr>
</table>
<p class="sm" style="margin:8px 0 0">No 1kΩ base resistors (built into the ULN). Each LED: <code>+5V → LED → 220Ω → OUTx</code>. Same sketch, same scenes.</p>
</div>
</div>
<script>
const ZONES = ['gf','up','ext','fire'];
const NAME = {gf:'the ground floor', up:'the upstairs', ext:'the exterior lights', fire:'the fireplace'};
const state = {gf:false, up:false, ext:false, fire:false};
let fireTick = 0;
const $ = s => document.querySelector(s);
const leds = z => document.querySelectorAll(`.led[data-zone="${z}"]`);
const glow = z => document.querySelector(`.glow[data-zone="${z}"]`);
function paintZone(z){
const on = state[z];
const isFire = z==='fire';
// fireplace flickers; others steady
let t = on ? 1 : 0;
if (on && isFire) t = 0.55 + 0.45*Math.abs(Math.sin(fireTick*0.9)) * (0.6+0.4*Math.random());
const rgb = isFire ? '255,70,50' : '255,248,236';
leds(z).forEach(el=>{
el.style.opacity = on ? (0.35 + 0.65*t).toFixed(2) : '0.12';
el.style.filter = on ? `drop-shadow(0 0 ${(4+8*t).toFixed(1)}px rgba(${rgb},${(0.9*t).toFixed(2)}))` : 'none';
});
glow(z).style.opacity = on ? (isFire ? 0.5*t : 0.42).toFixed(2) : '0';
// command wire
const w = $(`#wire-${z}`);
w.setAttribute('stroke', on ? 'var(--cyan)' : 'var(--slate)');
w.setAttribute('stroke-width', on ? '3' : '2.2');
w.setAttribute('stroke-dasharray', on ? '0' : '6 4');
w.style.color = on ? '#7fe0ff' : '#9aa6c8'; // arrowhead uses currentColor
w.style.opacity = on ? '1' : '0.45';
// pill
const pill = $(`#pill-${z}`), txt = $(`#pilltxt-${z}`);
const label = NAME[z].replace('the ','');
if (on){ pill.setAttribute('fill', isFire?'#2a1720':'#153a2a'); pill.setAttribute('stroke', isFire?'var(--red)':'var(--grn)');
txt.setAttribute('fill', isFire?'var(--red)':'#7ff0b0'); txt.textContent = `${label} · ON`; }
else { pill.setAttribute('fill','#241d33'); pill.setAttribute('stroke','var(--rule)');
txt.setAttribute('fill','var(--muted)'); txt.textContent = `${label} · off`; }
if (isFire) $('#torch').style.opacity = on ? t.toFixed(2) : '0';
}
function render(){ ZONES.forEach(paintZone); syncButtons(); }
function syncButtons(){
document.querySelectorAll('button.zone').forEach(b=>{
const z = b.dataset.cmd;
b.classList.toggle('on', z==='all' ? ZONES.every(k=>state[k]) : state[z]);
});
}
function say(msg){ $('#speech').textContent = msg; }
document.querySelectorAll('button').forEach(btn=>{
btn.onclick = () => {
const c = btn.dataset.cmd;
if (c==='all'){ const allOn = ZONES.every(z=>state[z]); ZONES.forEach(z=>state[z]=!allOn);
say(allOn ? 'Lights out!' : 'Light the whole house!'); }
else { state[c] = !state[c]; say(state[c] ? `Light ${NAME[c]}!` : `Turn off ${NAME[c]}!`); }
render();
};
});
// fireplace flicker loop
setInterval(()=>{ if(state.fire){ fireTick++; paintZone('fire'); } }, 90);
render();
</script>