{"$schema":"https://ui.shadcn.com/schema/registry.json","author":"Kaiyu Hsu <uicapsule@kyh.io>","categories":["effects","colorful"],"dependencies":["lucide-react","motion"],"description":"Material You dynamic color — pick a wallpaper and a tonal palette derived from its seed hue re-themes the whole system.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { useState } from \"react\";\nimport {\n  Bluetooth,\n  Flashlight,\n  MapPin,\n  MessageCircle,\n  Moon,\n  Music2,\n  Plane,\n  SkipForward,\n  Wifi,\n} from \"lucide-react\";\nimport type { LucideIcon } from \"lucide-react\";\nimport { motion } from \"motion/react\";\n\nimport { PhoneFrame } from \"./phone-frame\";\n\ninterface Wallpaper {\n  id: string;\n  name: string;\n  hue: number;\n  hueB: number;\n}\n\nconst WALLPAPERS: Wallpaper[] = [\n  { hue: 200, hueB: 160, id: \"lagoon\", name: \"Lagoon\" },\n  { hue: 20, hueB: 350, id: \"clay\", name: \"Clay\" },\n  { hue: 130, hueB: 80, id: \"moss\", name: \"Moss\" },\n  { hue: 285, hueB: 320, id: \"orchid\", name: \"Orchid\" },\n];\n\n// A tiny Material-You-style tonal palette derived from the wallpaper seed hue.\nconst paletteFor = (wallpaper: Wallpaper) => ({\n  accent: `hsl(${wallpaper.hueB} 50% 70%)`,\n  container: `hsl(${wallpaper.hue} 32% 26%)`,\n  muted: `hsl(${wallpaper.hue} 16% 58%)`,\n  onContainer: `hsl(${wallpaper.hue} 55% 88%)`,\n  onPrimary: `hsl(${wallpaper.hue} 45% 14%)`,\n  outline: `hsl(${wallpaper.hue} 18% 32%)`,\n  primary: `hsl(${wallpaper.hue} 65% 78%)`,\n  surface: `hsl(${wallpaper.hue} 22% 10%)`,\n  surfaceHigh: `hsl(${wallpaper.hue} 20% 15%)`,\n});\n\nconst wallpaperGradient = (wallpaper: Wallpaper) =>\n  `linear-gradient(140deg, hsl(${wallpaper.hue} 60% 45%), hsl(${wallpaper.hueB} 55% 30%) 60%, hsl(${wallpaper.hue} 40% 16%))`;\n\ninterface QuickTile {\n  icon: LucideIcon;\n  label: string;\n  on: boolean;\n}\n\nconst QUICK_TILES: QuickTile[] = [\n  { icon: Wifi, label: \"Internet\", on: true },\n  { icon: Bluetooth, label: \"Bluetooth\", on: true },\n  { icon: Flashlight, label: \"Torch\", on: false },\n  { icon: Moon, label: \"Bedtime\", on: false },\n  { icon: MapPin, label: \"Location\", on: true },\n  { icon: Plane, label: \"Airplane\", on: false },\n];\n\nconst TRANSITION = \"background-color 0.7s ease, color 0.7s ease, border-color 0.7s ease\";\n\nexport const MaterialYouTheming = () => {\n  const [active, setActive] = useState<Wallpaper>(\n    WALLPAPERS[0] ?? { hue: 200, hueB: 160, id: \"\", name: \"\" },\n  );\n  const palette = paletteFor(active);\n\n  return (\n    <PhoneFrame tone=\"moss\">\n      <div\n        className=\"absolute inset-0 flex flex-col\"\n        style={{ backgroundColor: palette.surface, transition: TRANSITION }}\n      >\n        <motion.div\n          key={active.id}\n          initial={{ opacity: 0.4 }}\n          animate={{ opacity: 1 }}\n          transition={{ duration: 0.7 }}\n          className=\"absolute inset-x-0 top-0 h-56\"\n          style={{\n            background: wallpaperGradient(active),\n            maskImage: \"linear-gradient(to bottom, black 30%, transparent)\",\n          }}\n        />\n\n        <div className=\"relative px-6 pt-[60px]\">\n          <p\n            className=\"text-[56px] leading-none font-medium tracking-tight\"\n            style={{ color: palette.onContainer, transition: TRANSITION }}\n          >\n            9:41\n          </p>\n          <p className=\"mt-1 text-[13px]\" style={{ color: palette.muted, transition: TRANSITION }}>\n            Mon, Jul 7 · Material You\n          </p>\n        </div>\n        <div className=\"relative mt-6 grid grid-cols-2 gap-2.5 px-5\">\n          {QUICK_TILES.map(({ icon: Icon, label, on }) => (\n            <div\n              key={label}\n              className=\"flex items-center gap-2.5 rounded-[24px] px-4 py-3.5\"\n              style={{\n                backgroundColor: on ? palette.primary : palette.surfaceHigh,\n                color: on ? palette.onPrimary : palette.muted,\n                transition: TRANSITION,\n              }}\n            >\n              <Icon className=\"size-4 shrink-0\" />\n              <span className=\"truncate text-[12px] font-semibold\">{label}</span>\n            </div>\n          ))}\n        </div>\n        <div\n          className=\"relative mx-5 mt-2.5 flex items-center gap-3 rounded-[26px] p-4\"\n          style={{ backgroundColor: palette.container, transition: TRANSITION }}\n        >\n          <div\n            className=\"grid size-11 shrink-0 place-items-center rounded-2xl\"\n            style={{\n              backgroundColor: palette.primary,\n              color: palette.onPrimary,\n              transition: TRANSITION,\n            }}\n          >\n            <Music2 className=\"size-5\" />\n          </div>\n          <div className=\"min-w-0 flex-1\">\n            <p\n              className=\"truncate text-[13px] font-semibold\"\n              style={{ color: palette.onContainer, transition: TRANSITION }}\n            >\n              Night Drive\n            </p>\n            <p className=\"text-[11px]\" style={{ color: palette.muted, transition: TRANSITION }}>\n              Solenne\n            </p>\n            <div\n              className=\"mt-2 h-1 w-full overflow-hidden rounded-full\"\n              style={{ backgroundColor: palette.outline, transition: TRANSITION }}\n            >\n              <div\n                className=\"h-full w-2/5 rounded-full\"\n                style={{ backgroundColor: palette.accent, transition: TRANSITION }}\n              />\n            </div>\n          </div>\n          <SkipForward className=\"size-5 shrink-0\" style={{ color: palette.muted }} />\n        </div>\n        <div\n          className=\"relative mx-5 mt-2.5 flex items-center gap-3 rounded-[26px] px-4 py-3.5\"\n          style={{ backgroundColor: palette.surfaceHigh, transition: TRANSITION }}\n        >\n          <div\n            className=\"grid size-9 shrink-0 place-items-center rounded-full\"\n            style={{\n              backgroundColor: palette.container,\n              color: palette.primary,\n              transition: TRANSITION,\n            }}\n          >\n            <MessageCircle className=\"size-4\" />\n          </div>\n          <div className=\"min-w-0 flex-1\">\n            <p\n              className=\"truncate text-[12px] font-semibold\"\n              style={{ color: palette.onContainer, transition: TRANSITION }}\n            >\n              Mia\n            </p>\n            <p\n              className=\"truncate text-[11px]\"\n              style={{ color: palette.muted, transition: TRANSITION }}\n            >\n              That new wallpaper looks great on you\n            </p>\n          </div>\n          <span className=\"text-[10px]\" style={{ color: palette.muted, transition: TRANSITION }}>\n            now\n          </span>\n        </div>\n        <div className=\"relative mt-auto pb-8\">\n          <p\n            className=\"px-6 text-[11px] font-medium\"\n            style={{ color: palette.muted, transition: TRANSITION }}\n          >\n            Wallpaper & style\n          </p>\n          <div className=\"mt-2.5 flex justify-center gap-3 px-5\">\n            {WALLPAPERS.map((wallpaper) => (\n              <motion.button\n                type=\"button\"\n                key={wallpaper.id}\n                aria-label={`Use ${wallpaper.name} wallpaper`}\n                aria-pressed={wallpaper.id === active.id}\n                onClick={() => setActive(wallpaper)}\n                whileTap={{ scale: 0.92 }}\n                animate={{ scale: wallpaper.id === active.id ? 1.06 : 1 }}\n                className=\"relative h-[116px] w-[62px] overflow-hidden rounded-2xl ring-2\"\n                style={{\n                  background: wallpaperGradient(wallpaper),\n                  borderColor: \"transparent\",\n                  ...(wallpaper.id === active.id\n                    ? { boxShadow: `0 0 0 2px ${palette.surface}, 0 0 0 4px ${palette.primary}` }\n                    : { boxShadow: \"none\" }),\n                }}\n              >\n                <span className=\"absolute inset-x-0 bottom-1 text-center text-[9px] font-medium text-white/85\">\n                  {wallpaper.name}\n                </span>\n              </motion.button>\n            ))}\n          </div>\n          <p\n            className=\"mt-3 text-center text-[11px]\"\n            style={{ color: palette.muted, transition: TRANSITION }}\n          >\n            Pick a wallpaper — the whole system re-themes\n          </p>\n        </div>\n      </div>\n    </PhoneFrame>\n  );\n};\n","path":"/material-you-theming.tsx","target":"uicapsule/material-you-theming/material-you-theming.tsx","type":"registry:file"},{"content":"import type { ReactNode } from \"react\";\n\n// Screen is 330×716 (iPhone 15 proportions); the clay shell adds a 16px lip on every side.\nexport const SCREEN_WIDTH = 330;\nexport const SCREEN_HEIGHT = 716;\n\ninterface Clay {\n  base: string;\n  deep: string;\n  light: string;\n  /** RGB triplet for inner shading, tinted so each tone reads as one material. */\n  rim: string;\n}\n\n// Dark matte clay: `light` is the lit top face, `base` the body, `deep` the shaded underside.\nconst TONES = {\n  espresso: { base: \"#3a2b24\", deep: \"#1f1612\", light: \"#5a4436\", rim: \"32 20 14\" },\n  graphite: { base: \"#303036\", deep: \"#18181c\", light: \"#4a4a52\", rim: \"10 10 14\" },\n  moss: { base: \"#2f3a2c\", deep: \"#171d15\", light: \"#4a5a44\", rim: \"12 18 10\" },\n  ochre: { base: \"#4a3c20\", deep: \"#261e0e\", light: \"#6e5a30\", rim: \"30 22 6\" },\n  plum: { base: \"#3c2a3a\", deep: \"#1e141d\", light: \"#5c4058\", rim: \"26 12 24\" },\n  slate: { base: \"#2a3448\", deep: \"#141a26\", light: \"#435270\", rim: \"10 16 30\" },\n  teal: { base: \"#233a3a\", deep: \"#101d1d\", light: \"#38585a\", rim: \"6 22 22\" },\n  terracotta: { base: \"#4a2c22\", deep: \"#26150f\", light: \"#6c4232\", rim: \"34 14 8\" },\n} satisfies Record<string, Clay>;\n\nexport type ClayTone = keyof typeof TONES;\n\nconst shellShadow = (rim: string) =>\n  [\n    \"0 60px 100px -40px rgb(0 0 0 / 0.85)\",\n    \"0 26px 40px -24px rgb(0 0 0 / 0.7)\",\n    \"inset 0 6px 6px rgb(255 255 255 / 0.16)\",\n    `inset 0 -14px 22px rgb(${rim} / 0.75)`,\n    \"inset 12px 0 18px -12px rgb(255 255 255 / 0.1)\",\n    `inset -12px 0 18px -12px rgb(${rim} / 0.6)`,\n  ].join(\", \");\n\nconst Nub = ({ className, clay }: { className: string; clay: Clay }) => (\n  <span\n    aria-hidden=\"true\"\n    className={`absolute w-[9px] rounded-full ${className}`}\n    style={{\n      background: clay.base,\n      boxShadow: `inset 2px 2px 2px rgb(255 255 255 / 0.14), inset -2px -2px 3px rgb(${clay.rim} / 0.7), 0 2px 4px rgb(0 0 0 / 0.5)`,\n    }}\n  />\n);\n\ninterface PhoneFrameProps {\n  children: ReactNode;\n  /** Classes for the screen: background, text color, pointer handling. */\n  className?: string;\n  /** Hide the Dynamic Island when the screen draws its own status area. */\n  island?: boolean;\n  tone?: ClayTone;\n}\n\nexport const PhoneFrame = ({\n  children,\n  className,\n  island = true,\n  tone = \"graphite\",\n}: PhoneFrameProps) => {\n  const clay = TONES[tone];\n  return (\n    <div\n      className=\"relative shrink-0 rounded-[66px] p-[16px]\"\n      style={{\n        background: `linear-gradient(165deg, ${clay.light} 0%, ${clay.base} 48%, ${clay.deep} 100%)`,\n        boxShadow: shellShadow(clay.rim),\n      }}\n    >\n      <Nub clay={clay} className=\"top-[132px] -left-[6px] h-[36px]\" />\n      <Nub clay={clay} className=\"top-[188px] -left-[6px] h-[62px]\" />\n      <Nub clay={clay} className=\"top-[262px] -left-[6px] h-[62px]\" />\n      <Nub clay={clay} className=\"top-[206px] -right-[6px] h-[92px]\" />\n      <div\n        className={`relative isolate overflow-hidden rounded-[50px] select-none ${className ?? \"bg-black\"}`}\n        style={{\n          boxShadow: `0 0 0 2px rgb(${clay.rim} / 0.9), 0 -2px 3px rgb(${clay.rim} / 0.8), 0 3px 4px rgb(255 255 255 / 0.12)`,\n          height: SCREEN_HEIGHT,\n          width: SCREEN_WIDTH,\n        }}\n      >\n        {children}\n        {island && (\n          <div\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute top-[11px] left-1/2 z-50 h-[30px] w-[98px] -translate-x-1/2 rounded-full bg-black\"\n          />\n        )}\n      </div>\n    </div>\n  );\n};\n","path":"/phone-frame.tsx","target":"uicapsule/material-you-theming/phone-frame.tsx","type":"registry:file"},{"content":"\"use client\";\n\nimport type { CSSProperties } from \"react\";\n\nimport { MaterialYouTheming } from \"./material-you-theming\";\n\nconst GRAIN =\n  \"url(\\\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.07'/%3E%3C/svg%3E\\\")\";\n\nconst backdrop: CSSProperties = {\n  background: [\n    GRAIN,\n    \"radial-gradient(ellipse 55% 60% at 30% 18%, rgb(118 128 70 / 0.34), transparent 70%)\",\n    \"radial-gradient(ellipse 50% 55% at 50% 58%, rgb(74 90 52 / 0.3), transparent 75%)\",\n    \"linear-gradient(170deg, #232a17 0%, #161b0f 50%, #0a0d07 100%)\",\n  ].join(\", \"),\n};\n\nconst Preview = () => (\n  <main className=\"flex h-dvh items-center justify-center overflow-hidden p-5\" style={backdrop}>\n    <MaterialYouTheming />\n  </main>\n);\n\nexport default Preview;\n","path":"/preview.tsx","target":"uicapsule/material-you-theming/preview.tsx","type":"registry:file"}],"homepage":"https://www.uicapsule.com/ui/material-you-theming","name":"material-you-theming","registryDependencies":[],"title":"Material You Theming","type":"registry:block"}