Skip to content
LAM
Read Home Blog
Make Projects HTML Tools Games
Touch grass Notes Resume Links
Home Blog HTML Projects
Tools Games Notes Resume Links
Back Master Trig Visuals — Unit Circle, Graphs, Identities Math
Download Open
Show description 1,791 chars · Math

Master Trig Visuals — Unit Circle, Graphs, Identities

Master Trig Visuals — Unit Circle, Graphs, Identities





Master Trig Visuals


Unit Circle
Reference Triangle
Circle → Waves
Trig Graphs
Inverse Graphs
Transform Sandbox
Degree ↔ Radian Ring
Special Angle Table
Identity Map









Unit Circle (special angles, ASTC)


Degrees
Reference lines
θ
Sine = y, Cosine = x, Tangent = y/x








Reference Triangle

Right triangle with sides x = r·cosθ, y = r·sinθ, hypotenuse r. Toggle r and θ.


r
θ








Unit Circle → Sine & Cosine (projection)


▶ Play
speed



sine
cosine (dashed)








Trig Function Graphs



sin
cos
tan
sec
csc

zoom
asymptotes
π marks








Inverse Trig Graphs (principal branches)



arcsin
arccos
arctan









Transform Sandbox: y = A·sin(Bx − C) + D


A (amplitude)
B (freq)
C (phase)
D (vertical)
use cos


Period = 2π, Midline = y = 0, Phase shift = 0







Degree ↔ Radian Ring

Hover/tap to see matching degree & radian labels.







Special Angles (degrees ↔ radians ↔ values)







Identity Map



Pythagorean


sin²θ + cos²θ = 1

1 + tan²θ = sec²θ

1 + cot²θ = csc²θ


Reciprocal


secθ = 1 / cosθ, cscθ = 1 / sinθ, cotθ = 1 / tanθ


Quotient


tanθ = sinθ / cosθ, cotθ = cosθ / sinθ





Cofunction (complements)


sin(π/2−θ)=cosθ, cos(π/2−θ)=sinθ

tan(π/2−θ)=cotθ, sec(π/2−θ)=cscθ


Sum/Difference


sin(a±b)=sin a cos b ± cos a sin b

cos(a±b)=cos a cos b ∓ sin a sin b

tan(a±b)= (tan a ± tan b)/(1 ∓ tan a tan b)


Double / Half


sin 2θ=2 sinθ cosθ, cos 2θ=cos²θ−sin²θ=1−2 sin²θ=2 cos²θ−1

tan 2θ=2 tanθ/(1−tan²θ)






ASTC rule: All (Q1) — Sine (Q2) — Tangent (Q3) — Cosine (Q4).





Built for lalo. No libs. Pure HTML/CSS/JS.

Master Trig Visuals — Unit Circle, Graphs, Identities

