{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mesh-background",
  "title": "Mesh Background",
  "description": "Large soft colour from the accent and its neighbours, barely tinted to vivid, from one corner or split on a diagonal, with an optional slow drift.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/mesh-background.tsx",
      "content": "import type * as React from \"react\"\nimport { driftStyle, useDrift } from \"@/registry/miseui/ui/mesh-background-drift\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype MeshBackgroundVariant = \"soft\" | \"vivid\" | \"corner\" | \"split\"\ntype MeshBackgroundEffect = \"drift\"\ntype MeshTone = 1 | 2 | 3\n\ninterface MeshBlob {\n  /** Box in percent of the background: left, top, width, height. */\n  box: readonly [number, number, number, number]\n  tone: MeshTone\n  opacity: number\n}\n\n/** Lives with the component so an install needs no global CSS. Distances are percent of each blob, so a big box drifts further at the same pace. */\nconst KEYFRAMES =\n  \"@keyframes mise-mesh-a{to{transform:translate(10%,6%)}}@keyframes mise-mesh-b{to{transform:translate(-8%,8%)}}@keyframes mise-mesh-c{to{transform:translate(6%,-10%)}}\"\nconst DRIFTS = [\n  { name: \"mise-mesh-a\", seconds: 38 },\n  { name: \"mise-mesh-b\", seconds: 52 },\n  { name: \"mise-mesh-c\", seconds: 67 },\n] as const\n\n/**\n * Neighbours walk away from hue 100, where a darkened yellow turns to mud; the sine picks the direction per accent.\n * Lightness is clamped per theme: a near-black accent becomes silver on white, a near-white one becomes fog on black, both grey\n * by design because that palette has no chroma to spend. An older browser falls back to the plain accent in all three.\n * Written out in full: Tailwind reads classes from the source text, so a class built from a template string is never generated.\n */\nconst TONES_CLASS =\n  \"[--mise-mesh-1:var(--mise)] [--mise-mesh-2:var(--mise)] [--mise-mesh-3:var(--mise)] supports-[color:oklch(from_red_l_c_h)]:[--mise-mesh-1:oklch(from_var(--mise)_clamp(0.62,l,0.78)_c_h)] supports-[color:oklch(from_red_l_c_h)]:[--mise-mesh-2:oklch(from_var(--mise)_clamp(0.62,l,0.78)_c_calc(h_+_40_*_sin((h_-_100)_*_1deg)))] supports-[color:oklch(from_red_l_c_h)]:[--mise-mesh-3:oklch(from_var(--mise)_clamp(0.62,l,0.78)_c_calc(h_+_80_*_sin((h_-_100)_*_1deg)))] dark:supports-[color:oklch(from_red_l_c_h)]:[--mise-mesh-1:oklch(from_var(--mise)_clamp(0.5,l,0.66)_c_h)] dark:supports-[color:oklch(from_red_l_c_h)]:[--mise-mesh-2:oklch(from_var(--mise)_clamp(0.5,l,0.66)_c_calc(h_+_40_*_sin((h_-_100)_*_1deg)))] dark:supports-[color:oklch(from_red_l_c_h)]:[--mise-mesh-3:oklch(from_var(--mise)_clamp(0.5,l,0.66)_c_calc(h_+_80_*_sin((h_-_100)_*_1deg)))]\"\n\n/** Radial gradients that fade to nothing at their own box. They look blurred without a filter, so moving them costs one transform each. */\nconst MESH_BLOBS: Record<Exclude<MeshBackgroundVariant, \"split\">, readonly MeshBlob[]> = {\n  soft: [\n    { box: [-20, -40, 80, 110], tone: 1, opacity: 0.14 },\n    { box: [45, -50, 80, 100], tone: 2, opacity: 0.12 },\n    { box: [20, 40, 90, 100], tone: 3, opacity: 0.1 },\n  ],\n  vivid: [\n    { box: [-25, -35, 85, 120], tone: 1, opacity: 0.42 },\n    { box: [40, -45, 85, 115], tone: 2, opacity: 0.4 },\n    { box: [-15, 35, 80, 110], tone: 3, opacity: 0.32 },\n    { box: [50, 30, 75, 110], tone: 1, opacity: 0.3 },\n  ],\n  corner: [\n    { box: [40, -60, 90, 130], tone: 1, opacity: 0.34 },\n    { box: [65, -45, 55, 85], tone: 2, opacity: 0.32 },\n  ],\n}\n\n/** Larger than the box, so the drift never uncovers its edge. */\nconst SPLIT_STYLE: React.CSSProperties = {\n  inset: \"-20%\",\n  backgroundImage: \"linear-gradient(115deg in oklab,var(--mise-mesh-1) 25%,var(--mise-mesh-3) 75%)\",\n  opacity: 0.26,\n}\n\nconst blobStyle = ({ box: [left, top, width, height], tone, opacity }: MeshBlob): React.CSSProperties => ({\n  left: `${left}%`,\n  top: `${top}%`,\n  width: `${width}%`,\n  height: `${height}%`,\n  backgroundImage: `radial-gradient(closest-side,var(--mise-mesh-${tone}),transparent)`,\n  opacity,\n})\n\ntype MeshBackgroundProps = React.ComponentProps<\"div\"> & {\n  /** `soft` barely tinted, `vivid` for a hero with a solid button on top, `corner` colour from one corner, `split` two colours meeting at a soft diagonal. */\n  variant?: MeshBackgroundVariant\n  /** `drift` moves the colours very slowly. It stops off screen and in a hidden tab. */\n  effect?: MeshBackgroundEffect\n  /** Holds the drift. Tie it to a pause control: motion past five seconds needs a way to stop it. */\n  paused?: boolean\n}\n\nfunction MeshBackground({ variant = \"soft\", effect, paused = false, className, children, ...props }: MeshBackgroundProps) {\n  const { ref, moving, playing } = useDrift(effect === \"drift\", paused)\n  const motionFor = (index: number) => {\n    const drift = DRIFTS[index % DRIFTS.length]\n    return moving ? driftStyle(drift.name, drift.seconds, playing) : undefined\n  }\n  return (\n    <div\n      {...props}\n      data-slot=\"mesh-background\"\n      data-variant={variant}\n      data-effect={effect}\n      data-playing={moving ? (playing ? \"true\" : \"false\") : undefined}\n      className={cn(\"relative isolate overflow-clip\", className)}\n    >\n      {moving ? <style href=\"mise-mesh-background\" precedence=\"default\">{KEYFRAMES}</style> : null}\n      <div ref={ref} aria-hidden=\"true\" data-slot=\"mesh-background-layer\" className={cn(\"pointer-events-none absolute inset-0 rounded-[inherit] contrast-more:hidden forced-colors:hidden dark:opacity-70\", TONES_CLASS)}>\n        {variant === \"split\" ? (\n          <div data-slot=\"mesh-background-split\" className=\"absolute\" style={{ ...SPLIT_STYLE, ...motionFor(0) }} />\n        ) : (\n          MESH_BLOBS[variant].map((blob, index) => <div key={blob.box.join()} data-slot=\"mesh-background-blob\" className=\"absolute\" style={{ ...blobStyle(blob), ...motionFor(index) }} />)\n        )}\n      </div>\n      <div data-slot=\"mesh-background-content\" className=\"relative h-full min-h-[inherit]\">\n        {children}\n      </div>\n    </div>\n  )\n}\n\nexport { MeshBackground, MESH_BLOBS, type MeshBackgroundProps, type MeshBackgroundVariant, type MeshBackgroundEffect, type MeshBlob }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/mesh-background-drift.ts",
      "content": "import type * as React from \"react\"\nimport { useRef, useSyncExternalStore } from \"react\"\nimport { useInView, useReducedMotion } from \"motion/react\"\n\nconst subscribeToVisibility = (onChange: () => void) => {\n  document.addEventListener(\"visibilitychange\", onChange)\n  return () => document.removeEventListener(\"visibilitychange\", onChange)\n}\n\nfunction useTabVisible() {\n  return useSyncExternalStore(\n    subscribeToVisibility,\n    () => !document.hidden,\n    () => true,\n  )\n}\n\n/** Longhands, because React warns when a shorthand and one of its longhands are updated together. `steady` is for a full turn, which has no way back to ease into. */\nfunction driftStyle(name: string, seconds: number, playing: boolean, steady = false): React.CSSProperties {\n  return {\n    animationName: name,\n    animationDuration: `${seconds}s`,\n    animationTimingFunction: steady ? \"linear\" : \"ease-in-out\",\n    animationIterationCount: \"infinite\",\n    animationDirection: steady ? \"normal\" : \"alternate\",\n    animationPlayState: playing ? \"running\" : \"paused\",\n    willChange: \"transform\",\n  }\n}\n\n/** Reduced motion turns the whole effect off, which leaves the same layers standing still: a finished picture. The ref goes on the drawing layer, so the owner keeps theirs on the root. */\nfunction useDrift(requested: boolean, paused: boolean) {\n  const reduceMotion = useReducedMotion() ?? false\n  const ref = useRef<HTMLDivElement>(null)\n  const inView = useInView(ref)\n  const tabVisible = useTabVisible()\n  const moving = requested && !reduceMotion\n  return { ref, moving, playing: moving && !paused && inView && tabVisible }\n}\n\nexport { driftStyle, useDrift }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "backgrounds"
  ],
  "type": "registry:ui"
}