# Instant Camera That Ejects Photos You Shake to Develop

> Press the shutter and a photo feeds out of the camera, lands on the table and develops while you drag and shake it. Free React and Tailwind code, or HTML.

A shadcn/ui block from the [@weinc](https://we.inc/components?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/weinc/instant-camera-that-ejects-photos-you-shake-to-develop).

- Page, with a live preview: https://shadcn.exchange/r/weinc/instant-camera-that-ejects-photos-you-shake-to-develop
- Screenshot: https://shadcn.exchange/shot/weinc/instant-camera-that-ejects-photos-you-shake-to-develop/light.png
- Registry item: https://we.inc/r/instant-camera-that-ejects-photos-you-shake-to-develop.json
- Listed: 2026-10-28, source last changed 2026-10-28

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/weinc/instant-camera-that-ejects-photos-you-shake-to-develop.json
```

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

## Dependencies

- npm packages: `framer-motion`

## Files

### `components/we-inc/instant-camera-that-ejects-photos-you-shake-to-develop.tsx`

```tsx
import { useCallback, useEffect, useRef, useState } from "react";
import { useReducedMotion } from "framer-motion";

/** The roll of film: shot in this order, then round again. `note` is pencilled on the white border. */
const photos = [
  { src: "https://images.pexels.com/photos/11039284/pexels-photo-11039284.jpeg?auto=compress&cs=tinysrgb&w=500", alt: "Woman in a black dress and sunglasses against a yellow wall", note: "Noon, Calle Ocho" },
  { src: "https://images.pexels.com/photos/11608677/pexels-photo-11608677.jpeg?auto=compress&cs=tinysrgb&w=500", alt: "Woman with a bob looking over her shoulder in purple and orange light", note: "Odile, studio B" },
  { src: "https://images.pexels.com/photos/28893013/pexels-photo-28893013.jpeg?auto=compress&cs=tinysrgb&w=500", alt: "Half an orange on a white ledge against a peach wall", note: "Breakfast" },
  { src: "https://images.pexels.com/photos/30089260/pexels-photo-30089260.jpeg?auto=compress&cs=tinysrgb&w=500", alt: "Silhouette of a person in sunglasses in deep blue light", note: "Blue hour" },
  { src: "https://images.pexels.com/photos/32825246/pexels-photo-32825246.jpeg?auto=compress&cs=tinysrgb&w=500", alt: "Man in a red beret and white jacket seated against an orange backdrop", note: "Remy waits" },
  { src: "https://images.pexels.com/photos/2907454/pexels-photo-2907454.jpeg?auto=compress&cs=tinysrgb&w=500", alt: "Bananas and dragon fruit on yellow and pink plates", note: "Still life no. 4" },
];

/** Where photos come to rest: x in units of the spread, y in pixels from the top, tilt in degrees. */
const spots = [
  { kx: -1.5, y: 474, r: -9 },
  { kx: 1.5, y: 466, r: 7 },
  { kx: -0.5, y: 508, r: 6 },
  { kx: 0.52, y: 478, r: -8 },
];

const NODES = 5;
const PHOTO_W = 188;
const PHOTO_H = 226;
const SLOT_Y = 205;
const DEVELOP_SECONDS = 7;
const STAGE_H = 700;

type Phase = "off" | "eject" | "free" | "held" | "leave";
type Body = { phase: Phase; x: number; y: number; r: number; vx: number; vy: number; vr: number; tx: number; ty: number; tr: number; lift: number; d: number; t0: number; energy: number; fade: number; spot: number };

const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
const smooth = (v: number) => {
  const t = clamp(v, 0, 1);
  return t * t * (3 - 2 * t);
};

export default function InstantCameraThatEjectsPhotos() {
  const reduce = useReducedMotion();
  const [film, setFilm] = useState<number[]>([0, 1, 2, 3, 4]);
  const [busy, setBusy] = useState(false);
  const [status, setStatus] = useState("Two photos on the table.");

  const stageRef = useRef<HTMLDivElement>(null);
  const cameraRef = useRef<HTMLDivElement>(null);
  const flashRef = useRef<HTMLDivElement>(null);
  const nodeRefs = useRef<(HTMLDivElement | null)[]>([]);
  const imageRefs = useRef<(HTMLImageElement | null)[]>([]);
  const veilRefs = useRef<(HTMLDivElement | null)[]>([]);
  const glossRefs = useRef<(HTMLDivElement | null)[]>([]);
  const shadeRefs = useRef<(HTMLDivElement | null)[]>([]);

  const world = useRef({
    bodies: Array.from({ length: NODES }, (_, i): Body => ({ phase: i < 2 ? "free" : "off", x: 0, y: 0, r: 0, vx: 0, vy: 0, vr: 0, tx: 0, ty: 0, tr: 0, lift: 0, d: i < 2 ? 1 : 0, t0: 0, energy: 0, fade: 1, spot: i < 2 ? i : -1 })),
    shots: 0,
    z: 20,
    raf: 0,
    last: 0,
    spread: 190,
    scale: 1,
    width: 1200,
    held: -1,
    grabX: 0,
    grabY: 0,
    lastShot: -1e9,
    lastTouch: -1e9,
    visible: true,
    ready: false,
  });

  const draw = useCallback((i: number) => {
    const w = world.current;
    const b = w.bodies[i];
    const node = nodeRefs.current[i];
    if (!node) return;
    if (b.phase === "off") {
      node.style.opacity = "0";
      node.style.pointerEvents = "none";
      return;
    }
    const size = w.scale * (1 + 0.07 * b.lift);
    node.style.opacity = b.fade.toFixed(3);
    node.style.pointerEvents = b.phase === "eject" || b.phase === "leave" ? "none" : "auto";
    node.style.transform = `translate(-50%,-50%) translate3d(${b.x.toFixed(1)}px,${b.y.toFixed(1)}px,0) rotate(${b.r.toFixed(2)}deg) scale(${size.toFixed(3)})`;
    if (b.phase === "eject") {
      const top = b.y - (PHOTO_H * w.scale) / 2;
      node.style.clipPath = `inset(${Math.max(0, (SLOT_Y - top) / w.scale).toFixed(1)}px -60px -80px -60px)`;
    } else if (node.style.clipPath) node.style.clipPath = "";
    const shade = shadeRefs.current[i];
    if (shade) {
      shade.style.transform = `translate(${(6 * b.lift).toFixed(1)}px,${(9 + 30 * b.lift).toFixed(1)}px) scale(${(1 + 0.05 * b.lift).toFixed(3)})`;
      shade.style.opacity = (0.62 - 0.26 * b.lift).toFixed(3);
    }
    const gloss = glossRefs.current[i];
    if (gloss) gloss.style.backgroundPosition = `${(50 + b.r * 3.2 + b.x * 0.05).toFixed(1)}% 0`;
    // Developing: a milky veil clears while contrast and colour come up from a warm, flat start.
    const image = imageRefs.current[i];
    const veil = veilRefs.current[i];
    if (image && veil) {
      const d = b.d;
      veil.style.opacity = (1 - smooth(d * 1.5)).toFixed(3);
      image.style.filter = d >= 1 ? "none" : `contrast(${(0.5 + 0.5 * smooth(d)).toFixed(3)}) saturate(${(0.15 + 0.85 * smooth((d - 0.25) / 0.75)).toFixed(3)}) brightness(${(1.3 - 0.3 * smooth(d)).toFixed(3)}) sepia(${(0.55 * (1 - smooth(d))).toFixed(3)})`;
    }
  }, []);

  const measure = useCallback(() => {
    const w = world.current;
    const width = stageRef.current?.clientWidth ?? 1200;
    w.width = width;
    w.spread = clamp(0.22 * width - 22, 52, 190);
    w.scale = width < 480 ? 0.82 : 1;
    w.bodies.forEach((b) => {
      if (b.spot >= 0 && b.phase === "free") {
        b.tx = spots[b.spot].kx * w.spread;
        b.ty = spots[b.spot].y;
        b.tr = spots[b.spot].r;
      }
    });
  }, []);

  const step = useCallback(
    (now: number) => {
      const w = world.current;
      w.raf = 0;
      const dt = Math.min(0.033, (now - w.last) / 1000 || 0.016);
      w.last = now;
      let awake = false;
      for (let i = 0; i < NODES; i++) {
        const b = w.bodies[i];
        if (b.phase === "off") continue;
        if (b.phase === "eject") {
          const t = clamp((now - b.t0) / 950, 0, 1);
          const from = SLOT_Y - (PHOTO_H * w.scale) / 2 + 4;
          const to = SLOT_Y + (PHOTO_H * w.scale) / 2 + 10;
          // The rollers push it out in three short pulls.
          const feed = (Math.floor(t * 3) + smooth((t * 3) % 1)) / 3;
          b.x = 0;
          b.y = from + (to - from) * Math.min(1, feed);
          b.r = 0;
          awake = true;
          if (t >= 1) {
            b.phase = "free";
            b.vy = 260;
            b.vx = (b.tx - b.x) * 0.6;
            b.vr = b.tr * 5;
            const old = w.bodies.findIndex((o, n) => n !== i && o.spot === b.spot && o.phase !== "off");
            if (old >= 0) {
              w.bodies[old].phase = "leave";
              w.bodies[old].spot = -1;
            }
          }
        } else {
          const held = b.phase === "held";
          const k = held ? 300 : b.phase === "leave" ? 30 : 110;
          const c = held ? 26 : b.phase === "leave" ? 6 : 15;
          if (b.phase === "leave") {
            b.ty = STAGE_H + 260;
            b.fade = clamp(b.fade - dt * 1.6, 0, 1);
            if (b.fade <= 0) b.phase = "off";
          }
          if (held) b.tr = clamp(b.vx * 0.045, -24, 24);
          b.vx += (k * (b.tx - b.x) - c * b.vx) * dt;
          b.vy += (k * (b.ty - b.y) - c * b.vy) * dt;
          b.vr += (90 * (b.tr - b.r) - 11 * b.vr) * dt;
          b.x += b.vx * dt;
          b.y += b.vy * dt;
          b.r += b.vr * dt;
          b.lift += ((held ? 1 : 0) - b.lift) * Math.min(1, dt * 12);
          const speed = Math.hypot(b.vx, b.vy);
          b.energy = held ? b.energy * 0.92 + Math.min(1, speed / 900) * 0.4 : b.energy * 0.9;
          if (held || b.phase === "leave" || speed > 3 || Math.abs(b.vr) > 2 || Math.hypot(b.tx - b.x, b.ty - b.y) > 0.4 || b.lift > 0.01) awake = true;
        }
        if (b.d < 1) {
          b.d = Math.min(1, b.d + (dt / DEVELOP_SECONDS) * (1 + b.energy * 2.4));
          awake = true;
        }
        draw(i);
      }
      if (awake) w.raf = requestAnimationFrame(step);
    },
    [draw],
  );

  const wake = useCallback(() => {
    const w = world.current;
    if (w.raf) return;
    w.last = performance.now();
    w.raf = requestAnimationFrame(step);
  }, [step]);

  const shoot = useCallback(() => {
    const w = world.current;
    const now = performance.now();
    if (now - w.lastShot < 1250) return;
    w.lastShot = now;
    const k = w.shots++;
    const i = (2 + k) % NODES;
    const spot = (2 + k) % spots.length;
    const frame = (2 + k) % photos.length;
    setFilm((list) => list.map((value, n) => (n === i ? frame : value)));
    setStatus(`New photo: ${photos[frame].alt}. It takes a few seconds to develop.`);
    const b = w.bodies[i];
    Object.assign(b, { x: 0, y: SLOT_Y - PHOTO_H / 2, r: 0, vx: 0, vy: 0, vr: 0, lift: 0, d: 0, energy: 0, fade: 1, spot, t0: now });
    b.tx = spots[spot].kx * w.spread;
    b.ty = spots[spot].y;
    b.tr = spots[spot].r + (k % 2 ? 2 : -2);
    const node = nodeRefs.current[i];
    if (node) node.style.zIndex = String(++w.z);
    if (reduce) {
      const old = w.bodies.findIndex((o, n) => n !== i && o.spot === spot && o.phase !== "off");
      if (old >= 0) {
        w.bodies[old].phase = "off";
        w.bodies[old].spot = -1;
        draw(old);
      }
      Object.assign(b, { phase: "free", x: b.tx, y: b.ty, r: b.tr, d: 1 });
      draw(i);
      return;
    }
    b.phase = "eject";
    setBusy(true);
    window.setTimeout(() => setBusy(false), 1250);
    flashRef.current?.animate?.([{ opacity: 0.9 }, { opacity: 0.25, offset: 0.25 }, { opacity: 0 }], { duration: 520, easing: "ease-out" });
    cameraRef.current?.animate?.([{ transform: "translateY(0)" }, { transform: "translateY(3px) rotate(-0.6deg)" }, { transform: "translateY(0)" }], { duration: 260, easing: "ease-out" });
    wake();
  }, [draw, reduce, wake]);

  // Lay out the table, then keep shooting until someone takes over (and again after a quiet spell).
  useEffect(() => {
    const w = world.current;
    const stage = stageRef.current;
    measure();
    w.bodies.forEach((b, i) => {
      if (b.phase === "free" && !w.ready) Object.assign(b, { x: b.tx, y: b.ty, r: b.tr });
      draw(i);
    });
    w.ready = true;
    const resize = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => { measure(); if (reduce) w.bodies.forEach((b, i) => { if (b.phase === "free") { Object.assign(b, { x: b.tx, y: b.ty, r: b.tr }); draw(i); } }); else wake(); }) : null;
    const seen = typeof IntersectionObserver !== "undefined" ? new IntersectionObserver(([entry]) => { w.visible = entry.isIntersecting; }) : null;
    if (stage) {
      resize?.observe(stage);
      seen?.observe(stage);
    }
    let first = 0;
    let loop = 0;
    if (!reduce) {
      const auto = () => {
        if (w.visible && !document.hidden && performance.now() - w.lastTouch > 9000) shoot();
      };
      first = window.setTimeout(auto, 350);
      loop = window.setInterval(auto, 4300);
    }
    return () => {
      window.clearTimeout(first);
      window.clearInterval(loop);
      resize?.disconnect();
      seen?.disconnect();
      if (w.raf) cancelAnimationFrame(w.raf);
      w.raf = 0;
    };
  }, [draw, measure, reduce, shoot, wake]);

  const local = (event: React.PointerEvent) => {
    const box = stageRef.current?.getBoundingClientRect();
    return { x: event.clientX - (box ? box.left + box.width / 2 : 0), y: event.clientY - (box ? box.top : 0) };
  };
  const grab = (i: number) => (event: React.PointerEvent<HTMLDivElement>) => {
    const w = world.current;
    const b = w.bodies[i];
    w.lastTouch = performance.now();
    if (reduce || (b.phase !== "free" && b.phase !== "held")) return;
    event.currentTarget.setPointerCapture(event.pointerId);
    const p = local(event);
    w.held = i;
    w.grabX = p.x - b.x;
    w.grabY = p.y - b.y;
    b.phase = "held";
    b.spot = -1;
    event.currentTarget.style.zIndex = String(++w.z);
    wake();
  };
  const drag = (i: number) => (event: React.PointerEvent<HTMLDivElement>) => {
    const w = world.current;
    if (w.held !== i) return;
    const p = local(event);
    const b = w.bodies[i];
    b.tx = p.x - w.grabX;
    b.ty = p.y - w.grabY;
    w.lastTouch = performance.now();
    wake();
  };
  const drop = (i: number) => () => {
    const w = world.current;
    if (w.held !== i) return;
    w.held = -1;
    const b = w.bodies[i];
    b.phase = "free";
    const reach = Math.max(0, w.width / 2 - 70);
    b.tx = clamp(b.tx, -reach, reach);
    b.ty = clamp(b.ty, 330, STAGE_H - 110);
    b.tr = clamp(b.r * 0.5 + b.vx * 0.01, -14, 14);
    wake();
  };

  return (
    <section
      ref={stageRef}
      className="relative w-full select-none overflow-hidden"
      style={{ height: STAGE_H, background: "radial-gradient(90% 70% at 50% 22%,#3a3d38 0%,#262925 45%,#161816 100%)", ["--s" as string]: "clamp(52px, 22vw - 22px, 190px)" }}
    >
      {/* a pool of light on the table */}
      <div className="pointer-events-none absolute inset-x-0 bottom-0 h-[60%]" style={{ background: "radial-gradient(60% 70% at 50% 60%,rgba(255,244,224,.09),transparent 70%)" }} aria-hidden="true" />

      {/* the photos */}
      {film.map((frame, i) => {
        const photo = photos[frame];
        const start = i < 2 ? spots[i] : null;
        return (
          <div
            key={i}
            ref={(node) => {
              nodeRefs.current[i] = node;
            }}
            onPointerDown={grab(i)}
            onPointerMove={drag(i)}
            onPointerUp={drop(i)}
            onPointerCancel={drop(i)}
            className="absolute left-1/2 top-0 cursor-grab touch-none active:cursor-grabbing"
            style={{
              width: PHOTO_W,
              height: PHOTO_H,
              zIndex: 10 + i,
              opacity: start ? 1 : 0,
              willChange: "transform",
              transform: start ? `translate(-50%,-50%) translate(calc(var(--s) * ${start.kx}),${start.y}px) rotate(${start.r}deg)` : "translate(-50%,-50%)",
            }}
          >
            <div
              ref={(node) => {
                shadeRefs.current[i] = node;
              }}
              className="absolute inset-0 rounded-[6px] bg-black"
              style={{ filter: "blur(13px)", opacity: 0.62, transform: "translate(0,9px)" }}
              aria-hidden="true"
            />
            <div
              className="absolute inset-0 rounded-[3px]"
              style={{
                background: "linear-gradient(170deg,#fdfcf8 0%,#f3f0e8 60%,#e9e5da 100%)",
                boxShadow: "0 1px 0 #d6d1c4, 0 2px 0 #b9b4a6, 0 3px 1px rgba(0,0,0,.5), inset 0 1px 0 #fff, inset 0 0 0 1px rgba(120,110,90,.12)",
              }}
            >
              <div className="absolute left-[13px] top-[13px] overflow-hidden rounded-[1px] bg-[#8f9a98]" style={{ width: PHOTO_W - 26, height: PHOTO_W - 26, boxShadow: "inset 0 0 0 1px rgba(0,0,0,.35)" }}>
                <img
                  alt={photo.alt}
                  src={photo.src}
                  ref={(node) => {
                    imageRefs.current[i] = node;
                  }}
                  width={PHOTO_W - 26}
                  height={PHOTO_W - 26}
                  loading="lazy"
                  draggable={false}
                  className="h-full w-full object-cover"
                />
                <div
                  ref={(node) => {
                    veilRefs.current[i] = node;
                  }}
                  className="absolute inset-0"
                  style={{ opacity: 0, background: "radial-gradient(120% 120% at 30% 20%,#b9c2bd 0%,#8f9a98 55%,#6e7a7c 100%)" }}
                  aria-hidden="true"
                />
                <div
                  ref={(node) => {
                    glossRefs.current[i] = node;
                  }}
                  className="absolute inset-0"
                  style={{ background: "linear-gradient(112deg,rgba(255,255,255,0) 38%,rgba(255,255,255,.28) 47%,rgba(255,255,255,.06) 53%,rgba(255,255,255,0) 62%)", backgroundSize: "300% 100%", backgroundPosition: "50% 0", boxShadow: "inset 0 2px 5px rgba(0,0,0,.35), inset 0 0 0 1px rgba(0,0,0,.2)" }}
                  aria-hidden="true"
                />
              </div>
              <p className="absolute inset-x-0 bottom-[11px] text-center font-serif text-[13px] italic text-[#4a5568]" style={{ transform: "rotate(-1.5deg)" }}>
                {photo.note}
              </p>
            </div>
          </div>
        );
      })}

      {/* the camera */}
      <div ref={cameraRef} className="absolute left-1/2 top-[40px] z-[5] -ml-[140px] h-[176px] w-[280px]">
        <div
          className="absolute inset-0 rounded-[30px]"
          style={{
            background: "linear-gradient(180deg,#f6f1e6 0%,#e9e2d2 52%,#d3cab6 100%)",
            boxShadow: "inset 0 2px 0 rgba(255,255,255,.9), inset 0 -5px 8px rgba(96,80,50,.35), inset 3px 0 5px rgba(255,255,255,.4), inset -3px 0 6px rgba(96,80,50,.2), 0 28px 40px -14px rgba(0,0,0,.75), 0 6px 10px rgba(0,0,0,.4)",
          }}
          aria-hidden="true"
        >
          {/* colour band */}
          <div className="absolute inset-x-0 top-[82px] h-[5px] bg-[#e4572e]" />
          <div className="absolute inset-x-0 top-[87px] h-[5px] bg-[#f2a541]" />
          {/* flash */}
          <div className="absolute left-[22px] top-[20px] h-[36px] w-[62px] rounded-[9px] p-[4px]" style={{ background: "linear-gradient(180deg,#2c2c2f,#141416)", boxShadow: "0 1px 0 rgba(255,255,255,.7), inset 0 1px 2px rgba(0,0,0,.8)" }}>
            <div className="h-full w-full rounded-[5px]" style={{ background: "repeating-linear-gradient(90deg,rgba(255,255,255,.55) 0 1px,rgba(255,255,255,0) 1px 4px),radial-gradient(60% 90% at 50% 50%,#fffdf2 0%,#d9d6c8 55%,#8c8a80 100%)" }} />
          </div>
          {/* viewfinder */}
          <div className="absolute right-[26px] top-[20px] h-[30px] w-[38px] rounded-[7px] p-[4px]" style={{ background: "linear-gradient(180deg,#2c2c2f,#141416)", boxShadow: "0 1px 0 rgba(255,255,255,.7)" }}>
            <div className="h-full w-full rounded-[3px]" style={{ background: "linear-gradient(135deg,#5b6f86 0%,#1b2330 45%,#0c1017 100%)" }} />
          </div>
          {/* lens */}
          <div className="absolute left-1/2 top-[24px] -ml-[59px] h-[118px] w-[118px] rounded-full" style={{ background: "conic-gradient(from 210deg,#3a3a3d,#0f0f11,#4a4a4e,#161618,#3a3a3d)", boxShadow: "0 0 0 5px #ede7d9, 0 0 0 6px rgba(96,80,50,.35), 0 8px 12px rgba(0,0,0,.45), inset 0 2px 3px rgba(255,255,255,.25)" }}>
            <div className="absolute inset-[11px] rounded-full" style={{ background: "repeating-conic-gradient(#1a1a1c 0 4deg,#2b2b2e 4deg 8deg)", boxShadow: "inset 0 0 0 2px #050506" }} />
            <div className="absolute inset-[23px] rounded-full" style={{ background: "radial-gradient(circle at 50% 50%,#05060a 0 22%,#1b2a5a 40%,#3a2468 58%,#0a0c18 78%,#000 100%)", boxShadow: "inset 0 0 12px #000, 0 0 0 2px #0a0a0b" }} />
            <div className="absolute left-[36px] top-[31px] h-[20px] w-[30px] rounded-full" style={{ background: "radial-gradient(closest-side,rgba(255,255,255,.75),rgba(255,255,255,0))", transform: "rotate(-32deg)" }} />
            <div className="absolute bottom-[34px] right-[36px] h-[9px] w-[13px] rounded-full" style={{ background: "radial-gradient(closest-side,rgba(180,210,255,.6),rgba(255,255,255,0))" }} />
          </div>
          {/* print slot */}
          <div className="absolute inset-x-[26px] bottom-[9px] h-[9px] rounded-full" style={{ background: "linear-gradient(180deg,#050505,#2a2a2a)", boxShadow: "0 1px 0 rgba(255,255,255,.75), inset 0 2px 3px #000" }} />
          <span className="absolute bottom-[26px] left-[26px] text-[8px] font-bold uppercase tracking-[0.28em] text-[#8a7d62]">Instant 70</span>
        </div>
        <button
          type="button"
          onClick={() => {
            world.current.lastTouch = performance.now();
            shoot();
          }}
          aria-label="Take a photo"
          aria-disabled={busy}
          className="absolute right-[20px] top-[100px] h-[44px] w-[44px] rounded-full outline-none transition-transform duration-100 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-[#e9e2d2] active:scale-90"
          style={{ background: "radial-gradient(circle at 38% 30%,#ff8f73 0%,#e4572e 45%,#a5321a 100%)", boxShadow: "0 0 0 4px #d9d0bd, 0 0 0 5px rgba(96,80,50,.4), 0 4px 6px rgba(0,0,0,.4), inset 0 2px 2px rgba(255,255,255,.5), inset 0 -3px 4px rgba(90,20,5,.5)" }}
        />
      </div>

      {/* flash */}
      <div ref={flashRef} className="pointer-events-none absolute inset-0 z-[90]" style={{ opacity: 0, background: "radial-gradient(70% 60% at calc(50% - 87px) 78px,#fffdf5 0%,rgba(255,250,235,.85) 30%,rgba(255,250,235,.35) 100%)" }} aria-hidden="true" />

      <p className="pointer-events-none absolute inset-x-4 bottom-5 z-[4] text-center text-[13px] text-stone-400">Press the red button. Pick a photo up and shake it to develop it faster.</p>
      <p className="sr-only" aria-live="polite">
        {status}
      </p>
    </section>
  );
}
```