27,239 bytes · HTML source
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Master Trig Visuals — Unit Circle, Graphs, Identities</title>
<style>
  :root{
    --fg:#111; --bg:#fff; --ink:#000; --sub:#555; --accent:#000;
    --card:#fff; --border:#000; --muted:#888; --grid:#ddd;
  }
  *{box-sizing:border-box}
  html,body{height:100%}
  body{
    margin:0; font:16px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", sans-serif;
    color:var(--fg); background:var(--bg);
  }
  header{
    position:sticky; top:0; z-index:10; backdrop-filter:saturate(1.5) blur(6px);
    background:rgba(255,255,255,.85); border-bottom:1px solid var(--border);
  }
  .wrap{max-width:1200px; margin:auto; padding:16px;}
  h1{margin:0 0 8px; letter-spacing:.3px}
  nav{display:flex; gap:8px; flex-wrap:wrap}
  nav a{
    text-decoration:none; color:var(--ink); border:1px solid var(--border);
    padding:6px 10px; border-radius:10px; font-weight:600;
  }
  nav a:hover{background:#f3f3f3}
  section{scroll-margin-top:90px}
  .grid{display:grid; gap:16px}
  .grid.cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
  .grid.cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
  @media (max-width:1000px){ .grid.cols-3{grid-template-columns:repeat(2,1fr)} }
  @media (max-width:740px){
    .grid.cols-2,.grid.cols-3{grid-template-columns:1fr}
    nav{overflow:auto; white-space:nowrap}
  }
  .card{
    border:1.25px solid var(--border); border-radius:16px; background:var(--card);
    padding:16px; box-shadow:0 4px 0 #000;
  }
  .card h2{margin:0 0 10px}
  .dim{color:var(--sub)}
  .kbd{border:1px solid var(--border); border-radius:8px; padding:2px 6px; font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace}
  canvas{width:100%; height:420px; display:block; border:1px solid var(--border); border-radius:12px; background:#fff}
  .small canvas{height:320px}
  .controls{display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin:8px 0 4px}
  .controls label{font-size:13px; color:var(--sub)}
  .pill{
    display:inline-flex; gap:6px; align-items:center; border:1px solid var(--border);
    padding:6px 10px; border-radius:999px; background:#fff; font-weight:600;
  }
  .tbl{width:100%; border-collapse:collapse; font-variant-numeric:tabular-nums}
  .tbl th,.tbl td{border:1px solid var(--border); padding:8px 10px; text-align:center}
  .tbl th{background:#f8f8f8}
  code{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:13px}
  .legend{display:flex; gap:12px; flex-wrap:wrap; margin:6px 0 10px}
  .swatch{display:inline-flex; align-items:center; gap:6px}
  .dot{width:12px; height:12px; border-radius:50%; background:#000; border:1px solid #000}
  .muted{color:#777}
</style>
</head>
<body>
<header>
  <div class="wrap">
    <h1>Master Trig Visuals</h1>
    <nav>
      <a href="#unit-circle">Unit Circle</a>
      <a href="#reference-triangle">Reference Triangle</a>
      <a href="#wave-projection">Circle → Waves</a>
      <a href="#graphs">Trig Graphs</a>
      <a href="#inverse">Inverse Graphs</a>
      <a href="#transform">Transform Sandbox</a>
      <a href="#deg-rad">Degree ↔ Radian Ring</a>
      <a href="#special-table">Special Angle Table</a>
      <a href="#identities">Identity Map</a>
    </nav>
  </div>
</header>

<main class="wrap">
  <!-- Unit Circle -->
  <section id="unit-circle" class="card">
    <h2>Unit Circle (special angles, ASTC)</h2>
    <div class="controls">
      <span class="pill"><input type="checkbox" id="uc-deg" /><label for="uc-deg">Degrees</label></span>
      <span class="pill"><input type="checkbox" id="uc-refs" checked/><label for="uc-refs">Reference lines</label></span>
      <span class="pill"><input type="range" id="uc-theta" min="0" max="6.28318530718" step="0.001" value="0"/><label for="uc-theta">θ</label></span>
      <span class="muted">Sine = y, Cosine = x, Tangent = y/x</span>
    </div>
    <canvas id="unitCircle"></canvas>
  </section>

  <!-- Reference Triangle -->
  <section id="reference-triangle" class="card">
    <h2>Reference Triangle</h2>
    <p class="dim">Right triangle with sides x = r·cosθ, y = r·sinθ, hypotenuse r. Toggle r and θ.</p>
    <div class="controls">
      <span class="pill"><input type="range" id="rt-r" min="0.5" max="2.0" step="0.01" value="1"/><label for="rt-r">r</label></span>
      <span class="pill"><input type="range" id="rt-theta" min="0.05" max="1.4" step="0.005" value="0.9"/><label for="rt-theta">θ</label></span>
    </div>
    <canvas id="refTri" class="small"></canvas>
  </section>

  <!-- Circle to Waves Projection -->
  <section id="wave-projection" class="card">
    <h2>Unit Circle → Sine &amp; Cosine (projection)</h2>
    <div class="controls">
      <button id="wp-play" class="pill">▶ Play</button>
      <span class="pill"><input type="range" id="wp-speed" min="0.2" max="3" value="1" step="0.1"><label for="wp-speed">speed</label></span>
    </div>
    <div class="legend">
      <span class="swatch"><span class="dot"></span><span>sine</span></span>
      <span class="swatch"><span class="dot" style="background:#fff;border-color:#000; box-shadow:inset 0 0 0 2px #000;"></span><span>cosine (dashed)</span></span>
    </div>
    <canvas id="proj"></canvas>
  </section>

  <!-- Function Graphs -->
  <section id="graphs" class="card">
    <h2>Trig Function Graphs</h2>
    <div class="controls">
      <span class="pill">
        <label><input type="radio" name="fn" value="sin" checked/> sin</label>
        <label><input type="radio" name="fn" value="cos"/> cos</label>
        <label><input type="radio" name="fn" value="tan"/> tan</label>
        <label><input type="radio" name="fn" value="sec"/> sec</label>
        <label><input type="radio" name="fn" value="csc"/> csc</label>
      </span>
      <span class="pill"><input type="range" id="g-zoom" min="0.5" max="4" step="0.1" value="1"><label for="g-zoom">zoom</label></span>
      <span class="pill"><input type="checkbox" id="g-asym" checked/><label for="g-asym">asymptotes</label></span>
      <span class="pill"><input type="checkbox" id="g-marks" checked/><label for="g-marks">π marks</label></span>
    </div>
    <canvas id="graph"></canvas>
  </section>

  <!-- Inverse Graphs -->
  <section id="inverse" class="card">
    <h2>Inverse Trig Graphs (principal branches)</h2>
    <div class="controls">
      <span class="pill">
        <label><input type="radio" name="inv" value="asin" checked/> arcsin</label>
        <label><input type="radio" name="inv" value="acos"/> arccos</label>
        <label><input type="radio" name="inv" value="atan"/> arctan</label>
      </span>
    </div>
    <canvas id="inv"></canvas>
  </section>

  <!-- Transform Sandbox -->
  <section id="transform" class="card">
    <h2>Transform Sandbox: y = A·sin(Bx − C) + D</h2>
    <div class="controls">
      <span class="pill"><input type="range" id="t-A" min="-3" max="3" step="0.1" value="1"><label for="t-A">A (amplitude)</label></span>
      <span class="pill"><input type="range" id="t-B" min="0.2" max="4" step="0.1" value="1"><label for="t-B">B (freq)</label></span>
      <span class="pill"><input type="range" id="t-C" min="-6.283" max="6.283" step="0.01" value="0"><label for="t-C">C (phase)</label></span>
      <span class="pill"><input type="range" id="t-D" min="-3" max="3" step="0.1" value="0"><label for="t-D">D (vertical)</label></span>
      <span class="pill"><label><input type="checkbox" id="t-cos" /> use cos</label></span>
    </div>
    <div class="dim">Period = <span id="t-period">2π</span>, Midline = y = <span id="t-mid">0</span>, Phase shift = <span id="t-shift">0</span></div>
    <canvas id="transformer"></canvas>
  </section>

  <!-- Degree/Radian Ring -->
  <section id="deg-rad" class="card">
    <h2>Degree ↔ Radian Ring</h2>
    <p class="dim">Hover/tap to see matching degree &amp; radian labels.</p>
    <canvas id="ring" class="small"></canvas>
  </section>

  <!-- Special Angle Table -->
  <section id="special-table" class="card">
    <h2>Special Angles (degrees ↔ radians ↔ values)</h2>
    <table class="tbl" id="angTbl"></table>
  </section>

  <!-- Identity Map -->
  <section id="identities" class="card">
    <h2>Identity Map</h2>
    <div class="grid cols-2">
      <div>
        <h3>Pythagorean</h3>
        <ul>
          <li><code>sin²θ + cos²θ = 1</code></li>
          <li><code>1 + tan²θ = sec²θ</code></li>
          <li><code>1 + cot²θ = csc²θ</code></li>
        </ul>
        <h3>Reciprocal</h3>
        <ul>
          <li><code>secθ = 1 / cosθ</code>, <code>cscθ = 1 / sinθ</code>, <code>cotθ = 1 / tanθ</code></li>
        </ul>
        <h3>Quotient</h3>
        <ul>
          <li><code>tanθ = sinθ / cosθ</code>, <code>cotθ = cosθ / sinθ</code></li>
        </ul>
      </div>
      <div>
        <h3>Cofunction (complements)</h3>
        <ul>
          <li><code>sin(π/2−θ)=cosθ</code>, <code>cos(π/2−θ)=sinθ</code></li>
          <li><code>tan(π/2−θ)=cotθ</code>, <code>sec(π/2−θ)=cscθ</code></li>
        </ul>
        <h3>Sum/Difference</h3>
        <ul>
          <li><code>sin(a±b)=sin a cos b ± cos a sin b</code></li>
          <li><code>cos(a±b)=cos a cos b ∓ sin a sin b</code></li>
          <li><code>tan(a±b)= (tan a ± tan b)/(1 ∓ tan a tan b)</code></li>
        </ul>
        <h3>Double / Half</h3>
        <ul>
          <li><code>sin 2θ=2 sinθ cosθ</code>, <code>cos 2θ=cos²θ−sin²θ=1−2 sin²θ=2 cos²θ−1</code></li>
          <li><code>tan 2θ=2 tanθ/(1−tan²θ)</code></li>
        </ul>
      </div>
    </div>
    <p class="dim">ASTC rule: All (Q1) — Sine (Q2) — Tangent (Q3) — Cosine (Q4).</p>
  </section>

  <footer class="dim" style="margin:26px 0 40px">
    Built for lalo. No libs. Pure HTML/CSS/JS.
  </footer>
</main>

<script>
/* ---------- utilities ---------- */
const TAU = Math.PI*2, PI=Math.PI;
const clamp=(x,a,b)=>Math.max(a,Math.min(b,x));
function px(ctx,x,y){ctx.moveTo(x-3,y);ctx.lineTo(x+3,y);ctx.moveTo(x,y-3);ctx.lineTo(x,y+3)}
function grid(ctx,w,h,ox,oy,sx=PI/2,sy=0.5){
  ctx.save(); ctx.strokeStyle='#ddd'; ctx.lineWidth=1;
  ctx.beginPath();
  for(let x=ox% (sx*50); x<w; x+=sx*50){ ctx.moveTo(x,0); ctx.lineTo(x,h); }
  for(let y=oy% (sy*50); y<h; y+=sy*50){ ctx.moveTo(0,y); ctx.lineTo(w,y); }
  ctx.stroke(); ctx.restore();
}
function axis(ctx,w,h,ox,oy){ // x-axis y=0, y-axis x=0
  ctx.save(); ctx.strokeStyle='#000'; ctx.lineWidth=1.25;
  ctx.beginPath(); ctx.moveTo(0,oy); ctx.lineTo(w,oy); ctx.moveTo(ox,0); ctx.lineTo(ox,h); ctx.stroke();
  ctx.restore();
}
function label(ctx,txt,x,y,align='center'){ctx.save(); ctx.fillStyle='#000'; ctx.textAlign=align; ctx.textBaseline='middle'; ctx.font='12px ui-monospace,Menlo,Consolas,monospace'; ctx.fillText(txt,x,y); ctx.restore();}

/* ---------- Unit Circle ---------- */
(function(){
  const canvas=document.getElementById('unitCircle'), ctx=canvas.getContext('2d');
  const thetaSlider=document.getElementById('uc-theta');
  const degChk=document.getElementById('uc-deg');
  const refsChk=document.getElementById('uc-refs');
  function draw(){
    const dpr=window.devicePixelRatio||1; const rect=canvas.getBoundingClientRect();
    canvas.width=rect.width*dpr; canvas.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
    const w=rect.width, h=420, cx=w/2, cy=h/2+10, R=Math.min(w,h)*0.32;
    ctx.clearRect(0,0,w,h);
    // circle
    ctx.beginPath(); ctx.arc(cx,cy,R,0,TAU); ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
    // axes
    ctx.beginPath(); ctx.moveTo(cx-R-20,cy); ctx.lineTo(cx+R+20,cy); ctx.moveTo(cx,cy-R-20); ctx.lineTo(cx,cy+R+20); ctx.strokeStyle='#000'; ctx.stroke();
    // ASTC labels
    ctx.fillStyle='#000'; ctx.font='12px Inter,system-ui,sans-serif';
    ctx.fillText('All',cx+22,cy-22);
    ctx.fillText('Sin+',cx-44,cy-22);
    ctx.fillText('Tan+',cx-44,cy+34);
    ctx.fillText('Cos+',cx+22,cy+34);
    // ticks + special angles
    const specials=[0,PI/6,PI/4,PI/3,PI/2,2*PI/3,3*PI/4,5*PI/6,PI,7*PI/6,5*PI/4,4*PI/3,3*PI/2,5*PI/3,7*PI/4,11*PI/6,TAU];
    ctx.strokeStyle='#000';
    specials.forEach(a=>{
      const x=cx+R*Math.cos(a), y=cy-R*Math.sin(a);
      ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(cx+(R+10)*Math.cos(a), cy-(R+10)*Math.sin(a)); ctx.stroke();
      const txt=degChk.checked ? Math.round(a*180/PI)+'°' :
        (a===0||a===TAU)?'0':(a===PI)?'π':(a===PI/2)?'π/2':(a===3*PI/2)?'3π/2':
        (a===PI/6)?'π/6':(a===PI/4)?'π/4':(a===PI/3)?'π/3':
        (a===2*PI/3)?'2π/3':(a===3*PI/4)?'3π/4':(a===5*PI/6)?'5π/6':
        (a===7*PI/6)?'7π/6':(a===5*PI/4)?'5π/4':(a===4*PI/3)?'4π/3':
        (a===5*PI/3)?'5π/3':(a===7*PI/4)?'7π/4':'';
      if(txt) label(ctx,txt,x+(x>cx?14:-14),y+(y<cy?-10:10),x>cx?'left':'right');
      if(refsChk.checked){ ctx.save(); ctx.strokeStyle='#ddd'; ctx.beginPath(); ctx.moveTo(cx,cy); ctx.lineTo(x,y); ctx.stroke(); ctx.restore(); }
    });
    // moving point
    const t=parseFloat(thetaSlider.value); const px=cx+R*Math.cos(t), py=cy-R*Math.sin(t);
    ctx.fillStyle='#000'; ctx.beginPath(); ctx.arc(px,py,4,0,TAU); ctx.fill();
    // projections
    ctx.setLineDash([6,4]); ctx.strokeStyle='#000'; ctx.beginPath(); ctx.moveTo(px,cy); ctx.lineTo(px,py); ctx.moveTo(cx,py); ctx.lineTo(px,py); ctx.stroke(); ctx.setLineDash([]);
    // labels
    const x=Math.cos(t).toFixed(3), y=Math.sin(t).toFixed(3);
    label(ctx,`x = cosθ = ${x}`,px,cy+18);
    label(ctx,`y = sinθ = ${y}`,cx-38,py);
    label(ctx,`tanθ = y/x ${(Math.abs(Math.cos(t))<1e-6)?'= undefined':('= '+(Math.tan(t)).toFixed(3))}`, cx, cy+R+28);
  }
  thetaSlider.addEventListener('input',draw); degChk.addEventListener('change',draw); refsChk.addEventListener('change',draw);
  addEventListener('resize',draw,{passive:true}); draw();
})();

/* ---------- Reference Triangle ---------- */
(function(){
  const c=document.getElementById('refTri'), ctx=c.getContext('2d');
  const rEl=document.getElementById('rt-r'), tEl=document.getElementById('rt-theta');
  function draw(){
    const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
    c.width=rect.width*dpr; c.height=320*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
    const w=rect.width, h=320; ctx.clearRect(0,0,w,h);
    const base=240, x0=40, y0=h-40, r=parseFloat(rEl.value), th=parseFloat(tEl.value);
    const x=r*Math.cos(th)*base/r, y=r*Math.sin(th)*base/r;
    // triangle
    ctx.strokeStyle='#000'; ctx.lineWidth=1.5;
    ctx.beginPath(); ctx.moveTo(x0,y0); ctx.lineTo(x0+base,y0); ctx.lineTo(x0+base,y0- base*Math.tan(th)); ctx.closePath(); ctx.stroke();
    // project to point with radius r scaled
    const hx=x0 + x, hy=y0 - y;
    ctx.beginPath(); ctx.moveTo(x0,y0); ctx.lineTo(hx,hy); ctx.lineTo(x0+ x, y0); ctx.lineTo(x0,y0); ctx.stroke();
    ctx.fillStyle='#000'; ctx.fillText(`x = r cosθ = ${(r*Math.cos(th)).toFixed(2)}`, x0+ x/2, y0+14);
    ctx.fillText(`y = r sinθ = ${(r*Math.sin(th)).toFixed(2)}`, x0+ x+8, y0- y/2);
    ctx.fillText(`r = ${(r).toFixed(2)}`, x0+(x)/2-4, y0 - (y)/2);
    // angle arc
    ctx.beginPath(); ctx.arc(x0,y0,30,-Math.PI, -Math.PI+th, false); ctx.stroke();
    ctx.fillText(`θ=${th.toFixed(2)} rad`, x0+36, y0-10);
  }
  rEl.addEventListener('input',draw); tEl.addEventListener('input',draw); addEventListener('resize',draw); draw();
})();

/* ---------- Circle → Waves Projection ---------- */
(function(){
  const c=document.getElementById('proj'), ctx=c.getContext('2d');
  const btn=document.getElementById('wp-play'), speedEl=document.getElementById('wp-speed');
  let running=false, t=0;
  btn.onclick=()=>{running=!running; btn.textContent=running?'⏸ Pause':'▶ Play'};
  function step(dt){ if(running){ t+=dt*parseFloat(speedEl.value); } }
  function draw(){
    const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
    c.width=rect.width*dpr; c.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
    const w=rect.width, h=420; ctx.clearRect(0,0,w,h);
    const R=120, cx=R+40, cy=h/2, kx= (w-cx-60)/ (2*PI), ox=cx, oy=cy;
    // circle
    ctx.beginPath(); ctx.arc(cx,cy,R,0,TAU); ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
    // moving point
    const ang=t%TAU, x=cx+R*Math.cos(ang), y=cy-R*Math.sin(ang);
    ctx.fillStyle='#000'; ctx.beginPath(); ctx.arc(x,y,4,0,TAU); ctx.fill();
    // projections to waves
    const startX=ox+20, waveW=w-startX-20, baseY=cy;
    // axes
    ctx.beginPath(); ctx.moveTo(startX, baseY); ctx.lineTo(startX+waveW, baseY); ctx.moveTo(startX, baseY- R); ctx.lineTo(startX, baseY+R); ctx.strokeStyle='#000'; ctx.stroke();
    // sine
    ctx.beginPath(); for(let i=0;i<=waveW;i++){
      const a= (i/kx); const yy=baseY - R*Math.sin(ang - a);
      if(i===0) ctx.moveTo(startX+i,yy); else ctx.lineTo(startX+i,yy);
    } ctx.strokeStyle='#000'; ctx.stroke();
    // cosine dashed
    ctx.setLineDash([6,4]); ctx.beginPath(); for(let i=0;i<=waveW;i++){
      const a= (i/kx); const yy=baseY - R*Math.cos(ang - a);
      if(i===0) ctx.moveTo(startX+i,yy); else ctx.lineTo(startX+i,yy);
    } ctx.stroke(); ctx.setLineDash([]);
    // guide lines
    ctx.setLineDash([3,3]); ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(startX, baseY - (y-cy)); ctx.moveTo(x,y); ctx.lineTo(startX, baseY - (x-cx)); ctx.strokeStyle='#000'; ctx.stroke(); ctx.setLineDash([]);
    requestAnimationFrame((ts)=>{ const now=performance.now(); step(1/60); draw(); });
  }
  draw();
})();

/* ---------- Function Graphs ---------- */
(function(){
  const c=document.getElementById('graph'), ctx=c.getContext('2d');
  const radios=[...document.querySelectorAll('input[name=fn]')];
  const zoomEl=document.getElementById('g-zoom');
  const asymEl=document.getElementById('g-asym');
  const marksEl=document.getElementById('g-marks');
  function f(name,x){
    if(name==='sin') return Math.sin(x);
    if(name==='cos') return Math.cos(x);
    if(name==='tan') return Math.tan(x);
    if(name==='sec') return 1/Math.cos(x);
    if(name==='csc') return 1/Math.sin(x);
  }
  function asymptotes(name,xmin,xmax){
    const arr=[];
    if(name==='tan' || name==='sec'){ // cos=0 at (π/2)+kπ
      for(let k=Math.floor((xmin-PI/2)/PI)-1; k<=Math.ceil((xmax-PI/2)/PI)+1; k++){
        arr.push(PI/2 + k*PI);
      }
    }
    if(name==='csc'){ // sin=0 at kπ
      for(let k=Math.floor(xmin/PI)-1;k<=Math.ceil(xmax/PI)+1;k++) arr.push(k*PI);
    }
    return arr.filter(a=>a>xmin && a<xmax);
  }
  function draw(){
    const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
    c.width=rect.width*dpr; c.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
    const w=rect.width,h=420; ctx.clearRect(0,0,w,h);
    const name=radios.find(r=>r.checked).value, zoom=parseFloat(zoomEl.value);
    // world coords
    const xMin=-PI*zoom*2, xMax=PI*zoom*2, yMin=-3, yMax=3;
    const ox = (-xMin)/(xMax-xMin)*w, oy = (yMax/(yMax-yMin))*h;
    grid(ctx,w,h,ox,oy,PI,0.5); axis(ctx,w,h,ox,oy);
    // π marks
    if(marksEl.checked){
      ctx.fillStyle='#000'; ctx.font='12px ui-monospace,monospace';
      for(let k=Math.ceil(xMin/ (PI/2)); k<=Math.floor(xMax/(PI/2)); k++){
        const x=k*(PI/2); const sx=(x-xMin)/(xMax-xMin)*w;
        const label=(k===0)?'0':(k===1)?'π/2':(k===2)?'π':(k===3)?'3π/2':(k===4)?'2π':
          (k===-1)?'-π/2':(k===-2)?'-π':(k===-3)?'-3π/2':(Math.abs(k)%2?`${k}·π/2`:`${k/2}π`);
        ctx.fillText(label,sx+4,oy+12);
      }
    }
    // asymptotes
    if(asymEl.checked){
      ctx.save(); ctx.setLineDash([6,6]); ctx.strokeStyle='#000'; ctx.lineWidth=1;
      asymptotes(name,xMin,xMax).forEach(a=>{
        const sx=(a-xMin)/(xMax-xMin)*w; ctx.beginPath(); ctx.moveTo(sx,0); ctx.lineTo(sx,h); ctx.stroke();
      });
      ctx.restore();
    }
    // plot
    ctx.beginPath();
    for(let i=0;i<w;i++){
      const x=xMin + (i/w)*(xMax-xMin);
      let y=f(name,x);
      if(!isFinite(y) || Math.abs(y)>1e6){ ctx.moveTo(i, -999); continue; }
      const sy = (1-(y - yMin)/(yMax-yMin))*h;
      if(i===0) ctx.moveTo(i,sy); else ctx.lineTo(i,sy);
    }
    ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
  }
  [zoomEl,asymEl,marksEl,...radios].forEach(el=>el.addEventListener('input',draw));
  addEventListener('resize',draw); draw();
})();

/* ---------- Inverse Graphs ---------- */
(function(){
  const c=document.getElementById('inv'), ctx=c.getContext('2d');
  const radios=[...document.querySelectorAll('input[name=inv]')];
  function draw(){
    const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
    c.width=rect.width*dpr; c.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
    const w=rect.width,h=420; ctx.clearRect(0,0,w,h);
    const name=radios.find(r=>r.checked).value;
    const xMin=-3, xMax=3, yMin=-PI, yMax=PI;
    const ox=(-xMin)/(xMax-xMin)*w, oy=(yMax/(yMax-yMin))*h;
    grid(ctx,w,h,ox,oy,1,PI/4/PI); axis(ctx,w,h,ox,oy);
    ctx.beginPath();
    for(let i=0;i<w;i++){
      const x=xMin + (i/w)*(xMax-xMin);
      let y=(name==='asin')?Math.asin(clamp(x,-1,1)):
            (name==='acos')?Math.acos(clamp(x,-1,1)):
            Math.atan(x);
      const sy=(1-(y - yMin)/(yMax-yMin))*h;
      if(i===0) ctx.moveTo(i,sy); else ctx.lineTo(i,sy);
    }
    ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
    // domains
    ctx.fillStyle='#000';
    const dom = name==='asin' || name==='acos' ? 'domain: [-1,1]' : 'domain: (-∞,∞)';
    const ran = name==='asin' ? 'range: [-π/2, π/2]' :
                name==='acos' ? 'range: [0, π]' : 'range: (-π/2, π/2)';
    ctx.fillText(dom + '   ' + ran, 10, 18);
  }
  radios.forEach(r=>r.addEventListener('input',draw)); addEventListener('resize',draw); draw();
})();

/* ---------- Transform Sandbox ---------- */
(function(){
  const c=document.getElementById('transformer'), ctx=c.getContext('2d');
  const A=document.getElementById('t-A'), B=document.getElementById('t-B'),
        C=document.getElementById('t-C'), D=document.getElementById('t-D'),
        COS=document.getElementById('t-cos'),
        P=document.getElementById('t-period'), M=document.getElementById('t-mid'), S=document.getElementById('t-shift');
  function draw(){
    const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
    c.width=rect.width*dpr; c.height=420*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
    const w=rect.width,h=420; ctx.clearRect(0,0,w,h);
    const a=+A.value,b=+B.value,cph=+C.value,d=+D.value, useCos=COS.checked;
    const xMin=-2*PI, xMax=2*PI, yMin=-3, yMax=3;
    const ox=(-xMin)/(xMax-xMin)*w, oy=(yMax/(yMax-yMin))*h;
    grid(ctx,w,h,ox,oy,PI,0.5); axis(ctx,w,h,ox,oy);
    ctx.beginPath();
    for(let i=0;i<w;i++){
      const x=xMin + (i/w)*(xMax-xMin);
      const y=a*((useCos?Math.cos:Math.sin)(b*x - cph)) + d;
      const sy=(1-(y - yMin)/(yMax-yMin))*h;
      if(i===0) ctx.moveTo(i,sy); else ctx.lineTo(i,sy);
    }
    ctx.strokeStyle='#000'; ctx.lineWidth=1.6; ctx.stroke();
    // readouts
    const period=(2*PI/Math.abs(b)); P.textContent=(isFinite(period)?formatPi(period):'—');
    M.textContent=d.toFixed(2);
    S.textContent=formatPi(cph/b);
  }
  function formatPi(val){
    const s = (Math.abs(val-PI)<1e-4)?'π':
      (Math.abs(val-2*PI)<1e-3)?'2π':
      (Math.abs(val-PI/2)<1e-4)?'π/2':
      (Math.abs(val-PI/3)<1e-4)?'π/3': val.toFixed(2);
    return s;
  }
  [A,B,C,D,COS].forEach(el=>el.addEventListener('input',draw));
  addEventListener('resize',draw); draw();
})();

/* ---------- Degree/Radian Ring ---------- */
(function(){
  const c=document.getElementById('ring'), ctx=c.getContext('2d');
  let hover=null;
  c.addEventListener('mousemove',e=>{
    const r=c.getBoundingClientRect(), x=e.clientX-r.left, y=e.clientY-r.top;
    hover={x,y};
  });
  c.addEventListener('mouseleave',()=>hover=null);
  function draw(){
    const dpr=window.devicePixelRatio||1; const rect=c.getBoundingClientRect();
    c.width=rect.width*dpr; c.height=320*dpr; ctx.setTransform(dpr,0,0,dpr,0,0);
    const w=rect.width,h=320; ctx.clearRect(0,0,w,h);
    const cx=w/2, cy=h/2, R=120;
    ctx.beginPath(); ctx.arc(cx,cy,R,0,TAU); ctx.strokeStyle='#000'; ctx.lineWidth=1.5; ctx.stroke();
    const angles=[0,PI/6,PI/4,PI/3,PI/2,2*PI/3,3*PI/4,5*PI/6,PI,7*PI/6,5*PI/4,4*PI/3,3*PI/2,5*PI/3,7*PI/4,11*PI/6,TAU];
    angles.forEach(a=>{
      const x=cx+R*Math.cos(a), y=cy-R*Math.sin(a);
      ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(cx+(R+10)*Math.cos(a), cy-(R+10)*Math.sin(a)); ctx.strokeStyle='#000'; ctx.stroke();
      const deg=Math.round(a*180/Math.PI);
      const rad=(a===0||a===TAU)?'0':(a===PI)?'π':(a===PI/2)?'π/2':(a===3*PI/2)?'3π/2':
        (a===PI/6)?'π/6':(a===PI/4)?'π/4':(a===PI/3)?'π/3':
        (a===2*PI/3)?'2π/3':(a===3*PI/4)?'3π/4':(a===5*PI/6)?'5π/6':
        (a===7*PI/6)?'7π/6':(a===5*PI/4)?'5π/4':(a===4*PI/3)?'4π/3':
        (a===5*PI/3)?'5π/3':(a===7*PI/4)?'7π/4':'';
      label(ctx,`${deg}° ↔ ${rad}`, x+(x>cx?16:-16), y+(y<cy?-10:10), x>cx?'left':'right');
    });
    if(hover){
      const dx=hover.x-cx, dy=hover.y-cy, a=(Math.atan2(-dy,dx)+TAU)%TAU;
      const x=cx+R*Math.cos(a), y=cy-R*Math.sin(a);
      ctx.beginPath(); ctx.moveTo(cx,cy); ctx.lineTo(x,y); ctx.strokeStyle='#000'; ctx.stroke();
      ctx.beginPath(); ctx.arc(x,y,4,0,TAU); ctx.fillStyle='#000'; ctx.fill();
      const deg=(a*180/Math.PI).toFixed(1), rad=a.toFixed(3);
      ctx.fillStyle='#000'; ctx.fillText(`${deg}°  |  ${rad} rad`, cx, cy+R+20);
    }
    requestAnimationFrame(draw);
  }
  draw();
})();

/* ---------- Special Angle Table ---------- */
(function(){
  const tbl=document.getElementById('angTbl');
  const rows=[
    [0,'0','0','1','0'],
    [30,'π/6','1/2','√3/2','1/√3'],
    [45,'π/4','√2/2','√2/2','1'],
    [60,'π/3','√3/2','1/2','√3'],
    [90,'π/2','1','0','—'],
    [120,'2π/3','√3/2','-1/2','-√3'],
    [135,'3π/4','√2/2','-√2/2','-1'],
    [150,'5π/6','1/2','-√3/2','-1/√3'],
    [180,'π','0','-1','0'],
    [210,'7π/6','-1/2','-√3/2','1/√3'],
    [225,'5π/4','-√2/2','-√2/2','1'],
    [240,'4π/3','-√3/2','-1/2','√3'],
    [270,'3π/2','-1','0','—'],
    [300,'5π/3','-√3/2','1/2','-√3'],
    [315,'7π/4','-√2/2','√2/2','-1'],
    [330,'11π/6','-1/2','√3/2','-1/√3'],
    [360,'2π','0','1','0'],
  ];
  tbl.innerHTML=`<tr><th>Degrees</th><th>Radians</th><th>sin</th><th>cos</th><th>tan</th></tr>`+
    rows.map(r=>`<tr><td>${r[0]}°</td><td>${r[1]}</td><td>${r[2]}</td><td>${r[3]}</td><td>${r[4]}</td></tr>`).join('');
})();
</script>
</body>
</html>