Skip to main content

ns-ui / loud

Tricone Bit Teeth

A full-bleed rock face worked by a rotary tricone bit: three cone cutters orbit the bit centre in a fixed 120-degree pattern, each tooth row stamping a crater cluster into the face that heals back to flat before the next pass, so the surface reads as a ring being continuously worked rather than a diffuse pockmarked plate.

Use when Pick tricone-bit-teeth when the backdrop should read as a rock face being actively worked by a rotating cutting tool — three orbiting cone bodies visibly drive a textured ring of crater strikes, and the ring's fixed 120-degree structure is the legible feature. Pick edm-crater-field instead when the surface should read as a flat metal plate under diffuse, uniform-random erosion with no visible tool or directional structure at all — that component's craters land anywhere with equal probability and never trace a ring, which is the opposite read from this one's rotating-bit mechanic.

Install

npx shadcn add https://design.helpmarq.com/r/tricone-bit-teeth.json

Ask AI

Point an assistant at this component's docs (llms-full.txt) with one click.

Claude, ChatGPT, Grok, and Perplexity open with the prompt already in. Gemini copies it to your clipboard first. Paste it in once the chat opens.

Source
registry/loud/tricone-bit-teeth/component.tsx
"use client";

import { useEffect, useId, useRef } from "react";

// ---------------------------------------------------------------------------
// TriconeBitTeeth — a full-bleed hero built on a rotary tricone rock bit: the
// three cone cutters at a bit's periphery orbit the borehole axis while each
// spins on its own skewed journal, so their teeth both crush and scrape the
// formation with every pass. Unlike edm-crater-field's steady-state RANDOM
// discharge scatter, this crater field is never diffuse or uniform — every
// dent lands only where a cone's tooth row actually swept, in three
// simultaneous, 120-degree-offset clusters, so the field always reads as a
// RING traced by a rotating tool rather than a pockmarked plate. The bit
// bodies themselves are drawn as visible orbiting wedges so "three cones
// rotating" is legible on its own, independent of the crater ring it leaves.
//
// The real bit rotates around 60-120 RPM and strikes 33 times/lap (3 cones x
// 11 teeth) — both far too fast to paint 1:1 against a 60Hz page without
// aliasing into a strobe (round 9's meter-matrix-scan lesson). Both rates are
// deliberately decoupled: the bit is rendered orbiting at an 8-RPM-equivalent
// sweep (one lap every 7.5s), and the 33 individual tooth strikes per lap are
// collapsed into one 3-cone simultaneous cluster roughly every 680ms, which
// is what a viewer can actually track landing and healing.
//
// IMPLEMENTATION: like edm-crater-field, the rock face is a small CPU-side
// height/depth buffer (cells derived from the container's SMALLER dimension,
// 96 cells across it) that strikes stamp a crater into and that decays back
// toward flat every frame — never a monotonic fill, a resident population in
// equilibrium. The buffer is painted to an offscreen ImageData canvas and
// blitted, scaled and smoothed, onto the display canvas; the three cone
// bodies and the mud-jet sweep ring are drawn on top in vector form each
// frame so the "rotating tool" reading never depends on the field alone.
//
// Colours: three luminance stops derived from --background, --foreground and
// --ns-muted (getComputedStyle at mount, re-read on a MutationObserver
// watching documentElement's class) — no literals anywhere. --ns-accent is
// never touched: there is no interactive climactic moment (an optional
// pointer only brightens the swept ring in luminance), matching the showpiece
// recipe's standing "accent-tinted pointer highlight" defect.
// ---------------------------------------------------------------------------

export interface TriconeBitTeethProps {
  /** Freezes the surface on a composed post-strike still frame without unmounting. */
  paused?: boolean;
  /** Rendered in the DOM over the field — eyebrow, headline, CTA. */
  children?: React.ReactNode;
  className?: string;
  style?: React.CSSProperties;
}

type RGB = [number, number, number];

function parseHex(raw: string): RGB | null {
  const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(raw.trim());
  if (!m) return null;
  let h = m[1];
  if (h.length === 3) h = h.split("").map((c) => c + c).join("");
  const n = parseInt(h, 16);
  return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
}

function mixRGB(a: RGB, b: RGB, t: number): RGB {
  return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
}

