{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tablet-frame",
  "title": "Tablet Frame",
  "description": "A modern tablet drawn in CSS with an aluminum frame, thin bezel, front camera, status bar and home indicator, in landscape or portrait. Children render as the screen at tablet resolution, scaled to fit.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@tealmac/media"
  ],
  "files": [
    {
      "path": "registry/tealmac/tablet-frame.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport { useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Media } from \"@/registry/tealmac/media\"\n\ntype TabletFrameProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** Screen content, laid out below the status bar. Ignored when `src` or `videoSrc` is set. */\n  children?: React.ReactNode\n  /** Which way the tablet is held. The frame keeps the real aspect ratio either way. */\n  orientation?: \"landscape\" | \"portrait\"\n  /** Finish of the aluminum frame. \"auto\" is silver in light mode and space black in dark mode. */\n  variant?: \"auto\" | \"silver\" | \"black\"\n  /** Image shown on the screen, cropped to fill it. */\n  src?: string\n  /** Alternative text for `src`. */\n  alt?: string\n  /** Video shown on the screen: muted, looping, and paused under reduced motion. */\n  videoSrc?: string\n  /** Poster image for `videoSrc`. */\n  poster?: string\n  /**\n   * Width in CSS pixels that the screen content is laid out at (1194 landscape or 834 portrait match an 11\" tablet);\n   * it is then scaled to fit. Omit to lay content out at the frame's actual size.\n   */\n  screenWidth?: number\n  /** Show the status bar (time, date, Wi-Fi, battery). */\n  statusBar?: boolean\n  /** Time shown in the status bar. */\n  time?: string\n  /** Date shown next to the time. */\n  date?: string\n  /** Show the home indicator bar at the bottom of the screen. */\n  homeIndicator?: boolean\n  /** Class names for the screen element (inside the bezel). Use \"dark\" to give the screen its own theme. */\n  screenClassName?: string\n}\n\nconst SILVER =\n  \"[--tf-hi:color-mix(in_oklch,white_96%,black)] [--tf-mid:color-mix(in_oklch,white_82%,black)] [--tf-lo:color-mix(in_oklch,white_62%,black)]\"\nconst BLACK =\n  \"[--tf-hi:color-mix(in_oklch,white_36%,black)] [--tf-mid:color-mix(in_oklch,white_18%,black)] [--tf-lo:color-mix(in_oklch,white_9%,black)]\"\nconst BLACK_DARK =\n  \"dark:[--tf-hi:color-mix(in_oklch,white_36%,black)] dark:[--tf-mid:color-mix(in_oklch,white_18%,black)] dark:[--tf-lo:color-mix(in_oklch,white_9%,black)]\"\n\n/** Lays screen content out at `screenWidth` CSS pixels and scales it to fit, without re-rendering. */\nfunction useScreenScale(screenWidth: number | undefined) {\n  const ref = React.useRef<HTMLDivElement>(null)\n  React.useEffect(() => {\n    const screen = ref.current\n    if (!screen || !screenWidth) return\n    const measure = () =>\n      screen.style.setProperty(\n        \"--screen-scale\",\n        String(screen.clientWidth / screenWidth)\n      )\n    measure()\n    const observer = new ResizeObserver(measure)\n    observer.observe(screen)\n    return () => observer.disconnect()\n  }, [screenWidth])\n  return ref\n}\n\n/**\n * A modern tablet drawn in CSS: aluminum frame with a brushed highlight, thin glass bezel, front camera,\n * status bar and home indicator. Sizes use one unit (`--u`, one percent of the short side), so portrait and\n * landscape share the same proportions. Children render as the screen, laid out at a tablet resolution and scaled to fit.\n */\nfunction TabletFrame({\n  orientation = \"landscape\",\n  variant = \"auto\",\n  src,\n  alt = \"\",\n  videoSrc,\n  poster,\n  screenWidth,\n  statusBar = true,\n  time = \"9:41 AM\",\n  date = \"Thu Oct 1\",\n  homeIndicator = true,\n  screenClassName,\n  className,\n  children,\n  ...props\n}: TabletFrameProps) {\n  const landscape = orientation === \"landscape\"\n  const media = !!(src || videoSrc)\n  const viewportRef = useScreenScale(\n    media ? undefined : (screenWidth ?? undefined)\n  )\n  const reduceMotion = !!useReducedMotion()\n\n  return (\n    <div\n      data-slot=\"tablet-frame\"\n      data-variant={variant}\n      data-orientation={orientation}\n      className={cn(\n        \"@container relative w-full select-none\",\n        landscape\n          ? \"aspect-[247.6/178.5] max-w-[640px] [--u:0.7209cqw]\"\n          : \"aspect-[178.5/247.6] max-w-[460px] [--u:1cqw]\",\n        variant === \"silver\" && SILVER,\n        variant === \"black\" && BLACK,\n        variant === \"auto\" && cn(SILVER, BLACK_DARK),\n        className\n      )}\n      {...props}\n    >\n      {/* Side buttons: power on the top edge, volume near the top-right of a landscape tablet. */}\n      {(landscape\n        ? [\n            \"top-[calc(var(--u)*-0.7)] left-[7%] h-[calc(var(--u)*1.1)] w-[4.5%] rounded-t-[0.4cqw]\",\n            \"top-[calc(var(--u)*-0.7)] right-[7%] h-[calc(var(--u)*1.1)] w-[7%] rounded-t-[0.4cqw]\",\n          ]\n        : [\n            \"top-[calc(var(--u)*-0.7)] right-[9%] h-[calc(var(--u)*1.1)] w-[8%] rounded-t-[0.4cqw]\",\n            \"right-[calc(var(--u)*-0.7)] top-[9%] h-[8%] w-[calc(var(--u)*1.1)] rounded-r-[0.4cqw]\",\n          ]\n      ).map((position) => (\n        <span\n          key={position}\n          aria-hidden=\"true\"\n          className={cn(\n            \"absolute bg-[linear-gradient(90deg,var(--tf-lo),var(--tf-hi)_50%,var(--tf-lo))]\",\n            position\n          )}\n        />\n      ))}\n\n      {/* Aluminum frame */}\n      <div\n        data-slot=\"tablet-frame-body\"\n        className=\"absolute inset-0 rounded-[calc(var(--u)*7.4)] bg-[linear-gradient(135deg,var(--tf-hi),var(--tf-mid)_32%,var(--tf-lo)_58%,var(--tf-mid)_82%,var(--tf-hi))] p-[calc(var(--u)*0.55)] shadow-[inset_0_0_0_0.2cqw_rgb(255_255_255/0.14),0_2.4cqw_6cqw_-1.6cqw_rgb(0_0_0/0.4),0_0.4cqw_1.2cqw_rgb(0_0_0/0.2)]\"\n      >\n        {/* Glass bezel */}\n        <div className=\"relative size-full rounded-[calc(var(--u)*6.9)] bg-black p-[calc(var(--u)*3.1)] shadow-[inset_0_0_0_0.25cqw_rgb(255_255_255/0.07)]\">\n          {/* Front camera, on the long edge */}\n          <span\n            aria-hidden=\"true\"\n            data-slot=\"tablet-frame-camera\"\n            className={cn(\n              \"absolute z-30 size-[calc(var(--u)*1.1)] rounded-full bg-[radial-gradient(circle_at_35%_35%,rgb(90_110_200/0.55),rgb(255_255_255/0.05)_50%,transparent_72%)] ring-[0.18cqw] ring-white/10\",\n              landscape\n                ? \"top-[calc(var(--u)*1)] left-1/2 -translate-x-1/2\"\n                : \"top-1/2 left-[calc(var(--u)*1)] -translate-y-1/2\"\n            )}\n          />\n          <div\n            data-slot=\"tablet-frame-screen\"\n            className={cn(\n              \"bg-background text-foreground relative isolate flex size-full flex-col overflow-hidden rounded-[calc(var(--u)*4.4)]\",\n              screenClassName\n            )}\n          >\n            {media ? (\n              videoSrc ? (\n                <video\n                  data-slot=\"tablet-frame-video\"\n                  className=\"absolute inset-0 size-full object-cover\"\n                  src={videoSrc}\n                  poster={poster}\n                  autoPlay={!reduceMotion}\n                  muted\n                  loop\n                  playsInline\n                />\n              ) : (\n                <Media\n                  data-slot=\"tablet-frame-image\"\n                  media={src}\n                  alt={alt}\n                  fill\n                  className=\"absolute inset-0\"\n                />\n              )\n            ) : null}\n            {statusBar && (\n              <div\n                data-slot=\"tablet-frame-status-bar\"\n                className=\"text-foreground relative z-20 flex h-[calc(var(--u)*4.6)] shrink-0 items-center justify-between px-[calc(var(--u)*4.6)] text-[calc(var(--u)*1.9)] font-semibold tracking-tight\"\n              >\n                <span className=\"flex gap-[calc(var(--u)*1.4)] tabular-nums\">\n                  <span>{time}</span>\n                  <span className=\"font-medium opacity-80\">{date}</span>\n                </span>\n                <span\n                  aria-hidden=\"true\"\n                  className=\"flex items-center gap-[calc(var(--u)*1.1)]\"\n                >\n                  <svg\n                    viewBox=\"0 0 16 12\"\n                    className=\"h-[calc(var(--u)*1.8)]\"\n                    fill=\"currentColor\"\n                  >\n                    <path d=\"M8 2.2c2.4 0 4.6.9 6.2 2.5l1.3-1.3A10.6 10.6 0 0 0 8 .4 10.6 10.6 0 0 0 .5 3.4l1.3 1.3A8.8 8.8 0 0 1 8 2.2Z\" />\n                    <path d=\"M8 5.8c1.4 0 2.7.5 3.7 1.4L13 5.9A7 7 0 0 0 8 4a7 7 0 0 0-5 1.9l1.3 1.3c1-.9 2.3-1.4 3.7-1.4Z\" />\n                    <path d=\"M8 9.3c.5 0 1 .2 1.3.5L8 11.6 6.7 9.8c.3-.3.8-.5 1.3-.5Z\" />\n                  </svg>\n                  <span className=\"text-[calc(var(--u)*1.7)] font-medium opacity-80\">\n                    100%\n                  </span>\n                  <svg viewBox=\"0 0 27 13\" className=\"h-[calc(var(--u)*1.9)]\">\n                    <rect\n                      x=\"0.5\"\n                      y=\"0.5\"\n                      width=\"23\"\n                      height=\"12\"\n                      rx=\"3.6\"\n                      fill=\"none\"\n                      stroke=\"currentColor\"\n                      strokeOpacity=\"0.4\"\n                    />\n                    <rect\n                      x=\"2\"\n                      y=\"2\"\n                      width=\"20\"\n                      height=\"9\"\n                      rx=\"2.2\"\n                      fill=\"currentColor\"\n                    />\n                    <path\n                      d=\"M25 4.5v4c.8-.3 1.4-1.1 1.4-2s-.6-1.7-1.4-2Z\"\n                      fill=\"currentColor\"\n                      fillOpacity=\"0.45\"\n                    />\n                  </svg>\n                </span>\n              </div>\n            )}\n            {!media && (\n              <div\n                ref={viewportRef}\n                data-slot=\"tablet-frame-content\"\n                className=\"relative min-h-0 flex-1\"\n              >\n                {screenWidth ? (\n                  <div\n                    className=\"absolute top-0 left-0 origin-top-left\"\n                    style={{\n                      width: screenWidth,\n                      height: \"calc(100% / var(--screen-scale, 1))\",\n                      transform: \"scale(var(--screen-scale, 1))\",\n                    }}\n                  >\n                    {children}\n                  </div>\n                ) : (\n                  children\n                )}\n              </div>\n            )}\n            {homeIndicator && (\n              <span\n                aria-hidden=\"true\"\n                data-slot=\"tablet-frame-home-indicator\"\n                className=\"bg-foreground/85 pointer-events-none absolute bottom-[calc(var(--u)*1.2)] left-1/2 z-30 h-[calc(var(--u)*0.7)] w-[calc(var(--u)*18)] -translate-x-1/2 rounded-full\"\n              />\n            )}\n            {/* Glass reflection */}\n            <span\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute inset-0 z-40 bg-[linear-gradient(115deg,rgb(255_255_255/0.07)_0%,rgb(255_255_255/0.015)_34%,transparent_34.2%)]\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport { TabletFrame, type TabletFrameProps }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}