{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "google-chrome-windows",
  "title": "Google Chrome (Windows)",
  "description": "Windows Google Chrome window mock with title-bar tabs, Win32 controls, omnibox with Google G, and Google new-tab preview (light/dark) in the viewport.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/new-york/blocks/google-chrome-windows/page.tsx",
      "content": "import GoogleChromeWindows from \"./components/google-chrome-windows\"\n\nexport default function Page() {\n  return (\n    <div className=\"px-4 py-4 md:px-6 md:py-6\">\n      <GoogleChromeWindows />\n    </div>\n  )\n}\n",
      "type": "registry:page",
      "target": "app/google-chrome-windows/page.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/components/google-chrome-windows.tsx",
      "content": "\"use client\"\n\nimport { useCallback } from \"react\"\n\nimport {\n  ArrowLeft,\n  ArrowRight,\n  MoreVertical,\n  Plus,\n  Puzzle,\n  RotateCw,\n  Star,\n  X,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport BrowserViewport, { type BrowserViewportProps } from \"./browser-viewport\"\nimport ChromeViewportDefault from \"./chrome-viewport-default\"\nimport GoogleGIcon from \"./google-g-icon\"\nimport OmniboxTypingText from \"./omnibox-typing-text\"\nimport ProfileAvatar from \"./profile-avatar\"\nimport WindowControls from \"./window-controls\"\nimport { useOmniboxTyping } from \"../hooks/use-omnibox-typing\"\nimport { useSimulatedPageLoad } from \"../hooks/use-simulated-page-load\"\nimport { chromeWindowsConfig } from \"../lib/config\"\n\nimport \"../styles/google-chrome-windows.css\"\n\nconst tabIconClass = \"size-3.5 shrink-0 stroke-[2]\"\nconst menuIconClass = \"size-[18px] shrink-0 stroke-[1.75]\"\nconst toolbarIconSize = 18\n\nconst toolbarIcon = {\n  size: toolbarIconSize,\n  strokeWidth: 1.75,\n  className: \"shrink-0\",\n}\n\nfunction ToolbarSeparator() {\n  return (\n    <span\n      className=\"mx-1.5 h-4.5 w-px shrink-0 self-center bg-(--gcw-toolbar-sep)\"\n      aria-hidden\n    />\n  )\n}\n\ntype GoogleChromeWindowsProps = {\n  className?: string\n  omniboxTypingPrompts?: readonly string[]\n  /** When false, omnibox stops after typing the first prompt once. */\n  omniboxTypingLoop?: boolean\n  /** Fired when omnibox typing completes (entering hold). */\n  onOmniboxTypedComplete?: () => void\n  /** Fired when omnibox text is cleared (entering between). */\n  onOmniboxTypedCleared?: () => void\n} & Omit<BrowserViewportProps, \"className\">\n\nexport default function GoogleChromeWindows({\n  className,\n  omniboxTypingPrompts = chromeWindowsConfig.omniboxTypingPrompts,\n  omniboxTypingLoop = true,\n  onOmniboxTypedComplete,\n  onOmniboxTypedCleared,\n  minHeight,\n  aspectRatio,\n  src,\n  alt,\n  videoSrc,\n  poster,\n  autoPlay,\n  children,\n}: GoogleChromeWindowsProps) {\n  const hasCustomViewport = children != null\n  const { phase, progress, startLoading } = useSimulatedPageLoad()\n\n  const handleTypedComplete = useCallback(() => {\n    if (!hasCustomViewport) startLoading()\n    onOmniboxTypedComplete?.()\n  }, [hasCustomViewport, startLoading, onOmniboxTypedComplete])\n\n  const { displayText, showCursor, caretSolid, isActive } = useOmniboxTyping({\n    prompts: omniboxTypingPrompts,\n    loop: omniboxTypingLoop,\n    onTypedComplete: handleTypedComplete,\n    onTypedCleared: onOmniboxTypedCleared,\n  })\n\n  return (\n    <div\n      className={cn(\n        \"gcw-root mx-auto w-full max-w-6xl min-w-0 overflow-hidden rounded-sm border border-(--gcw-border) bg-(--gcw-chrome-bg) font-['Segoe_UI',system-ui,-apple-system,Roboto,'Helvetica_Neue',Arial,sans-serif] shadow-(--gcw-shadow) outline-none outline-1 -outline-offset-1 outline-(--gcw-ring)\",\n        className\n      )}\n      role=\"img\"\n      aria-label=\"Google Chrome on Windows browser window mockup\"\n    >\n      <div className=\"flex min-h-11 items-stretch justify-between gap-1.5 bg-(--gcw-chrome-bg) pb-0 pl-1.5\">\n        <div className=\"flex min-w-0 flex-1 items-end gap-0.5 pt-1\">\n          <div className=\"grid w-[clamp(9.5rem,32vw,12rem)] max-w-48 min-w-38 shrink-0 grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-2 rounded-t-lg bg-(--gcw-tab-bg) px-1.5 py-1.5 pl-2.5 text-xs leading-[1.2] text-(--gcw-tab-fg)\">\n            <GoogleGIcon muted className=\"size-3.5\" />\n            <span className=\"min-w-0 truncate\">\n              {chromeWindowsConfig.tabTitle}\n            </span>\n            <button\n              type=\"button\"\n              className=\"mr-0.5 flex size-5 items-center justify-center self-center justify-self-end rounded-full border-none bg-transparent p-0 text-(--gcw-tab-close) opacity-85 hover:opacity-100\"\n              tabIndex={-1}\n              aria-hidden\n            >\n              <X className={tabIconClass} />\n            </button>\n          </div>\n          <button\n            type=\"button\"\n            className=\"mb-1.5 flex size-5 shrink-0 items-center justify-center border-none bg-transparent p-0 text-(--gcw-new-tab)\"\n            tabIndex={-1}\n            aria-hidden\n          >\n            <Plus className={tabIconClass} />\n          </button>\n        </div>\n        <WindowControls />\n      </div>\n\n      <div className=\"box-border flex min-h-10.5 items-center gap-2 border-b border-(--gcw-toolbar-border) bg-(--gcw-toolbar-bg) py-1.5 pr-2.5 pl-3\">\n        <div className=\"flex shrink-0 items-center gap-px\">\n          <button\n            type=\"button\"\n            className=\"flex size-8 shrink-0 items-center justify-center rounded-full border-none bg-transparent p-0 text-(--gcw-nav-fg)\"\n            tabIndex={-1}\n            aria-hidden\n          >\n            <ArrowLeft {...toolbarIcon} />\n          </button>\n          <button\n            type=\"button\"\n            className=\"flex size-8 shrink-0 cursor-default items-center justify-center rounded-full border-none bg-transparent p-0 text-(--gcw-nav-disabled)\"\n            disabled\n            tabIndex={-1}\n            aria-hidden\n          >\n            <ArrowRight {...toolbarIcon} />\n          </button>\n          <button\n            type=\"button\"\n            className=\"flex size-8 shrink-0 items-center justify-center rounded-full border-none bg-transparent p-0 text-(--gcw-nav-fg)\"\n            tabIndex={-1}\n            aria-hidden\n          >\n            <RotateCw {...toolbarIcon} />\n          </button>\n        </div>\n\n        <div\n          className={cn(\n            \"flex min-w-0 flex-1 items-center gap-2 rounded-full border border-(--gcw-omnibox-border) bg-(--gcw-omnibox-bg) py-1 pr-2 pl-2.5 transition-[box-shadow,border-color] duration-200 ease-out motion-reduce:transition-none\",\n            isActive &&\n              \"border-transparent shadow-[0_0_0_1px_var(--gcw-omnibox-focus-ring)]\"\n          )}\n        >\n          <GoogleGIcon className=\"size-4\" />\n          <OmniboxTypingText\n            displayText={displayText}\n            showCursor={showCursor}\n            caretSolid={caretSolid}\n          />\n          <div className=\"flex shrink-0 items-center gap-0.5\" aria-hidden>\n            <button\n              type=\"button\"\n              className=\"flex size-6.5 items-center justify-center rounded-full border-none bg-transparent p-0 text-(--gcw-omnibox-icon)\"\n              tabIndex={-1}\n            >\n              <Star {...toolbarIcon} />\n            </button>\n          </div>\n        </div>\n\n        <div className=\"flex shrink-0 items-center gap-1\">\n          <button\n            type=\"button\"\n            className=\"flex size-8 shrink-0 items-center justify-center rounded-full border-none bg-transparent p-0 text-(--gcw-toolbar-icon)\"\n            tabIndex={-1}\n            aria-hidden\n          >\n            <Puzzle {...toolbarIcon} />\n          </button>\n          <ToolbarSeparator />\n          <ProfileAvatar />\n          <button\n            type=\"button\"\n            className=\"flex size-8 shrink-0 items-center justify-center rounded-full border-none bg-transparent p-0 text-(--gcw-toolbar-icon)\"\n            tabIndex={-1}\n            aria-hidden\n          >\n            <MoreVertical className={menuIconClass} />\n          </button>\n        </div>\n      </div>\n\n      <BrowserViewport\n        minHeight={minHeight}\n        aspectRatio={aspectRatio}\n        src={src}\n        alt={alt}\n        videoSrc={videoSrc}\n        poster={poster}\n        autoPlay={autoPlay}\n      >\n        {children ?? (\n          <ChromeViewportDefault phase={phase} progress={progress} />\n        )}\n      </BrowserViewport>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/google-chrome-windows/components/google-chrome-windows.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/components/browser-viewport.tsx",
      "content": "import type { CSSProperties, ReactNode } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { chromeWindowsConfig } from \"../lib/config\"\nimport GoogleHomePreview from \"./google-home-preview\"\n\nexport type BrowserViewportProps = {\n  className?: string\n  minHeight?: string | number\n  aspectRatio?: string\n  src?: string\n  alt?: string\n  videoSrc?: string\n  poster?: string\n  autoPlay?: boolean\n  children?: ReactNode\n}\n\nexport default function BrowserViewport({\n  className,\n  minHeight = chromeWindowsConfig.defaultViewportMinHeight,\n  aspectRatio,\n  src,\n  alt = \"\",\n  videoSrc,\n  poster,\n  autoPlay = true,\n  children,\n}: BrowserViewportProps) {\n  const style = {\n    ...(aspectRatio ? { aspectRatio } : {}),\n    minHeight: typeof minHeight === \"number\" ? `${minHeight}px` : minHeight,\n  } satisfies CSSProperties\n\n  return (\n    <div\n      className={cn(\n        \"relative w-full overflow-hidden bg-(--gcw-viewport-bg)\",\n        className\n      )}\n      style={style}\n    >\n      {children ? (\n        <div className=\"gcw-viewport-slot absolute inset-0 flex size-full overflow-hidden\">\n          {children}\n        </div>\n      ) : videoSrc ? (\n        <video\n          className=\"absolute inset-0 block size-full border-none object-cover object-center\"\n          src={videoSrc}\n          poster={poster}\n          muted\n          loop\n          playsInline\n          autoPlay={autoPlay}\n        />\n      ) : src ? (\n        // eslint-disable-next-line @next/next/no-img-element -- registry block; consumers may swap for next/image\n        <img\n          className=\"absolute inset-0 block size-full border-none object-cover object-center\"\n          src={src}\n          alt={alt}\n        />\n      ) : (\n        <GoogleHomePreview className=\"absolute inset-0\" />\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/google-chrome-windows/components/browser-viewport.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/components/chrome-viewport-default.tsx",
      "content": "import GoogleHomePreview from \"./google-home-preview\"\nimport PageLoadingBar from \"./page-loading-bar\"\nimport type { PageLoadPhase } from \"../hooks/use-simulated-page-load\"\n\ntype ChromeViewportDefaultProps = {\n  progress: number\n  phase: PageLoadPhase\n}\n\nexport default function ChromeViewportDefault({\n  progress,\n  phase,\n}: ChromeViewportDefaultProps) {\n  return (\n    <div className=\"relative size-full\">\n      <PageLoadingBar progress={progress} visible={phase === \"loading\"} />\n      <GoogleHomePreview className=\"absolute inset-0\" />\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/google-chrome-windows/components/chrome-viewport-default.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/components/page-loading-bar.tsx",
      "content": "type PageLoadingBarProps = {\n  progress: number\n  visible: boolean\n}\n\nexport default function PageLoadingBar({ progress, visible }: PageLoadingBarProps) {\n  if (!visible) return null\n\n  return (\n    <div\n      className=\"pointer-events-none absolute inset-x-0 top-0 z-10 h-px overflow-hidden\"\n      aria-hidden\n    >\n      <div\n        className=\"h-full bg-[#1a73e8] dark:bg-[#8ab4f8]\"\n        style={{ width: `${Math.min(100, progress)}%` }}\n      />\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/google-chrome-windows/components/page-loading-bar.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/hooks/use-simulated-page-load.ts",
      "content": "\"use client\"\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore } from \"react\"\n\nexport type PageLoadPhase = \"idle\" | \"loading\" | \"loaded\"\n\nfunction subscribeReducedMotion(onStoreChange: () => void) {\n  const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n  mq.addEventListener(\"change\", onStoreChange)\n  return () => mq.removeEventListener(\"change\", onStoreChange)\n}\n\nfunction getReducedMotion() {\n  return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n}\n\nfunction getReducedMotionServer() {\n  return false\n}\n\n/** Chrome-like load curve: fast start, slow crawl, quick finish. */\nfunction chromeLoadProgress(elapsedMs: number): number {\n  if (elapsedMs < 350) {\n    return (elapsedMs / 350) * 68\n  }\n  if (elapsedMs < 1400) {\n    const t = (elapsedMs - 350) / 1050\n    return 68 + t * 22\n  }\n  if (elapsedMs < 1650) {\n    const t = (elapsedMs - 1400) / 250\n    return 90 + t * 10\n  }\n  return 100\n}\n\nexport function useSimulatedPageLoad() {\n  const reducedMotion = useSyncExternalStore(\n    subscribeReducedMotion,\n    getReducedMotion,\n    getReducedMotionServer,\n  )\n  const [phase, setPhase] = useState<PageLoadPhase>(\"idle\")\n  const [progress, setProgress] = useState(0)\n  const rafRef = useRef<number>(0)\n  const startRef = useRef<number>(0)\n\n  const reset = useCallback(() => {\n    cancelAnimationFrame(rafRef.current)\n    setPhase(\"idle\")\n    setProgress(0)\n  }, [])\n\n  const startLoading = useCallback(() => {\n    cancelAnimationFrame(rafRef.current)\n    if (reducedMotion) {\n      setPhase(\"loaded\")\n      setProgress(100)\n      return\n    }\n    setPhase(\"loading\")\n    setProgress(0)\n    startRef.current = performance.now()\n\n    const tick = (now: number) => {\n      const elapsed = now - startRef.current\n      const next = chromeLoadProgress(elapsed)\n      setProgress(next)\n      if (next >= 100) {\n        setPhase(\"loaded\")\n        return\n      }\n      rafRef.current = requestAnimationFrame(tick)\n    }\n    rafRef.current = requestAnimationFrame(tick)\n  }, [reducedMotion])\n\n  useEffect(() => () => cancelAnimationFrame(rafRef.current), [])\n\n  useEffect(() => {\n    // Intentional: reacts to the reducedMotion media query changing after\n    // mount by synchronously finishing the load animation. startLoading is\n    // also a public event-handler API, so this can't be moved to render.\n    // eslint-disable-next-line react-hooks/set-state-in-effect\n    if (reducedMotion) startLoading()\n  }, [reducedMotion, startLoading])\n\n  return { phase, progress, startLoading, reset }\n}\n",
      "type": "registry:hook",
      "target": "app/google-chrome-windows/hooks/use-simulated-page-load.ts"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/components/google-home-preview.tsx",
      "content": "import { Mic, Plus, ScanSearch, Search, Sparkles } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport \"../styles/google-home-preview.css\"\n\nconst iconClass = \"size-[18px] shrink-0 stroke-[1.75]\"\n\ntype GoogleHomePreviewProps = {\n  className?: string\n}\n\nfunction GoogleWordmark() {\n  return (\n    <h1\n      className=\"mb-7 flex items-baseline gap-0 font-[system-ui,-apple-system,'Segoe_UI',Roboto,sans-serif] text-[clamp(2.75rem,8vw,4.25rem)] leading-none font-normal tracking-[-0.04em] select-none\"\n      aria-hidden\n    >\n      <span style={{ color: \"var(--ghp-logo-g)\" }}>G</span>\n      <span style={{ color: \"var(--ghp-logo-o1)\" }}>o</span>\n      <span style={{ color: \"var(--ghp-logo-o2)\" }}>o</span>\n      <span style={{ color: \"var(--ghp-logo-g2)\" }}>g</span>\n      <span style={{ color: \"var(--ghp-logo-l)\" }}>l</span>\n      <span style={{ color: \"var(--ghp-logo-e)\" }}>e</span>\n    </h1>\n  )\n}\n\nexport default function GoogleHomePreview({ className }: GoogleHomePreviewProps) {\n  return (\n    <div\n      className={cn(\n        \"ghp-root flex size-full flex-col items-center justify-start bg-(--ghp-bg) px-4 pt-[16%] pb-10\",\n        className\n      )}\n      aria-hidden\n    >\n      <GoogleWordmark />\n\n      <div\n        className=\"flex w-full max-w-xl items-center gap-2 rounded-full border border-(--ghp-bar-border) bg-(--ghp-bar-bg) py-2.5 pr-2 pl-4 shadow-(--ghp-bar-shadow)\"\n      >\n        <Plus className={cn(iconClass, \"text-(--ghp-icon)\")} aria-hidden />\n        <span className=\"min-w-0 flex-1 truncate text-[0.9375rem] text-(--ghp-placeholder)\">\n          Ask Google\n        </span>\n        <button\n          type=\"button\"\n          tabIndex={-1}\n          className=\"flex size-9 shrink-0 items-center justify-center rounded-full border-none bg-transparent p-0 text-(--ghp-icon)\"\n          aria-hidden\n        >\n          <Mic className={iconClass} />\n        </button>\n        <button\n          type=\"button\"\n          tabIndex={-1}\n          className=\"flex size-9 shrink-0 items-center justify-center rounded-full border-none bg-transparent p-0 text-(--ghp-icon)\"\n          aria-hidden\n        >\n          <ScanSearch className={iconClass} />\n        </button>\n        <button\n          type=\"button\"\n          tabIndex={-1}\n          className=\"flex h-9 shrink-0 items-center gap-1.5 rounded-full border-none bg-(--ghp-ai-bg) px-3.5 text-[0.8125rem] font-medium text-(--ghp-ai-fg)\"\n          aria-hidden\n        >\n          <span className=\"relative flex size-[18px] items-center justify-center\">\n            <Search\n              className=\"size-3.5 stroke-2 text-(--ghp-ai-icon)\"\n              aria-hidden\n            />\n            <Sparkles\n              className=\"absolute -top-0.5 -right-0.5 size-2.5 fill-(--ghp-ai-icon) stroke-none text-(--ghp-ai-icon)\"\n              aria-hidden\n            />\n          </span>\n          AI Mode\n        </button>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/google-chrome-windows/components/google-home-preview.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/styles/google-home-preview.css",
      "content": "/* Google new-tab preview — light/dark via .dark ancestor */\n\n.ghp-root {\n  --ghp-bg: #ffffff;\n  --ghp-logo-g: #4285f4;\n  --ghp-logo-o1: #ea4335;\n  --ghp-logo-o2: #fbbc05;\n  --ghp-logo-g2: #4285f4;\n  --ghp-logo-l: #34a853;\n  --ghp-logo-e: #ea4335;\n  --ghp-logo-mono: #202124;\n  --ghp-bar-bg: #ffffff;\n  --ghp-bar-border: rgb(218 220 224);\n  --ghp-bar-shadow: 0 1px 6px rgb(32 33 36 / 0.18);\n  --ghp-placeholder: #70757a;\n  --ghp-icon: #5f6368;\n  --ghp-ai-bg: #f1f3f4;\n  --ghp-ai-fg: #202124;\n  --ghp-ai-icon: #1a73e8;\n}\n\n.dark .ghp-root {\n  --ghp-bg: #3c4043;\n  --ghp-logo-g: #ffffff;\n  --ghp-logo-o1: #ffffff;\n  --ghp-logo-o2: #ffffff;\n  --ghp-logo-g2: #ffffff;\n  --ghp-logo-l: #ffffff;\n  --ghp-logo-e: #ffffff;\n  --ghp-logo-mono: #ffffff;\n  --ghp-bar-bg: #ffffff;\n  --ghp-bar-border: transparent;\n  --ghp-bar-shadow: none;\n  --ghp-placeholder: #9aa0a6;\n  --ghp-icon: #5f6368;\n  --ghp-ai-bg: #f1f3f4;\n  --ghp-ai-fg: #202124;\n  --ghp-ai-icon: #1a73e8;\n}\n",
      "type": "registry:file",
      "target": "app/google-chrome-windows/styles/google-home-preview.css"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/components/window-controls.tsx",
      "content": "import { Copy, Minus, X } from \"lucide-react\"\n\nconst iconClass = \"size-3.5 shrink-0 stroke-[1.5]\"\n\nexport default function WindowControls() {\n  return (\n    <div\n      className=\"ml-auto flex h-11 shrink-0 items-stretch self-stretch\"\n      aria-hidden\n    >\n      <button\n        type=\"button\"\n        tabIndex={-1}\n        className=\"flex h-full w-11 items-center justify-center rounded-none border-none bg-transparent p-0 text-(--gcw-win-fg) transition-[background-color,color] duration-120 ease-out hover:bg-(--gcw-win-hover-muted)\"\n      >\n        <Minus className={iconClass} />\n      </button>\n      <button\n        type=\"button\"\n        tabIndex={-1}\n        className=\"flex h-full w-11 items-center justify-center rounded-none border-none bg-transparent p-0 text-(--gcw-win-fg) transition-[background-color,color] duration-120 ease-out hover:bg-(--gcw-win-hover-muted)\"\n      >\n        <Copy className={iconClass} />\n      </button>\n      <button\n        type=\"button\"\n        tabIndex={-1}\n        className=\"flex h-full w-11 items-center justify-center rounded-none border-none bg-transparent p-0 text-(--gcw-win-fg) transition-[background-color,color] duration-120 ease-out hover:bg-(--gcw-win-hover-close) hover:text-(--gcw-win-hover-close-fg)\"\n      >\n        <X className={iconClass} />\n      </button>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/google-chrome-windows/components/window-controls.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/components/profile-avatar.tsx",
      "content": "import { chromeWindowsConfig } from \"../lib/config\"\n\nexport default function ProfileAvatar() {\n  return (\n    <span\n      className=\"grid size-8 shrink-0 place-items-center rounded-full\"\n      style={{ backgroundColor: chromeWindowsConfig.profileBg }}\n      aria-hidden\n    >\n      <span className=\"translate-y-[-0.08em] text-sm leading-none font-semibold text-(--gcw-profile-letter-fg) lowercase [text-shadow:0_0.5px_0_var(--gcw-profile-letter-shadow)]\">\n        {chromeWindowsConfig.profileInitial}\n      </span>\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/google-chrome-windows/components/profile-avatar.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/components/google-g-icon.tsx",
      "content": "import { cn } from \"@/lib/utils\"\n\ntype GoogleGIconProps = {\n  muted?: boolean\n  className?: string\n}\n\nexport default function GoogleGIcon({\n  muted = false,\n  className,\n}: GoogleGIconProps) {\n  return (\n    <svg\n      className={cn(\n        \"size-4.5 shrink-0\",\n        muted && \"opacity-72 grayscale\",\n        className\n      )}\n      viewBox=\"0 0 24 24\"\n      aria-hidden\n      focusable=\"false\"\n    >\n      <path\n        fill=\"#4285F4\"\n        d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\"\n      />\n      <path\n        fill=\"#34A853\"\n        d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"\n      />\n      <path\n        fill=\"#FBBC05\"\n        d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"\n      />\n      <path\n        fill=\"#EA4335\"\n        d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"\n      />\n    </svg>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/google-chrome-windows/components/google-g-icon.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/components/omnibox-typing-text.tsx",
      "content": "import { cn } from \"@/lib/utils\"\n\ntype OmniboxTypingTextProps = {\n  displayText: string\n  showCursor: boolean\n  caretSolid: boolean\n  textClassName?: string\n  caretClassName?: string\n}\n\nexport default function OmniboxTypingText({\n  displayText,\n  showCursor,\n  caretSolid,\n  textClassName = \"min-w-0 truncate text-3.25 leading-[1.2] text-[var(--gcw-omnibox-typed)] select-none\",\n  caretClassName = \"gcw-omnibox-caret\",\n}: OmniboxTypingTextProps) {\n  return (\n    <span\n      className=\"inline-flex min-w-0 flex-1 items-center overflow-hidden\"\n      aria-hidden\n    >\n      {displayText ? (\n        <span className={textClassName}>{displayText}</span>\n      ) : null}\n      {showCursor ? (\n        <span\n          className={cn(\n            caretClassName,\n            \"ml-px inline-block h-[1.05em] w-px shrink-0 translate-y-px bg-(--gcw-omnibox-caret) align-text-bottom\",\n            caretSolid && `${caretClassName}--solid`\n          )}\n          aria-hidden\n        />\n      ) : null}\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/google-chrome-windows/components/omnibox-typing-text.tsx"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/hooks/use-omnibox-typing.ts",
      "content": "import { useEffect, useLayoutEffect, useReducer, useRef, useSyncExternalStore } from \"react\"\n\nimport {\n  BETWEEN_MS,\n  DELETE_CHAR_MS_FAST,\n  DELETE_CHAR_MS_SLOW,\n  HOLD_MS,\n  START_DELAY_MS,\n  TYPE_CHAR_MS_FAST,\n  TYPE_CHAR_MS_SLOW,\n} from \"../lib/typing-animation\"\n\nexport type OmniboxTypingOptions = {\n  /** Phrases to cycle through in the omnibox */\n  prompts: readonly string[]\n  typeCharMsFast?: number\n  typeCharMsSlow?: number\n  deleteCharMsFast?: number\n  deleteCharMsSlow?: number\n  holdMs?: number\n  betweenMs?: number\n  startDelayMs?: number\n  /** When false, stops after the first phrase is typed (no delete/retype cycle). */\n  loop?: boolean\n  /** Fired when a phrase finishes typing (entering hold). */\n  onTypedComplete?: () => void\n  /** Fired when the omnibox is cleared (entering between). */\n  onTypedCleared?: () => void\n}\n\ntype Phase = \"idle\" | \"typingIn\" | \"hold\" | \"typingOut\" | \"between\"\n\ntype State = {\n  phase: Phase\n  promptIdx: number\n  charIdx: number\n}\n\ntype Action =\n  | { type: \"ADVANCE_CHAR\" }\n  | { type: \"SET_PHASE\"; phase: Phase; charIdx?: number; promptIdx?: number }\n  | { type: \"REWIND_CHAR\" }\n\nfunction jitter(ms: number, spread = 0.1): number {\n  const delta = ms * spread * (Math.random() * 2 - 1)\n  return Math.max(10, Math.round(ms + delta))\n}\n\nfunction typeDelayMs(charIdx: number, len: number, fast: number, slow: number): number {\n  if (len <= 0) return slow\n  const t = charIdx / len\n  const ease = Math.pow(1 - t, 0.32)\n  return jitter(Math.round(fast + (slow - fast) * ease))\n}\n\nfunction deleteDelayMs(charIdx: number, len: number, fast: number, slow: number): number {\n  if (len <= 0) return slow\n  const t = charIdx / len\n  const ease = Math.pow(t, 0.4)\n  return jitter(Math.round(fast + (slow - fast) * ease))\n}\n\nfunction subscribeReducedMotion(onStoreChange: () => void) {\n  const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n  mq.addEventListener(\"change\", onStoreChange)\n  return () => mq.removeEventListener(\"change\", onStoreChange)\n}\n\nfunction getReducedMotion() {\n  return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n}\n\nfunction getReducedMotionServer() {\n  return false\n}\n\nexport function useOmniboxTyping({\n  prompts,\n  typeCharMsFast = TYPE_CHAR_MS_FAST,\n  typeCharMsSlow = TYPE_CHAR_MS_SLOW,\n  deleteCharMsFast = DELETE_CHAR_MS_FAST,\n  deleteCharMsSlow = DELETE_CHAR_MS_SLOW,\n  holdMs = HOLD_MS,\n  betweenMs = BETWEEN_MS,\n  startDelayMs = START_DELAY_MS,\n  loop = true,\n  onTypedComplete,\n  onTypedCleared,\n}: OmniboxTypingOptions) {\n  const reducedMotion = useSyncExternalStore(\n    subscribeReducedMotion,\n    getReducedMotion,\n    getReducedMotionServer,\n  )\n\n  const [state, dispatch] = useReducer(reducer, {\n    phase: \"idle\",\n    promptIdx: 0,\n    charIdx: 0,\n  })\n\n  const { phase, promptIdx, charIdx } = state\n  const text = prompts[promptIdx] ?? \"\"\n  const len = text.length\n\n  const displayText = reducedMotion ? (prompts[0] ?? \"\") : text.slice(0, charIdx)\n  const showCursor = !reducedMotion && !(loop === false && phase === \"hold\")\n  const caretSolid = phase === \"typingIn\" || phase === \"typingOut\"\n  const isActive =\n    !reducedMotion &&\n    (phase === \"typingIn\" || phase === \"hold\" || phase === \"typingOut\" || charIdx > 0)\n\n  const prevPhaseRef = useRef<Phase>(phase)\n\n  useEffect(() => {\n    const prev = prevPhaseRef.current\n    prevPhaseRef.current = phase\n\n    if (reducedMotion) return\n    if (phase === \"hold\" && prev !== \"hold\") onTypedComplete?.()\n    if (phase === \"between\" && prev !== \"between\") onTypedCleared?.()\n  }, [phase, reducedMotion, onTypedComplete, onTypedCleared])\n\n  useLayoutEffect(() => {\n    if (reducedMotion || prompts.length === 0) return\n\n    let t: ReturnType<typeof setTimeout>\n\n    switch (phase) {\n      case \"idle\":\n        t = setTimeout(() => dispatch({ type: \"SET_PHASE\", phase: \"typingIn\" }), startDelayMs)\n        break\n\n      case \"typingIn\":\n        if (charIdx < len) {\n          t = setTimeout(\n            () => dispatch({ type: \"ADVANCE_CHAR\" }),\n            typeDelayMs(charIdx, len, typeCharMsFast, typeCharMsSlow),\n          )\n        } else {\n          dispatch({ type: \"SET_PHASE\", phase: \"hold\" })\n        }\n        break\n\n      case \"hold\":\n        if (loop) {\n          t = setTimeout(() => dispatch({ type: \"SET_PHASE\", phase: \"typingOut\" }), holdMs)\n        }\n        break\n\n      case \"typingOut\":\n        if (charIdx > 0) {\n          t = setTimeout(\n            () => dispatch({ type: \"REWIND_CHAR\" }),\n            deleteDelayMs(charIdx, len, deleteCharMsFast, deleteCharMsSlow),\n          )\n        } else {\n          dispatch({ type: \"SET_PHASE\", phase: \"between\", charIdx: 0 })\n        }\n        break\n\n      case \"between\":\n        t = setTimeout(() => {\n          const next = (promptIdx + 1) % prompts.length\n          dispatch({\n            type: \"SET_PHASE\",\n            phase: \"typingIn\",\n            charIdx: 0,\n            promptIdx: next,\n          })\n        }, betweenMs)\n        break\n    }\n\n    return () => clearTimeout(t)\n  }, [\n    reducedMotion,\n    phase,\n    charIdx,\n    len,\n    promptIdx,\n    prompts.length,\n    typeCharMsFast,\n    typeCharMsSlow,\n    deleteCharMsFast,\n    deleteCharMsSlow,\n    holdMs,\n    betweenMs,\n    startDelayMs,\n    loop,\n  ])\n\n  return { displayText, showCursor, caretSolid, isActive }\n}\n\nfunction reducer(state: State, action: Action): State {\n  switch (action.type) {\n    case \"ADVANCE_CHAR\":\n      return { ...state, charIdx: state.charIdx + 1 }\n    case \"REWIND_CHAR\":\n      return { ...state, charIdx: Math.max(0, state.charIdx - 1) }\n    case \"SET_PHASE\":\n      return {\n        phase: action.phase,\n        charIdx: action.charIdx ?? state.charIdx,\n        promptIdx: action.promptIdx ?? state.promptIdx,\n      }\n    default:\n      return state\n  }\n}\n",
      "type": "registry:hook",
      "target": "app/google-chrome-windows/hooks/use-omnibox-typing.ts"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/lib/config.ts",
      "content": "export const chromeWindowsConfig = {\n  /**\n   * Taller than macOS viewport min so total shell height matches `google-chrome`\n   * (macOS adds a bookmarks bar; Windows titlebar/toolbar are aligned to mac min-heights).\n   */\n  defaultViewportMinHeight: \"33.75rem\",\n  tabTitle: \"New Tab\",\n  omniboxPlaceholder: \"Ask Google or type a URL\",\n  /** Default omnibox typewriter phrases — pass your own to `GoogleChromeWindows` for SaaS demos */\n  omniboxTypingPrompts: [\"youtube.com\"] as const,\n  profileInitial: \"j\",\n  profileBg: \"#14b8a6\",\n} as const\n",
      "type": "registry:lib",
      "target": "app/google-chrome-windows/lib/config.ts"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/lib/typing-animation.ts",
      "content": "/** Eased type speed (ms per character) */\nexport const TYPE_CHAR_MS_SLOW = 72\nexport const TYPE_CHAR_MS_FAST = 28\n\n/** Eased delete speed (ms per character) */\nexport const DELETE_CHAR_MS_SLOW = 42\nexport const DELETE_CHAR_MS_FAST = 14\n\n/** Pause on full phrase before deleting */\nexport const HOLD_MS = 2200\n\n/** Pause after clearing before typing again */\nexport const BETWEEN_MS = 420\n\n/** Pause before the first keystroke (simulates focus / click) */\nexport const START_DELAY_MS = 680\n",
      "type": "registry:lib",
      "target": "app/google-chrome-windows/lib/typing-animation.ts"
    },
    {
      "path": "registry/new-york/blocks/google-chrome-windows/styles/google-chrome-windows.css",
      "content": "/* google-chrome-windows — theme tokens, caret blink, viewport slot children */\n\n.gcw-root {\n  --gcw-chrome-bg: #dee1e6;\n  --gcw-border: rgb(0 0 0 / 0.14);\n  --gcw-ring: rgb(0 0 0 / 0.06);\n  --gcw-shadow: 0 20px 40px -12px rgb(0 0 0 / 0.14);\n  --gcw-tab-bg: #ffffff;\n  --gcw-tab-fg: #202124;\n  --gcw-tab-close: #5f6368;\n  --gcw-new-tab: #5f6368;\n  --gcw-win-fg: #202124;\n  --gcw-win-hover-muted: rgb(0 0 0 / 0.06);\n  --gcw-win-hover-close: #e81123;\n  --gcw-win-hover-close-fg: #ffffff;\n  --gcw-toolbar-bg: #ffffff;\n  --gcw-toolbar-border: rgb(0 0 0 / 0.08);\n  --gcw-nav-fg: #5f6368;\n  --gcw-nav-disabled: rgb(95 99 104 / 0.38);\n  --gcw-omnibox-bg: #f1f3f4;\n  --gcw-omnibox-border: transparent;\n  --gcw-omnibox-typed: #202124;\n  --gcw-omnibox-caret: #1a73e8;\n  --gcw-omnibox-focus-ring: rgb(26 115 232 / 0.18);\n  --gcw-omnibox-icon: #5f6368;\n  --gcw-toolbar-icon: #5f6368;\n  --gcw-toolbar-sep: rgb(0 0 0 / 0.12);\n  --gcw-viewport-bg: #ffffff;\n  --gcw-profile-letter-fg: oklch(0.995 0.01 195);\n  --gcw-profile-letter-shadow: oklch(0.25 0.06 195 / 0.22);\n}\n\n.dark .gcw-root {\n  --gcw-chrome-bg: #202124;\n  --gcw-border: rgb(255 255 255 / 0.1);\n  --gcw-ring: rgb(0 0 0 / 0.35);\n  --gcw-shadow: 0 20px 40px -12px rgb(0 0 0 / 0.5);\n  --gcw-tab-bg: #35363a;\n  --gcw-tab-fg: #e8eaed;\n  --gcw-tab-close: #9aa0a6;\n  --gcw-new-tab: #9aa0a6;\n  --gcw-win-fg: #e8eaed;\n  --gcw-win-hover-muted: rgb(255 255 255 / 0.1);\n  --gcw-win-hover-close: #c42b1c;\n  --gcw-win-hover-close-fg: #ffffff;\n  --gcw-toolbar-bg: #202124;\n  --gcw-toolbar-border: rgb(255 255 255 / 0.06);\n  --gcw-nav-fg: #9aa0a6;\n  --gcw-nav-disabled: rgb(154 160 166 / 0.35);\n  --gcw-omnibox-bg: #35363a;\n  --gcw-omnibox-border: rgb(255 255 255 / 0.1);\n  --gcw-omnibox-typed: #e8eaed;\n  --gcw-omnibox-caret: #8ab4f8;\n  --gcw-omnibox-focus-ring: rgb(138 180 248 / 0.22);\n  --gcw-omnibox-icon: #9aa0a6;\n  --gcw-toolbar-icon: #9aa0a6;\n  --gcw-toolbar-sep: rgb(255 255 255 / 0.12);\n  --gcw-viewport-bg: #202124;\n  --gcw-profile-letter-fg: oklch(0.97 0.028 190);\n  --gcw-profile-letter-shadow: oklch(0.15 0.04 250 / 0.45);\n}\n\n.gcw-omnibox-caret {\n  animation: gcw-omnibox-caret-blink 1s step-end infinite;\n}\n\n.gcw-omnibox-caret--solid {\n  animation: none;\n  opacity: 1;\n}\n\n@keyframes gcw-omnibox-caret-blink {\n  0%,\n  45% {\n    opacity: 1;\n  }\n  55%,\n  100% {\n    opacity: 0;\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .gcw-omnibox-caret {\n    animation: none;\n    opacity: 0;\n  }\n}\n\n.gcw-viewport-slot > img,\n.gcw-viewport-slot > video,\n.gcw-viewport-slot > picture,\n.gcw-viewport-slot > iframe,\n.gcw-viewport-slot :is(img, video, iframe) {\n  display: block;\n  width: 100%;\n  height: 100%;\n  border: none;\n  object-fit: cover;\n  object-position: center;\n}\n\n.gcw-viewport-slot > * {\n  flex: 1;\n  min-width: 0;\n  min-height: 0;\n}\n",
      "type": "registry:file",
      "target": "app/google-chrome-windows/styles/google-chrome-windows.css"
    }
  ],
  "type": "registry:block"
}