# Modular block default

A shadcn/ui block from the [@craftui](https://www.craftui.space) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/craftui/modular-block-default).

- Page, with a live preview: https://shadcn.exchange/r/craftui/modular-block-default
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/craftui/modular-block-default-light-78800ca785663b7a.png
- Registry item: https://www.craftui.space/r/modular-block-default.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.craftui.space/r/modular-block-default.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://www.craftui.space/r/craftui-base.json`

## Files

### `components/craftui/illustrations/modular-block/default.tsx`

```tsx
// A cube with one corner block cut out and set down beside it. Registry items
// ship as one self-contained file to a consumer's project, so the isometric
// helpers below live here rather than in a shared module — a shared module
// would arrive as a second file the consumer never asked for.
//
// No motion: the diagram is drawn once and holds still.

interface ModularBlockProps {
  /** Additional classes on the <svg> root. */
  className?: string;
}

// ─── Isometric projection ─────────────────────────────────────────────────────
//
// A true 2:1 isometric would use k = 0.5; 0.866 (cos 30°) gives the taller,
// squarer look shared with the other isometric illustrations. ISO_S is the unit
// size in px, ISO_OX/OY the origin — tuned so the cube plus the detached block
// sit centred in the 420×450 viewBox.

const ISO_K = 0.8660254;
const ISO_S = 18;
const ISO_OX = 190;
const ISO_OY = 225;

type Pt = readonly [number, number];

const p = (x: number, y: number, z = 0): Pt => [
  (x - y) * ISO_K * ISO_S + ISO_OX,
  ((x + y) / 2 - z) * ISO_S + ISO_OY,
];

/** Sharp corners on purpose — every vertex is a hard miter, no bevel. */
const poly = (pts: Pt[]) =>
  pts
    .map(([x, y], i) => `${i ? "L" : "M"}${x.toFixed(2)} ${y.toFixed(2)}`)
    .join(" ") + "Z";

// ─── The solid ────────────────────────────────────────────────────────────────
//
// C is the cube's edge, N where the notch starts on all three axes, so the
// removed piece is the cube x,y,z ∈ [N, C] — the corner that projects nearest
// the viewer. OFF slides that piece out along +x and −y by equal amounts, which
// on screen is pure horizontal travel: (x − y) grows while (x + y) / 2 holds, so
// the block moves right without drifting up or down out of its own socket.

const C = 10;
const N = 5.5;
const OFF = 4;

/** The three faces of a box that are visible from this camera. */
const boxFaces = (
  x: number,
  y: number,
  z: number,
  w: number,
  d: number,
  h: number,
) => ({
  top: [
    p(x, y, z + h),
    p(x + w, y, z + h),
    p(x + w, y + d, z + h),
    p(x, y + d, z + h),
  ] as Pt[],
  front: [
    p(x, y + d, z),
    p(x + w, y + d, z),
    p(x + w, y + d, z + h),
    p(x, y + d, z + h),
  ] as Pt[],
  flank: [
    p(x + w, y, z),
    p(x + w, y + d, z),
    p(x + w, y + d, z + h),
    p(x + w, y, z + h),
  ] as Pt[],
});

// ─── Ink ──────────────────────────────────────────────────────────────────────
//
// Neutral greys, no hue at all. Light keeps the simple three-step ramp — one
// value per face orientation, lightest on the up-facing planes — so the shading
// does the work a light source would and the solid never needs a gradient.
//
// Dark does NOT reuse that ramp. It is lifted value-for-value from the Paper
// file ("SVG Frame 01"), where the three groups were graded apart rather than
// sharing one palette: the extracted piece and the socket it came out of keep a
// bright #3A3A3A up-face, while the host cube's top is knocked back to #282828.
// That gap is the whole idea — the eye lands on the piece and the hole first,
// and the big form recedes. The two inner walls go darker still, and the left
// one darkest of all, which is what gives the socket its depth.
//
// In Paper these came out of a base fill plus a black overlay on the outline
// path; the overlays only ever darken, so each pair is folded into one flat
// value here. Same pixels, half the paths, no alpha stacking to reason about.
//
// Every colour is a class pair rather than a fill/stroke attribute, because an
// attribute cannot answer to the theme.
//
// Classes rather than cssVars on purpose: cssVars are written into a consumer's
// stylesheet at install time, so they do nothing for the copy rendering from
// source in this repo's own gallery. This travels with the file. CSS beats a
// presentation attribute, so the fill="none" left on the <svg> root does not
// fight them.

const INK = "stroke-[#1A1A1A] dark:stroke-[#A8A8A8]";

/** The one warm edge in the drawing — the host cube's lower-left silhouette. */
const INK_WARM = "stroke-[#1A1A1A] dark:stroke-[#D7CECD]";

const SURFACE = {
  cubeTop: "fill-[#FFFFFF] dark:fill-[#282828]",
  cubeFront: "fill-[#EFEFEF] dark:fill-[#131313]",
  cubeFlank: "fill-[#DADADA] dark:fill-[#0D0D0D]",

  notchFloor: "fill-[#FFFFFF] dark:fill-[#3A3A3A]",
  notchFront: "fill-[#EFEFEF] dark:fill-[#131313]",
  notchFlank: "fill-[#DADADA] dark:fill-[#060606]",

  pieceTop: "fill-[#FFFFFF] dark:fill-[#3A3A3A]",
  pieceFront: "fill-[#EFEFEF] dark:fill-[#131313]",
  pieceFlank: "fill-[#DADADA] dark:fill-[#141414]",
} as const;

const LINE = {
  fill: "none",
  strokeLinejoin: "miter",
  strokeLinecap: "square",
} as const;

export function ModularBlock({ className }: ModularBlockProps) {
  // Outer skin. Each of the three visible faces loses the same square corner to
  // the notch, so all three are L-shaped rather than plain quads.
  const topFace: Pt[] = [
    p(0, 0, C),
    p(C, 0, C),
    p(C, N, C),
    p(N, N, C),
    p(N, C, C),
    p(0, C, C),
  ];
  const frontFace: Pt[] = [
    p(0, C, 0),
    p(C, C, 0),
    p(C, C, N),
    p(N, C, N),
    p(N, C, C),
    p(0, C, C),
  ];
  const flankFace: Pt[] = [
    p(C, 0, 0),
    p(C, C, 0),
    p(C, C, N),
    p(C, N, N),
    p(C, N, C),
    p(C, 0, C),
  ];

  // Cut surfaces inside the notch: a floor and the two walls that face the
  // camera. The third wall of the socket points away and is never seen.
  const notchFloor: Pt[] = [p(N, N, N), p(C, N, N), p(C, C, N), p(N, C, N)];
  const notchFlank: Pt[] = [p(N, N, N), p(N, C, N), p(N, C, C), p(N, N, C)];
  const notchFront: Pt[] = [p(N, N, N), p(C, N, N), p(C, N, C), p(N, N, C)];

  // The removed piece, slid out of its socket along the screen horizontal.
  const size = C - N;
  const piece = boxFaces(N + OFF, N - OFF, N, size, size, size);

  return (
    // viewBox-driven with no width/height attributes, so the diagram fills
    // whatever box its container reserves and scales down instead of
    // overflowing.
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 420 450"
      className={className}
      fill="none"
      role="img"
      aria-label="Isometric diagram: a cube with one corner block cut out and set beside it"
    >
      {/* Painter's algorithm. The recessed cut surfaces are farthest from the
          camera, so they go down first; the outer skin overdraws them where the
          two meet; the detached piece is nearest and lands last. Fills for a
          group precede its outlines, because the outlines have to survive the
          next fill that touches them. */}
      <g>
        <path d={poly(notchFlank)} className={SURFACE.notchFlank} />
        <path d={poly(notchFront)} className={SURFACE.notchFront} />
        <path d={poly(notchFloor)} className={SURFACE.notchFloor} />
        <g {...LINE} className={INK} strokeWidth={1.3}>
          <path d={poly(notchFlank)} />
          <path d={poly(notchFront)} />
          <path d={poly(notchFloor)} />
        </g>
      </g>

      <g>
        <path d={poly(flankFace)} className={SURFACE.cubeFlank} />
        <path d={poly(frontFace)} className={SURFACE.cubeFront} />
        <path d={poly(topFace)} className={SURFACE.cubeTop} />
        <g {...LINE} strokeWidth={1.3}>
          <path d={poly(flankFace)} className={INK} />
          <path d={poly(frontFace)} className={INK_WARM} />
          <path d={poly(topFace)} className={INK} />
        </g>
      </g>

      <g>
        <path d={poly(piece.flank)} className={SURFACE.pieceFlank} />
        <path d={poly(piece.front)} className={SURFACE.pieceFront} />
        <path d={poly(piece.top)} className={SURFACE.pieceTop} />
        <g {...LINE} className={INK} strokeWidth={1.3}>
          <path d={poly(piece.flank)} />
          <path d={poly(piece.front)} />
          <path d={poly(piece.top)} />
        </g>
      </g>
    </svg>
  );
}
```