function luminance([r, g, b]: RGB): number {
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

function rgbCss([r, g, b]: RGB, a = 1): string {
  return `rgba(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)},${a})`;
}

const CONES = 3;
const TEETH_PER_CONE = 11;
const LAP_SECONDS = 7.5; // 8-RPM-equivalent decoupled sweep, see header
const STRIKE_INTERVAL = 0.68; // s between simultaneous 3-cone strike clusters
const HEAL_RATE = 0.9; // depth *= exp(-HEAL_RATE * dt), ~90% refill in ~1.1s
const FIELD_CELLS = 96; // cells across the container's smaller dimension
const JET_PERIOD = 0.9; // s per mud-jet sweep lap
const JET_SPEED = 140; // px/s the sweep ring expands at

// The frame drawn under prefers-reduced-motion (and paused): right after a
// 3-cone simultaneous strike lands, at maximum unhealed depth — the single
// most structured frame, showing the tooth pattern rather than a mid-heal
// blur or the flat rock the loop would otherwise freeze on.
const STATIC_STRIKE_PHASE = 0; // seconds past the most recent strike event

export function TriconeBitTeeth({
  paused = false,
  children,
  className = "",
  style,
}: TriconeBitTeethProps) {
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const uid = useId();
  const pausedRef = useRef(paused);
  pausedRef.current = paused;

  useEffect(() => {
    const wrap = wrapRef.current;
    const canvas = canvasRef.current;
    if (!wrap || !canvas) return;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    let cssW = 0;
    let cssH = 0;
    let dpr = 1;
    let disposed = false;
    let running = false;
    let raf = 0;
    let staticMode = false;
    let lastMs = performance.now();
    let simTime = 0;
    let lastStrikeAt = -STRIKE_INTERVAL;

    let fieldW = FIELD_CELLS;
    let fieldH = FIELD_CELLS;
    let depth = new Float32Array(fieldW * fieldH);
    const field = document.createElement("canvas");
    const fctx = field.getContext("2d", { willReadFrequently: true });
    let fieldImg: ImageData | null = null;

    let bg: RGB = [1, 1, 1];
    let raised: RGB = [0.86, 0.86, 0.86];
    let struckDeep: RGB = [0.06, 0.06, 0.06];
    let muted: RGB = [0.55, 0.55, 0.55];

    const readColors = () => {
      const cs = getComputedStyle(document.documentElement);
      const bgTok = parseHex(cs.getPropertyValue("--background")) ?? [1, 1, 1];
      const fg = parseHex(cs.getPropertyValue("--foreground")) ?? [0.09, 0.09, 0.09];
      const mut = parseHex(cs.getPropertyValue("--ns-muted")) ?? [0.55, 0.55, 0.55];
      bg = bgTok;
      muted = mut;
      // dark theme: unstruck rock sits slightly above background; struck
      // craters read near-black. Light theme needs the same three legible
      // steps compressed into far less headroom below --background, so the
      // dark anchor is pulled from --foreground rather than pure black.
      if (luminance(bgTok) < 0.5) {
        raised = mixRGB(bgTok, [1, 1, 1], 0.14);
        struckDeep = mixRGB(bgTok, [0, 0, 0], 0.55);
      } else {
        raised = mixRGB(bgTok, fg, 0.1);
        struckDeep = mixRGB(fg, [0, 0, 0], 0.25);
      }
    };
    readColors();

    const setFieldRes = () => {
      const ref = Math.min(cssW, cssH) || 1;
      fieldW = FIELD_CELLS;
      fieldH = Math.max(24, Math.round(FIELD_CELLS * (cssH / ref)));
      const w2 = Math.max(24, Math.round(FIELD_CELLS * (cssW / ref)));
      fieldW = w2;
      depth = new Float32Array(fieldW * fieldH);
      field.width = fieldW;
      field.height = fieldH;
      fieldImg = fctx ? fctx.createImageData(fieldW, fieldH) : null;
    };

    // A crater is a recessed bowl: a soft radial falloff stamped additively
    // into the depth buffer (0 = flat, up to 1 = deepest fresh crater).
    const stampCrater = (cx: number, cy: number, radiusCells: number, amp: number) => {
      const r = Math.max(1, radiusCells);
      const x0 = Math.max(0, Math.floor(cx - r));
      const x1 = Math.min(fieldW - 1, Math.ceil(cx + r));
      const y0 = Math.max(0, Math.floor(cy - r));
      const y1 = Math.min(fieldH - 1, Math.ceil(cy + r));
      for (let y = y0; y <= y1; y++) {
        for (let x = x0; x <= x1; x++) {
          const dx = (x - cx) / r;
          const dy = (y - cy) / r;
          const d2 = dx * dx + dy * dy;
          if (d2 > 1) continue;
          const fall = Math.exp(-d2 * 2.2);
          const idx = y * fieldW + x;
          depth[idx] = Math.min(1, depth[idx] + amp * fall);
        }
      }
    };

    // The three cones sit 120 degrees apart at the bit's periphery and orbit
    // the bit centre together. Each cone's tooth row strikes a cluster of
    // TEETH_PER_CONE small craters scattered along a short arc of its own
    // track — this is what keeps the ring textured (individual tooth marks)
    // rather than a single smeared dent per cone per strike.
    const strikeCluster = (bitAngle: number) => {
      const ref = Math.min(fieldW, fieldH);
      const orbitR = ref * 0.32;
      const cx = fieldW / 2;
      const cy = fieldH / 2;
      for (let c = 0; c < CONES; c++) {
        const coneAngle = bitAngle + (c * Math.PI * 2) / CONES;
        const coneCx = cx + Math.cos(coneAngle) * orbitR;
        const coneCy = cy + Math.sin(coneAngle) * orbitR;
        for (let t = 0; t < TEETH_PER_CONE; t++) {
          // teeth spread across a short arc of the cone's own track, not a
          // single point, so one strike cluster reads as a row, not a dot
          const spread = ((t / (TEETH_PER_CONE - 1)) - 0.5) * 0.5;
          const ta = coneAngle + spread;
          const tr = orbitR * (0.94 + 0.09 * ((t % 3) - 1));
          const tx = cx + Math.cos(ta) * tr;
          const ty = cy + Math.sin(ta) * tr;
          stampCrater(tx, ty, ref * 0.028, 0.75 + 0.25 * Math.random());
        }
      }
    };

    const paintField = () => {
      if (!fctx || !fieldImg) return;
      const data = fieldImg.data;
      for (let i = 0; i < depth.length; i++) {
        const d = depth[i];
        // raised (unstruck) rock at d=0 down to the deepest struck stop at d=1
        const col = d <= 0.001 ? raised : mixRGB(raised, struckDeep, Math.min(1, d));
        const j = i * 4;
        data[j] = Math.round(col[0] * 255);
        data[j + 1] = Math.round(col[1] * 255);
        data[j + 2] = Math.round(col[2] * 255);
        data[j + 3] = 255;
      }
      fctx.putImageData(fieldImg, 0, 0);
    };

    const drawCones = (t: number, bitAngle: number) => {
      const ref = Math.min(cssW, cssH);
      const cx = cssW / 2;
      const cy = cssH / 2;
      const orbitR = ref * 0.32;
      const bodyR = ref * 0.1;
      for (let c = 0; c < CONES; c++) {
        const coneAngle = bitAngle + (c * Math.PI * 2) / CONES;
        const px = cx + Math.cos(coneAngle) * orbitR;
        const py = cy + Math.sin(coneAngle) * orbitR;
        const spin = t * 6.0 + c; // decorative own-axis spin, purely visual
        ctx.save();
        ctx.translate(px, py);
        ctx.rotate(spin);
        ctx.beginPath();
        ctx.moveTo(0, -bodyR);
        ctx.lineTo(bodyR * 0.82, bodyR * 0.62);
        ctx.lineTo(-bodyR * 0.82, bodyR * 0.62);
        ctx.closePath();
        ctx.fillStyle = rgbCss(muted, 0.9);
        ctx.fill();
        ctx.strokeStyle = rgbCss(struckDeep, 0.7);
        ctx.lineWidth = Math.max(1, bodyR * 0.05);
        ctx.stroke();
        ctx.restore();
      }
    };

    const drawJetSweep = (t: number) => {
      const ref = Math.min(cssW, cssH);
      const maxR = ref * 0.46;
      const phase = ((t % JET_PERIOD) / JET_PERIOD) * (JET_SPEED * JET_PERIOD);
      const r = Math.min(maxR, phase);
      const alpha = 0.08 * (1 - r / maxR);
      if (alpha <= 0.002) return;
      ctx.save();
      ctx.strokeStyle = rgbCss(mixRGB(raised, [1, 1, 1], 0.5), alpha);
      ctx.lineWidth = Math.max(2, ref * 0.02);
      ctx.beginPath();
      ctx.arc(cssW / 2, cssH / 2, r, 0, Math.PI * 2);
      ctx.stroke();
      ctx.restore();
    };

    const draw = () => {
      if (cssW <= 0 || cssH <= 0 || !fctx) return;
      const t = staticMode ? lastStrikeAt + STATIC_STRIKE_PHASE : simTime;
      const bitAngle = (t / LAP_SECONDS) * Math.PI * 2;

      paintField();
      ctx.save();
      ctx.imageSmoothingEnabled = true;
      ctx.fillStyle = rgbCss(raised);
      ctx.fillRect(0, 0, cssW, cssH);
      ctx.drawImage(field, 0, 0, cssW, cssH);
      ctx.restore();

      drawJetSweep(t);
      drawCones(t, bitAngle);
    };

    const loop = (nowMs: number) => {
      const rawMs = nowMs - lastMs;
      const dt = Math.min(0.05, Math.max(0, rawMs / 1000));
      lastMs = nowMs;
      simTime += dt;

      // heal every resident crater back toward flat, framerate-independent
      const heal = Math.exp(-HEAL_RATE * dt);
      for (let i = 0; i < depth.length; i++) depth[i] *= heal;

      const bitAngle = (simTime / LAP_SECONDS) * Math.PI * 2;
      if (simTime - lastStrikeAt >= STRIKE_INTERVAL) {
        lastStrikeAt = simTime;
        strikeCluster(bitAngle);
      }

      draw();
      raf = requestAnimationFrame(loop);
    };

    const wake = () => {
      if (running || disposed) return;
      running = true;
      lastMs = performance.now();
      raf = requestAnimationFrame(loop);
    };
    const sleep = () => {
      cancelAnimationFrame(raf);
      running = false;
    };

    const dprCap = () => Math.min(window.devicePixelRatio || 1, 2);

    const applyBacking = () => {
      if (cssW < 2 || cssH < 2) return;
      dpr = dprCap();
      const pw = Math.round(cssW * dpr);
      const ph = Math.round(cssH * dpr);
      if (canvas.width !== pw || canvas.height !== ph) {
        canvas.width = pw;
        canvas.height = ph;
      }
      canvas.style.width = `${cssW}px`;
      canvas.style.height = `${cssH}px`;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };

    const resize = () => {
      const rect = wrap.getBoundingClientRect();
      if (rect.width < 2 || rect.height < 2) return;
      cssW = rect.width;
      cssH = rect.height;
      applyBacking();
      setFieldRes();
      // seed a struck ring immediately so a freshly-mounted/resized surface
      // never shows a blank flat frame while waiting for the first cycle
      strikeCluster(0);
      strikeCluster((Math.PI * 2) / LAP_SECONDS / 3);
      draw();
    };

    const ro = new ResizeObserver(resize);
    ro.observe(wrap);
    resize();

    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    let reduced = mq.matches;
    const applyMode = () => {
      if (reduced || pausedRef.current) {
        staticMode = true;
        sleep();
        draw();
      } else {
        staticMode = false;
        wake();
      }
    };
    const onMq = () => {
      reduced = mq.matches;
      applyMode();
    };
    mq.addEventListener("change", onMq);

    let onScreen = true;
    const io = new IntersectionObserver(
      (entries) => {
        onScreen = entries.some((en) => en.isIntersecting);
        if (!onScreen) sleep();
        else if (!staticMode && !document.hidden) wake();
      },
      { threshold: 0 }
    );
    io.observe(wrap);

    const onVis = () => {
      if (document.hidden) sleep();
      else if (!staticMode && onScreen) wake();
    };
    document.addEventListener("visibilitychange", onVis);

    const themeObserver = new MutationObserver(() => {
      readColors();
      if (staticMode) draw();
    });
    themeObserver.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"],
    });

    let lastPolledPaused = pausedRef.current;
    let poll = 0;
    const tick = () => {
      if (pausedRef.current !== lastPolledPaused) {
        lastPolledPaused = pausedRef.current;
        applyMode();
      }
      poll = window.setTimeout(tick, 140);
    };
    tick();

    applyMode();

    return () => {
      disposed = true;
      ro.disconnect();
      io.disconnect();
      mq.removeEventListener("change", onMq);
      document.removeEventListener("visibilitychange", onVis);
      themeObserver.disconnect();
      window.clearTimeout(poll);
      sleep();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div
      ref={wrapRef}
      data-tricone-bit-teeth={uid}
      className={`relative isolate h-full w-full overflow-hidden bg-background ${className}`}
      style={style}
    >
      <canvas ref={canvasRef} aria-hidden="true" className="absolute inset-0 block h-full w-full" />
      {children ? <div className="relative z-[1] h-full w-full">{children}</div> : null}
    </div>
  );
}

TriconeBitTeeth.displayName = "TriconeBitTeeth";
Build spec

Build <TriconeBitTeeth paused? children? className? style?> as a full-bleed 2D-canvas hero built on a rotary tricone rock bit: three cone cutters orbit the bit's centre together (bitAngle = t / LAP_SECONDS * 2*PI, LAP_SECONDS=7.5, an 8-RPM-equivalent sweep deliberately decoupled from the real 60-120 RPM rate per the round 9 aliasing rule) while a CPU-side depth buffer records where their teeth have struck. REAL NUMBERS: 3 cones, 120 degrees apart, orbit radius = 0.32 * min(fieldW, fieldH); TEETH_PER_CONE=11 teeth per cone, spread across a short arc of the cone's own track (not a single point) so one strike cluster reads as a row; a simultaneous 3-cone STRIKE_INTERVAL=0.68s cluster (33 real tooth-strikes/lap collapsed into one legible 3-cone event, per the round 9 'roughly a second between discrete events' rule); each tooth stamps a soft radial-falloff crater (exp(-d^2*2.2) within radius ref*0.028) at amplitude 0.75-1.0 into a Float32Array depth buffer sized FIELD_CELLS=96 cells across the container's SMALLER dimension (aspect-scaled on the other axis); every real frame the WHOLE buffer decays via depth *= exp(-HEAL_RATE*dt), HEAL_RATE=0.9/s (~90% refill in ~1.1s) — birth and decay are both constant and independent, so the ring is a resident population in equilibrium, never a monotonic fill (same steady-state discipline as registry/loud/edm-crater-field, but here births are STRUCTURED — always at the three cones' current orbit position — never uniform-random, which is what keeps this from reading as edm-crater-field's diffuse plate). RENDERING: the depth buffer is painted into an offscreen low-res canvas each frame (raised rock at depth<=0 mixed toward a struck-deep stop as depth approaches 1, both derived from --background/--foreground) and blitted scaled-up with imageSmoothingEnabled=true onto the display canvas — a soft, non-pixel-hard crater read appropriate to rock rather than a machined plate. The three cone BODIES are drawn as small triangular wedges directly on the display canvas each frame, orbiting at bitAngle with an additional decorative own-axis spin (t*6 rad/s, cosmetic only, not tied to any real number) — this vector overlay is what makes 'three cones rotating' legible on its own, independent of the crater ring, and is the deliberate differentiator from edm-crater-field's shader-only height field with no visible tool. A faint mud-jet sweep ring (0.08 peak luminance, radiating from bit centre at JET_SPEED=140px/s, resetting every JET_PERIOD=0.9s) stands in for nozzle discharge and must stay under the crater ring in read priority — a wash, not a highlight. MUST NOT: tint the crater ring, cone bodies or jet sweep with --ns-accent — there is no interactive control on this component (a pointer, if ever added, may only brighten the ring in luminance near the cursor), matching the showpiece recipe's standing 'accent-tinted pointer highlight' defect (edge-yield, granule-churn, shear-billow). RESTING LOOP: t0 shows an already-mid-cycle ring (seeded by two immediate strikeCluster() calls in resize(), at bit angles 0 and one-third of a lap, so mount never shows a blank flat frame); by 2.5s roughly 3-4 more clusters have landed and one or two early clusters have healed back toward flat; by 5s the ring shows continuous turnover — fresh strikes at the leading edge, healed craters fading at the trailing edge — which is the 'alive at rest' proof, not a filling-in or emptying-out pattern. TOKENS: three luminance stops (raised rock, struck-deep, muted) derived from --background, --foreground, --ns-muted via getComputedStyle(document.documentElement) at mount, re-read on a MutationObserver watching documentElement's class — NO PAINT BEFORE THE FIRST READ, readColors() runs before the first resize()/draw(). Light theme (checked first): the dark anchor for struck-deep is mixed from --foreground rather than pure black, since light theme has far less headroom below --background than dark theme does above it — verify the raised/struck-deep/muted three-step ramp stays legible there specifically. `ResizeObserver` reallocates the depth buffer and reseeds two clusters; `IntersectionObserver` (threshold 0) and `visibilitychange` both pause the single rAF loop; `prefers-reduced-motion`/`paused` freeze on STATIC_STRIKE_PHASE — the instant right after the most recent strike lands, at maximum unhealed depth, the single most structured frame in the loop. Canvas is `aria-hidden` decoration with `w-full h-full`; children render over the field in a `relative z-[1]` layer and need a token scrim, since the crater ring can pass under any line of overlaid type.

Props

PropTypeDefaultDescription
paused?booleanfalseFreezes the surface on a composed post-strike still frame without unmounting.
children?React.ReactNodeRendered in the DOM over the field — eyebrow, headline, CTA.
className?string
style?React.CSSProperties