// hole-designer.jsx
// Exports window.HoleDesigner
// Requires React 18 + Babel standalone

(function () {
  const { useRef, useState, useEffect, useCallback } = React;

  // ── Canvas dimensions ─────────────────────────────────────────
  const W = 280, H = 400;

  // ── Engraved club-print palette ──────────────────────────────
  // Cool ivory paper, deep restrained greens, gold-leaf accents.
  const COLOR = {
    paper:     '#F2EAD3',   // cool ivory
    paperEdge: 'rgba(56,40,20,0.16)',
    speck:     'rgba(56,40,20,0.07)',
    ink:       '#1F1810',
    inkMid:    '#4A3820',
    inkSoft:   'rgba(56,40,20,0.40)',
    rough:     '#5C7449',
    fairway:   '#7A9659',
    fairwayDk: '#5E7B43',
    fairwayLt: '#94B071',
    green:     '#8AAA61',
    greenDk:   '#4D6A38',
    tee:       '#5E7B43',
    teeDk:     '#3D5429',
    water:     '#8FB1C8',
    waterDk:   '#4A7494',
    bunker:    '#EFE0B0',
    bunkerOut: '#9A7B46',
    trees:     '#3C5532',
    treesDk:   '#22341E',
    flag:      '#9F3838',
    flagDk:    '#641E1E',
    gold:      '#B89548',
    goldLt:    '#D4B265',
    ob:        '#9F3838',
    shadow:    'rgba(31,24,16,0.20)',
  };

  // Deterministic pseudo-RNG so noise/stippling stays put between redraws.
  function mulberry32(seed) {
    return function () {
      let t = seed += 0x6D2B79F5;
      t = Math.imul(t ^ (t >>> 15), t | 1);
      t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
      return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
    };
  }

  // ── Hole path keypoints (normalised 0–1) ──────────────────────
  const PATHS = {
    straight:      [[0.5, 0.87], [0.5, 0.15]],
    'dogleg-left': [[0.7, 0.87], [0.7, 0.45], [0.22, 0.15]],
    'dogleg-right':[[0.3, 0.87], [0.3, 0.45], [0.78, 0.15]],
  };

  function toCanvas(pt) { return [pt[0] * W, pt[1] * H]; }

  // ── Background: ivory paper, restrained texture, gold cornices ──
  // Far fewer specks so the paper feels considered, not aged-distressed.
  const PAPER_STIPPLES = (() => {
    const rng = mulberry32(1337);
    const pts = [];
    for (let i = 0; i < 90; i++) {
      pts.push({
        x: rng() * W,
        y: rng() * H,
        r: 0.15 + rng() * 0.4,
        a: 0.03 + rng() * 0.06,
      });
    }
    return pts;
  })();

  // Reusable filigree corner ornament — drawn at (cx,cy) rotated by angle (radians).
  // A small curl + diamond, evoking certificate engraving.
  function drawCornerOrnament(ctx, cx, cy, angle) {
    ctx.save();
    ctx.translate(cx, cy);
    ctx.rotate(angle);
    ctx.strokeStyle = COLOR.gold;
    ctx.fillStyle   = COLOR.gold;
    ctx.lineWidth   = 0.8;
    ctx.lineCap     = 'round';
    // Curling vine
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.quadraticCurveTo(8, 0, 10, 4);
    ctx.quadraticCurveTo(11, 7, 8, 8);
    ctx.quadraticCurveTo(6, 8, 7, 5);
    ctx.stroke();
    // Mirror curl on other axis
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.quadraticCurveTo(0, 8, 4, 10);
    ctx.quadraticCurveTo(7, 11, 8, 8);
    ctx.quadraticCurveTo(8, 6, 5, 7);
    ctx.stroke();
    // Diamond accent at the inner corner
    ctx.beginPath();
    ctx.moveTo(2, 2);
    ctx.lineTo(4, 1);
    ctx.lineTo(5, 3);
    ctx.lineTo(3, 4);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

  function drawPaper(ctx) {
    // Base ivory
    ctx.fillStyle = COLOR.paper;
    ctx.fillRect(0, 0, W, H);

    // Faint corner vignette (just at the four corners, not full edge)
    const vg = ctx.createRadialGradient(W/2, H/2, H*0.4, W/2, H/2, H*0.72);
    vg.addColorStop(0, 'rgba(56,40,20,0)');
    vg.addColorStop(1, 'rgba(56,40,20,0.16)');
    ctx.fillStyle = vg;
    ctx.fillRect(0, 0, W, H);

    // Sparse, considered paper specks
    PAPER_STIPPLES.forEach(s => {
      ctx.fillStyle = `rgba(56,40,20,${s.a})`;
      ctx.beginPath();
      ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
      ctx.fill();
    });

    // Outer hairline border — gold, fine
    ctx.strokeStyle = COLOR.gold;
    ctx.lineWidth = 0.7;
    ctx.strokeRect(6.5, 6.5, W - 13, H - 13);
    // Inner sepia hairline (the classic two-line plate border)
    ctx.strokeStyle = 'rgba(56,40,20,0.55)';
    ctx.lineWidth = 0.45;
    ctx.strokeRect(9.5, 9.5, W - 19, H - 19);

    // Gold filigree corner ornaments — one per corner, oriented inward
    const INSET = 9;
    drawCornerOrnament(ctx, INSET,        INSET,        0);                 // top-left
    drawCornerOrnament(ctx, W - INSET,    INSET,        Math.PI / 2);       // top-right
    drawCornerOrnament(ctx, W - INSET,    H - INSET,    Math.PI);           // bottom-right
    drawCornerOrnament(ctx, INSET,        H - INSET,    -Math.PI / 2);      // bottom-left
  }

  // ── Rough: deep green plate filling the play area ────────────
  // Sits inside the parchment margin (so gold border + corner ornaments stay
  // visible on the cream paper). Seeded stipple gives it a touch of texture
  // without going noisy.
  const ROUGH_STIPPLES = (() => {
    const rng = mulberry32(7919);
    const pts = [];
    for (let i = 0; i < 140; i++) {
      pts.push({
        x: rng(),
        y: rng(),
        r: 0.2 + rng() * 0.5,
        a: 0.10 + rng() * 0.10,
        dark: rng() > 0.5,
      });
    }
    return pts;
  })();

  function drawRough(ctx) {
    const inset = 12;
    const x = inset, y = inset, w = W - 2 * inset, h = H - 2 * inset;
    ctx.save();

    // Rough fill
    ctx.fillStyle = COLOR.rough;
    ctx.fillRect(x, y, w, h);

    // Soft vertical light fall-off (top a touch brighter, bottom a touch darker)
    const lg = ctx.createLinearGradient(0, y, 0, y + h);
    lg.addColorStop(0, 'rgba(255,255,255,0.06)');
    lg.addColorStop(0.5, 'rgba(0,0,0,0)');
    lg.addColorStop(1, 'rgba(0,0,0,0.16)');
    ctx.fillStyle = lg;
    ctx.fillRect(x, y, w, h);

    // Corner vignette
    const vg = ctx.createRadialGradient(W/2, H/2, h * 0.35, W/2, H/2, h * 0.7);
    vg.addColorStop(0, 'rgba(0,0,0,0)');
    vg.addColorStop(1, 'rgba(31,24,16,0.18)');
    ctx.fillStyle = vg;
    ctx.fillRect(x, y, w, h);

    // Light stipple for grass texture (clipped to rough rect)
    ctx.save();
    ctx.beginPath();
    ctx.rect(x, y, w, h);
    ctx.clip();
    ROUGH_STIPPLES.forEach(s => {
      ctx.fillStyle = s.dark
        ? `rgba(31,40,24,${s.a})`
        : `rgba(168,194,140,${s.a * 0.7})`;
      ctx.beginPath();
      ctx.arc(x + s.x * w, y + s.y * h, s.r, 0, Math.PI * 2);
      ctx.fill();
    });
    ctx.restore();

    // Hairline ink border between rough and parchment margin
    ctx.strokeStyle = 'rgba(31,24,16,0.55)';
    ctx.lineWidth = 0.7;
    ctx.strokeRect(x + 0.5, y + 0.5, w - 1, h - 1);

    ctx.restore();
  }

  // ── Compass rose: engraved 8-point star with serif "N" ───────
  function drawCompassRose(ctx) {
    const cx = W - 26, cy = 30, r = 11;
    ctx.save();
    // Subtle ivory disc behind
    ctx.fillStyle = 'rgba(242,234,211,0.9)';
    ctx.beginPath();
    ctx.arc(cx, cy, r + 1, 0, Math.PI * 2);
    ctx.fill();
    // Two thin rings
    ctx.strokeStyle = COLOR.gold;
    ctx.lineWidth = 0.6;
    ctx.beginPath();
    ctx.arc(cx, cy, r, 0, Math.PI * 2);
    ctx.stroke();
    ctx.strokeStyle = 'rgba(56,40,20,0.55)';
    ctx.lineWidth = 0.4;
    ctx.beginPath();
    ctx.arc(cx, cy, r - 2.5, 0, Math.PI * 2);
    ctx.stroke();

    // 8-point star: 4 long cardinal + 4 short diagonal blades, each shaded half/half
    const long  = r - 1.5;
    const short = r * 0.45;
    for (let i = 0; i < 8; i++) {
      const a = i * Math.PI / 4;
      const tip = i % 2 === 0 ? long : short;
      const sx = Math.cos(a) * tip + cx;
      const sy = Math.sin(a) * tip + cy;
      const lx = Math.cos(a - Math.PI / 8) * (short * 0.55) + cx;
      const ly = Math.sin(a - Math.PI / 8) * (short * 0.55) + cy;
      const rx = Math.cos(a + Math.PI / 8) * (short * 0.55) + cx;
      const ry = Math.sin(a + Math.PI / 8) * (short * 0.55) + cy;
      // Light side
      ctx.fillStyle = COLOR.goldLt;
      ctx.beginPath();
      ctx.moveTo(cx, cy);
      ctx.lineTo(lx, ly);
      ctx.lineTo(sx, sy);
      ctx.closePath();
      ctx.fill();
      // Dark side
      ctx.fillStyle = COLOR.inkMid;
      ctx.beginPath();
      ctx.moveTo(cx, cy);
      ctx.lineTo(sx, sy);
      ctx.lineTo(rx, ry);
      ctx.closePath();
      ctx.fill();
    }
    // Center dot
    ctx.fillStyle = COLOR.ink;
    ctx.beginPath();
    ctx.arc(cx, cy, 1, 0, Math.PI * 2);
    ctx.fill();
    // Tiny serif "N" floating above the star
    ctx.fillStyle = COLOR.ink;
    ctx.font = 'italic bold 7.5px Georgia, serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('N', cx, cy - r - 3.5);
    ctx.restore();
  }

  // ── Fairway: layered greens with hairline ink outline ──────────
  // Wide-to-narrow stroke layering builds an embossed look:
  // sepia outline → rough → fairway → soft highlight centerline.
  function drawFairway(ctx, holeType) {
    const pts = PATHS[holeType].map(toCanvas);

    ctx.save();
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';

    // Soft drop shadow on the outline pass only
    ctx.shadowColor = 'rgba(31,24,16,0.22)';
    ctx.shadowBlur = 6;
    ctx.shadowOffsetY = 2;

    // Hairline ink edge (a thin sepia stroke shows where rough meets fairway)
    ctx.strokeStyle = COLOR.ink;
    ctx.lineWidth = W * 0.36;
    drawSmoothPath(ctx, pts);
    ctx.stroke();

    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetY = 0;

    // Rough margin
    ctx.strokeStyle = COLOR.rough;
    ctx.lineWidth = W * 0.345;
    drawSmoothPath(ctx, pts);
    ctx.stroke();

    // Fairway body (deeper, more confident green)
    ctx.strokeStyle = COLOR.fairway;
    ctx.lineWidth = W * 0.28;
    drawSmoothPath(ctx, pts);
    ctx.stroke();

    // Subtle highlight strip — gives the embossed/mowed feel
    ctx.strokeStyle = 'rgba(212,232,180,0.30)';
    ctx.lineWidth = W * 0.11;
    drawSmoothPath(ctx, pts);
    ctx.stroke();

    ctx.restore();
  }

  function drawSmoothPath(ctx, pts) {
    ctx.beginPath();
    ctx.moveTo(pts[0][0], pts[0][1]);
    if (pts.length === 2) {
      ctx.lineTo(pts[1][0], pts[1][1]);
    } else {
      for (let i = 1; i < pts.length - 1; i++) {
        const mx = (pts[i][0] + pts[i+1][0]) / 2;
        const my = (pts[i][1] + pts[i+1][1]) / 2;
        ctx.quadraticCurveTo(pts[i][0], pts[i][1], mx, my);
      }
      ctx.lineTo(pts[pts.length-1][0], pts[pts.length-1][1]);
    }
  }

  function drawGreenAndTee(ctx, holeType) {
    const pts = PATHS[holeType].map(toCanvas);
    const tee   = pts[0];
    const green = pts[pts.length - 1];

    // ── Green: layered emboss with restrained stipple ──
    ctx.save();
    ctx.shadowColor = 'rgba(31,24,16,0.30)';
    ctx.shadowBlur = 7;
    ctx.shadowOffsetY = 2.5;
    // Ink ring (becomes a 1px outline)
    ctx.fillStyle = COLOR.ink;
    ctx.beginPath();
    ctx.ellipse(green[0], green[1], 44, 34, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetY = 0;
    // Dark green ring (recessed-edge feel)
    ctx.fillStyle = COLOR.greenDk;
    ctx.beginPath();
    ctx.ellipse(green[0], green[1], 43, 33, 0, 0, Math.PI * 2);
    ctx.fill();
    // Main green
    ctx.fillStyle = COLOR.green;
    ctx.beginPath();
    ctx.ellipse(green[0], green[1], 40, 30, 0, 0, Math.PI * 2);
    ctx.fill();
    // Subtle radial highlight (top-left)
    const gloss = ctx.createRadialGradient(green[0] - 12, green[1] - 8, 2, green[0], green[1], 36);
    gloss.addColorStop(0, 'rgba(220,238,180,0.45)');
    gloss.addColorStop(1, 'rgba(220,238,180,0)');
    ctx.fillStyle = gloss;
    ctx.beginPath();
    ctx.ellipse(green[0], green[1], 40, 30, 0, 0, Math.PI * 2);
    ctx.fill();
    // Restrained stipple — fewer dots, smaller, only in lower-right shadow
    const grng = mulberry32(Math.floor(green[0]) * 73 + Math.floor(green[1]) * 5);
    ctx.save();
    ctx.beginPath();
    ctx.ellipse(green[0], green[1], 39, 29, 0, 0, Math.PI * 2);
    ctx.clip();
    for (let i = 0; i < 22; i++) {
      // Bias toward the lower-right (shadow side)
      const a = (grng() * 0.7 + 0.05) * Math.PI;  // 0..0.75π (right + down)
      const r = Math.sqrt(grng()) * 36;
      ctx.fillStyle = `rgba(34,52,30,${0.16 + grng() * 0.14})`;
      ctx.beginPath();
      ctx.arc(green[0] + Math.cos(a) * r * 0.95,
              green[1] + Math.sin(a) * r * 0.7,
              0.4 + grng() * 0.4, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.restore();
    // Hole cup — small, dark, refined
    ctx.fillStyle = COLOR.ink;
    ctx.beginPath();
    ctx.arc(green[0], green[1], 2.4, 0, Math.PI * 2);
    ctx.fill();
    // Tiny gold rim around the cup
    ctx.strokeStyle = COLOR.gold;
    ctx.lineWidth = 0.4;
    ctx.beginPath();
    ctx.arc(green[0], green[1], 2.8, 0, Math.PI * 2);
    ctx.stroke();
    ctx.restore();

    // ── Flag: clean two-tone red with gold ball finial ──
    const fy = green[1] - 38;
    ctx.save();
    // Pole drop shadow
    ctx.strokeStyle = 'rgba(31,24,16,0.35)';
    ctx.lineWidth = 1.4;
    ctx.beginPath();
    ctx.moveTo(green[0] + 0.7, green[1] - 1);
    ctx.lineTo(green[0] + 0.7, fy);
    ctx.stroke();
    // Pole proper
    ctx.strokeStyle = COLOR.ink;
    ctx.lineWidth = 1.2;
    ctx.beginPath();
    ctx.moveTo(green[0], green[1] - 1);
    ctx.lineTo(green[0], fy);
    ctx.stroke();
    // Gold ball finial atop the pole
    ctx.fillStyle = COLOR.goldLt;
    ctx.beginPath();
    ctx.arc(green[0], fy, 2.1, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = COLOR.gold;
    ctx.beginPath();
    ctx.arc(green[0] + 0.4, fy + 0.4, 1.4, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = COLOR.ink;
    ctx.lineWidth = 0.5;
    ctx.beginPath();
    ctx.arc(green[0], fy, 2.1, 0, Math.PI * 2);
    ctx.stroke();
    // Flag triangle — solid red with darker shadow band on bottom
    ctx.fillStyle = COLOR.flag;
    ctx.beginPath();
    ctx.moveTo(green[0] + 1, fy + 1.5);
    ctx.lineTo(green[0] + 17, fy + 6.5);
    ctx.lineTo(green[0] + 1, fy + 12);
    ctx.closePath();
    ctx.fill();
    // Shadow gradient (bottom-right of triangle)
    const fg = ctx.createLinearGradient(green[0], fy + 4, green[0], fy + 13);
    fg.addColorStop(0, 'rgba(0,0,0,0)');
    fg.addColorStop(1, 'rgba(31,24,16,0.30)');
    ctx.fillStyle = fg;
    ctx.beginPath();
    ctx.moveTo(green[0] + 1, fy + 1.5);
    ctx.lineTo(green[0] + 17, fy + 6.5);
    ctx.lineTo(green[0] + 1, fy + 12);
    ctx.closePath();
    ctx.fill();
    // Hairline outline
    ctx.strokeStyle = COLOR.flagDk;
    ctx.lineWidth = 0.7;
    ctx.beginPath();
    ctx.moveTo(green[0] + 1, fy + 1.5);
    ctx.lineTo(green[0] + 17, fy + 6.5);
    ctx.lineTo(green[0] + 1, fy + 12);
    ctx.closePath();
    ctx.stroke();
    ctx.restore();

  }

  // ── Tee box: drawn last so shot lines never cover it ──────────
  function drawTeeBox(ctx, holeType) {
    const pts = PATHS[holeType].map(toCanvas);
    const tee = pts[0];
    const TW = 42, TH = 14;
    const tx0 = tee[0] - TW / 2, ty0 = tee[1] - TH / 2;
    ctx.save();
    // Ground shadow under the platform
    ctx.fillStyle = 'rgba(31,24,16,0.32)';
    ctx.beginPath();
    ctx.ellipse(tee[0], tee[1] + TH / 2 + 2, TW / 2 + 1, 2.4, 0, 0, Math.PI * 2);
    ctx.fill();

    // Dark base layer (stepped pedestal)
    ctx.fillStyle = COLOR.teeDk;
    ctx.beginPath();
    ctx.roundRect(tx0 - 0.5, ty0 + 1.5, TW + 1, TH, 3);
    ctx.fill();

    // Top plate with vertical gradient
    ctx.shadowColor = 'rgba(31,24,16,0.30)';
    ctx.shadowBlur = 4;
    ctx.shadowOffsetY = 1.5;
    const teeG = ctx.createLinearGradient(tee[0], ty0, tee[0], ty0 + TH);
    teeG.addColorStop(0, '#7A9656');
    teeG.addColorStop(1, COLOR.teeDk);
    ctx.fillStyle = teeG;
    ctx.beginPath();
    ctx.roundRect(tx0, ty0, TW, TH, 2.5);
    ctx.fill();
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetY = 0;

    // Outlines — dark ink + inner gold
    ctx.strokeStyle = COLOR.ink;
    ctx.lineWidth = 0.9;
    ctx.stroke();
    ctx.strokeStyle = COLOR.goldLt;
    ctx.lineWidth = 0.7;
    ctx.beginPath();
    ctx.roundRect(tx0 + 1, ty0 + 1, TW - 2, TH - 2, 2);
    ctx.stroke();

    // Top highlight strip
    ctx.strokeStyle = 'rgba(255,250,220,0.45)';
    ctx.lineWidth = 0.6;
    ctx.beginPath();
    ctx.moveTo(tx0 + 3, ty0 + 1.6);
    ctx.lineTo(tx0 + TW - 3, ty0 + 1.6);
    ctx.stroke();

    // Two white tee-pegs poking out of the top
    [-10, 10].forEach(dx => {
      ctx.fillStyle = 'rgba(31,24,16,0.35)';
      ctx.beginPath();
      ctx.ellipse(tee[0] + dx + 0.5, ty0 + 0.5, 1.6, 1, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.fillStyle = COLOR.paper;
      ctx.beginPath();
      ctx.arc(tee[0] + dx, ty0 - 0.5, 1.5, 0, Math.PI * 2);
      ctx.fill();
      ctx.strokeStyle = COLOR.inkMid;
      ctx.lineWidth = 0.5;
      ctx.stroke();
    });

    // Engraved "TEE" — bright white with crisp drop shadow
    ctx.font = 'italic bold 9px Georgia, serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    // Shadow first
    ctx.fillStyle = 'rgba(31,24,16,0.75)';
    ctx.fillText('TEE', tee[0], tee[1] + 1.4);
    // Bright white on top
    ctx.fillStyle = '#FFFFFF';
    ctx.fillText('TEE', tee[0], tee[1] + 0.5);
    ctx.restore();
  }

  // ── Draw hazard ───────────────────────────────────────────────
  function drawHazard(ctx, h, isDragging) {
    ctx.save();
    if (isDragging) { ctx.shadowColor = COLOR.gold; ctx.shadowBlur = 12; }

    const w = h.w || (h.type === 'water' ? 28 : h.type === 'bunker' ? 22 : 14);
    const hh = h.h || (h.type === 'water' ? 18 : h.type === 'bunker' ? 14 : 14);

    // Seeded RNG so per-hazard texture stays stable across redraws.
    const seed = Math.floor(h.x) * 1009 + Math.floor(h.y) * 31 + (h.type === 'water' ? 1 : h.type === 'bunker' ? 2 : 3);
    const rng = mulberry32(seed);

    if (h.type === 'water') {
      // Soft drop shadow
      ctx.shadowColor = 'rgba(45,30,15,0.30)';
      ctx.shadowBlur = 5;
      ctx.shadowOffsetY = 2;
      // Water body
      ctx.fillStyle = COLOR.water;
      ctx.beginPath();
      ctx.ellipse(h.x, h.y, w, hh, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.shadowColor = 'transparent';
      ctx.shadowBlur = 0;
      ctx.shadowOffsetY = 0;
      // Ink outline
      ctx.strokeStyle = COLOR.waterDk;
      ctx.lineWidth = 1.1;
      ctx.stroke();
      // Wavy hatch lines (classic cartography water)
      ctx.save();
      ctx.beginPath();
      ctx.ellipse(h.x, h.y, w - 1, hh - 1, 0, 0, Math.PI * 2);
      ctx.clip();
      ctx.strokeStyle = 'rgba(45,90,130,0.55)';
      ctx.lineWidth = 0.6;
      const lines = Math.max(3, Math.round(hh / 4));
      for (let i = 0; i < lines; i++) {
        const y = h.y - hh + (i + 0.5) * (hh * 2 / lines);
        ctx.beginPath();
        let started = false;
        for (let x = h.x - w; x <= h.x + w; x += 2) {
          const yy = y + Math.sin((x - h.x) * 0.35 + i) * 0.9;
          if (!started) { ctx.moveTo(x, yy); started = true; }
          else ctx.lineTo(x, yy);
        }
        ctx.stroke();
      }
      ctx.restore();

    } else if (h.type === 'bunker') {
      // Drop shadow
      ctx.shadowColor = 'rgba(45,30,15,0.28)';
      ctx.shadowBlur = 4;
      ctx.shadowOffsetY = 2;
      // Sand body — irregular ellipse (subtle tilt for hand-drawn feel)
      ctx.fillStyle = COLOR.bunker;
      ctx.beginPath();
      ctx.ellipse(h.x, h.y, w, hh, 0.2, 0, Math.PI * 2);
      ctx.fill();
      ctx.shadowColor = 'transparent';
      ctx.shadowBlur = 0;
      ctx.shadowOffsetY = 0;
      // Ink outline
      ctx.strokeStyle = COLOR.bunkerOut;
      ctx.lineWidth = 1.1;
      ctx.stroke();
      // Stippled sand texture (clipped to bunker shape)
      ctx.save();
      ctx.beginPath();
      ctx.ellipse(h.x, h.y, w - 1, hh - 1, 0.2, 0, Math.PI * 2);
      ctx.clip();
      const dots = Math.max(8, Math.round(w * hh / 20));
      for (let i = 0; i < dots; i++) {
        const a = rng() * Math.PI * 2;
        const r = Math.sqrt(rng()) * 0.95;
        const dx = Math.cos(a) * r * w;
        const dy = Math.sin(a) * r * hh;
        ctx.fillStyle = `rgba(91,69,40,${0.30 + rng() * 0.25})`;
        ctx.beginPath();
        ctx.arc(h.x + dx, h.y + dy, 0.4 + rng() * 0.45, 0, Math.PI * 2);
        ctx.fill();
      }
      ctx.restore();

    } else if (h.type === 'trees') {
      // Cluster of puffy "cloud" trees — drawn as a group, not individuals.
      const positions = [[-10,-7],[8,-5],[-3,8],[10,7],[0,-1]];
      // Shadow blob underneath the whole cluster
      ctx.save();
      ctx.fillStyle = 'rgba(45,30,15,0.25)';
      ctx.beginPath();
      ctx.ellipse(h.x + 2, h.y + 12, 16, 4, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
      positions.forEach(([dx, dy]) => {
        const tx = h.x + dx, ty = h.y + dy;
        // Dark base
        ctx.fillStyle = COLOR.treesDk;
        ctx.beginPath();
        ctx.arc(tx, ty + 1, 8.5, 0, Math.PI * 2);
        ctx.fill();
        // Main foliage
        ctx.fillStyle = COLOR.trees;
        ctx.beginPath();
        ctx.arc(tx, ty, 7.5, 0, Math.PI * 2);
        ctx.fill();
        // Ink outline
        ctx.strokeStyle = 'rgba(45,30,15,0.5)';
        ctx.lineWidth = 0.7;
        ctx.stroke();
        // Highlight (top-left)
        ctx.fillStyle = 'rgba(180,210,150,0.45)';
        ctx.beginPath();
        ctx.arc(tx - 2.5, ty - 2.5, 3, 0, Math.PI * 2);
        ctx.fill();
      });
      // Stipple dots scattered over the cluster
      ctx.save();
      ctx.fillStyle = 'rgba(45,30,15,0.45)';
      for (let i = 0; i < 16; i++) {
        const a = rng() * Math.PI * 2;
        const r = rng() * 14;
        ctx.beginPath();
        ctx.arc(h.x + Math.cos(a) * r, h.y + Math.sin(a) * r * 0.7, 0.4 + rng() * 0.3, 0, Math.PI * 2);
        ctx.fill();
      }
      ctx.restore();
    }

    // Drag handle ring + resize handle (when selected)
    if (isDragging) {
      ctx.globalAlpha = 1;
      ctx.strokeStyle = COLOR.gold;
      ctx.lineWidth = 2;
      ctx.setLineDash([3, 3]);
      ctx.beginPath();
      ctx.arc(h.x, h.y, Math.max(w, hh) + 6, 0, Math.PI * 2);
      ctx.stroke();
      ctx.setLineDash([]);
      // Resize handle (bottom-right) — only for water/bunker
      if (h.type !== 'trees') {
        const hx = h.x + w + 4;
        const hy = h.y + hh + 4;
        ctx.fillStyle = COLOR.gold;
        ctx.strokeStyle = '#1a0e00';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.arc(hx, hy, 5, 0, Math.PI * 2);
        ctx.fill();
        ctx.stroke();
        // arrow lines
        ctx.strokeStyle = '#1a0e00';
        ctx.lineWidth = 1.2;
        ctx.beginPath();
        ctx.moveTo(hx - 2, hy - 2); ctx.lineTo(hx + 2, hy + 2);
        ctx.moveTo(hx + 2, hy - 1); ctx.lineTo(hx + 2, hy + 2); ctx.lineTo(hx - 1, hy + 2);
        ctx.stroke();
      }
    }
    ctx.restore();
  }

  // ── Shot arc: hand-drawn ink dashes on parchment ──────────────
  function drawShotArc(ctx, from, to, isOB) {
    const dx = to.x - from.x;
    const dy = to.y - from.y;
    const len = Math.sqrt(dx * dx + dy * dy);
    if (len < 4) return;

    const mx = (from.x + to.x) / 2;
    const my = (from.y + to.y) / 2;
    const nx = -dy / len;
    const ny =  dx / len;
    const arc = Math.min(len * 0.38, 48);
    const cpx = mx + nx * arc;
    const cpy = my + ny * arc;

    ctx.save();
    const lineColor = isOB ? COLOR.ob : COLOR.ink;
    ctx.lineCap = 'round';

    // 1. Outermost sepia drop-shadow (offset slightly)
    ctx.strokeStyle = 'rgba(31,24,16,0.22)';
    ctx.lineWidth = 4.4;
    ctx.beginPath();
    ctx.moveTo(from.x + 0.5, from.y + 1.2);
    ctx.quadraticCurveTo(cpx + 0.5, cpy + 1.2, to.x + 0.5, to.y + 1.2);
    ctx.stroke();

    // 2. White halo — bright, solid, no dashes
    ctx.strokeStyle = 'rgba(255,253,245,0.98)';
    ctx.lineWidth = 3.6;
    ctx.beginPath();
    ctx.moveTo(from.x, from.y);
    ctx.quadraticCurveTo(cpx, cpy, to.x, to.y);
    ctx.stroke();

    // 3. Ink dashes on top
    ctx.strokeStyle = isOB ? 'rgba(159,56,56,0.95)' : 'rgba(31,24,16,0.95)';
    ctx.lineWidth = 1.6;
    ctx.setLineDash([4.5, 3]);
    ctx.beginPath();
    ctx.moveTo(from.x, from.y);
    ctx.quadraticCurveTo(cpx, cpy, to.x, to.y);
    ctx.stroke();
    ctx.setLineDash([]);

    // Arrowhead at landing point
    const t = 0.95;
    const ax = (1-t)*(1-t)*from.x + 2*(1-t)*t*cpx + t*t*to.x;
    const ay = (1-t)*(1-t)*from.y + 2*(1-t)*t*cpy + t*t*to.y;
    const adx = to.x - ax;
    const ady = to.y - ay;
    const al = Math.sqrt(adx*adx + ady*ady);
    const ux = adx/al, uy = ady/al;
    const arrowSize = 6.5;
    ctx.fillStyle = lineColor;
    ctx.beginPath();
    ctx.moveTo(to.x, to.y);
    ctx.lineTo(to.x - ux*arrowSize + uy*arrowSize*0.5,
               to.y - uy*arrowSize - ux*arrowSize*0.5);
    ctx.lineTo(to.x - ux*arrowSize - uy*arrowSize*0.5,
               to.y - uy*arrowSize + ux*arrowSize*0.5);
    ctx.closePath();
    ctx.fill();

    ctx.restore();
  }

  // ── Shot marker: cream wax-stamp circle with sepia number ─────
  function drawShotMarker(ctx, shot, num) {
    const r = 10;
    ctx.save();
    // Soft drop shadow
    ctx.shadowColor = 'rgba(45,30,15,0.30)';
    ctx.shadowBlur = 4;
    ctx.shadowOffsetY = 1.5;
    // Body — gold-foil disc on regular shots, muted red on OB
    ctx.fillStyle = shot.ob ? COLOR.ob : COLOR.gold;
    ctx.beginPath();
    ctx.arc(shot.x, shot.y, r, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetY = 0;
    // Sepia ink ring (gives it a "stamp" feel)
    ctx.strokeStyle = COLOR.ink;
    ctx.lineWidth = 1.1;
    ctx.beginPath();
    ctx.arc(shot.x, shot.y, r, 0, Math.PI * 2);
    ctx.stroke();
    // Inner highlight ring
    ctx.strokeStyle = shot.ob ? 'rgba(255,200,200,0.55)' : 'rgba(255,232,160,0.7)';
    ctx.lineWidth = 0.7;
    ctx.beginPath();
    ctx.arc(shot.x, shot.y, r - 2.2, 0, Math.PI * 2);
    ctx.stroke();
    // Number — serif italic for the editorial feel
    ctx.fillStyle = shot.ob ? '#FFEDED' : COLOR.ink;
    ctx.font = 'italic bold 10px Georgia, serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(String(num), shot.x, shot.y + 0.5);
    ctx.restore();
  }

  // ── Tee position from hole type ───────────────────────────────
  function getTeePos(holeType) {
    const pts = PATHS[holeType].map(toCanvas);
    return { x: pts[0][0], y: pts[0][1] };
  }

  // ── Palette hazard items ──────────────────────────────────────
  const HAZARD_PALETTE = [
    { id: 'water',  label: 'Water',  color: '#1e5a9a', icon: '💧' },
    { id: 'bunker', label: 'Bunker', color: '#f0d8a8', icon: '⛱' },
    { id: 'trees',  label: 'Trees',  color: '#1e3d1e', icon: '🌲' },
  ];

  // ── Drop-zone marker: sepia X with dashed ring + serif label ──
  function drawDropMarker(ctx, pos, num) {
    ctx.save();
    // Dashed ring
    ctx.strokeStyle = 'rgba(179,61,61,0.85)';
    ctx.lineWidth = 1.3;
    ctx.setLineDash([3, 3]);
    ctx.beginPath();
    ctx.arc(pos.x, pos.y, 9, 0, Math.PI * 2);
    ctx.stroke();
    ctx.setLineDash([]);
    // X mark inside
    ctx.strokeStyle = 'rgba(179,61,61,0.95)';
    ctx.lineWidth = 1.4;
    const d = 4;
    ctx.beginPath();
    ctx.moveTo(pos.x - d, pos.y - d); ctx.lineTo(pos.x + d, pos.y + d);
    ctx.moveTo(pos.x + d, pos.y - d); ctx.lineTo(pos.x - d, pos.y + d);
    ctx.stroke();
    // Serif label below
    ctx.fillStyle = COLOR.ob;
    ctx.font = 'italic bold 7.5px Georgia, serif';
    ctx.textAlign = 'center';
    ctx.fillText(`drop ${num}`, pos.x, pos.y + 19);
    ctx.restore();
  }

  // ── HoleDesigner component ────────────────────────────────────
  function HoleDesigner({ onExport }) {
    const canvasRef  = useRef(null);
    const [holeType, setHoleType] = useState('straight');
    const [obMode,   setObMode]   = useState(false);
    const [obPendingDrop, setObPendingDrop] = useState(false); // waiting for 2nd OB click
    const [shots,    setShots]    = useState([]);
    const [hazards,  setHazards]  = useState([]);
    const [activeTool, setActiveTool] = useState('shot');
    const [selectedHazard, setSelectedHazard] = useState(-1);
    const dragging = useRef(null); // {type:'move'|'resize', index}
    const pointerDown = useRef(false);
    const lastPlace = useRef(0); // timestamp of last shot/hazard placement (mobile double-tap guard)

    // Redraw
    const redraw = useCallback(() => {
      const canvas = canvasRef.current;
      if (!canvas) return;
      const ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, W, H);

      drawPaper(ctx);
      drawRough(ctx);
      drawFairway(ctx, holeType);
      drawGreenAndTee(ctx, holeType);
      drawCompassRose(ctx);

      // Hazards
      hazards.forEach((h, i) => drawHazard(ctx, h, i === selectedHazard));

      // Shot arcs first (under everything else),
      // then the tee box (so shot lines don't cover the TEE label),
      // then shot markers + drop markers on top.
      const tee = getTeePos(holeType);
      shots.forEach((shot, i) => {
        if (shot.isDrop) return;
        const from = i > 0 ? shots[i - 1] : tee;
        drawShotArc(ctx, from, shot, shot.ob);
      });
      drawTeeBox(ctx, holeType);
      shots.forEach((shot, i) => {
        if (shot.isDrop) drawDropMarker(ctx, shot, i + 1);
        else              drawShotMarker(ctx, shot, i + 1);
      });
    }, [holeType, shots, hazards, selectedHazard]);

    useEffect(() => { redraw(); }, [redraw]);

    // ── Pointer helpers ─────────────────────────────────────────
    const getCanvasPos = (e) => {
      const canvas = canvasRef.current;
      const rect = canvas.getBoundingClientRect();
      const scaleX = W / rect.width;
      const scaleY = H / rect.height;
      const cl = e.touches ? e.touches[0] : e;
      return {
        x: (cl.clientX - rect.left) * scaleX,
        y: (cl.clientY - rect.top)  * scaleY,
      };
    };

    const getHazardSize = (h) => {
      const w = h.w || (h.type === 'water' ? 28 : h.type === 'bunker' ? 22 : 14);
      const hh = h.h || (h.type === 'water' ? 18 : h.type === 'bunker' ? 14 : 14);
      return { w, h: hh };
    };

    const findHazardAt = (pos) => {
      for (let i = hazards.length - 1; i >= 0; i--) {
        const h = hazards[i];
        const { w, h: hh } = getHazardSize(h);
        const dx = (h.x - pos.x) / (w + 4);
        const dy = (h.y - pos.y) / (hh + 4);
        if (dx*dx + dy*dy < 1) return i;
      }
      return -1;
    };

    const findResizeHandle = (pos) => {
      if (selectedHazard < 0) return false;
      const h = hazards[selectedHazard];
      if (!h || h.type === 'trees') return false;
      const { w, h: hh } = getHazardSize(h);
      const hx = h.x + w + 4;
      const hy = h.y + hh + 4;
      const dx = pos.x - hx, dy = pos.y - hy;
      return Math.sqrt(dx*dx + dy*dy) < 9;
    };

    const onPointerDown = (e) => {
      e.preventDefault();
      pointerDown.current = true;
      const pos = getCanvasPos(e);

      // Check resize handle on currently-selected hazard first
      if (findResizeHandle(pos)) {
        dragging.current = { type: 'resize', index: selectedHazard };
        return;
      }

      // Check if clicking existing hazard (to select/drag)
      const hi = findHazardAt(pos);
      if (hi >= 0) {
        setSelectedHazard(hi);
        dragging.current = { type: 'move', index: hi };
        return;
      }

      // Click empty space — deselect
      setSelectedHazard(-1);

      // Debounce NEW placements (~450ms ≈ half a second). On phones a tap fires a
      // synthetic mouse event ~300ms later — React's touch listener is passive so
      // the preventDefault above can't stop it — and fingers bounce; both dropped a
      // duplicate shot. Select/drag of existing hazards above is unaffected.
      const now = Date.now();
      if (now - lastPlace.current < 450) return;
      lastPlace.current = now;

      // Shot or place hazard
      if (activeTool === 'shot') {
        if (obMode && !obPendingDrop) {
          // First OB click — mark where ball went OB
          setShots(prev => [...prev, { ...pos, ob: true, isDrop: false }]);
          setObPendingDrop(true);
        } else if (obPendingDrop) {
          // Second OB click — mark re-play/drop position
          setShots(prev => [...prev, { ...pos, ob: false, isDrop: true }]);
          setObPendingDrop(false);
        } else {
          setShots(prev => [...prev, { ...pos, ob: false, isDrop: false }]);
        }
      } else {
        const defaults = activeTool === 'water' ? { w: 28, h: 18 }
                       : activeTool === 'bunker' ? { w: 22, h: 14 }
                       : { w: 14, h: 14 };
        setHazards(prev => [...prev, { type: activeTool, x: pos.x, y: pos.y, ...defaults }]);
        setSelectedHazard(hazards.length); // auto-select new hazard
      }
    };

    const onPointerMove = (e) => {
      e.preventDefault();
      if (!dragging.current) return;
      const pos = getCanvasPos(e);
      if (dragging.current.type === 'move') {
        setHazards(prev => prev.map((h, i) =>
          i === dragging.current.index ? { ...h, x: pos.x, y: pos.y } : h
        ));
      } else if (dragging.current.type === 'resize') {
        setHazards(prev => prev.map((h, i) => {
          if (i !== dragging.current.index) return h;
          const newW = Math.max(8, Math.min(80, pos.x - h.x - 4));
          const newH = Math.max(6, Math.min(60, pos.y - h.y - 4));
          return { ...h, w: newW, h: newH };
        }));
      }
    };

    const onPointerUp = () => {
      pointerDown.current = false;
      dragging.current = null;
    };

    // ── Actions ─────────────────────────────────────────────────
    const undo = () => {
      if (activeTool === 'shot') {
        // If waiting for drop, undo the OB shot and cancel pending
        if (obPendingDrop) { setShots(s => s.slice(0, -1)); setObPendingDrop(false); }
        else setShots(s => s.slice(0, -1));
      } else {
        setHazards(h => h.slice(0, -1));
      }
    };
    const clear = () => { setShots([]); setHazards([]); setObPendingDrop(false); setSelectedHazard(-1); };
    const exportPng = () => {
      // Deselect any hazard first so the gold glow isn't baked into the export,
      // then wait one render+repaint frame before capturing the canvas.
      setSelectedHazard(-1);
      setObPendingDrop(false);
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          const canvas = canvasRef.current;
          if (canvas && onExport) onExport(canvas.toDataURL('image/png'));
        });
      });
    };

    // ── Styles ──────────────────────────────────────────────────
    const S = {
      wrap: { display:'flex', flexDirection:'column', gap:10 },
      row:  { display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' },
      lbl:  { fontSize:10, fontWeight:600, letterSpacing:'1.5px', textTransform:'uppercase',
               color:'rgba(201,168,76,0.6)', fontFamily:'Inter,sans-serif', minWidth:72 },
      seg:  { padding:'5px 9px', fontSize:11, fontWeight:500, borderRadius:4, cursor:'pointer',
               fontFamily:'Inter,sans-serif', transition:'all 0.15s', border:'1px solid rgba(201,168,76,0.2)',
               background:'rgba(201,168,76,0.06)', color:'rgba(232,220,200,0.55)' },
      segOn:{ background:'rgba(201,168,76,0.2)', borderColor:'rgba(201,168,76,0.55)', color:'#c9a84c' },
      canvasWrap: { border:'1.5px solid rgba(201,168,76,0.35)', borderRadius:6,
                     overflow:'hidden', width:'100%', maxWidth:260, alignSelf:'center',
                     cursor: activeTool === 'shot' ? 'crosshair' : 'copy',
                     boxShadow:'0 4px 20px rgba(0,0,0,0.5)' },
      canvas: { display:'block', width:'100%', height:'auto', touchAction:'none' },
      palRow: { display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' },
      palBtn: (id) => ({
        display:'flex', alignItems:'center', gap:6,
        padding:'7px 11px', fontSize:11, fontWeight:600,
        borderRadius:5, cursor:'pointer', fontFamily:'Inter,sans-serif',
        border:'1.5px solid',
        transition:'all 0.15s',
        borderColor: activeTool===id ? 'rgba(201,168,76,0.7)' : 'rgba(201,168,76,0.15)',
        background:  activeTool===id ? 'rgba(201,168,76,0.18)' : 'rgba(255,255,255,0.03)',
        color: activeTool===id ? '#c9a84c' : 'rgba(232,220,200,0.5)',
      }),
      shotBtn: {
        display:'flex', alignItems:'center', gap:5, padding:'7px 12px',
        fontSize:11, fontWeight:600, borderRadius:5, cursor:'pointer',
        fontFamily:'Inter,sans-serif', transition:'all 0.15s',
        borderColor: activeTool==='shot' ? 'rgba(201,168,76,0.7)' : 'rgba(201,168,76,0.15)',
        background:  activeTool==='shot' ? 'rgba(201,168,76,0.18)' : 'rgba(255,255,255,0.03)',
        color: activeTool==='shot' ? '#c9a84c' : 'rgba(232,220,200,0.5)',
        border:'1.5px solid',
      },
      actBtn: { padding:'6px 11px', fontSize:11, fontWeight:500, borderRadius:4,
                 cursor:'pointer', fontFamily:'Inter,sans-serif',
                 background:'rgba(255,255,255,0.04)', border:'1px solid rgba(201,168,76,0.18)',
                 color:'rgba(232,220,200,0.55)', transition:'all 0.15s' },
      obBtn: (on) => ({ padding:'6px 11px', fontSize:11, fontWeight:600, borderRadius:4,
                         cursor:'pointer', fontFamily:'Inter,sans-serif',
                         background: obPendingDrop ? 'rgba(204,51,51,0.3)' : on ? 'rgba(204,51,51,0.18)' : 'rgba(255,255,255,0.04)',
                         border:'1px solid', borderColor: on ? '#cc3333' : 'rgba(201,168,76,0.18)',
                         color: on ? '#ff8888' : 'rgba(232,220,200,0.55)',
                         outline: obPendingDrop ? '1px solid #ff4444' : 'none' }),
      hint: { fontSize:10, color:'rgba(232,220,200,0.3)', fontFamily:'Inter,sans-serif', fontStyle:'italic' },
    };

    const toolHint = obPendingDrop
      ? '📍 Now tap where you are re-playing from (drop zone)'
      : activeTool === 'shot'
        ? obMode ? 'Tap where the ball went OB' : 'Tap hole to place shots from the tee'
        : `Tap to place ${activeTool} · Drag to move · Drag gold handle to resize`;

    return (
      <div style={S.wrap}>
        {/* Hole shape */}
        <div style={S.row}>
          <span style={S.lbl}>Shape</span>
          <div style={{display:'flex', gap:3}}>
            {[['straight','Straight'],['dogleg-left','Dogleg L'],['dogleg-right','Dogleg R']].map(([v,l]) => (
              <button key={v} onClick={()=>{setHoleType(v);setShots([]);setHazards([]);}}
                style={{...S.seg,...(holeType===v?S.segOn:{})}}>
                {l}
              </button>
            ))}
          </div>
        </div>

        {/* Canvas */}
        <div style={S.canvasWrap}>
          <canvas
            ref={canvasRef}
            width={W} height={H}
            style={S.canvas}
            onMouseDown={onPointerDown}
            onMouseMove={onPointerMove}
            onMouseUp={onPointerUp}
            onTouchStart={onPointerDown}
            onTouchMove={onPointerMove}
            onTouchEnd={onPointerUp}
          />
        </div>
        <div style={S.hint}>{toolHint}</div>

        {/* Tool palette */}
        <div style={S.row}>
          <span style={S.lbl}>Tool</span>
          <div style={{display:'flex', gap:5, flexWrap:'wrap'}}>
            <button style={S.shotBtn} onClick={()=>setActiveTool('shot')}>
              <span style={{width:10,height:10,borderRadius:'50%',background:'#c9a84c',display:'inline-block'}}></span>
              Shot
            </button>
            {HAZARD_PALETTE.map(h => (
              <button key={h.id} style={S.palBtn(h.id)} onClick={()=>setActiveTool(h.id)}>
                <span style={{width:10,height:10,borderRadius:'50%',background:h.color,display:'inline-block'}}></span>
                {h.label}
              </button>
            ))}
          </div>
        </div>

        {/* Actions row */}
        <div style={{display:'flex', alignItems:'center', gap:6, flexWrap:'wrap'}}>
          <button style={S.obBtn(obMode)} onClick={()=>{ setObMode(!obMode); setObPendingDrop(false); }}>
            {obPendingDrop ? '📍 Mark Drop Zone' : obMode ? '⚑ OB: On' : '⚑ OB: Off'}
          </button>
          <div style={{display:'flex', gap:5, marginLeft:'auto'}}>
            <button style={S.actBtn} onClick={undo}>Undo</button>
            <button style={S.actBtn} onClick={clear}>Clear</button>
            <button onClick={exportPng}
              style={{...S.actBtn, background:'rgba(201,168,76,0.14)',
                       borderColor:'rgba(201,168,76,0.45)', color:'#c9a84c', fontWeight:600}}>
              Export →
            </button>
          </div>
        </div>
      </div>
    );
  }

  window.HoleDesigner = HoleDesigner;
})();
