# 8-bit Brick Breaker 404

> A playable brick-breaker 404 page with keyboard, pointer, and touch controls.

A shadcn/ui block from the [@8bitcn](https://www.8bitcn.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/8bitcn/not-found-brick-breaker).

- Page, with a live preview: https://shadcn.exchange/r/8bitcn/not-found-brick-breaker
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/8bitcn/not-found-brick-breaker-light-8637a283bfc192d6.png
- Registry item: https://www.8bitcn.com/r/not-found-brick-breaker.json
- Categories: layout, gaming
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.8bitcn.com/r/not-found-brick-breaker.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: `badge`, `button`, `card`

## Files

### `components/ui/8bit/blocks/not-found-brick-breaker.tsx`

```tsx
"use client";

import Link from "next/link";
import {
  type KeyboardEvent,
  type PointerEvent as ReactPointerEvent,
  useCallback,
  useEffect,
  useId,
  useRef,
  useState,
} from "react";

import { Badge } from "@/components/ui/8bit/badge";
import { Button } from "@/components/ui/8bit/button";
import {
  Card,
  CardContent,
  CardFooter,
  CardHeader,
} from "@/components/ui/8bit/card";
import { Kbd, KbdGroup } from "@/components/ui/8bit/kbd";
import {
  advanceBrickBreaker,
  BRICK_BREAKER_BOARD_HEIGHT,
  BRICK_BREAKER_BOARD_WIDTH,
  BRICK_BREAKER_FIXED_STEP,
  BRICK_BREAKER_MAX_FRAME_DELTA,
  type BrickBreakerEvent,
  type BrickBreakerInput,
  type BrickBreakerPhase,
  type BrickBreakerState,
  createBrickBreakerState,
  launchBrickBreakerBall,
  pauseBrickBreaker,
  restartBrickBreaker,
  resumeBrickBreaker,
  startBrickBreaker,
} from "@/lib/games/brick-breaker";
import { cn } from "@/lib/utils";

import "@/components/ui/8bit/styles/retro.css";

const MAX_PHYSICS_STEPS_PER_FRAME = 6;
const MAX_DEVICE_PIXEL_RATIO = 2;
const NON_REPEAT_KEYS = new Set([" ", "p", "escape", "r"]);
const MOVEMENT_KEYS: Readonly<Partial<Record<string, "left" | "right">>> = {
  a: "left",
  arrowleft: "left",
  arrowright: "right",
  d: "right",
};

const PHASE_LABELS: Record<BrickBreakerPhase, string> = {
  idle: "WAITING",
  lost: "NO SIGNAL",
  paused: "PAUSED",
  playing: "PLAYING",
  ready: "READY",
  won: "ROUTE RESTORED",
};

const ACTION_LABELS: Record<BrickBreakerPhase, string> = {
  idle: "START GAME",
  lost: "PLAY AGAIN",
  paused: "RESUME",
  playing: "PAUSE",
  ready: "LAUNCH",
  won: "PLAY AGAIN",
};

interface BrickBreakerHud {
  lives: number;
  phase: BrickBreakerPhase;
  score: number;
}

interface CanvasColors {
  accent: string;
  background: string;
  border: string;
  foreground: string;
  muted: string;
  primary: string;
  secondary: string;
}

interface FrameAdvanceResult {
  accumulator: number;
  events: BrickBreakerEvent[];
}

export interface NotFoundBrickBreakerProps
  extends React.ComponentPropsWithoutRef<"section"> {
  badge?: string;
  cta?: string;
  description?: string;
  href?: string;
  title?: string;
}

function createInputState(): BrickBreakerInput {
  return {
    left: false,
    pointerX: null,
    right: false,
  };
}

function createHud(state: BrickBreakerState): BrickBreakerHud {
  return {
    lives: state.lives,
    phase: state.phase,
    score: state.score,
  };
}

function getCssColor(
  styles: CSSStyleDeclaration,
  token: string,
  fallback: string
): string {
  return styles.getPropertyValue(token).trim() || fallback;
}

function readCanvasColors(source: Element): CanvasColors {
  const styles = getComputedStyle(source);
  return {
    accent: getCssColor(styles, "--accent", "Highlight"),
    background: getCssColor(styles, "--background", "Canvas"),
    border: getCssColor(styles, "--border", "GrayText"),
    foreground: getCssColor(styles, "--foreground", "CanvasText"),
    muted: getCssColor(styles, "--muted", "GrayText"),
    primary: getCssColor(styles, "--primary", "CanvasText"),
    secondary: getCssColor(styles, "--secondary", "GrayText"),
  };
}

function configureCanvas(canvas: HTMLCanvasElement): void {
  const pixelRatio = Math.min(
    window.devicePixelRatio || 1,
    MAX_DEVICE_PIXEL_RATIO
  );
  canvas.width = BRICK_BREAKER_BOARD_WIDTH * pixelRatio;
  canvas.height = BRICK_BREAKER_BOARD_HEIGHT * pixelRatio;

  const context = canvas.getContext("2d");
  if (!context) {
    return;
  }

  context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
  context.imageSmoothingEnabled = false;
}

function drawBrickBreaker(
  canvas: HTMLCanvasElement,
  state: BrickBreakerState,
  colors: CanvasColors
): void {
  const context = canvas.getContext("2d");
  if (!context) {
    return;
  }

  context.clearRect(
    0,
    0,
    BRICK_BREAKER_BOARD_WIDTH,
    BRICK_BREAKER_BOARD_HEIGHT
  );
  context.fillStyle = colors.background;
  context.fillRect(0, 0, BRICK_BREAKER_BOARD_WIDTH, BRICK_BREAKER_BOARD_HEIGHT);

  context.lineWidth = 4;
  context.strokeStyle = colors.border;
  context.strokeRect(2, 2, BRICK_BREAKER_BOARD_WIDTH - 4, 316);

  for (const brick of state.bricks) {
    if (!brick.active) {
      continue;
    }

    const row = Math.round((brick.y - 38) / 18);
    context.fillStyle = row % 2 === 0 ? colors.primary : colors.secondary;
    context.fillRect(brick.x, brick.y, brick.width, brick.height);
    context.lineWidth = 2;
    context.strokeStyle = colors.foreground;
    context.strokeRect(
      brick.x + 1,
      brick.y + 1,
      brick.width - 2,
      brick.height - 2
    );
    context.fillStyle = colors.background;
    context.fillRect(brick.x + 5, brick.y + 4, brick.width - 10, 2);
  }

  context.fillStyle = colors.primary;
  context.fillRect(
    Math.round(state.paddle.x),
    state.paddle.y,
    state.paddle.width,
    state.paddle.height
  );
  context.fillStyle = colors.accent;
  context.fillRect(
    Math.round(state.paddle.x) + 6,
    state.paddle.y + 2,
    state.paddle.width - 12,
    2
  );

  context.beginPath();
  context.fillStyle = colors.foreground;
  context.arc(state.ball.x, state.ball.y, state.ball.radius, 0, Math.PI * 2);
  context.fill();

  if (state.phase === "paused") {
    context.fillStyle = colors.muted;
    context.fillRect(218, 142, 16, 36);
    context.fillRect(246, 142, 16, 36);
  }
}

function getAnnouncement(
  events: BrickBreakerEvent[],
  state: BrickBreakerState
): string | undefined {
  if (events.some((event) => event.type === "won")) {
    return "Route restored. Final score 404.";
  }
  if (events.some((event) => event.type === "lost")) {
    return "Game over. No signal.";
  }

  const lifeEvent = events.find(
    (event): event is Extract<BrickBreakerEvent, { type: "life-lost" }> =>
      event.type === "life-lost"
  );
  if (lifeEvent) {
    return `Ball lost. ${lifeEvent.livesRemaining} ${
      lifeEvent.livesRemaining === 1 ? "life" : "lives"
    } remaining.`;
  }

  if (state.phase === "won") {
    return "Route restored. Final score 404.";
  }
  return;
}

function isPrimaryActionKey(key: string, phase: BrickBreakerPhase): boolean {
  switch (key) {
    case " ":
      return phase === "idle" || phase === "ready";
    case "p":
    case "escape":
      return phase === "playing" || phase === "paused";
    case "r":
      return phase === "won" || phase === "lost";
    default:
      return false;
  }
}

function advanceFixedFrame(
  state: BrickBreakerState,
  input: BrickBreakerInput,
  elapsed: number,
  accumulator: number
): FrameAdvanceResult {
  const events: BrickBreakerEvent[] = [];
  let remainingTime = accumulator + elapsed;
  let steps = 0;

  while (
    remainingTime >= BRICK_BREAKER_FIXED_STEP &&
    steps < MAX_PHYSICS_STEPS_PER_FRAME &&
    state.phase === "playing"
  ) {
    events.push(...advanceBrickBreaker(state, input, BRICK_BREAKER_FIXED_STEP));
    remainingTime -= BRICK_BREAKER_FIXED_STEP;
    steps += 1;
  }

  if (
    steps === MAX_PHYSICS_STEPS_PER_FRAME &&
    remainingTime >= BRICK_BREAKER_FIXED_STEP
  ) {
    remainingTime = 0;
  }

  return { accumulator: remainingTime, events };
}

export function NotFoundBrickBreaker({
  badge = "LOST ROUTE",
  className,
  cta = "RETURN HOME",
  description = "The route is gone. Clear the 404 wall or return home.",
  href = "/",
  title = "BREAK OUT OF HERE",
  ...sectionProps
}: NotFoundBrickBreakerProps) {
  const headingId = useId();
  const instructionsId = useId();
  const [initialEngine] = useState(createBrickBreakerState);
  const engineRef = useRef(initialEngine);
  const inputRef = useRef<BrickBreakerInput>(createInputState());
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const playfieldRef = useRef<HTMLButtonElement>(null);
  const colorsRef = useRef<CanvasColors>({
    accent: "Highlight",
    background: "Canvas",
    border: "GrayText",
    foreground: "CanvasText",
    muted: "GrayText",
    primary: "CanvasText",
    secondary: "GrayText",
  });
  const animationFrameRef = useRef<number | null>(null);
  const previousTimestampRef = useRef<number | null>(null);
  const accumulatorRef = useRef(0);
  const frameCallbackRef = useRef<FrameRequestCallback>(() => undefined);
  const activePointerRef = useRef<number | null>(null);
  const mountedRef = useRef(false);
  const [hud, setHud] = useState<BrickBreakerHud>(() =>
    createHud(initialEngine)
  );
  const [announcement, setAnnouncement] = useState(
    "Brick Breaker is ready to start."
  );

  const clearHeldInput = useCallback(() => {
    inputRef.current.left = false;
    inputRef.current.right = false;
    inputRef.current.pointerX = null;
  }, []);

  const cancelLoop = useCallback(() => {
    if (animationFrameRef.current !== null) {
      cancelAnimationFrame(animationFrameRef.current);
      animationFrameRef.current = null;
    }
    previousTimestampRef.current = null;
    accumulatorRef.current = 0;
  }, []);

  const renderCanvas = useCallback(() => {
    if (canvasRef.current) {
      drawBrickBreaker(canvasRef.current, engineRef.current, colorsRef.current);
    }
  }, []);

  const publishEngine = useCallback((message?: string) => {
    if (!mountedRef.current) {
      return;
    }
    setHud(createHud(engineRef.current));
    if (message) {
      setAnnouncement(message);
    }
  }, []);

  const scheduleFrame = useCallback(() => {
    if (
      animationFrameRef.current === null &&
      engineRef.current.phase === "playing" &&
      mountedRef.current
    ) {
      animationFrameRef.current = requestAnimationFrame((timestamp) => {
        frameCallbackRef.current(timestamp);
      });
    }
  }, []);

  const pauseGame = useCallback(
    (message = "Game paused.") => {
      if (engineRef.current.phase !== "playing") {
        return;
      }

      pauseBrickBreaker(engineRef.current);
      clearHeldInput();
      cancelLoop();
      publishEngine(message);
      renderCanvas();
    },
    [cancelLoop, clearHeldInput, publishEngine, renderCanvas]
  );

  const resumeGame = useCallback(() => {
    if (engineRef.current.phase !== "paused") {
      return;
    }

    resumeBrickBreaker(engineRef.current);
    previousTimestampRef.current = null;
    accumulatorRef.current = 0;
    publishEngine("Game resumed.");
    renderCanvas();
    scheduleFrame();
    playfieldRef.current?.focus();
  }, [publishEngine, renderCanvas, scheduleFrame]);

  const launchGame = useCallback(() => {
    if (engineRef.current.phase !== "ready") {
      return;
    }

    launchBrickBreakerBall(engineRef.current);
    previousTimestampRef.current = null;
    accumulatorRef.current = 0;
    publishEngine("Ball launched.");
    renderCanvas();
    scheduleFrame();
    playfieldRef.current?.focus();
  }, [publishEngine, renderCanvas, scheduleFrame]);

  const startGame = useCallback(() => {
    if (engineRef.current.phase !== "idle") {
      return;
    }

    startBrickBreaker(engineRef.current);
    publishEngine("Game ready. Launch the ball.");
    renderCanvas();
    playfieldRef.current?.focus();
  }, [publishEngine, renderCanvas]);

  const restartGame = useCallback(() => {
    cancelLoop();
    clearHeldInput();
    engineRef.current = restartBrickBreaker();
    startBrickBreaker(engineRef.current);
    publishEngine("New game ready. Launch the ball.");
    renderCanvas();
    playfieldRef.current?.focus();
  }, [cancelLoop, clearHeldInput, publishEngine, renderCanvas]);

  const primaryAction = useCallback(() => {
    switch (engineRef.current.phase) {
      case "idle":
        startGame();
        break;
      case "ready":
        launchGame();
        break;
      case "playing":
        pauseGame();
        break;
      case "paused":
        resumeGame();
        break;
      case "won":
      case "lost":
        restartGame();
        break;
      default:
        break;
    }
  }, [launchGame, pauseGame, restartGame, resumeGame, startGame]);

  const activatePlayfield = useCallback(() => {
    if (engineRef.current.phase === "idle") {
      startGame();
    } else if (engineRef.current.phase === "ready") {
      launchGame();
    }
  }, [launchGame, startGame]);

  useEffect(() => {
    frameCallbackRef.current = (timestamp) => {
      animationFrameRef.current = null;
      const engine = engineRef.current;

      if (!mountedRef.current || engine.phase !== "playing") {
        return;
      }

      if (previousTimestampRef.current === null) {
        previousTimestampRef.current = timestamp;
        renderCanvas();
        scheduleFrame();
        return;
      }

      const elapsed = Math.min(
        Math.max(0, (timestamp - previousTimestampRef.current) / 1000),
        BRICK_BREAKER_MAX_FRAME_DELTA
      );
      previousTimestampRef.current = timestamp;
      const frameResult = advanceFixedFrame(
        engine,
        inputRef.current,
        elapsed,
        accumulatorRef.current
      );
      accumulatorRef.current = frameResult.accumulator;

      renderCanvas();
      if (frameResult.events.length > 0) {
        publishEngine(getAnnouncement(frameResult.events, engine));
      }

      if (engine.phase !== "playing") {
        previousTimestampRef.current = null;
        accumulatorRef.current = 0;
        return;
      }
      scheduleFrame();
    };
  }, [publishEngine, renderCanvas, scheduleFrame]);

  useEffect(() => {
    mountedRef.current = true;

    const refreshCanvas = () => {
      if (!canvasRef.current) {
        return;
      }
      colorsRef.current = readCanvasColors(canvasRef.current);
      configureCanvas(canvasRef.current);
      renderCanvas();
    };
    const handleVisibilityChange = () => {
      if (document.visibilityState === "hidden") {
        pauseGame("Game paused because this tab is hidden.");
      }
    };

    refreshCanvas();
    window.addEventListener("resize", refreshCanvas);
    document.addEventListener("visibilitychange", handleVisibilityChange);

    const themeObserver =
      typeof MutationObserver === "undefined"
        ? null
        : new MutationObserver(refreshCanvas);
    themeObserver?.observe(document.documentElement, {
      attributeFilter: ["class", "data-theme", "style"],
      attributes: true,
    });

    return () => {
      mountedRef.current = false;
      cancelLoop();
      clearHeldInput();
      window.removeEventListener("resize", refreshCanvas);
      document.removeEventListener("visibilitychange", handleVisibilityChange);
      themeObserver?.disconnect();

      const pointerId = activePointerRef.current;
      const playfield = playfieldRef.current;
      if (pointerId !== null && playfield?.hasPointerCapture?.(pointerId)) {
        playfield.releasePointerCapture(pointerId);
      }
      activePointerRef.current = null;
    };
  }, [cancelLoop, clearHeldInput, pauseGame, renderCanvas]);

  const updatePointerPosition = (
    event: ReactPointerEvent<HTMLButtonElement>
  ): void => {
    const bounds = event.currentTarget.getBoundingClientRect();
    const renderedX =
      bounds.width > 0
        ? ((event.clientX - bounds.left) / bounds.width) *
          BRICK_BREAKER_BOARD_WIDTH
        : BRICK_BREAKER_BOARD_WIDTH / 2;
    inputRef.current.pointerX = Math.min(
      Math.max(renderedX, 0),
      BRICK_BREAKER_BOARD_WIDTH
    );

    if (engineRef.current.phase === "ready") {
      advanceBrickBreaker(
        engineRef.current,
        inputRef.current,
        BRICK_BREAKER_FIXED_STEP
      );
      renderCanvas();
    }
  };

  const handlePlayfieldPointerDown = (
    event: ReactPointerEvent<HTMLButtonElement>
  ): void => {
    if (event.button !== 0) {
      return;
    }

    event.currentTarget.focus();
    activePointerRef.current = event.pointerId;
    event.currentTarget.setPointerCapture?.(event.pointerId);
    updatePointerPosition(event);

    if (engineRef.current.phase === "idle" && event.pointerType !== "mouse") {
      startGame();
      updatePointerPosition(event);
    }
    if (engineRef.current.phase === "ready") {
      launchGame();
    }
  };

  const handlePlayfieldPointerMove = (
    event: ReactPointerEvent<HTMLButtonElement>
  ): void => {
    const isActivePointer = activePointerRef.current === event.pointerId;
    if (event.pointerType === "mouse" || isActivePointer) {
      updatePointerPosition(event);
    }
  };

  const handlePlayfieldPointerLeave = (): void => {
    if (activePointerRef.current === null) {
      inputRef.current.pointerX = null;
    }
  };

  const clearPointer = (pointerId: number): void => {
    if (activePointerRef.current !== pointerId) {
      return;
    }
    activePointerRef.current = null;
    inputRef.current.pointerX = null;
  };

  const handlePlayfieldPointerUp = (
    event: ReactPointerEvent<HTMLButtonElement>
  ): void => {
    if (event.currentTarget.hasPointerCapture?.(event.pointerId)) {
      event.currentTarget.releasePointerCapture(event.pointerId);
    }
    clearPointer(event.pointerId);
  };

  const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>): void => {
    const key = event.key.toLowerCase();
    if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
      return;
    }
    if (event.repeat && NON_REPEAT_KEYS.has(key)) {
      if (isPrimaryActionKey(key, engineRef.current.phase)) {
        event.preventDefault();
      }
      return;
    }

    const canMove =
      engineRef.current.phase === "ready" ||
      engineRef.current.phase === "playing";
    const movementDirection = MOVEMENT_KEYS[key];
    let handled = false;

    if (movementDirection && canMove) {
      inputRef.current.pointerX = null;
      inputRef.current[movementDirection] = true;
      handled = true;
    } else if (isPrimaryActionKey(key, engineRef.current.phase)) {
      primaryAction();
      handled = true;
    }

    if (handled) {
      event.preventDefault();
    }
  };

  const handleKeyUp = (event: KeyboardEvent<HTMLButtonElement>): void => {
    const movementDirection = MOVEMENT_KEYS[event.key.toLowerCase()];
    const wasHeld = movementDirection
      ? inputRef.current[movementDirection]
      : false;
    if (movementDirection) {
      inputRef.current[movementDirection] = false;
    }
    if (wasHeld) {
      event.preventDefault();
    }
  };

  const actionLabel = ACTION_LABELS[hud.phase];

  return (
    <section
      {...sectionProps}
      aria-labelledby={headingId}
      className={cn(
        "retro flex w-full items-center justify-center bg-background px-4 py-10 text-foreground",
        className
      )}
    >
      <div className="mx-auto grid w-full max-w-3xl gap-5">
        <header className="grid justify-items-center gap-3 text-center">
          <Badge variant="secondary">{badge}</Badge>
          <h1 className="grid gap-3 font-bold" id={headingId}>
            <span className="text-sm tracking-[0.2em]">ERROR 404</span>
            <span className="text-2xl tracking-tight sm:text-4xl">{title}</span>
          </h1>
          <p className="max-w-xl text-muted-foreground text-xs">
            {description}
          </p>
        </header>

        <Card className="w-full">
          <CardHeader className="gap-3 p-4">
            <div className="flex flex-wrap items-center justify-center gap-3 sm:justify-between">
              <Badge variant="outline">
                SCORE {hud.score.toString().padStart(3, "0")}
              </Badge>
              <Badge variant="outline">LIVES {hud.lives}</Badge>
              <Badge variant="outline">{PHASE_LABELS[hud.phase]}</Badge>
            </div>
          </CardHeader>

          <CardContent className="grid gap-4 px-4 pb-4">
            <button
              aria-describedby={instructionsId}
              aria-keyshortcuts="ArrowLeft ArrowRight A D Space P Escape R"
              aria-label="Brick Breaker playfield"
              className="pixelated block aspect-[3/2] w-full min-w-0 touch-none appearance-none overflow-hidden border-4 border-foreground bg-background p-0 text-left outline-none focus-visible:ring-4 focus-visible:ring-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background"
              data-testid="brick-breaker-playfield"
              onBlur={clearHeldInput}
              onClick={activatePlayfield}
              onKeyDown={handleKeyDown}
              onKeyUp={handleKeyUp}
              onLostPointerCapture={(event) => clearPointer(event.pointerId)}
              onPointerCancel={(event) => clearPointer(event.pointerId)}
              onPointerDown={handlePlayfieldPointerDown}
              onPointerLeave={handlePlayfieldPointerLeave}
              onPointerMove={handlePlayfieldPointerMove}
              onPointerUp={handlePlayfieldPointerUp}
              ref={playfieldRef}
              type="button"
            >
              <span aria-hidden="true" className="contents">
                <canvas
                  className="pixelated h-full w-full"
                  height={BRICK_BREAKER_BOARD_HEIGHT}
                  ref={canvasRef}
                  width={BRICK_BREAKER_BOARD_WIDTH}
                >
                  Brick Breaker game. Clear the 404 wall or use the Return Home
                  link.
                </canvas>
              </span>
            </button>

            <div
              className="grid justify-items-center gap-2 text-center"
              id={instructionsId}
            >
              <p className="text-muted-foreground text-xs">
                Touch and drag on the playfield to move the paddle. Your first
                touch starts and launches the ball.
              </p>
              <KbdGroup className="hidden flex-wrap justify-center sm:inline-flex">
                <Kbd>LEFT</Kbd>
                <Kbd>A</Kbd>
                <span className="text-xs">MOVE</span>
                <Kbd>RIGHT</Kbd>
                <Kbd>D</Kbd>
                <span className="text-xs">MOVE</span>
                <Kbd>SPACE</Kbd>
                <span className="text-xs">LAUNCH</span>
                <Kbd>P</Kbd>
                <span className="text-xs">PAUSE</span>
              </KbdGroup>
            </div>
          </CardContent>

          <CardFooter className="grid grid-cols-2 gap-3 px-4 pb-5 sm:flex sm:justify-center sm:gap-4">
            <Button
              className="min-h-11 w-full motion-reduce:transition-none sm:w-auto sm:min-w-32"
              onClick={primaryAction}
              type="button"
            >
              {actionLabel}
            </Button>
            <Button
              asChild
              className="min-h-11 w-full motion-reduce:transition-none sm:w-auto"
              variant="secondary"
            >
              <Link href={href}>{cta}</Link>
            </Button>
          </CardFooter>
        </Card>

        <p aria-atomic="true" aria-live="polite" className="sr-only">
          {announcement}
        </p>
      </div>
    </section>
  );
}

export default NotFoundBrickBreaker;
```

### `lib/games/brick-breaker.ts`

```ts
export const BRICK_BREAKER_BOARD_WIDTH = 480;
export const BRICK_BREAKER_BOARD_HEIGHT = 320;
export const BRICK_BREAKER_BRICK_WIDTH = 22;
export const BRICK_BREAKER_BRICK_HEIGHT = 12;
export const BRICK_BREAKER_FIXED_STEP = 1 / 120;
export const BRICK_BREAKER_MAX_FRAME_DELTA = 0.05;
export const BRICK_BREAKER_MIN_VERTICAL_SPEED = 90;
export const BRICK_BREAKER_MAX_BALL_SPEED = 280;
export const BRICK_BREAKER_PADDLE_SPEED = 320;

export type BrickBreakerPhase =
  | "idle"
  | "ready"
  | "playing"
  | "paused"
  | "won"
  | "lost";

export interface BallState {
  docked: boolean;
  radius: number;
  vx: number;
  vy: number;
  x: number;
  y: number;
}

export interface BrickState {
  active: boolean;
  height: number;
  id: string;
  width: number;
  x: number;
  y: number;
}

export interface PaddleState {
  height: number;
  velocityX: number;
  width: number;
  x: number;
  y: number;
}

export interface BrickBreakerState {
  ball: BallState;
  bricks: BrickState[];
  destroyedBricks: number;
  lives: number;
  paddle: PaddleState;
  phase: BrickBreakerPhase;
  score: number;
}

export interface BrickBreakerInput {
  left: boolean;
  pointerX: number | null;
  right: boolean;
}

export type BrickBreakerEvent =
  | { brickId: string; type: "brick-destroyed" }
  | { livesRemaining: number; type: "life-lost" }
  | { type: "won" }
  | { type: "lost" };

interface BrickCollision {
  brick: BrickState;
  normalX: number;
  normalY: number;
  time: number;
}

const GLYPH_404 = [
  ["#..#", "#..#", "####", "...#", "...#"],
  [".##.", "#..#", "#..#", "#..#", ".##."],
  ["#..#", "#..#", "####", "...#", "...#"],
] as const;

const BRICK_COLUMN_STEP = 26;
const BRICK_ROW_STEP = 18;
const GLYPH_STEP = 120;
const WALL_LEFT = 70;
const WALL_TOP = 38;
const PADDLE_WIDTH = 72;
const PADDLE_HEIGHT = 8;
const PADDLE_Y = 292;
const BALL_RADIUS = 6;
const INITIAL_BALL_SPEED = 200;
const INITIAL_BALL_VELOCITY_X = 90;

function clamp(value: number, minimum: number, maximum: number): number {
  return Math.min(Math.max(value, minimum), maximum);
}

function enforceBallSpeed(ball: BallState, requestedSpeed?: number): void {
  const currentSpeed = Math.hypot(ball.vx, ball.vy) || INITIAL_BALL_SPEED;
  const speed = clamp(
    requestedSpeed ?? currentSpeed,
    BRICK_BREAKER_MIN_VERTICAL_SPEED,
    BRICK_BREAKER_MAX_BALL_SPEED
  );
  const verticalDirection = ball.vy < 0 ? -1 : 1;
  const maximumHorizontalSpeed = Math.sqrt(
    Math.max(0, speed ** 2 - BRICK_BREAKER_MIN_VERTICAL_SPEED ** 2)
  );
  ball.vx = clamp(ball.vx, -maximumHorizontalSpeed, maximumHorizontalSpeed);
  ball.vy =
    verticalDirection * Math.sqrt(Math.max(0, speed ** 2 - ball.vx ** 2));
}

function dockBall(state: BrickBreakerState): void {
  state.ball.docked = true;
  state.ball.x = state.paddle.x + state.paddle.width / 2;
  state.ball.y = state.paddle.y - state.ball.radius - 1;
  state.ball.vx = INITIAL_BALL_VELOCITY_X;
  state.ball.vy = -Math.sqrt(
    INITIAL_BALL_SPEED ** 2 - INITIAL_BALL_VELOCITY_X ** 2
  );
}

function getAxisEntryAndExit(
  origin: number,
  delta: number,
  minimum: number,
  maximum: number
): [number, number] | null {
  if (delta === 0) {
    return origin >= minimum && origin <= maximum
      ? [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]
      : null;
  }

  const firstTime = (minimum - origin) / delta;
  const secondTime = (maximum - origin) / delta;
  return [Math.min(firstTime, secondTime), Math.max(firstTime, secondTime)];
}

function findSweptBrickCollision(
  ball: BallState,
  brick: BrickState,
  previousX: number,
  previousY: number
): BrickCollision | null {
  const deltaX = ball.x - previousX;
  const deltaY = ball.y - previousY;
  const horizontalTimes = getAxisEntryAndExit(
    previousX,
    deltaX,
    brick.x - ball.radius,
    brick.x + brick.width + ball.radius
  );
  const verticalTimes = getAxisEntryAndExit(
    previousY,
    deltaY,
    brick.y - ball.radius,
    brick.y + brick.height + ball.radius
  );

  if (!(horizontalTimes && verticalTimes)) {
    return null;
  }

  const entryTime = Math.max(horizontalTimes[0], verticalTimes[0]);
  const exitTime = Math.min(horizontalTimes[1], verticalTimes[1]);
  if (entryTime > exitTime || exitTime < 0 || entryTime < 0 || entryTime > 1) {
    return null;
  }

  const hitHorizontalSide = horizontalTimes[0] > verticalTimes[0];
  const exactCorner = horizontalTimes[0] === verticalTimes[0];
  const useHorizontalNormal =
    hitHorizontalSide || (exactCorner && Math.abs(deltaX) > Math.abs(deltaY));
  let normalX = 0;
  let normalY = 0;

  if (useHorizontalNormal) {
    normalX = deltaX > 0 ? -1 : 1;
  } else {
    normalY = deltaY > 0 ? -1 : 1;
  }

  return {
    brick,
    normalX,
    normalY,
    time: entryTime,
  };
}

function movePaddle(
  state: BrickBreakerState,
  input: BrickBreakerInput,
  deltaTime: number
): void {
  const previousX = state.paddle.x;

  if (input.pointerX === null) {
    const direction = Number(input.right) - Number(input.left);
    state.paddle.x = clamp(
      state.paddle.x + direction * BRICK_BREAKER_PADDLE_SPEED * deltaTime,
      0,
      BRICK_BREAKER_BOARD_WIDTH - state.paddle.width
    );
  } else {
    state.paddle.x = clamp(
      input.pointerX - state.paddle.width / 2,
      0,
      BRICK_BREAKER_BOARD_WIDTH - state.paddle.width
    );
  }

  state.paddle.velocityX =
    deltaTime > 0
      ? clamp(
          (state.paddle.x - previousX) / deltaTime,
          -BRICK_BREAKER_PADDLE_SPEED,
          BRICK_BREAKER_PADDLE_SPEED
        )
      : 0;

  if (state.ball.docked) {
    dockBall(state);
  }
}

function getBallSpeedForDestroyedBricks(destroyedBricks: number): number {
  if (destroyedBricks >= 20) {
    return 250;
  }
  if (destroyedBricks >= 10) {
    return 225;
  }
  return INITIAL_BALL_SPEED;
}

export function createBrickPattern404(): BrickState[] {
  const bricks: BrickState[] = [];

  for (const [glyphIndex, glyph] of GLYPH_404.entries()) {
    for (const [rowIndex, row] of glyph.entries()) {
      for (const [columnIndex, cell] of [...row].entries()) {
        if (cell !== "#") {
          continue;
        }

        bricks.push({
          active: true,
          height: BRICK_BREAKER_BRICK_HEIGHT,
          id: `brick-${glyphIndex}-${rowIndex}-${columnIndex}`,
          width: BRICK_BREAKER_BRICK_WIDTH,
          x:
            WALL_LEFT +
            glyphIndex * GLYPH_STEP +
            columnIndex * BRICK_COLUMN_STEP,
          y: WALL_TOP + rowIndex * BRICK_ROW_STEP,
        });
      }
    }
  }

  return bricks;
}

export function createBrickBreakerState(): BrickBreakerState {
  const paddle: PaddleState = {
    height: PADDLE_HEIGHT,
    velocityX: 0,
    width: PADDLE_WIDTH,
    x: (BRICK_BREAKER_BOARD_WIDTH - PADDLE_WIDTH) / 2,
    y: PADDLE_Y,
  };
  const state: BrickBreakerState = {
    ball: {
      docked: true,
      radius: BALL_RADIUS,
      vx: INITIAL_BALL_VELOCITY_X,
      vy: 0,
      x: BRICK_BREAKER_BOARD_WIDTH / 2,
      y: PADDLE_Y - BALL_RADIUS - 1,
    },
    bricks: createBrickPattern404(),
    destroyedBricks: 0,
    lives: 3,
    paddle,
    phase: "idle",
    score: 0,
  };

  dockBall(state);
  return state;
}

export function startBrickBreaker(state: BrickBreakerState): BrickBreakerState {
  if (state.phase === "idle") {
    state.phase = "ready";
    dockBall(state);
  }

  return state;
}

export function launchBrickBreakerBall(
  state: BrickBreakerState
): BrickBreakerState {
  if (state.phase === "ready") {
    state.ball.docked = false;
    state.phase = "playing";
  }

  return state;
}

export function resolveBrickBreakerWalls(ball: BallState): void {
  if (ball.x - ball.radius < 0) {
    ball.x = ball.radius;
    ball.vx = Math.abs(ball.vx);
  } else if (ball.x + ball.radius > BRICK_BREAKER_BOARD_WIDTH) {
    ball.x = BRICK_BREAKER_BOARD_WIDTH - ball.radius;
    ball.vx = -Math.abs(ball.vx);
  }

  if (ball.y - ball.radius < 0) {
    ball.y = ball.radius;
    ball.vy = Math.abs(ball.vy);
  }
}

export function resolveBrickBreakerPaddle(
  ball: BallState,
  paddle: PaddleState
): boolean {
  if (ball.vy <= 0) {
    return false;
  }

  const closestX = clamp(ball.x, paddle.x, paddle.x + paddle.width);
  const closestY = clamp(ball.y, paddle.y, paddle.y + paddle.height);
  const distanceX = ball.x - closestX;
  const distanceY = ball.y - closestY;
  const overlaps = distanceX ** 2 + distanceY ** 2 <= ball.radius ** 2;

  if (!overlaps) {
    return false;
  }

  const speed = clamp(
    Math.hypot(ball.vx, ball.vy),
    INITIAL_BALL_SPEED,
    BRICK_BREAKER_MAX_BALL_SPEED
  );
  const paddleCenter = paddle.x + paddle.width / 2;
  const contactOffset = clamp(
    (ball.x - paddleCenter) / (paddle.width / 2),
    -1,
    1
  );

  ball.y = paddle.y - ball.radius;
  ball.vx = contactOffset * speed * 0.82 + paddle.velocityX * 0.12;
  ball.vy = -Math.abs(ball.vy);
  enforceBallSpeed(ball, speed);
  return true;
}

export function resolveBrickBreakerBrick(
  ball: BallState,
  bricks: BrickState[],
  previousX: number,
  previousY: number
): BrickState | null {
  const collision = bricks
    .filter((brick) => brick.active)
    .map((brick) => findSweptBrickCollision(ball, brick, previousX, previousY))
    .filter((candidate): candidate is BrickCollision => candidate !== null)
    .sort(
      (first, second) =>
        first.time - second.time ||
        first.brick.id.localeCompare(second.brick.id)
    )[0];

  if (!collision) {
    return null;
  }

  const deltaX = ball.x - previousX;
  const deltaY = ball.y - previousY;
  ball.x = previousX + deltaX * collision.time + collision.normalX * 0.001;
  ball.y = previousY + deltaY * collision.time + collision.normalY * 0.001;

  if (collision.normalX !== 0) {
    ball.vx *= -1;
  }
  if (collision.normalY !== 0) {
    ball.vy *= -1;
  }

  collision.brick.active = false;
  enforceBallSpeed(ball);
  return collision.brick;
}

function advanceBrickBreakerStep(
  state: BrickBreakerState,
  input: BrickBreakerInput,
  deltaTime: number,
  events: BrickBreakerEvent[]
): void {
  if (state.phase !== "ready" && state.phase !== "playing") {
    return;
  }

  movePaddle(state, input, deltaTime);

  if (state.phase !== "playing" || state.ball.docked) {
    return;
  }

  enforceBallSpeed(state.ball);
  const previousX = state.ball.x;
  const previousY = state.ball.y;
  state.ball.x += state.ball.vx * deltaTime;
  state.ball.y += state.ball.vy * deltaTime;

  resolveBrickBreakerWalls(state.ball);
  resolveBrickBreakerPaddle(state.ball, state.paddle);
  const hitBrick = resolveBrickBreakerBrick(
    state.ball,
    state.bricks,
    previousX,
    previousY
  );

  if (hitBrick) {
    state.destroyedBricks += 1;
    state.score += 10;
    events.push({ brickId: hitBrick.id, type: "brick-destroyed" });
    enforceBallSpeed(
      state.ball,
      getBallSpeedForDestroyedBricks(state.destroyedBricks)
    );

    if (state.destroyedBricks === state.bricks.length) {
      state.score += 104;
      state.phase = "won";
      events.push({ type: "won" });
      return;
    }
  }

  if (state.ball.y - state.ball.radius > BRICK_BREAKER_BOARD_HEIGHT) {
    state.lives -= 1;
    events.push({
      livesRemaining: state.lives,
      type: "life-lost",
    });
    dockBall(state);

    if (state.lives === 0) {
      state.phase = "lost";
      events.push({ type: "lost" });
    } else {
      state.phase = "ready";
    }
  }
}

export function advanceBrickBreaker(
  state: BrickBreakerState,
  input: BrickBreakerInput,
  deltaTime: number
): BrickBreakerEvent[] {
  const events: BrickBreakerEvent[] = [];
  const clampedDelta = clamp(
    Number.isFinite(deltaTime) ? deltaTime : 0,
    0,
    BRICK_BREAKER_MAX_FRAME_DELTA
  );

  if (clampedDelta === 0) {
    return events;
  }

  const stepCount = Math.max(
    1,
    Math.ceil(clampedDelta / BRICK_BREAKER_FIXED_STEP)
  );
  const stepDelta = clampedDelta / stepCount;

  for (let step = 0; step < stepCount; step += 1) {
    advanceBrickBreakerStep(state, input, stepDelta, events);
  }

  return events;
}

export function pauseBrickBreaker(state: BrickBreakerState): BrickBreakerState {
  if (state.phase === "playing") {
    state.phase = "paused";
    state.paddle.velocityX = 0;
  }

  return state;
}

export function resumeBrickBreaker(
  state: BrickBreakerState
): BrickBreakerState {
  if (state.phase === "paused") {
    state.phase = "playing";
  }

  return state;
}

export function restartBrickBreaker(): BrickBreakerState {
  return createBrickBreakerState();
}
```

### `components/ui/8bit/styles/retro.css`

```css
@import url("https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap");

.retro {
  font-family:
    "Press Start 2P",
    system-ui,
    -apple-system,
    sans-serif;
  line-height: 1.5;
  letter-spacing: 0.5px;
}

.pixelated {
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
```

### `components/ui/8bit/badge.tsx`

```tsx
import { type VariantProps, cva } from "class-variance-authority";

import { cn } from "@/lib/utils";

import { Badge as ShadcnBadge } from "@/components/ui/badge";

export const badgeVariants = cva("", {
  variants: {
    font: {
      normal: "",
      retro: "retro",
    },
    variant: {
      default: "border-primary bg-primary",
      destructive: "border-destructive bg-destructive",
      outline: "border-background bg-background",
      secondary: "border-secondary bg-secondary",
    },
  },
  defaultVariants: {
    variant: "default",
  },
});

export interface BitButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof badgeVariants> {
  asChild?: boolean;
}

function Badge({
  children,
  className = "",
  font,
  variant,
  ...props
}: BitButtonProps) {
  const color = badgeVariants({ variant, font });

  const classes = className.split(" ");

  // visual classes for badge and sidebars
  const visualClasses = classes.filter(
    (c) =>
      c.startsWith("bg-") ||
      c.startsWith("border-") ||
      c.startsWith("text-") ||
      c.startsWith("rounded-")
  );

  // Container should accept all non-visual utility classes (e.g., size, spacing, layout)
  const containerClasses = classes.filter(
    (c) =>
      !(
        c.startsWith("bg-") ||
        c.startsWith("border-") ||
        c.startsWith("text-") ||
        c.startsWith("rounded-")
      )
  );

  return (
    <div className={cn("relative inline-flex items-stretch", containerClasses)}>
      <ShadcnBadge
        {...props}
        className={cn(
          "h-full",
          "rounded-none",
          "w-full",
          font !== "normal" && "retro",
          visualClasses
        )}
        variant={variant}
      >
        {children}
      </ShadcnBadge>

      {/* Left pixel bar */}
      <div
        className={cn(
          "-left-1.5 absolute inset-y-[4px] w-1.5",
          color,
          visualClasses
        )}
      />
      {/* Right pixel bar */}
      <div
        className={cn(
          "-right-1.5 absolute inset-y-[4px] w-1.5",
          color,
          visualClasses
        )}
      />
    </div>
  );
}

export { Badge };
```

### `components/ui/8bit/button.tsx`

```tsx
import { type VariantProps, cva } from "class-variance-authority";
import { Slot } from "radix-ui";
import type { ButtonHTMLAttributes, Ref } from "react";

import { Button as ShadcnButton } from "@/components/ui/button";
import { cn } from "@/lib/utils";

import "@/components/ui/8bit/styles/retro.css";

export const buttonVariants = cva("", {
  variants: {
    font: {
      normal: "",
      retro: "retro",
    },
    variant: {
      default: "bg-foreground",
      destructive: "bg-foreground",
      outline: "bg-foreground",
      secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
      ghost: "hover:bg-accent hover:text-accent-foreground",
      link: "text-primary underline-offset-4 hover:underline",
    },
    size: {
      default: "",
      sm: "",
      lg: "",
      icon: "",
    },
  },
  defaultVariants: {
    variant: "default",
    size: "default",
  },
});

export interface BitButtonProps
  extends ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
  ref?: Ref<HTMLButtonElement>;
}

interface ButtonDecorationsProps {
  size: BitButtonProps["size"];
  variant: BitButtonProps["variant"];
}

function ButtonDecorations({ size, variant }: ButtonDecorationsProps) {
  return (
    <span
      aria-hidden="true"
      className="pointer-events-none contents"
      data-slot="button-decorations"
    >
      {variant !== "ghost" && variant !== "link" && size !== "icon" && (
        <>
          {/* Pixelated border */}
          <span className="absolute -top-1.5 left-1.5 h-1.5 w-1/2 bg-foreground dark:bg-ring" />
          <span className="absolute -top-1.5 right-1.5 h-1.5 w-1/2 bg-foreground dark:bg-ring" />
          <span className="absolute -bottom-1.5 left-1.5 h-1.5 w-1/2 bg-foreground dark:bg-ring" />
          <span className="absolute -bottom-1.5 right-1.5 h-1.5 w-1/2 bg-foreground dark:bg-ring" />
          <span className="absolute top-0 left-0 size-1.5 bg-foreground dark:bg-ring" />
          <span className="absolute top-0 right-0 size-1.5 bg-foreground dark:bg-ring" />
          <span className="absolute bottom-0 left-0 size-1.5 bg-foreground dark:bg-ring" />
          <span className="absolute right-0 bottom-0 size-1.5 bg-foreground dark:bg-ring" />
          <span className="absolute top-1.5 -left-1.5 h-[calc(100%-12px)] w-1.5 bg-foreground dark:bg-ring" />
          <span className="absolute top-1.5 -right-1.5 h-[calc(100%-12px)] w-1.5 bg-foreground dark:bg-ring" />
          {variant !== "outline" && (
            <>
              {/* Top shadow */}
              <span className="absolute top-0 left-0 h-1.5 w-full bg-foreground/20" />
              <span className="absolute top-1.5 left-0 h-1.5 w-3 bg-foreground/20" />

              {/* Bottom shadow */}
              <span className="absolute bottom-0 left-0 h-1.5 w-full bg-foreground/20" />
              <span className="absolute right-0 bottom-1.5 h-1.5 w-3 bg-foreground/20" />
            </>
          )}
        </>
      )}

      {size === "icon" && (
        <>
          <span className="absolute top-0 left-0 h-[5px] w-full bg-foreground md:h-1.5 dark:bg-ring" />
          <span className="absolute bottom-0 h-[5px] w-full bg-foreground md:h-1.5 dark:bg-ring" />
          <span className="absolute top-1 -left-1 h-1/2 w-[5px] bg-foreground md:w-1.5 dark:bg-ring" />
          <span className="absolute bottom-1 -left-1 h-1/2 w-[5px] bg-foreground md:w-1.5 dark:bg-ring" />
          <span className="absolute top-1 -right-1 h-1/2 w-[5px] bg-foreground md:w-1.5 dark:bg-ring" />
          <span className="absolute -right-1 bottom-1 h-1/2 w-[5px] bg-foreground md:w-1.5 dark:bg-ring" />
        </>
      )}
    </span>
  );
}

function Button({
  asChild = false,
  children,
  className,
  font,
  size,
  variant,
  ...props
}: BitButtonProps) {
  const decorations = <ButtonDecorations size={size} variant={variant} />;

  return (
    <ShadcnButton
      {...props}
      className={cn(
        "rounded-none active:translate-y-1 transition-transform relative inline-flex items-center justify-center gap-1.5 border-none",
        size === "icon" && "mx-1 my-0",
        font !== "normal" && "retro",
        className
      )}
      size={size}
      variant={variant}
      asChild={asChild}
    >
      {asChild ? <Slot.Slottable>{children}</Slot.Slottable> : children}
      {decorations}
    </ShadcnButton>
  );
}

export { Button };
```

### `components/ui/8bit/card.tsx`

```tsx
import { type VariantProps, cva } from "class-variance-authority";

import { cn } from "@/lib/utils";

import {
  Card as ShadcnCard,
  CardAction as ShadcnCardAction,
  CardContent as ShadcnCardContent,
  CardDescription as ShadcnCardDescription,
  CardFooter as ShadcnCardFooter,
  CardHeader as ShadcnCardHeader,
  CardTitle as ShadcnCardTitle,
} from "@/components/ui/card";

import "@/components/ui/8bit/styles/retro.css";

export const cardVariants = cva("", {
  variants: {
    font: {
      normal: "",
      retro: "retro",
    },
  },
  defaultVariants: {
    font: "retro",
  },
});

export interface BitCardProps
  extends React.ComponentProps<"div">,
    VariantProps<typeof cardVariants> {
  asChild?: boolean;
}

function Card({ className, font, ...props }: BitCardProps) {
  return (
    <div
      className={cn(
        "relative bg-card text-card-foreground border-y-6 border-foreground dark:border-ring p-0!",
        className
      )}
    >
      <ShadcnCard
        {...props}
        className={cn(
          "rounded-none border-0 w-full! h-full flex flex-col bg-card text-card-foreground shadow-none",
          font !== "normal" && "retro",
          className
        )}
      />

      <div
        className={cn("absolute inset-0 border-x-6 -mx-1.5 border-inherit pointer-events-none")}
        aria-hidden="true"
      />
    </div>
  );
}

function CardHeader({ ...props }: BitCardProps) {
  const { className, font } = props;

  return (
    <ShadcnCardHeader
      className={cn(font !== "normal" && "retro", className)}
      {...props}
    />
  );
}

function CardTitle({ ...props }: BitCardProps) {
  const { className, font } = props;

  return (
    <ShadcnCardTitle
      className={cn(font !== "normal" && "retro", className)}
      {...props}
    />
  );
}

function CardDescription({ ...props }: BitCardProps) {
  const { className, font } = props;

  return (
    <ShadcnCardDescription
      className={cn(font !== "normal" && "retro", className)}
      {...props}
    />
  );
}

function CardAction({ ...props }: BitCardProps) {
  const { className, font } = props;

  return (
    <ShadcnCardAction
      className={cn(font !== "normal" && "retro", className)}
      {...props}
    />
  );
}

function CardContent({ ...props }: BitCardProps) {
  const { className, font } = props;

  return (
    <ShadcnCardContent
      className={cn("flex-1", font !== "normal" && "retro", className)}
      {...props}
    />
  );
}

function CardFooter({ ...props }: BitCardProps) {
  const { className, font } = props;

  return (
    <ShadcnCardFooter
      data-slot="card-footer"
      className={cn(font !== "normal" && "retro", className)}
      {...props}
    />
  );
}

export {
  Card,
  CardHeader,
  CardFooter,
  CardTitle,
  CardAction,
  CardDescription,
  CardContent,
};
```

### `components/ui/8bit/kbd.tsx`

```tsx
import { cn } from "@/lib/utils";

import "@/components/ui/8bit/styles/retro.css";

function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
  return (
    <kbd
      data-slot="kbd"
      className={cn(
        "retro bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none",
        "[&_svg:not([class*='size-'])]:size-3",
        "[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
        className
      )}
      {...props}
    />
  );
}

function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <kbd
      data-slot="kbd-group"
      className={cn("inline-flex items-center gap-1", className)}
      {...props}
    />
  );
}

export { Kbd, KbdGroup };
```
