|
|
"sourceCode": "\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.0\">\n <title>Pythagorean Theorem Visual Proof</title>\n <style>\n :root {\n --bg-color: #0f172a;\n --panel-bg: #1e293b;\n --text-main: #e2e8f0;\n --text-muted: #94a3b8;\n --accent-blue: #3b82f6;\n --accent-red: #ef4444; /* Side a */\n --accent-green: #22c55e; /* Side b */\n --accent-yellow: #eab308;/* Side c */\n --btn-hover: #334155;\n }\n\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n body {\n font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n background-color: var(--bg-color);\n color: var(--text-main);\n height: 100vh;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n /* Header */\n header {\n padding: 1rem 1.5rem;\n border-bottom: 1px solid #334155;\n flex-shrink: 0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n h1 {\n font-size: 1.25rem;\n font-weight: 600;\n letter-spacing: 0.05em;\n }\n\n /* Main Layout */\n main {\n flex: 1;\n display: flex;\n flex-direction: row;\n overflow: hidden;\n }\n\n /* Canvas Area */\n #canvas-container {\n flex: 2;\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n background-color: #020617;\n overflow: hidden;\n }\n\n canvas {\n display: block;\n max-width: 100%;\n max-height: 100%;\n }\n\n /* Info & Controls Panel */\n #info-panel {\n flex: 1;\n min-width: 320px;\n max-width: 500px;\n background-color: var(--panel-bg);\n border-left: 1px solid #334155;\n display: flex;\n flex-direction: column;\n padding: 2rem;\n box-shadow: -5px 0 15px rgba(0,0,0,0.3);\n z-index: 10;\n }\n\n /* Text Content */\n .step-indicator {\n text-transform: uppercase;\n font-size: 0.75rem;\n color: var(--accent-blue);\n font-weight: bold;\n margin-bottom: 0.5rem;\n }\n\n h2 {\n font-size: 1.5rem;\n margin-bottom: 1rem;\n color: #fff;\n }\n\n p {\n line-height: 1.6;\n color: var(--text-muted);\n margin-bottom: 1.5rem;\n font-size: 1rem;\n }\n\n /* Math Display Area */\n .math-display {\n background: #0f172a;\n border: 1px solid #334155;\n border-radius: 8px;\n padding: 1.5rem;\n margin-bottom: auto; /* Push controls to bottom */\n font-family: 'Courier New', Courier, monospace;\n font-size: 1.1rem;\n text-align: center;\n min-height: 100px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n transition: all 0.3s ease;\n }\n\n .math-line {\n margin: 5px 0;\n opacity: 0;\n transform: translateY(10px);\n animation: fadeInUp 0.5s forwards;\n }\n\n .highlight-eqn {\n color: var(--accent-yellow);\n font-weight: bold;\n font-size: 1.3rem;\n }\n\n .strike {\n text-decoration: line-through;\n text-decoration-color: var(--accent-red);\n text-decoration-thickness: 2px;\n opacity: 0.5;\n }\n\n /* Controls */\n .controls {\n margin-top: 2rem;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n }\n\n .slider-container {\n display: none; /* Hidden by default */\n width: 100%;\n margin-bottom: 1rem;\n }\n \n .slider-container label {\n display: block;\n margin-bottom: 0.5rem;\n font-size: 0.9rem;\n color: var(--text-muted);\n }\n\n input[type=range] {\n width: 100%;\n cursor: pointer;\n accent-color: var(--accent-blue);\n }\n\n .nav-buttons {\n display: flex;\n gap: 1rem;\n }\n\n button {\n flex: 1;\n padding: 0.75rem;\n border: none;\n border-radius: 6px;\n font-weight: 600;\n cursor: pointer;\n transition: background 0.2s;\n font-size: 1rem;\n }\n\n button.btn-primary {\n background-color: var(--accent-blue);\n color: white;\n }\n\n button.btn-primary:hover {\n background-color: #2563eb;\n }\n\n button.btn-secondary {\n background-color: #334155;\n color: var(--text-main);\n }\n\n button.btn-secondary:hover {\n background-color: #475569;\n }\n\n button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n /* Color Spans for Text */\n .c-a { color: var(--accent-red); font-weight: bold; }\n .c-b { color: var(--accent-green); font-weight: bold; }\n .c-c { color: var(--accent-yellow); font-weight: bold; }\n\n /* Animations */\n @keyframes fadeInUp {\n to { opacity: 1; transform: translateY(0); }\n }\n\n /* Responsive */\n @media (max-width: 800px) {\n main {\n flex-direction: column;\n }\n \n #canvas-container {\n flex: 1;\n min-height: 50vh;\n }\n\n #info-panel {\n flex: 1;\n width: 100%;\n max-width: 100%;\n border-left: none;\n border-top: 1px solid #334155;\n padding: 1.5rem;\n }\n\n h2 { font-size: 1.2rem; }\n }\n </style>\n</head>\n<body>\n\n<header>\n <h1>MathVisuals <span style=\"color:var(--accent-blue)\">//</span> Pythagorean Theorem</h1>\n</header>\n\n<main>\n <div id=\"canvas-container\">\n <canvas id=\"vizCanvas\"></canvas>\n </div>\n\n <div id=\"info-panel\">\n <div>\n <div class=\"step-indicator\" id=\"step-indicator\">Step 1 of 7</div>\n <h2 id=\"step-title\">Introduction</h2>\n <div id=\"step-desc\"></div>\n </div>\n\n <div class=\"slider-container\" id=\"slider-group\">\n <label for=\"square-slider\">Resize Squares</label>\n <input type=\"range\" id=\"square-slider\" min=\"0\" max=\"1\" step=\"0.01\" value=\"0\">\n </div>\n\n <div class=\"math-display\" id=\"math-display\">\n <!-- Dynamic Math Content -->\n </div>\n\n <div class=\"controls\">\n <div class=\"nav-buttons\">\n <button id=\"prev-btn\" class=\"btn-secondary\">Previous</button>\n <button id=\"next-btn\" class=\"btn-primary\">Next</button>\n </div>\n </div>\n </div>\n</main>\n\n<script>\n/**\n * Pythagorean Theorem Visualization Logic\n */\n\n// Configuration\nconst COLORS = {\n bg: '#0f172a',\n a: '#ef4444',\n b: '#22c55e',\n c: '#eab308',\n tri: 'rgba(59, 130, 246, 0.6)', // Blue with opacity\n triStroke: '#60a5fa',\n text: '#e2e8f0',\n grid: '#1e293b'\n};\n\n// Triangle properties (Base unit logic)\n// We use a 3-4-5 triangle for clean integer visuals, but code handles generic.\nconst TRI = {\n a: 3, // vertical leg\n b: 4, // horizontal leg\n c: 5\n};\n\nconst STEPS = [\n {\n id: 0,\n title: \"Introducing the Right Triangle\",\n desc: `Here is a right-angled triangle. The two shorter sides are labeled <span class=\"c-a\">a</span> and <span class=\"c-b\">b</span>. The longest side, opposite the right angle, is the hypotenuse, labeled <span class=\"c-c\">c</span>.`,\n math: [],\n hasSlider: false\n },\n {\n id: 1,\n title: \"Squares on the Sides\",\n desc: `Drag the slider to build squares on each side. The area of each square is the side length squared: <span class=\"c-a\">a²</span>, <span class=\"c-b\">b²</span>, and <span class=\"c-c\">c²</span>.`,\n math: [],\n hasSlider: true\n },\n {\n id: 2,\n title: \"Proof Setup: First Arrangement\",\n desc: `Let's build a large square with side length <strong>(a + b)</strong>. Inside, we arrange four copies of our triangle. Notice the empty space in the center forms a tilted square of side <span class=\"c-c\">c</span>.`,\n math: [],\n hasSlider: false\n },\n {\n id: 3,\n title: \"Area Calculation: First Arrangement\",\n desc: `The total area is the sum of the inner parts: the central square <span class=\"c-c\">(c²)</span> and the four triangles.`,\n math: [\n `Area<sub>Total</sub> = Area(<span class=\"c-c\">Square C</span>) + Area(4 Triangles)`,\n `Area<sub>Total</sub> = <span class=\"c-c\">c²</span> + 4 × (½ × <span class=\"c-a\">a</span> × <span class=\"c-b\">b</span>)`,\n `Area<sub>Total</sub> = <span class=\"c-c\">c²</span> + 2<span class=\"c-a\">a</span><span class=\"c-b\">b</span>`\n ],\n hasSlider: false\n },\n {\n id: 4,\n title: \"Proof Setup: Second Arrangement\",\n desc: `Now, take a second identical large square (side <strong>a + b</strong>). We rearrange the <em>same four triangles</em> differently. This leaves two smaller squares: <span class=\"c-a\">a²</span> and <span class=\"c-b\">b²</span>.`,\n math: [],\n hasSlider: false\n },\n {\n id: 5,\n title: \"Area Calculation: Second Arrangement\",\n desc: `Again, calculate the total area. It is the sum of the two squares (<span class=\"c-a\">a²</span>, <span class=\"c-b\">b²</span>) and the four triangles (which form two rectangles).`,\n math: [\n `Area<sub>Total</sub> = Area(<span class=\"c-a\">Sq A</span>) + Area(<span class=\"c-b\">Sq B</span>) + Area(4 Triangles)`,\n `Area<sub>Total</sub> = <span class=\"c-a\">a²</span> + <span class=\"c-b\">b²</span> + 2 × (<span class=\"c-a\">a</span> × <span class=\"c-b\">b</span>)`,\n `Area<sub>Total</sub> = <span class=\"c-a\">a²</span> + <span class=\"c-b\">b²</span> + 2<span class=\"c-a\">a</span><span class=\"c-b\">b</span>`\n ],\n hasSlider: false\n },\n {\n id: 6,\n title: \"The Conclusion\",\n desc: `Since both large squares are the same size, their area formulas must be equal. We can cancel out the triangles (2ab) from both sides.`,\n math: [\n `<span class=\"c-c\">c²</span> + <span class=\"strike\">2ab</span> = <span class=\"c-a\">a²</span> + <span class=\"c-b\">b²</span> + <span class=\"strike\">2ab</span>`,\n `<span class=\"highlight-eqn\"><span class=\"c-c\">c²</span> = <span class=\"c-a\">a²</span> + <span class=\"c-b\">b²</span></span>`\n ],\n hasSlider: false\n }\n];\n\n// Application State\nlet currentState = {\n step: 0,\n sliderValue: 0, // 0 to 1\n canvasWidth: 0,\n canvasHeight: 0,\n pixelsPerUnit: 40 // Scale factor\n};\n\n// DOM Elements\nconst canvas = document.getElementById('vizCanvas');\nconst ctx = canvas.getContext('2d');\nconst stepTitle = document.getElementById('step-title');\nconst stepDesc = document.getElementById('step-desc');\nconst stepIndicator = document.getElementById('step-indicator');\nconst mathDisplay = document.getElementById('math-display');\nconst sliderGroup = document.getElementById('slider-group');\nconst slider = document.getElementById('square-slider');\nconst prevBtn = document.getElementById('prev-btn');\nconst nextBtn = document.getElementById('next-btn');\n\n// Initialization\nfunction init() {\n resizeCanvas();\n window.addEventListener('resize', resizeCanvas);\n \n prevBtn.addEventListener('click', () => changeStep(-1));\n nextBtn.addEventListener('click', () => changeStep(1));\n \n slider.addEventListener('input', (e) => {\n currentState.sliderValue = parseFloat(e.target.value);\n draw();\n });\n\n updateUI();\n draw();\n}\n\nfunction resizeCanvas() {\n const container = document.getElementById('canvas-container');\n canvas.width = container.clientWidth;\n canvas.height = container.clientHeight;\n currentState.canvasWidth = canvas.width;\n currentState.canvasHeight = canvas.height;\n \n // Calculate scale based on a+b+padding\n const totalUnits = TRI.a + TRI.b + 2; // +2 for padding\n const minDim = Math.min(canvas.width, canvas.height);\n currentState.pixelsPerUnit = (minDim * 0.8) / totalUnits;\n \n draw();\n}\n\nfunction changeStep(delta) {\n const newStep = currentState.step + delta;\n if (newStep >= 0 && newStep < STEPS.length) {\n currentState.step = newStep;\n \n // Reset slider for step 2 interactions\n if (newStep === 1) {\n currentState.sliderValue = 0;\n slider.value = 0;\n } else {\n currentState.sliderValue = 1; // Full visualization for other steps\n }\n\n updateUI();\n draw();\n }\n}\n\nfunction updateUI() {\n const stepData = STEPS[currentState.step];\n \n // Text Updates\n stepIndicator.textContent = `Step ${stepData.id + 1} of ${STEPS.length}`;\n stepTitle.textContent = stepData.title;\n stepDesc.innerHTML = stepData.desc;\n \n // Math Display\n mathDisplay.innerHTML = '';\n stepData.math.forEach((line, index) => {\n const div = document.createElement('div');\n div.className = 'math-line';\n div.style.animationDelay = `${index * 0.5}s`;\n div.innerHTML = line;\n mathDisplay.appendChild(div);\n });\n\n // Controls Visibility\n sliderGroup.style.display = stepData.hasSlider ? 'block' : 'none';\n \n // Button States\n prevBtn.disabled = currentState.step === 0;\n nextBtn.disabled = currentState.step === STEPS.length - 1;\n}\n\n// --- Drawing Functions ---\n\nfunction draw() {\n // Clear Canvas\n ctx.fillStyle = COLORS.bg;\n ctx.fillRect(0, 0, currentState.canvasWidth, currentState.canvasHeight);\n \n ctx.save();\n \n // Center the coordinate system\n ctx.translate(currentState.canvasWidth / 2, currentState.canvasHeight / 2);\n \n const ppu = currentState.pixelsPerUnit;\n const { a, b, c } = TRI;\n \n // Switch based on step\n switch (currentState.step) {\n case 0: // Intro\n case 1: // Slider Squares\n drawSingleTriangleScene(ppu, a, b, c);\n break;\n \n case 2: // Arr 1 Setup\n case 3: // Arr 1 Calc\n case 6: // Conclusion (Comparing, usually shows Arr 1 or equation emphasis. Let's show Arr 1)\n drawArrangementOne(ppu, a, b, c);\n break;\n\n case 4: // Arr 2 Setup\n case 5: // Arr 2 Calc\n drawArrangementTwo(ppu, a, b, c);\n break;\n }\n\n ctx.restore();\n}\n\nfunction drawSingleTriangleScene(ppu, a, b, c) {\n // Center the triangle roughly\n const offsetX = -(b * ppu) / 2;\n const offsetY = (a * ppu) / 2;\n ctx.translate(offsetX, offsetY);\n\n // Draw Squares if Slider is active\n if (currentState.step === 1) {\n const t = currentState.sliderValue;\n \n // Square A (Left side)\n ctx.fillStyle = COLORS.a;\n ctx.globalAlpha = 0.2 + (0.6 * t);\n // Grow out to left: x from 0 to -a*t, width a*t\n // Actually simpler: Draw full square, scale it, or mask it.\n // Let's just scale the size of the square based on t\n const sA = a * ppu * t;\n if (sA > 1) {\n ctx.fillRect(-sA, -a * ppu, sA, sA); // Left of A leg\n ctx.globalAlpha = 1;\n if(t > 0.8) drawLabel(\"a²\", -sA/2, -a*ppu/2, COLORS.text);\n }\n\n // Square B (Bottom side)\n ctx.fillStyle = COLORS.b;\n ctx.globalAlpha = 0.2 + (0.6 * t);\n const sB = b * ppu * t;\n if (sB > 1) {\n ctx.fillRect(0, 0, sB, sB); // Below B leg\n ctx.globalAlpha = 1;\n if(t > 0.8) drawLabel(\"b²\", sB/2, sB/2, COLORS.text);\n }\n\n // Square C (Hypotenuse)\n ctx.save();\n // Rotate to align with hypotenuse\n // Angle of hypotenuse relative to x-axis (A is top (0, -a*ppu), B is right (b*ppu, 0))\n // Vector AB = (b, a). Angle = atan(a/b)\n const angle = Math.atan(a/b);\n // Move to top point\n ctx.translate(0, -a * ppu);\n ctx.rotate(angle); \n // Square projects \"up\" from the line A-B relative to the triangle? \n // Normal is perpendicular. \n // Let's simplify: Hypotenuse connects (0, -a) and (b, 0).\n // We want the square to grow outwards.\n ctx.fillStyle = COLORS.c;\n ctx.globalAlpha = 0.2 + (0.6 * t);\n const sC = c * ppu * t;\n if (sC > 1) {\n ctx.fillRect(0, -sC, c * ppu, sC);\n ctx.globalAlpha = 1;\n if(t > 0.8) {\n ctx.save();\n ctx.translate(c*ppu/2, -sC/2);\n ctx.rotate(-angle); // Unrotate text\n drawLabel(\"c²\", 0, 0, COLORS.text);\n ctx.restore();\n }\n }\n ctx.restore();\n }\n\n // Draw Triangle\n drawRightTriangle(ctx, 0, 0, a * ppu, b * ppu, COLORS.tri);\n \n // Labels\n ctx.font = \"bold 16px sans-serif\";\n \n // Side a\n ctx.fillStyle = COLORS.a;\n ctx.fillText(\"a\", -20, - (a * ppu) / 2);\n \n // Side b\n ctx.fillStyle = COLORS.b;\n ctx.fillText(\"b\", (b * ppu) / 2, 20);\n \n // Side c\n ctx.fillStyle = COLORS.c;\n ctx.fillText(\"c\", (b * ppu) / 2 + 10, - (a * ppu) / 2 - 10);\n}\n\nfunction drawArrangementOne(ppu, a, b, c) {\n const size = (a + b) * ppu;\n const startX = -size / 2;\n const startY = -size / 2;\n\n // Draw Big Square Container\n ctx.strokeStyle = COLORS.text;\n ctx.lineWidth = 2;\n ctx.strokeRect(startX, startY, size, size);\n\n // 4 Triangles\n const triColor = COLORS.tri;\n\n // Top-Left Triangle\n drawRightTriangleStandard(ctx, startX, startY, b*ppu, a*ppu, triColor, 0); // Base b, Height a ?? \n // Actually, standard proof:\n // Top-left corner: Go right 'b', down 'a' -> Hypotenuse c\n // Top-Right corner: Go down 'b', left 'a'\n \n // Let's do the standard coordinates explicitly for clarity\n // 1. Top Left (Rotated 0) - Vertical side 'a' on left wall, horizontal 'b' on top wall\n // Wait, if a=3, b=4.\n // Top Left Triangle: Vertices at (startX, startY), (startX + a, startY), (startX, startY + b). Hypotenuse internal.\n // No, the arrangement that forms C^2 in center:\n // T1: Top-Left corner. Leg 'a' along top edge. Leg 'b' along left edge. \n // T2: Top-Right corner. Leg 'b' along top edge. Leg 'a' along right edge.\n // ... This forms a small square (b-a) in center. Not c^2.\n \n // Correct Arrangement for C^2 in center (Bhaskara / Chinese):\n // T1: Base b along bottom, Height a along left. (Bottom-Left)\n // T2: Base b along top, Height a along right. (Top-Right)\n // T3: Base b along right... \n // \n // Let's use the outer square method:\n // Square side (a+b).\n // Point P1 on Top Edge at distance 'a' from TopLeft.\n // Point P2 on Right Edge at distance 'a' from TopRight.\n // Point P3 on Bottom Edge at distance 'a' from BottomRight.\n // Point P4 on Left Edge at distance 'a' from BottomLeft.\n // Connect P1-P2-P3-P4 -> This forms Square C.\n // The corners are the 4 triangles.\n \n const sa = a * ppu;\n const sb = b * ppu;\n \n // Triangle 1 (Top Left corner)\n // Vertices: (startX, startY), (startX+sa, startY), (startX, startY+sb). \n // This creates hypotenuse length c. \n // Let's fill the corners.\n \n // Corner 1: Top Left. Width 'a', Height 'b'.\n drawTrianglePoly(ctx, startX, startY, startX + sa, startY, startX, startY + sb, triColor);\n \n // Corner 2: Top Right. Width 'b', Height 'a'.\n drawTrianglePoly(ctx, startX + size, startY, startX + size - sb, startY, startX + size, startY + sa, triColor);\n\n // Corner 3: Bottom Right. Width 'a', Height 'b'.\n drawTrianglePoly(ctx, startX + size, startY + size, startX + size - sa, startY + size, startX + size, startY + size - sb, triColor);\n\n // Corner 4: Bottom Left. Width 'b', Height 'a'.\n drawTrianglePoly(ctx, startX, startY + size, startX + sb, startY + size, startX, startY + size - sa, triColor);\n\n // Label Center Square\n ctx.fillStyle = COLORS.c;\n ctx.globalAlpha = 0.2;\n ctx.beginPath();\n ctx.moveTo(startX + sa, startY);\n ctx.lineTo(startX + size, startY + sa);\n ctx.lineTo(startX + size - sa, startY + size);\n ctx.lineTo(startX, startY + size - sa);\n ctx.closePath();\n ctx.fill();\n \n ctx.globalAlpha = 1;\n drawLabel(\"c²\", 0, 0, COLORS.c);\n\n // Draw Side Labels on the outer box\n ctx.fillStyle = COLORS.text;\n ctx.font = \"14px sans-serif\";\n // Top Edge\n ctx.fillText(\"a\", startX + sa/2, startY - 10);\n ctx.fillText(\"b\", startX + sa + sb/2, startY - 10);\n}\n\nfunction drawArrangementTwo(ppu, a, b, c) {\n const size = (a + b) * ppu;\n const startX = -size / 2;\n const startY = -size / 2;\n const sa = a * ppu;\n const sb = b * ppu;\n\n // Outer Box\n ctx.strokeStyle = COLORS.text;\n ctx.lineWidth = 2;\n ctx.strokeRect(startX, startY, size, size);\n\n const triColor = COLORS.tri;\n\n // In this arrangement, we form a square a^2 and b^2.\n // Usually a^2 is top-left, b^2 is bottom-right.\n // The rectangles (a*b) are top-right and bottom-left.\n // Each rectangle is split into two triangles.\n\n // Square a^2 (Top Left)\n ctx.fillStyle = COLORS.a;\n ctx.globalAlpha = 0.3;\n ctx.fillRect(startX, startY, sa, sa);\n ctx.globalAlpha = 1;\n drawLabel(\"a²\", startX + sa/2, startY + sa/2, COLORS.text);\n\n // Square b^2 (Bottom Right)\n ctx.fillStyle = COLORS.b;\n ctx.globalAlpha = 0.3;\n ctx.fillRect(startX + sa, startY + sa, sb, sb);\n ctx.globalAlpha = 1;\n drawLabel(\"b²\", startX + sa + sb/2, startY + sa + sb/2, COLORS.text);\n\n // Rectangle 1 (Top Right) - Split into 2 triangles\n // Coords: x: startX+sa, y: startY, w: sb, h: sa\n drawTrianglePoly(ctx, startX + sa, startY, startX + size, startY, startX + size, startY + sa, triColor); // Top half\n drawTrianglePoly(ctx, startX + sa, startY, startX + sa, startY + sa, startX + size, startY + sa, triColor); // Bottom half\n // Draw diagonal line to show separation\n ctx.beginPath();\n ctx.moveTo(startX + sa, startY);\n ctx.lineTo(startX + size, startY + sa);\n ctx.strokeStyle = COLORS.bg;\n ctx.lineWidth = 1;\n ctx.stroke();\n\n // Rectangle 2 (Bottom Left) - Split into 2 triangles\n // Coords: x: startX, y: startY+sa, w: sa, h: sb\n drawTrianglePoly(ctx, startX, startY + sa, startX + sa, startY + sa, startX + sa, startY + size, triColor);\n drawTrianglePoly(ctx, startX, startY + sa, startX, startY + size, startX + sa, startY + size, triColor);\n // Draw diagonal\n ctx.beginPath();\n ctx.moveTo(startX, startY + sa);\n ctx.lineTo(startX + sa, startY + size);\n ctx.strokeStyle = COLORS.bg;\n ctx.lineWidth = 1;\n ctx.stroke();\n\n // Labels for sides\n ctx.fillStyle = COLORS.text;\n ctx.fillText(\"a\", startX + sa/2, startY - 10);\n ctx.fillText(\"b\", startX - 15, startY + sa + sb/2);\n}\n\n// --- Helpers ---\n\n// Draws triangle with right-angle at (x,y), vertical leg h (up negative), horizontal w (right positive)\nfunction drawRightTriangle(ctx, x, y, heightPixels, widthPixels, color) {\n ctx.beginPath();\n ctx.moveTo(x, y); // C (Right Angle)\n ctx.lineTo(x, y - heightPixels); // A\n ctx.lineTo(x + widthPixels, y); // B\n ctx.closePath();\n \n ctx.fillStyle = color;\n ctx.fill();\n ctx.strokeStyle = COLORS.triStroke;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Right angle marker\n const m = 15;\n ctx.beginPath();\n ctx.moveTo(x, y - m);\n ctx.lineTo(x + m, y - m);\n ctx.lineTo(x + m, y);\n ctx.strokeStyle = COLORS.text;\n ctx.lineWidth = 1;\n ctx.stroke();\n}\n\n// Generic Triangle Polygon\nfunction drawTrianglePoly(ctx, x1, y1, x2, y2, x3, y3, color) {\n ctx.beginPath();\n ctx.moveTo(x1, y1);\n ctx.lineTo(x2, y2);\n ctx.lineTo(x3, y3);\n ctx.closePath();\n ctx.fillStyle = color;\n ctx.fill();\n ctx.strokeStyle = COLORS.bg; // Separator\n ctx.lineWidth = 1;\n ctx.stroke();\n}\n\n// Standard visual placeholder\nfunction drawRightTriangleStandard(ctx, x, y, w, h, color, rotationDeg) {\n ctx.save();\n ctx.translate(x, y);\n ctx.rotate(rotationDeg * Math.PI / 180);\n drawRightTriangle(ctx, 0, 0, h, w, color);\n ctx.restore();\n}\n\nfunction drawLabel(text, x, y, color) {\n ctx.fillStyle = color;\n ctx.font = \"bold 20px Courier New\";\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(text, x, y);\n}\n\n// Start\ninit();\n\n</script>\n</body>\n</html>\n"
|