{"id":330,"date":"2025-09-10T15:19:25","date_gmt":"2025-09-10T15:19:25","guid":{"rendered":"https:\/\/learn-by-animation.com\/?page_id=330"},"modified":"2025-09-10T15:22:48","modified_gmt":"2025-09-10T15:22:48","slug":"wolff-kishner-reduction-mini-game","status":"publish","type":"page","link":"https:\/\/learn-by-animation.com\/?page_id=330","title":{"rendered":"Wolff\u2013Kishner Reduction Mini Game"},"content":{"rendered":"\n<!-- Start of Wolff\u2013Kishner Reduction Mini Game Code -->\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\n  <title>Wolff\u2013Kishner Reduction \u2014 Mini Game (Browser)<\/title>\n  <style>\n    :root{\n      --bg:#f5fbff; --ink:#0f141a; --panel:#ffffff; --accent:#7c5cff; --accent2:#ff944d;\n      --green:#4caf50; --red:#e74c3c; --blue:#4d88ff; --yellow:#ffd54f; --gray:#9e9e9e;\n    }\n    html, .wolff-kishner-body {height:100%;}\n    \n    \/* --- Basic Setup & Fonts --- *\/\n    .wolff-kishner-body{\n      margin:0; background:linear-gradient(180deg, var(--bg), #e9f4ff);\n      color:var(--ink); font-family: ui-rounded, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;\n      display:flex; align-items:center; justify-content:center; padding:16px;\n      box-sizing: border-box;\n    }\n    .shell{width: min(980px, 100%);}\n    .card{\n      background:var(--panel); border-radius:20px; box-shadow:0 12px 24px rgba(0,0,0,.08);\n      overflow:hidden; border:1px solid rgba(0,0,0,.06);\n    }\n    \n    \/* --- Header & Controls --- *\/\n    header{padding:14px 18px; display:flex; flex-wrap:wrap; align-items:center; gap:10px;}\n    header h1{font-size:clamp(18px, 2.5vw, 24px); margin:0; font-weight:800;}\n    header p{margin:0; opacity:.9; font-size:14px}\n    .controls{margin-left:auto; display:flex; gap:8px; flex-wrap:wrap}\n    .btn{border:none; border-radius:12px; padding:10px 14px; font-weight:700; cursor:pointer; box-shadow:0 4px 0 rgba(0,0,0,.18);\n         transform:translateY(0); transition:transform .05s ease, box-shadow .05s ease;}\n    .btn:active{transform:translateY(2px); box-shadow:0 2px 0 rgba(0,0,0,.2)}\n    .btn.primary{background:var(--accent); color:#fff}\n    .btn.alt{background:var(--accent2); color:#1f1300}\n\n    \/* --- Main Stage (Canvas) & Footer --- *\/\n    .surface{position:relative;}\n    canvas{display:block; width:100%; height:auto; background:#fff; border-top:1px solid rgba(0,0,0,.06)}\n    .tip{position:absolute; left:12px; top:12px; background:rgba(255,255,255,.9); border:1px solid rgba(0,0,0,.1); padding:8px 10px; border-radius:10px; font-weight:700}\n    .footer{padding:10px 16px; font-size:13px; color:#555; display:flex; gap:10px; align-items:center;}\n    .chip{background:#eef3ff; border:1px solid #dae5ff; padding:4px 8px; border-radius:999px; font-weight:700}\n  <\/style>\n<\/head>\n<div class=\"wolff-kishner-body\">\n  <div class=\"shell\">\n    <div class=\"card\">\n      <header>\n        <div>\n          <h1>Wolff\u2013Kishner Reduction \u2014 Mini Game<\/h1>\n          <p>Help Mr. Carbonyl become a Happy Alkane by doing the steps in order!<\/p>\n        <\/div>\n        <div class=\"controls\">\n          <button class=\"btn primary\" id=\"btnCall\">Call Hydrazine<\/button>\n          <button class=\"btn alt\" id=\"btnReplay\" title=\"Restart\" style=\"display:none\">\u21bb Replay<\/button>\n        <\/div>\n      <\/header>\n      <div class=\"surface\">\n        <div class=\"tip\" id=\"tip\">Click <b>Call Hydrazine<\/b> to begin!<\/div>\n        <canvas id=\"game\" width=\"960\" height=\"600\" aria-label=\"Wolff\u2013Kishner game canvas\"><\/canvas>\n      <\/div>\n      <div class=\"footer\">\n        <span class=\"chip\">Cartoon Chemistry<\/span>\n        Drag hydrazine \u2192 Click BASE then HEAT \u2192 Pop N\u2082 balloons \u2192 Celebrate alkane \ud83c\udf89\n      <\/div>\n    <\/div>\n  <\/div>\n\n<script>\n(() => {\n  \/\/ ----- MODIFICATION: Find the main container first, like in the other script -----\n  const container = document.querySelector('.wolff-kishner-body');\n  if (!container) return;\n\n  \/\/ ----- MODIFICATION: Find all elements within the container -----\n  const canvas = container.querySelector('#game');\n  const tip = container.querySelector('#tip');\n  const btnCall = container.querySelector('#btnCall');\n  const btnReplay = container.querySelector('#btnReplay');\n  \n  \/\/ Exit if essential elements aren't found\n  if (!canvas || !tip || !btnCall || !btnReplay) return;\n  \n  const ctx = canvas.getContext('2d');\n\n  const W = canvas.width, H = canvas.height;\n  const COLORS = {\n    bg: '#ffffff', ink:'#0f141a', gray:'#a0a7b3', light:'#e9edf3', red:'#e84c4c', blue:'#4d88ff', green:'#53b36b',\n    yellow:'#ffd34d', orange:'#ff944d', purple:'#7c5cff'\n  };\n\n  \/\/ ----- Utility drawing helpers -----\n  function circle(x,y,r, fill, stroke = '#111', line=2){\n    ctx.beginPath(); ctx.arc(x,y,r,0,Math.PI*2);\n    if(fill){ctx.fillStyle=fill; ctx.fill();}\n    if(stroke){ctx.lineWidth=line; ctx.strokeStyle=stroke; ctx.stroke();}\n  }\n  function line(x1,y1,x2,y2,w=3, stroke='#111'){\n    ctx.beginPath(); ctx.lineWidth=w; ctx.strokeStyle=stroke; ctx.moveTo(x1,y1); ctx.lineTo(x2,y2); ctx.stroke();\n  }\n  function doubleBond(x1,y1,x2,y2,color='#111'){\n    const dx=x2-x1, dy=y2-y1; const L=Math.hypot(dx,dy)||1; const nx=-dy\/L, ny=dx\/L; const off=4;\n    line(x1+nx*off, y1+ny*off, x2+nx*off, y2+ny*off, 4, color);\n    line(x1-nx*off, y1-ny*off, x2-nx*off, y2-ny*off, 4, color);\n  }\n  function text(txt,x,y,size=20, color='#111', align='center', base='middle', bold=false){\n    ctx.fillStyle=color; ctx.font=`${bold? '700': '600'} ${size}px system-ui, Segoe UI, Roboto, Arial`;\n    ctx.textAlign=align; ctx.textBaseline=base; ctx.fillText(txt,x,y);\n  }\n  function pill(x,y,w,h, fill, stroke='#111'){\n    const r = Math.min(h\/2, 16);\n    ctx.beginPath();\n    ctx.moveTo(x+r, y);\n    ctx.lineTo(x+w-r, y);\n    ctx.quadraticCurveTo(x+w, y, x+w, y+r);\n    ctx.lineTo(x+w, y+h-r);\n    ctx.quadraticCurveTo(x+w, y+h, x+w-r, y+h);\n    ctx.lineTo(x+r, y+h);\n    ctx.quadraticCurveTo(x, y+h, x, y+h-r);\n    ctx.lineTo(x, y+r);\n    ctx.quadraticCurveTo(x, y, x+r, y);\n    ctx.closePath();\n    ctx.fillStyle=fill; ctx.fill();\n    ctx.lineWidth=2; ctx.strokeStyle=stroke; ctx.stroke();\n  }\n\n  \/\/ ----- Game stages -----\n  const STAGES = { INTRO:1, DRAG:2, BASE_HEAT:3, BALLOONS:4, HAPPY:5 };\n  let stage = STAGES.INTRO;\n\n  \/\/ Hydrazine drag state\n  let hyd = { x: 180, y: 200, r: 30, dragging:false, dx:0, dy:0 };\n  const carbonyl = { c: {x: 520-40, y: 300}, o: {x: 520+40, y: 300} };\n  let baseClicked=false, heatClicked=false;\n\n  \/\/ Balloons (N2)\n  let balloons = [];\n\n  function reset(){\n    stage = STAGES.INTRO; tip.innerHTML = \"Click <b>Call Hydrazine<\/b> to begin!\";\n    hyd = { x: 180, y: 200, r: 30, dragging:false, dx:0, dy:0 };\n    baseClicked = false; heatClicked = false; balloons = [];\n    btnCall.style.display = '';\n    btnReplay.style.display = 'none';\n  }\n\n  \/\/ ----- Cartoon molecules -----\n  function drawCarbonyl(cx=520, cy=300){\n    \/\/ R  C=O  R'\n    circle(cx-40, cy, 22, '#b9bec7'); text('C', cx-40, cy, 18, '#fff', 'center', 'middle', true);\n    circle(cx+40, cy, 22, COLORS.red); text('O', cx+40, cy, 18, '#fff', 'center', 'middle', true);\n    doubleBond(cx-18, cy, cx+18, cy);\n    text('R', cx-120, cy-15, 22);\n    text(\"R'\", cx+110, cy-15, 22);\n  }\n\n  function drawHydrazine(x, y){\n    \/\/ N\u2013N with H labels\n    line(x-18, y, x+18, y, 6, COLORS.blue);\n    circle(x-24, y, 16, COLORS.blue); text('N', x-24, y, 14, '#fff');\n    circle(x+24, y, 16, COLORS.blue); text('N', x+24, y, 14, '#fff');\n    text('H', x-40, y-16, 14);\n    text('H', x-40, y+16, 14);\n    text('H', x+40, y-16, 14);\n    text('H', x+40, y+16, 14);\n    text('Hydrazine', x, y+28, 14);\n  }\n\n  function drawHydrazone(cx=520, cy=300){\n    \/\/ C=N\u2014NH2\n    circle(cx-40, cy, 22, '#b9bec7'); text('C', cx-40, cy, 18, '#fff', 'center', 'middle', true);\n    circle(cx+20, cy, 22, COLORS.blue); text('N', cx+20, cy, 18, '#fff', 'center', 'middle', true);\n    doubleBond(cx-18, cy, cx, cy);\n    line(cx+42, cy, cx+74, cy, 5);\n    circle(cx+74, cy, 18, COLORS.blue); text('N', cx+74, cy, 16, '#fff');\n    text('H', cx+92, cy-10, 14); text('H', cx+92, cy+10, 14);\n    text('R', cx-120, cy-15, 22); text(\"R'\", cx+120, cy-15, 22);\n  }\n\n  function drawBaseHeatUI(){\n    \/\/ Base (left) and Heat (right) clickable pills\n    const baseRect = {x:60, y:440, w:140, h:56};\n    const heatRect = {x: W-180, y: 432, w: 96, h: 96};\n    pill(baseRect.x, baseRect.y, baseRect.w, baseRect.h, COLORS.green);\n    text('BASE', baseRect.x + baseRect.w\/2, baseRect.y+baseRect.h\/2, 22, '#fff');\n\n    \/\/ Flame icon\n    circle(heatRect.x+heatRect.w\/2, heatRect.y+heatRect.h\/2, 36, COLORS.orange);\n    circle(heatRect.x+heatRect.w\/2, heatRect.y+heatRect.h\/2 - 12, 24, COLORS.yellow);\n    text('HEAT', heatRect.x+heatRect.w\/2, heatRect.y+heatRect.h - 10, 18);\n\n    if(baseClicked){ ctx.strokeStyle = COLORS.blue; ctx.lineWidth=4; ctx.strokeRect(baseRect.x-4, baseRect.y-4, baseRect.w+8, baseRect.h+8); }\n    if(heatClicked){ ctx.strokeStyle = COLORS.red; ctx.lineWidth=4; ctx.strokeRect(heatRect.x-4, heatRect.y-4, heatRect.w+8, heatRect.h+8); }\n\n    return { baseRect, heatRect };\n  }\n\n  function drawHappyAlkane(cx=500, cy=300){\n    text('R', cx-140, cy-15, 22); text(\"R'\", cx+140, cy-15, 22);\n    line(cx-110, cy, cx-30, cy, 6);\n    line(cx+30, cy, cx+110, cy, 6);\n    \/\/ CH2 box\n    ctx.fillStyle = '#eef2f7'; ctx.strokeStyle = '#111'; ctx.lineWidth=2;\n    ctx.beginPath(); ctx.roundRect(cx-28, cy-22, 56, 44, 10); ctx.fill(); ctx.stroke();\n    text('CH\u2082', cx, cy, 22);\n    \/\/ Smiley\n    circle(cx, cy+96, 40, '#c5f0c8');\n    circle(cx-12, cy+86, 5, '#111', '#111');\n    circle(cx+12, cy+86, 5, '#111', '#111');\n    ctx.beginPath(); ctx.lineWidth=3; ctx.arc(cx, cy+96, 16, Math.PI*0.1, Math.PI*0.9); ctx.stroke();\n  }\n\n  function confetti(){\n    for(let i=0;i<60;i++){\n      const x = Math.random()*W, y=Math.random()*H;\n      const colors=[COLORS.red,COLORS.blue,COLORS.green,COLORS.yellow,COLORS.orange,COLORS.purple];\n      ctx.fillStyle = colors[Math.floor(Math.random()*colors.length)];\n      ctx.fillRect(x,y,4,8);\n    }\n  }\n\n  \/\/ ----- Balloons -----\n  class Balloon{\n    constructor(x,y){ this.x=x; this.y=y; this.alive=true; this.speed=0.6+Math.random()*0.9; this.phase=Math.random()*Math.PI*2; }\n    update(t){ if(!this.alive) return; this.y -= this.speed; this.x += Math.sin(t\/600 + this.phase)*0.6; if(this.y < -30) this.alive=false; }\n    draw(){ if(!this.alive) return; circle(this.x,this.y,18, COLORS.yellow); text('N\u00b2'.replace('\u00b2','\u2082'), this.x, this.y, 16); line(this.x, this.y+18, this.x, this.y+32, 2); }\n    hit(mx,my){ if(!this.alive) return false; const dx=mx-this.x, dy=my-this.y; if(dx*dx+dy*dy <= 18*18){ this.alive=false; return true; } return false; }\n  }\n\n  \/\/ ----- Interaction helpers -----\n  function pointInRect(p, r){ return (p.x>=r.x && p.x<=r.x+r.w &#038;&#038; p.y>=r.y && p.y<=r.y+r.h); }\n\n  \/\/ Mouse \/ touch state\n  const input = {x:0,y:0, down:false};\n  function getCanvasPos(evt){\n    const rect = canvas.getBoundingClientRect();\n    if(evt.touches &#038;&#038; evt.touches[0]){\n      return { x:(evt.touches[0].clientX-rect.left)*(canvas.width\/rect.width), y:(evt.touches[0].clientY-rect.top)*(canvas.height\/rect.height) };\n    }\n    return { x:(evt.clientX-rect.left)*(canvas.width\/rect.width), y:(evt.clientY-rect.top)*(canvas.height\/rect.height) };\n  }\n\n  canvas.addEventListener('mousedown', e=>{ input.down=true; const p=getCanvasPos(e); input.x=p.x; input.y=p.y; onDown(p); });\n  canvas.addEventListener('mousemove', e=>{ const p=getCanvasPos(e); input.x=p.x; input.y=p.y; onMove(p); });\n  canvas.addEventListener('mouseup',   e=>{ input.down=false; const p=getCanvasPos(e); onUp(p); });\n  canvas.addEventListener('mouseleave',e=>{ input.down=false; if(hyd.dragging) hyd.dragging=false; });\n\n  canvas.addEventListener('touchstart', e=>{ const p=getCanvasPos(e); input.down=true; input.x=p.x; input.y=p.y; onDown(p); }, {passive:true});\n  canvas.addEventListener('touchmove',  e=>{ const p=getCanvasPos(e); input.x=p.x; input.y=p.y; onMove(p); }, {passive:true});\n  canvas.addEventListener('touchend',   e=>{ input.down=false; const p={x:input.x, y:input.y}; onUp(p); }, {passive:true});\n\n  \/\/ Header buttons\n  btnCall.addEventListener('click', ()=>{\n    if(stage === STAGES.INTRO){ stage = STAGES.DRAG; tip.textContent = 'Drag Hydrazine onto the Carbonyl (C=O).'; btnCall.style.display='none'; }\n  });\n  btnReplay.addEventListener('click', ()=>{ reset(); });\n\n  function onDown(p){\n    if(stage === STAGES.DRAG){\n      const d = Math.hypot(p.x-hyd.x, p.y-hyd.y);\n      if(d < 42){ hyd.dragging = true; hyd.dx = hyd.x - p.x; hyd.dy = hyd.y - p.y; }\n    } else if(stage === STAGES.BASE_HEAT){\n      const {baseRect, heatRect} = drawBaseHeatUI(); \/\/ use same rects\n      if(pointInRect(p, baseRect) &#038;&#038; !baseClicked){ baseClicked=true; tip.textContent = 'Good! Now click HEAT.'; }\n      else if(pointInRect(p, heatRect) &#038;&#038; baseClicked &#038;&#038; !heatClicked){\n        heatClicked=true; tip.textContent = 'Great! Pop the N\u2082 balloons!';\n        stage = STAGES.BALLOONS; balloons = Array.from({length:6},(_,i)=> new Balloon(520 + (Math.random()*80-40), 320 + (Math.random()*40-20)));\n        btnReplay.style.display='';\n      }\n    } else if(stage === STAGES.BALLOONS){\n      let popped=false; balloons.forEach(b=>{ popped = b.hit(p.x,p.y) || popped; });\n      if(popped) tip.textContent = 'Pop pop! Keep going until they\u2019re all gone!';\n    } else if(stage === STAGES.HAPPY){ \/* no-op *\/ }\n  }\n\n  function onMove(p){ if(stage===STAGES.DRAG && hyd.dragging){ hyd.x = p.x + hyd.dx; hyd.y = p.y + hyd.dy; } }\n  function onUp(p){\n    if(stage===STAGES.DRAG && hyd.dragging){\n      hyd.dragging=false;\n      const cx = carbonyl.c.x, cy = carbonyl.c.y;\n      if(Math.hypot(hyd.x-cx, hyd.y-cy) < 90){ stage = STAGES.BASE_HEAT; tip.textContent = 'Nice! Now click BASE, then HEAT.'; btnReplay.style.display=''; }\n    }\n  }\n\n  \/\/ ----- Main loop -----\n  function render(t){\n    \/\/ Clear\n    ctx.clearRect(0,0,W,H); ctx.fillStyle = COLORS.bg; ctx.fillRect(0,0,W,H);\n\n    \/\/ Stage visuals\n    if(stage === STAGES.INTRO){\n      drawCarbonyl();\n      drawHydrazine( hyd.x, hyd.y );\n      \/\/ hint arrow\n      ctx.globalAlpha=0.25; line(hyd.x, hyd.y, carbonyl.c.x, carbonyl.c.y, 2, COLORS.gray); ctx.globalAlpha=1;\n    }\n    else if(stage === STAGES.DRAG){\n      drawCarbonyl();\n      drawHydrazine( hyd.x, hyd.y );\n      ctx.globalAlpha=0.20; line(hyd.x, hyd.y, carbonyl.c.x, carbonyl.c.y, 2, COLORS.gray); ctx.globalAlpha=1;\n    }\n    else if(stage === STAGES.BASE_HEAT){\n      drawHydrazone();\n      drawBaseHeatUI();\n    }\n    else if(stage === STAGES.BALLOONS){\n      drawHydrazone(520, 320);\n      balloons.forEach(b=>{ b.update(t); b.draw(); });\n      if(balloons.length>0 && balloons.every(b=>!b.alive)){\n        stage = STAGES.HAPPY; tip.textContent = 'Great! Nitrogen flew away. You made an ALKANE!';\n      }\n    }\n    else if(stage === STAGES.HAPPY){\n      drawHappyAlkane(500, 300); confetti();\n    }\n\n    requestAnimationFrame(render);\n  }\n\n  reset();\n  requestAnimationFrame(render);\n})();\n<\/script>\n<\/div>\n<\/html>\n\n<!-- End of Wolff\u2013Kishner Reduction Mini Game Code -->\n","protected":false},"excerpt":{"rendered":"<p>Wolff\u2013Kishner Reduction \u2014 Mini Game (Browser) Wolff\u2013Kishner Reduction \u2014 Mini Game Help Mr. Carbonyl become a Happy Alkane by doing the steps in order! Call Hydrazine \u21bb Replay Click Call Hydrazine to begin! Cartoon Chemistry Drag hydrazine \u2192 Click BASE then HEAT \u2192 Pop N\u2082 balloons \u2192 Celebrate alkane \ud83c\udf89<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-330","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/learn-by-animation.com\/index.php?rest_route=\/wp\/v2\/pages\/330","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/learn-by-animation.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/learn-by-animation.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/learn-by-animation.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/learn-by-animation.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=330"}],"version-history":[{"count":2,"href":"https:\/\/learn-by-animation.com\/index.php?rest_route=\/wp\/v2\/pages\/330\/revisions"}],"predecessor-version":[{"id":334,"href":"https:\/\/learn-by-animation.com\/index.php?rest_route=\/wp\/v2\/pages\/330\/revisions\/334"}],"wp:attachment":[{"href":"https:\/\/learn-by-animation.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=330"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}