NEMAWASHI LAB

Pizza

SVG

Each of N radial pizza slices is triangulated by a skewed grid: two unit vectors along the slice edges define lattice coordinates, and every cell within the triangular sector is subdivided into two triangles whose fill alternates by (i + j) parity. The result is a tiled pinwheel where the tessellation density is independent of the number of slices. Slice count, row density, rotation, and dual colours are adjustable.

Pizza

8 slices · 4 rows

Controls
Slices8
Density4
Rotation0
Colour A
Colour B

Source

Pizza.tsx100 lines
"use client";

import { useState } from "react";
import ScrambleLink from "../ScrambleLink";
import DraggablePanel from "../DraggablePanel";
import { PanelColor } from "../_kansei/controls";

const R = 280; // viewBox radius — SVG is a 560×560 square scaled by CSS

export default function Pizza() {
  const [slices, setSlices] = useState(8);
  const [density, setDensity] = useState(4);
  const [rotation, setRotation] = useState(0);
  const [colorA, setColorA] = useState("#cc2010");
  const [colorB, setColorB] = useState("#f5f0e8");

  const cx = R;
  const cy = R;
  const size = R * 2;
  const N = slices;
  const sectorAngle = (2 * Math.PI) / N;
  const rows = density;
  const cellSize = R / rows;

  // Grid coord (i, j) within slice s → SVG point string
  const pt = (i: number, j: number, s: number) => {
    const base = s * sectorAngle;
    const ux = Math.cos(base);
    const uy = Math.sin(base);
    const vx = Math.cos(base + sectorAngle);
    const vy = Math.sin(base + sectorAngle);
    const x = cx + i * cellSize * ux + j * cellSize * vx;
    const y = cy + i * cellSize * uy + j * cellSize * vy;
    return `${x.toFixed(2)},${y.toFixed(2)}`;
  };

  const tris: React.ReactNode[] = [];
  for (let s = 0; s < N; s++) {
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < rows - i; j++) {
        const p00 = pt(i, j, s);
        const p10 = pt(i + 1, j, s);
        const p01 = pt(i, j + 1, s);
        if (i + j < rows - 1) {
          const p11 = pt(i + 1, j + 1, s);
          const flip = (i + j) % 2 === 0;
          tris.push(
            <polygon key={`${s}-${i}-${j}-a`} points={`${p00} ${p10} ${p11}`} fill={flip ? colorA : colorB} />,
            <polygon key={`${s}-${i}-${j}-b`} points={`${p00} ${p11} ${p01}`} fill={flip ? colorB : colorA} />,
          );
        } else {
          const fill = (i + j) % 2 === 0 ? colorA : colorB;
          tris.push(<polygon key={`${s}-${i}-${j}`} points={`${p00} ${p10} ${p01}`} fill={fill} />);
        }
      }
    }
  }

  return (
    <div className="fixed inset-0 flex items-center justify-center select-none" style={{ background: colorB }}>
      <svg viewBox={`0 0 ${size} ${size}`} className="w-[86vmin] h-[86vmin] z-[1]" xmlns="http://www.w3.org/2000/svg">
        <rect width={size} height={size} fill={colorB} />
        <g transform={`rotate(${rotation}, ${cx}, ${cy})`}>{tris}</g>
      </svg>

      <div
        className="fixed bottom-8 left-8 pointer-events-none z-10 font-[family-name:var(--font-flux)]"
        style={{ fontVariationSettings: "'wght' 300, 'SRIF' 100" }}
      >
        <p className="text-[12px] tracking-[0.08em] uppercase text-foreground/50">Pizza</p>
        <p className="text-[12px] tracking-[0.08em] mt-1 text-foreground/30">
          {slices} slices · {density} rows
        </p>
      </div>

      <DraggablePanel
        isLight={false}
        controls={[
          { label: "Slices", value: slices, set: (v) => setSlices(Math.round(v)), min: 3, max: 16, step: 1 },
          { label: "Density", value: density, set: (v) => setDensity(Math.round(v)), min: 2, max: 10, step: 1 },
          { label: "Rotation", value: rotation, set: setRotation, min: 0, max: 360, step: 1 },
        ]}
      >
        <PanelColor label="Colour A" value={colorA} set={setColorA} isLight={false} />
        <PanelColor label="Colour B" value={colorB} set={setColorB} isLight={false} />
      </DraggablePanel>

      <div className="fixed top-8 left-8 z-10">
        <ScrambleLink
          from="NEMAWASHI LAB"
          to="← BACK"
          href="/lab"
          className="text-[14px] tracking-[0.08em] uppercase pointer-events-auto font-[family-name:var(--font-flux)] text-foreground/60"
          style={{ fontVariationSettings: "'wght' 600, 'SRIF' 400" }}
        />
      </div>
    </div>
  );
}

NEMAWASHI — Kotaro Abe