{"$schema":"https://ui.shadcn.com/schema/registry.json","author":"Kaiyu Hsu <uicapsule@kyh.io>","categories":["navigation","skeuomorphism"],"dependencies":["lucide-react","motion"],"description":"A springy Dynamic Island interaction with ring and timer states.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { useState } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type { Transition } from \"motion/react\";\n\nimport { ExpandedWidget, expandedWidgetOptions } from \"./expanded-widgets\";\nimport type { ExpandedWidgetView } from \"./expanded-widgets\";\nimport { Ring } from \"./ring\";\nimport { Timer } from \"./timer\";\n\ntype BaseView = \"idle\" | \"ring\" | \"timer\";\ntype View = BaseView | ExpandedWidgetView;\ntype VariantKey = View | `${View}-${View}`;\ninterface AnimationVariant {\n  scale?: number;\n  scaleX?: number;\n  y?: number;\n  bounce?: number;\n}\n\nconst baseViews: { view: BaseView; label: string }[] = [\n  { label: \"idle\", view: \"idle\" },\n  { label: \"ring\", view: \"ring\" },\n  { label: \"timer\", view: \"timer\" },\n];\nconst views: { view: View; label: string }[] = [...baseViews, ...expandedWidgetOptions];\n\nconst variants = {\n  exit: (transition: AnimationVariant = {}) => ({\n    ...transition,\n    filter: \"blur(5px)\",\n    opacity: [1, 0],\n  }),\n};\n\nconst ANIMATION_VARIANTS = new Map<VariantKey, AnimationVariant>([\n  [\"ring-idle\", { bounce: 0.5, scale: 0.9, scaleX: 0.9 }],\n  [\"timer-ring\", { bounce: 0.35, scale: 0.7, y: -7.5 }],\n  [\"ring-timer\", { bounce: 0.35, scale: 1.4, y: 7.5 }],\n  [\"timer-idle\", { bounce: 0.3, scale: 0.7, y: -7.5 }],\n]);\n\n// Fallback for pairs the table below does not name (e.g. expanded ↔ expanded).\nconst DEFAULT_BOUNCE = 0.4;\n\nconst BOUNCE_VARIANTS = new Map<VariantKey, number>([\n  [\"idle\", 0.5],\n  [\"ring-idle\", 0.5],\n  [\"timer-ring\", 0.35],\n  [\"ring-timer\", 0.35],\n  [\"timer-idle\", 0.3],\n  [\"idle-timer\", 0.3],\n  [\"idle-ring\", 0.5],\n]);\n\nconst viewContent = (view: View) => {\n  switch (view) {\n    case \"ring\": {\n      return <Ring />;\n    }\n    case \"timer\": {\n      return <Timer />;\n    }\n    case \"idle\": {\n      return <div className=\"h-7\" />;\n    }\n    default: {\n      return <ExpandedWidget view={view} />;\n    }\n  }\n};\n\nconst DynamicIsland = () => {\n  const [view, setView] = useState<View>(\"idle\");\n  const [variantKey, setVariantKey] = useState<VariantKey>(\"idle\");\n\n  const content = viewContent(view);\n  const spring: Transition = {\n    bounce: BOUNCE_VARIANTS.get(variantKey) ?? DEFAULT_BOUNCE,\n    type: \"spring\",\n  };\n\n  return (\n    <div className=\"h-[260px]\">\n      <div className=\"relative flex h-full w-full flex-col justify-between\">\n        <motion.div\n          layout\n          transition={spring}\n          style={{ borderRadius: 32 }}\n          className=\"mx-auto w-fit min-w-[100px] overflow-hidden rounded-full bg-black\"\n        >\n          <motion.div\n            transition={spring}\n            initial={{\n              filter: \"blur(5px)\",\n              opacity: 0,\n              originX: 0.5,\n              originY: 0.5,\n              scale: 0.9,\n            }}\n            animate={{\n              filter: \"blur(0px)\",\n              opacity: 1,\n              originX: 0.5,\n              originY: 0.5,\n              scale: 1,\n              transition: {\n                delay: 0.05,\n              },\n            }}\n            key={view}\n          >\n            {content}\n          </motion.div>\n        </motion.div>\n\n        <div className=\"pointer-events-none absolute top-0 left-1/2 flex h-[200px] w-[300px] -translate-x-1/2 items-start justify-center\">\n          <AnimatePresence mode=\"popLayout\" custom={ANIMATION_VARIANTS.get(variantKey)}>\n            <motion.div initial={{ opacity: 0 }} exit=\"exit\" variants={variants} key={view}>\n              {content}\n            </motion.div>\n          </AnimatePresence>\n        </div>\n        <div className=\"mx-auto flex max-w-[720px] flex-wrap justify-center gap-2 px-3\">\n          {views.map(({ view: v, label }) => (\n            <button\n              type=\"button\"\n              className=\"h-8 rounded-full bg-white px-3 py-1.5 text-xs font-medium text-gray-900 capitalize shadow-sm ring-1 ring-gray-300/50 ring-inset hover:bg-gray-50 aria-pressed:bg-black aria-pressed:text-white aria-pressed:ring-white/20 aria-pressed:hover:bg-black\"\n              aria-pressed={view === v}\n              onClick={() => {\n                setView(v);\n                setVariantKey(`${view}-${v}`);\n              }}\n              key={v}\n            >\n              {label}\n            </button>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default DynamicIsland;\n","path":"/dynamic-island.tsx","target":"uicapsule/dynamic-island/dynamic-island.tsx","type":"registry:file"},{"content":"\"use client\";\n\nimport type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport {\n  FastForward,\n  Headphones,\n  Info,\n  Mic,\n  Phone,\n  PhoneOff,\n  Plane,\n  Play,\n  Rewind,\n  ScreenShare,\n  Video,\n} from \"lucide-react\";\nimport { motion } from \"motion/react\";\n\nexport type ExpandedWidgetView = \"music\" | \"call\" | \"memo\" | \"recording\" | \"incoming\" | \"flight\";\n\nexport interface ExpandedWidgetOption {\n  view: ExpandedWidgetView;\n  label: string;\n}\n\nexport const expandedWidgetOptions: ExpandedWidgetOption[] = [\n  { label: \"music\", view: \"music\" },\n  { label: \"call\", view: \"call\" },\n  { label: \"memo\", view: \"memo\" },\n  { label: \"record\", view: \"recording\" },\n  { label: \"incoming\", view: \"incoming\" },\n  { label: \"flight\", view: \"flight\" },\n];\n\n// Tokens from the Figma widget pack (Dynamic island pack → variable defs):\n// bg/black-solid, text/disabled, bg/overlay-alpha-secondary, red/500,\n// text/error-primary, green/500, border/tertiary, pink/500, fuchsia/500.\nconst ISLAND_BG = \"#0a0a0a\";\nconst TEXT_MUTED = \"#a3a3a3\";\nconst DARK_BUTTON = \"rgba(26,26,26,0.75)\";\nconst RED = \"#ef4444\";\nconst RED_DEEP = \"#dc2626\";\nconst GREEN = \"#16b364\";\nconst RING = \"#e5e5e5\";\nconst PINK = \"#ec4899\";\n\n// Label-1 (16/22, -0.18) and Label-3 (12/16, -0.12) from the pack's type ramp.\nconst TITLE_CLASS = \"truncate text-[16px] leading-[22px] tracking-[-0.18px]\";\nconst SUBTITLE_CLASS = \"truncate text-xs leading-4 tracking-[-0.12px]\";\n\n// Every expanded widget sits on the same 376px card; only the corner radius and\n// the internal flow (`layout`) differ between them.\nconst WIDGET_SHELL_CLASS = \"flex w-[376px] max-w-[calc(100vw-40px)] p-6 text-white\";\n\nconst WidgetShell = ({ children, layout }: { children: ReactNode; layout: string }) => (\n  <div className={`${WIDGET_SHELL_CLASS} ${layout}`} style={{ backgroundColor: ISLAND_BG }}>\n    {children}\n  </div>\n);\n\n// Stop control shared by the recording widgets, straight from the pack:\n// 44px circle with a 1px border/tertiary ring and a rounded red square inside.\nconst StopButton = ({ label }: { label: string }) => (\n  <button\n    aria-label={label}\n    className=\"grid size-11 shrink-0 place-items-center rounded-full border transition-transform hover:scale-105 active:scale-95\"\n    style={{ borderColor: RING }}\n    type=\"button\"\n  >\n    <span className=\"size-[18px] rounded-[4px]\" style={{ backgroundColor: RED }} />\n  </button>\n);\n\ninterface RoundActionProps {\n  children: ReactNode;\n  label: string;\n  tone: \"light\" | \"dark\" | \"danger\";\n}\n\nconst ROUND_ACTION_STYLES: Record<RoundActionProps[\"tone\"], CSSProperties> = {\n  danger: { backgroundColor: RED_DEEP, color: \"#ffffff\" },\n  dark: { backgroundColor: DARK_BUTTON, color: \"#ffffff\" },\n  light: { backgroundColor: \"#ffffff\", color: \"#000000\" },\n};\n\nconst RoundAction = ({ children, label, tone }: RoundActionProps) => (\n  <button\n    aria-label={label}\n    className=\"grid size-11 place-items-center rounded-full transition-transform hover:scale-105 active:scale-95\"\n    style={ROUND_ACTION_STYLES[tone]}\n    type=\"button\"\n  >\n    {children}\n  </button>\n);\n\nconst AlbumArt = () => (\n  <div className=\"size-12 shrink-0 rounded-full bg-[conic-gradient(from_30deg,#38bdf8,#facc15,#f97316,#22c55e,#38bdf8)] p-1\">\n    <div className=\"size-full rounded-full bg-[radial-gradient(circle_at_35%_35%,#f8fafc_0_10%,#0f172a_11%_42%,#14b8a6_43%_64%,#111827_65%)]\" />\n  </div>\n);\n\nconst Avatar = () => (\n  <div className=\"size-12 shrink-0 rounded-full bg-[radial-gradient(circle_at_42%_32%,#f4d4b8_0_16%,#2f241f_17%_38%,#f8fafc_39%_56%,#171717_57%)]\" />\n);\n\n// Five-bar equalizer fading pink/500 → fuchsia/500 like the pack's glyph.\nconst EQ_BARS = [\n  { color: \"#db2777\", delay: 0, height: 10, id: \"eq-0\" },\n  { color: \"#ec4899\", delay: 0.15, height: 18, id: \"eq-1\" },\n  { color: \"#ec4899\", delay: 0.3, height: 13, id: \"eq-2\" },\n  { color: \"#d946ef\", delay: 0.1, height: 20, id: \"eq-3\" },\n  { color: \"#c026d3\", delay: 0.25, height: 12, id: \"eq-4\" },\n];\n\nconst Equalizer = () => (\n  <div aria-hidden className=\"flex h-[22px] items-center gap-[3px]\">\n    {EQ_BARS.map((bar) => (\n      <motion.span\n        key={bar.id}\n        className=\"w-[3px] rounded-full\"\n        style={{ backgroundColor: bar.color, height: bar.height }}\n        animate={{ scaleY: [1, 0.55, 0.85, 1] }}\n        transition={{ delay: bar.delay, duration: 1.1, ease: \"easeInOut\", repeat: Infinity }}\n      />\n    ))}\n  </div>\n);\n\nconst MusicExpanded = () => (\n  <WidgetShell layout=\"flex-col gap-3 rounded-[32px]\">\n    <div className=\"flex items-center gap-2\">\n      <AlbumArt />\n      <div className=\"min-w-0 flex-1\">\n        <p className={TITLE_CLASS}>Selfless</p>\n        <p className={SUBTITLE_CLASS} style={{ color: TEXT_MUTED }}>\n          The New Abnormal\n        </p>\n      </div>\n      <Equalizer />\n    </div>\n    <div className=\"flex items-center gap-2\">\n      <span className=\"text-xs tracking-[-0.12px] tabular-nums\" style={{ color: TEXT_MUTED }}>\n        0:44\n      </span>\n      <div\n        className=\"h-2 flex-1 overflow-hidden rounded-full\"\n        style={{ backgroundColor: DARK_BUTTON }}\n      >\n        <div className=\"h-full w-[34.5%] rounded-full\" style={{ backgroundColor: PINK }} />\n      </div>\n      <span className=\"text-xs tracking-[-0.12px] tabular-nums\" style={{ color: TEXT_MUTED }}>\n        -3:00\n      </span>\n    </div>\n    <div className=\"flex items-center justify-center gap-6\">\n      <Rewind aria-hidden=\"true\" className=\"size-6\" fill=\"currentColor\" strokeWidth={0} />\n      <Play aria-hidden=\"true\" className=\"size-8\" fill=\"currentColor\" strokeWidth={0} />\n      <FastForward aria-hidden=\"true\" className=\"size-6\" fill=\"currentColor\" strokeWidth={0} />\n    </div>\n  </WidgetShell>\n);\n\nconst OngoingCallExpanded = () => (\n  <WidgetShell layout=\"flex-col gap-3 rounded-[32px]\">\n    <div className=\"flex items-center gap-2\">\n      <Avatar />\n      <div className=\"min-w-0 flex-1\">\n        <p className={TITLE_CLASS}>Mike Wheeler</p>\n        <p className={SUBTITLE_CLASS} style={{ color: TEXT_MUTED }}>\n          FaceTime Audio\n        </p>\n      </div>\n      <Info aria-hidden=\"true\" className=\"size-[22px]\" strokeWidth={1.5} />\n    </div>\n    <div className=\"flex items-center justify-between\">\n      <RoundAction label=\"Audio route\" tone=\"light\">\n        <Headphones aria-hidden=\"true\" className=\"size-[22px]\" strokeWidth={1.8} />\n      </RoundAction>\n      <RoundAction label=\"Mute microphone\" tone=\"light\">\n        <Mic aria-hidden=\"true\" className=\"size-[22px]\" strokeWidth={1.8} />\n      </RoundAction>\n      <RoundAction label=\"Video\" tone=\"dark\">\n        <Video aria-hidden=\"true\" className=\"size-[22px]\" strokeWidth={1.8} />\n      </RoundAction>\n      <RoundAction label=\"Share screen\" tone=\"dark\">\n        <ScreenShare aria-hidden=\"true\" className=\"size-[22px]\" strokeWidth={1.8} />\n      </RoundAction>\n      <RoundAction label=\"End call\" tone=\"danger\">\n        <PhoneOff aria-hidden=\"true\" className=\"size-[22px]\" strokeWidth={1.8} />\n      </RoundAction>\n    </div>\n  </WidgetShell>\n);\n\n// Waveform heights straight from the mock; the last bar fades and hands off to\n// a dot trail (red/50 at 40%) for the unrecorded remainder.\nconst MEMO_BARS = [25, 20, 16, 20, 25, 20, 14, 10, 16, 22, 27, 15, 19].map((height, index) => ({\n  height,\n  id: `memo-${index}`,\n}));\nconst MEMO_DOTS = Array.from({ length: 11 }, (_, index) => `dot-${index}`);\n\nconst VoiceMemoExpanded = () => (\n  <WidgetShell layout=\"items-center gap-3 rounded-full\">\n    <div aria-hidden className=\"flex h-11 shrink-0 items-center gap-[2px]\">\n      {MEMO_BARS.map(({ id, height }, index) => (\n        <motion.span\n          key={id}\n          className=\"w-[3px] shrink-0 rounded-[4px]\"\n          style={{ backgroundColor: RED, height }}\n          animate={{ scaleY: [1, 0.6, 0.9, 1] }}\n          transition={{\n            delay: (index % 5) * 0.09,\n            duration: 0.9 + (index % 4) * 0.12,\n            ease: \"easeInOut\",\n            repeat: Infinity,\n          }}\n        />\n      ))}\n      <span\n        className=\"h-3 w-[3px] shrink-0 rounded-[4px] opacity-40\"\n        style={{ backgroundColor: RED }}\n      />\n      {MEMO_DOTS.map((id) => (\n        <span key={id} className=\"size-[3px] shrink-0 rounded-[4px] bg-[#fef2f2] opacity-40\" />\n      ))}\n    </div>\n    <span\n      className=\"min-w-0 flex-1 text-right text-[17px] tabular-nums\"\n      style={{ color: RED_DEEP }}\n    >\n      05:00\n    </span>\n    <StopButton label=\"Stop recording\" />\n  </WidgetShell>\n);\n\nconst ScreenRecordingExpanded = () => (\n  <WidgetShell layout=\"items-center gap-3 rounded-full\">\n    <span className={`flex-1 ${TITLE_CLASS}`}>Screen Recording</span>\n    <div className=\"flex shrink-0 items-center gap-1\">\n      <motion.span\n        aria-hidden\n        className=\"size-2 rounded-full\"\n        style={{ backgroundColor: RED }}\n        animate={{ opacity: [1, 0.25, 1] }}\n        transition={{ duration: 1.6, ease: \"easeInOut\", repeat: Infinity }}\n      />\n      <span className=\"text-[17px] tabular-nums\" style={{ color: RED_DEEP }}>\n        05:00\n      </span>\n    </div>\n    <StopButton label=\"Stop screen recording\" />\n  </WidgetShell>\n);\n\nconst IncomingCallExpanded = () => (\n  <WidgetShell layout=\"items-center gap-2 rounded-full\">\n    <Avatar />\n    <div className=\"min-w-0 flex-1\">\n      <p className={`${TITLE_CLASS} font-semibold`}>Mike Wheeler</p>\n      <p className={SUBTITLE_CLASS} style={{ color: TEXT_MUTED }}>\n        Mobile\n      </p>\n    </div>\n    <button\n      aria-label=\"Decline call\"\n      className=\"grid size-11 shrink-0 place-items-center rounded-full transition-transform hover:scale-105 active:scale-95\"\n      style={{ backgroundColor: RED_DEEP }}\n      type=\"button\"\n    >\n      <PhoneOff aria-hidden=\"true\" className=\"size-[22px]\" strokeWidth={1.8} />\n    </button>\n    <button\n      aria-label=\"Accept call\"\n      className=\"grid size-11 shrink-0 place-items-center rounded-full transition-transform hover:scale-105 active:scale-95\"\n      style={{ backgroundColor: GREEN }}\n      type=\"button\"\n    >\n      <motion.span\n        className=\"grid place-items-center\"\n        animate={{ rotate: [0, -14, 12, -9, 7, 0] }}\n        transition={{ duration: 0.9, ease: \"easeInOut\", repeat: Infinity, repeatDelay: 1.4 }}\n      >\n        <Phone aria-hidden=\"true\" className=\"size-[22px]\" strokeWidth={1.8} />\n      </motion.span>\n    </button>\n  </WidgetShell>\n);\n\nconst FlightExpanded = () => (\n  <WidgetShell layout=\"flex-col gap-4 rounded-[32px]\">\n    <div className=\"flex items-center gap-2\">\n      <div\n        className=\"grid size-12 shrink-0 place-items-center rounded-2xl\"\n        style={{ backgroundColor: DARK_BUTTON }}\n      >\n        <Plane aria-hidden=\"true\" className=\"size-6 text-[#a78bfa]\" fill=\"currentColor\" />\n      </div>\n      <div className=\"min-w-0 flex-1\">\n        <p className={TITLE_CLASS}>Departure</p>\n        <p className={SUBTITLE_CLASS} style={{ color: TEXT_MUTED }}>\n          On time\n        </p>\n      </div>\n      <span className=\"text-[28px] leading-none font-light tabular-nums\">TL104</span>\n    </div>\n    <div className=\"flex items-center gap-2\">\n      <Plane aria-hidden=\"true\" className=\"size-5\" style={{ color: GREEN }} fill=\"currentColor\" />\n      <div\n        className=\"h-2 flex-1 overflow-hidden rounded-full\"\n        style={{ backgroundColor: DARK_BUTTON }}\n      >\n        <div className=\"h-full w-[72%] rounded-full\" style={{ backgroundColor: GREEN }} />\n      </div>\n      <span className=\"text-xs tracking-[-0.12px]\" style={{ color: TEXT_MUTED }}>\n        LAX\n      </span>\n    </div>\n    <button\n      className=\"h-12 w-full rounded-full bg-white text-[15px] font-medium text-black transition-transform hover:scale-[1.02] active:scale-[0.98]\"\n      type=\"button\"\n    >\n      Show Boarding Pass\n    </button>\n  </WidgetShell>\n);\n\nconst EXPANDED_WIDGETS: Record<ExpandedWidgetView, () => ReactElement> = {\n  call: OngoingCallExpanded,\n  flight: FlightExpanded,\n  incoming: IncomingCallExpanded,\n  memo: VoiceMemoExpanded,\n  music: MusicExpanded,\n  recording: ScreenRecordingExpanded,\n};\n\ninterface ExpandedWidgetProps {\n  view: ExpandedWidgetView;\n}\n\nexport const ExpandedWidget = ({ view }: ExpandedWidgetProps) => {\n  const Widget = EXPANDED_WIDGETS[view];\n  return <Widget />;\n};\n","path":"/expanded-widgets.tsx","target":"uicapsule/dynamic-island/expanded-widgets.tsx","type":"registry:file"},{"content":"\"use client\";\n\nimport DynamicIsland from \"./dynamic-island\";\n\nconst Preview = () => (\n  <main className=\"flex h-dvh items-center justify-center overflow-hidden bg-[radial-gradient(circle_at_50%_30%,#334155_0%,#0f172a_42%,#020617_100%)] p-5\">\n    <div className=\"w-full max-w-[760px]\">\n      <DynamicIsland />\n    </div>\n  </main>\n);\n\nexport default Preview;\n","path":"/preview.tsx","target":"uicapsule/dynamic-island/preview.tsx","type":"registry:file"},{"content":"import { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"motion/react\";\n\nexport const Ring = () => {\n  const [isSilent, setIsSilent] = useState(false);\n  const [firstTime, setFirstTime] = useState(true);\n\n  useEffect(() => {\n    const id = setTimeout(\n      () => {\n        setFirstTime(false);\n        setIsSilent(!isSilent);\n      },\n      firstTime ? 1000 : 2000,\n    );\n\n    return () => clearTimeout(id);\n  }, [isSilent, firstTime]);\n\n  return (\n    <motion.div\n      initial={false}\n      layout\n      className=\"relative flex h-7 items-center justify-between px-2.5\"\n      style={{ width: isSilent ? 148 : 128 }}\n      transition={{ bounce: 0.5, type: \"spring\" }}\n    >\n      <AnimatePresence>\n        {isSilent ? (\n          <motion.div\n            initial={{ filter: \"blur(4px)\", opacity: 0, scaleX: 0 }}\n            animate={{\n              filter: \"blur(0px)\",\n              opacity: 1,\n              scaleX: 1,\n            }}\n            exit={{ filter: \"blur(4px)\", opacity: 0, scaleX: 0 }}\n            transition={{ bounce: 0.35, type: \"spring\" }}\n            className=\"absolute left-[5px] h-[18px] w-10 origin-left rounded-full bg-[#FD4F30]\"\n          />\n        ) : null}\n      </AnimatePresence>\n      <motion.div\n        initial={false}\n        className=\"relative h-[12.75px] w-[11.25px]\"\n        animate={{\n          rotate: isSilent ? [0, -15, 5, -2, 0] : [0, 20, -15, 12.5, -10, 10, -7.5, 7.5, -5, 5, 0],\n          x: isSilent ? 9 : 0,\n        }}\n      >\n        <svg\n          width=\"11.25\"\n          height=\"12.75\"\n          viewBox=\"0 0 15 17\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M1.17969 13.3125H13.5625C14.2969 13.3125 14.7422 12.9375 14.7422 12.3672C14.7422 11.5859 13.9453 10.8828 13.2734 10.1875C12.7578 9.64844 12.6172 8.53906 12.5547 7.64062C12.5 4.64062 11.7031 2.57812 9.625 1.82812C9.32812 0.804688 8.52344 0 7.36719 0C6.21875 0 5.40625 0.804688 5.11719 1.82812C3.03906 2.57812 2.24219 4.64062 2.1875 7.64062C2.125 8.53906 1.98438 9.64844 1.46875 10.1875C0.789062 10.8828 0 11.5859 0 12.3672C0 12.9375 0.4375 13.3125 1.17969 13.3125ZM7.36719 16.4453C8.69531 16.4453 9.66406 15.4766 9.76562 14.3828H4.97656C5.07812 15.4766 6.04688 16.4453 7.36719 16.4453Z\"\n            fill=\"white\"\n          />\n        </svg>\n        <div className=\"absolute inset-0\">\n          <div className=\"h-5 translate-x-[5.25px] -translate-y-[5px] rotate-[-40deg] overflow-hidden\">\n            <motion.div\n              animate={{ scaleY: isSilent ? 1 : 0 }}\n              transition={{\n                delay: isSilent ? 0.15 : 0,\n                duration: isSilent ? 0.125 : 0.05,\n                ease: \"easeInOut\",\n              }}\n              className=\"h-4 w-fit origin-top rounded-full\"\n            >\n              <div className=\"flex h-full w-[3px] items-center justify-center rounded-full bg-[#FD4F30]\">\n                <div className=\"h-full w-[0.75px] rounded-full bg-white\" />\n              </div>\n            </motion.div>\n          </div>\n        </div>\n      </motion.div>\n      <div className=\"ml-auto flex items-center\">\n        {isSilent ? (\n          <span className=\"text-xs font-medium text-[#FD4F30]\">Silent</span>\n        ) : (\n          <span className=\"text-xs font-medium text-white\">Ring</span>\n        )}\n      </div>\n    </motion.div>\n  );\n};\n","path":"/ring.tsx","target":"uicapsule/dynamic-island/ring.tsx","type":"registry:file"},{"content":"import { AnimatePresence, motion } from \"motion/react\";\nimport { useEffect, useState } from \"react\";\n\nconst Counter = ({ paused }: { paused: boolean }) => {\n  const [count, setCount] = useState(60);\n\n  useEffect(() => {\n    if (paused) {\n      return;\n    }\n\n    const id = setInterval(() => {\n      setCount((c) => {\n        if (c === 0) {\n          return 60;\n        }\n        return c - 1;\n      });\n    }, 1000);\n\n    return () => {\n      clearInterval(id);\n    };\n  }, [paused]);\n\n  const digits = [\n    { place: \"tens\", value: Math.floor(count / 10) },\n    { place: \"ones\", value: count % 10 },\n  ];\n\n  return (\n    <div className=\"relative w-[64px] overflow-hidden text-3xl font-light whitespace-nowrap\">\n      0:\n      <AnimatePresence initial={false} mode=\"popLayout\">\n        {digits.map((digit) => (\n          <motion.div\n            className=\"inline-block tabular-nums\"\n            key={`${digit.place}-${digit.value}`}\n            initial={{ filter: \"blur(2px)\", opacity: 0, y: \"12px\" }}\n            animate={{ filter: \"blur(0px)\", opacity: 1, y: \"0\" }}\n            exit={{ filter: \"blur(2px)\", opacity: 0, y: \"-12px\" }}\n            transition={{ bounce: 0.35, type: \"spring\" }}\n          >\n            {digit.value}\n          </motion.div>\n        ))}\n      </AnimatePresence>\n    </div>\n  );\n};\n\nexport const Timer = () => {\n  const [isPaused, setIsPaused] = useState(false);\n\n  return (\n    <div className=\"flex w-[284px] items-center gap-2 py-3 pr-5 pl-3.5\">\n      <motion.button\n        type=\"button\"\n        aria-label=\"Pause timer\"\n        onClick={() => setIsPaused((p) => !p)}\n        whileTap={{ scale: 0.9 }}\n        className=\"flex h-10 w-10 items-center justify-center rounded-full bg-[#5A3C07] transition-colors hover:bg-[#694608]\"\n      >\n        <AnimatePresence initial={false} mode=\"wait\">\n          {isPaused ? (\n            <motion.svg\n              key=\"play\"\n              initial={{ filter: \"blur(4px)\", opacity: 0, scale: 0.5 }}\n              animate={{ filter: \"blur(0px)\", opacity: 1, scale: 1 }}\n              exit={{ filter: \"blur(4px)\", opacity: 0, scale: 0.5 }}\n              transition={{ duration: 0.1 }}\n              viewBox=\"0 0 12 14\"\n              fill=\"none\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n              className=\"h-4 w-4 fill-current text-[#FDB000]\"\n            >\n              <path d=\"M0.9375 13.2422C1.25 13.2422 1.51562 13.1172 1.82812 12.9375L10.9375 7.67188C11.5859 7.28906 11.8125 7.03906 11.8125 6.625C11.8125 6.21094 11.5859 5.96094 10.9375 5.58594L1.82812 0.3125C1.51562 0.132812 1.25 0.015625 0.9375 0.015625C0.359375 0.015625 0 0.453125 0 1.13281V12.1172C0 12.7969 0.359375 13.2422 0.9375 13.2422Z\" />\n            </motion.svg>\n          ) : (\n            <motion.svg\n              key=\"pause\"\n              initial={{ filter: \"blur(4px)\", opacity: 0, scale: 0.5 }}\n              animate={{ filter: \"blur(0px)\", opacity: 1, scale: 1 }}\n              exit={{ filter: \"blur(4px)\", opacity: 0, scale: 0.5 }}\n              transition={{ duration: 0.1 }}\n              viewBox=\"0 0 10 13\"\n              fill=\"none\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n              className=\"h-4 w-4 fill-current text-[#FDB000]\"\n            >\n              <path d=\"M1.03906 12.7266H2.82031C3.5 12.7266 3.85938 12.3672 3.85938 11.6797V1.03906C3.85938 0.328125 3.5 0 2.82031 0H1.03906C0.359375 0 0 0.359375 0 1.03906V11.6797C0 12.3672 0.359375 12.7266 1.03906 12.7266ZM6.71875 12.7266H8.49219C9.17969 12.7266 9.53125 12.3672 9.53125 11.6797V1.03906C9.53125 0.328125 9.17969 0 8.49219 0H6.71875C6.03125 0 5.67188 0.359375 5.67188 1.03906V11.6797C5.67188 12.3672 6.03125 12.7266 6.71875 12.7266Z\" />\n            </motion.svg>\n          )}\n        </AnimatePresence>\n      </motion.button>\n      <button\n        type=\"button\"\n        aria-label=\"Exit\"\n        className=\"flex h-10 w-10 items-center justify-center rounded-full bg-[#3C3D3C] text-white transition-colors hover:bg-[#4A4B4A]\"\n      >\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          fill=\"none\"\n          viewBox=\"0 0 24 24\"\n          strokeWidth={2}\n          stroke=\"currentColor\"\n          className=\"h-6 w-6\"\n        >\n          <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n        </svg>\n      </button>\n      <div className=\"ml-auto flex items-baseline gap-1.5 pr-0.5 text-[#F7A815]\">\n        <span className=\"text-sm leading-none font-medium text-inherit\">Timer</span>\n        <Counter paused={isPaused} />\n      </div>\n    </div>\n  );\n};\n","path":"/timer.tsx","target":"uicapsule/dynamic-island/timer.tsx","type":"registry:file"}],"homepage":"https://www.uicapsule.com/ui/dynamic-island","name":"dynamic-island","registryDependencies":[],"title":"Dynamic Island","type":"registry:block"}