About this technique →
supershape-2d/sketch.js
// 2D Supershape — Gielis superformula rendered as a stroked parametric polyline
//
// r(φ) = ( |cos(m·φ/4)/a|^n2 + |sin(m·φ/4)/b|^n3 )^(-1/n1)
//
// Six parameters control shape; m=6,n1=0.3 → flower; m=12,n1=10 → polygon.

export const PARAMS = {
  m:         { value: 6.0,  min: 0,    max: 16,   step: 0.1,  label: "Symmetry (m)",       folder: "Shape" },
  n1:        { value: 0.3,  min: 0.05, max: 5,    step: 0.01, label: "n1 (envelope)",       folder: "Shape" },
  n2:        { value: 0.3,  min: 0.05, max: 5,    step: 0.01, label: "n2",                  folder: "Shape" },
  n3:        { value: 0.3,  min: 0.05, max: 5,    step: 0.01, label: "n3",                  folder: "Shape" },
  a:         { value: 1.0,  min: 0.1,  max: 3,    step: 0.01, label: "a",                   folder: "Shape" },
  b:         { value: 1.0,  min: 0.1,  max: 3,    step: 0.01, label: "b",                   folder: "Shape" },
  resolution:{ value: 800,  min: 100,  max: 2000, step: 50,   label: "Vertices",            folder: "Performance" },
  scale:     { value: 0.4,  min: 0.1,  max: 0.9,  step: 0.01, label: "Scale (% of canvas)", folder: "Appearance" },
  hueA:      { value: 200,  min: 0,    max: 360,  step: 1,    label: "Stroke Hue Start",    folder: "Appearance" },
  hueB:      { value: 320,  min: 0,    max: 360,  step: 1,    label: "Stroke Hue End",      folder: "Appearance" },
  bg:        { value: "#0a0a0a", type: "color",               label: "Background",          folder: "Appearance" },
  strokeW:   { value: 1.5,  min: 0.5,  max: 6,    step: 0.1,  label: "Stroke Width",        folder: "Appearance" },
};

export const SHARE = {
  bookmarked: ["m", "n1", "n2", "n3", "hueA", "hueB"],
};

// --- Oklch helpers (inline, no deps) ---

function linearToSrgb(c) {
  return Math.max(0, Math.min(1, c < 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055));
}

function oklchToRgb255(L, C, hDeg) {
  const h = (hDeg * Math.PI) / 180;
  const a = C * Math.cos(h);
  const b = C * Math.sin(h);
  const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
  const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
  const s_ = L - 0.0894841775 * a - 1.291485548 * b;
  const lc = l_ ** 3, mc = m_ ** 3, sc = s_ ** 3;
  const r  = linearToSrgb( 4.0767416621 * lc - 3.3077115913 * mc + 0.2309699292 * sc);
  const g  = linearToSrgb(-1.2684380046 * lc + 2.6097574011 * mc - 0.3413193965 * sc);
  const bv = linearToSrgb(-0.0041960863 * lc - 0.7034186147 * mc + 1.707614701  * sc);
  return [Math.round(r * 255), Math.round(g * 255), Math.round(bv * 255)];
}

// Parse "#rrggbb" → [r,g,b] 0-255
function hexToRgb255(hex) {
  const h = hex.replace('#', '');
  return [
    parseInt(h.substring(0, 2), 16),
    parseInt(h.substring(2, 4), 16),
    parseInt(h.substring(4, 6), 16),
  ];
}

// --- Superformula ---

function superformula(phi, m, n1, n2, n3, a, b) {
  const t1 = Math.abs(Math.cos(m * phi / 4) / a);
  const t2 = Math.abs(Math.sin(m * phi / 4) / b);
  const base = Math.pow(t1, n2) + Math.pow(t2, n3);
  // Guard against base === 0 (e.g. m=0)
  if (base === 0) return 1;
  return Math.pow(base, -1 / n1);
}

// --- Sketch API ---

export function sketchSetup(ctx, w, h, tng) {
  return {};
}

export function sketchDraw(ctx, w, h, params, tng) {
  // Background
  const bgRgb = hexToRgb255(params.bg);
  ctx.fillStyle = `rgb(${bgRgb[0]},${bgRgb[1]},${bgRgb[2]})`;
  ctx.fillRect(0, 0, w, h);

  const cx = w / 2;
  const cy = h / 2;
  const maxR = Math.min(w, h) * params.scale;
  const steps = params.resolution | 0;

  const { m, n1, n2, n3, a, b } = params;

  // Pre-compute all vertices
  const xs = new Float64Array(steps + 1);
  const ys = new Float64Array(steps + 1);

  for (let i = 0; i <= steps; i++) {
    const phi = (i / steps) * Math.PI * 2;
    const r = superformula(phi, m, n1, n2, n3, a, b) * maxR;
    xs[i] = cx + r * Math.cos(phi);
    ys[i] = cy + r * Math.sin(phi);
  }

  // Draw segment-by-segment with hue gradient in Oklch
  ctx.lineWidth = params.strokeW;
  ctx.lineCap = 'round';
  ctx.lineJoin = 'round';

  const chroma = 0.18;
  const lightness = 0.65;

  for (let i = 0; i < steps; i++) {
    const t = i / (steps - 1);
    // Interpolate hue linearly in degrees (wrapping handled by modulo in oklch)
    const hue = params.hueA + (params.hueB - params.hueA) * t;
    const [r, g, bv] = oklchToRgb255(lightness, chroma, ((hue % 360) + 360) % 360);

    ctx.beginPath();
    ctx.strokeStyle = `rgb(${r},${g},${bv})`;
    ctx.moveTo(xs[i], ys[i]);
    ctx.lineTo(xs[i + 1], ys[i + 1]);
    ctx.stroke();
  }
}