ghostyles/maximalism.jsjavascript
/**
 * ==Ghostyle==
 * @name         Maximalism
 * @version      1.0.0
 * @author       vecna
 * @release_date 2026-06-29
 * @description  Pattern massimalista ispirato a trucchi club/drag contemporanei. Spezza la simmetria, ridisegna la bocca, copre lo zigomo, estende l'occhio verso l'alto.
 * ==/Ghostyle==
 */

function drawEyeshadow(ctx, eye, brow, side) {
  // Dettaglio replicato: ombretto verde acqua esteso oltre il sopracciglio,
  // con salita verticale ampia. Proprietà adversarial: rompe i contorni naturali
  // dell'occhio creando una regione cromatica "non anatomica" ad alto volume.
  const top = brow.map((b, i) => {
    const ref = eye[Math.min(i + 1, eye.length - 1)] || eye[eye.length - 1];
    return Ghostati.lerp(ref, b, 1.8);
  });
  const lower = [eye[3], eye[4], eye[5], eye[0], eye[1], eye[2]];
  const shape = side === 'left' ? [...top, ...lower] : [...top, ...lower.reverse()];
  Ghostati.drawClosedPath(ctx, shape, 'rgba(100, 220, 200, 0.70)', 'rgba(160, 255, 240, 0.70)', 2.2);
}

function drawDownwardEyeliner(ctx, eye, side) {
  // Dettaglio replicato: eyeliner nero spesso con coda "a lacrima" verso il basso.
  // Proprietà adversarial: orientamento opposto al cat-eye classico, introduce
  // asimmetria direzionale e altera i cue geometrici perioculari.
  const upper = [eye[0], eye[1], eye[2], eye[3]];
  const edge = side === 'left' ? eye[0] : eye[3];
  const inner = side === 'left' ? eye[1] : eye[2];

  const ext1 = Ghostati.lerp(inner, edge, 1.5);
  const ext2 = Ghostati.lerp(inner, edge, 2.2);
  ext1.y += 9;
  ext2.y += 14;

  const path = side === 'left' ? [ext2, ext1, ...upper] : [...upper, ext1, ext2];
  Ghostati.drawOpenPath(ctx, path, 'rgba(0, 0, 0, 0.95)', 4.2);
}

function drawFlowerMouth(ctx, mouth) {
  // Dettaglio replicato: bocca ridisegnata a petali, ignorando la forma anatomica.
  // Proprietà adversarial: occlude i bordi labiali reali, che sono feature
  // biometriche altamente informative nei descrittori facciali.
  const c = Ghostati.avgPoint(mouth);
  const petals = 5;
  const baseR = Ghostati.distance(mouth[0], mouth[6]) * 0.22;

  ctx.save();
  ctx.fillStyle = 'rgba(180, 40, 110, 0.85)';
  for (let i = 0; i < petals; i++) {
    const a = (Math.PI * 2 * i) / petals;
    const ox = Math.cos(a) * baseR * 0.9;
    const oy = Math.sin(a) * baseR * 0.7;
    ctx.beginPath();
    ctx.ellipse(c.x + ox, c.y + oy, baseR * 0.95, baseR * 0.55, a, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.restore();
}

export function onDraw(ctx, landmarks) {
  const leftEye = landmarks.getLeftEye();
  const rightEye = landmarks.getRightEye();
  const leftBrow = landmarks.getLeftEyeBrow();
  const rightBrow = landmarks.getRightEyeBrow();
  const mouth = landmarks.getMouth();

  drawEyeshadow(ctx, leftEye, leftBrow, 'left');
  drawEyeshadow(ctx, rightEye, rightBrow, 'right');

  drawDownwardEyeliner(ctx, leftEye, 'left');
  drawDownwardEyeliner(ctx, rightEye, 'right');

  drawFlowerMouth(ctx, mouth);
}

export function paintUV(ctx) {
  const landmarks3d = Ghostati.lastLandmarks3d;
  if (!Array.isArray(landmarks3d) || landmarks3d.length < 331) return;

  // Dettaglio replicato: blush magenta molto esteso e basso (sotto l'occhio).
  // Proprietà adversarial: copre area zigomatica/nasale con gradiente forte,
  // riducendo stabilità di texture locali nelle regioni guancia.
  const leftRange = landmarks3d.slice(50, 102);
  const rightRange = landmarks3d.slice(280, 331);
  if (!leftRange.length || !rightRange.length) return;

  const left = Ghostati.avgPoint(leftRange);
  const right = Ghostati.avgPoint(rightRange);
  const w = ctx.canvas.width;
  const h = ctx.canvas.height;

  const blush = (p, stretch) => {
    const x = p.x * w;
    const y = p.y * h;
    const r = w * 0.17 * stretch;
    const g = ctx.createRadialGradient(x, y, r * 0.15, x, y, r);
    g.addColorStop(0, 'rgba(230, 100, 180, 0.70)');
    g.addColorStop(1, 'rgba(230, 100, 180, 0.02)');

    ctx.fillStyle = g;
    ctx.beginPath();
    ctx.ellipse(x, y, r * 1.15, r * 0.88, 0.2, 0, Math.PI * 2);
    ctx.fill();
  };

  blush(left, 1.0);
  blush(right, 1.05);
}