21st.dev 원본

Logo Timeline · Gamma UI

데이터 · MIT

데이터 더 보기
ORIGINAL PREVIEW
Logo Timeline · Gamma UI 정적 미리보기

갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.

큰 화면으로 보기 새 탭실행 HTML 저장

저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.

SOURCE FILES

원본 코드 읽기

16개 파일

수집한 원본 소스와 실행 안내를 함께 제공합니다. 실행 HTML에는 미리보기를 위한 실행 환경이 들어 있습니다.

baseline-example.tsx
파일 저장

import {
  IconBrandApple,
  IconBrandDiscord,
  IconBrandGithub,
  IconBrandGoogleDrive,
  IconBrandMessenger,
  IconBrandNotion,
  IconBrandOpenai,
  IconBrandPaypal,
  IconBrandReact,
  IconBrandTailwind,
  IconBrandTypescript,
  IconBrandWhatsapp,
  IconBrandX,
} from "@tabler/icons-react"

import { LogoTimeline } from "@/components/ui/logo-timeline"
import type { LogoItem } from "@/components/ui/logo-timeline"

const logos: LogoItem[] = [
  // Row 1 - Communication & Social (2 logos, 50s duration, spaced 25s apart)
  {
    label: "Discord",
    icon: <IconBrandDiscord />,
    animationDelay: -50,
    animationDuration: 50,
    row: 1,
  },
  {
    label: "X (Twitter)",
    icon: <IconBrandX />,
    animationDelay: -25,
    animationDuration: 50,
    row: 1,
  },
  // Row 2 - Development Tools (2 logos, 45s duration, spaced 22.5s apart)
  {
    label: "GitHub",
    icon: <IconBrandGithub />,
    animationDelay: -45,
    animationDuration: 45,
    row: 2,
  },
  {
    label: "React",
    icon: <IconBrandReact />,
    animationDelay: -22.5,
    animationDuration: 45,
    row: 2,
  },
  // Row 3 - Development Tools Continued (3 logos, 60s duration, spaced 20s apart)
  {
    label: "TypeScript",
    icon: <IconBrandTypescript />,
    animationDelay: -60,
    animationDuration: 60,
    row: 3,
  },
  {
    label: "Tailwind",
    icon: <IconBrandTailwind />,
    animationDelay: -40,
    animationDuration: 60,
    row: 3,
  },

  // Row 4 - Productivity & Cloud (2 logos, 55s duration, spaced 27.5s apart)
  {
    label: "Google Drive",
    icon: <IconBrandGoogleDrive />,
    animationDelay: -55,
    animationDuration: 55,
    row: 4,
  },
  {
    label: "Notion",
    icon: <IconBrandNotion />,
    animationDelay: -27.5,
    animationDuration: 55,
    row: 4,
  },
  // Row 5 - Messaging (2 logos, 50s duration, spaced 25s apart)
  {
    label: "WhatsApp",
    icon: <IconBrandWhatsapp />,
    animationDelay: -50,
    animationDuration: 50,
    row: 5,
  },
  {
    label: "Messenger",
    icon: <IconBrandMessenger />, // Placeholder icon
    animationDelay: -25,
    animationDuration: 50,
    row: 5,
  },
  // Row 6 - AI & Automation (3 logos, 65s duration, spaced ~21.5s apart)
  {
    label: "OpenAI",
    icon: <IconBrandOpenai />, // Placeholder icon
    animationDelay: -65,
    animationDuration: 65,
    row: 6,
  },

  // Row 7 - Payment & Services (2 logos, 50s duration, spaced 25s apart)
  {
    label: "PayPal",
    icon: <IconBrandPaypal />, // Placeholder icon
    animationDelay: -50,
    animationDuration: 50,
    row: 7,
  },
  {
    label: "Apple",
    icon: <IconBrandApple />, // Placeholder icon
    animationDelay: -25,
    animationDuration: 50,
    row: 7,
  },
]

export function LogoTimelineDemo() {
  return (
    <div className="w-full overflow-hidden">
      <LogoTimeline
        items={logos}
        title="Built with the best tools"
        height="h-[400px] sm:h-[400px]"
        iconSize={18}
        showRowSeparator={true}
      />
    </div>
  )
}

export default LogoTimelineDemo
LICENSE
파일 저장

MIT License

Copyright (c) Gamma UI

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
함께 쓰는 파일 14개 보기
upstream/apps/www/content/docs/components/logo-timeline.mdx
파일 저장

---
title: Logo Timeline
date: 2025-11-01
description: A visually stunning animated logo timeline component that displays logos in horizontal scrolling rows with smooth infinite animations, perfect for showcasing partners, technologies, or integrations.
author: mazyar
published: true
---

import { CSSCodeBlock } from "@/components/css-code-block"

<ComponentPreview name="logo-timeline-demo" />

## Installation

<Tabs defaultValue="cli">

<TabsList>
  <TabsTrigger value="cli">CLI</TabsTrigger>
  <TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">

```bash
npx shadcn@latest add @gammaui/logo-timeline
```

<CSSCodeBlock
  fileName="global.css"
code={`
@keyframes move-x {
  from {
    transform: translateX(var(--move-x-from));
  }
  to {
    transform: translateX(var(--move-x-to));
  }
}`}

copyCode={true}
/>

</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>Copy and paste the following code into your project.</Step>

<ComponentSource
  name="logo-timeline"
  title="components/gammaui/logo-timeline.tsx"
/>

<Step>Add the following CSS to your global CSS file.</Step>

<CSSCodeBlock
  fileName="global.css"
code={`
@keyframes move-x {
  from {
    transform: translateX(var(--move-x-from));
  }
  to {
    transform: translateX(var(--move-x-to));
  }
}`}

copyCode={true}
/>

<Step>Update the import paths to match your project setup.</Step>

</Steps>

</TabsContent>

</Tabs>

## Examples

### With Hover Animation

<ComponentPreview name="logo-timeline-demo-2" />

## Usage

```tsx showLineNumbers
import { LogoTimeline, type LogoItem } from "@/components/gammaui/logo-timeline"
```

```tsx showLineNumbers
<LogoTimeline
  items={logos}
  height="h-[500px] sm:h-[600px]"
  iconSize={18}
  showRowSeparator={true}
/>
```

### Hover Animation Example

Animate logos only on hover for an interactive experience:

```tsx showLineNumbers {4}
<LogoTimeline
  items={logos}
  height="h-[400px]"
  animateOnHover={true}
  iconSize={20}
/>
```

### With Title

Add a centered title overlay for additional context:

```tsx showLineNumbers {3}
<LogoTimeline
  items={logos}
  title="Trusted by Industry Leaders"
  height="h-[600px]"
  iconSize={18}
  showRowSeparator={true}
/>
```

## Props

| Prop               | Type         | Default                    | Description                                                      |
| ------------------ | ------------ | -------------------------- | ---------------------------------------------------------------- |
| `items`            | `LogoItem[]` | `-`                        | Array of logo items to display on the timeline (required)        |
| `title`            | `string`     | `-`                        | Optional title text displayed in the center of the timeline      |
| `height`           | `string`     | `"h-[400px] sm:h-[800px]"` | Height of the timeline container (e.g., "h-[400px]", "h-screen") |
| `className`        | `string`     | `-`                        | Additional CSS classes for the container                         |
| `iconSize`         | `number`     | `16`                       | Size of icons in pixels                                          |
| `showRowSeparator` | `boolean`    | `true`                     | Whether to display separator lines between rows                  |
| `animateOnHover`   | `boolean`    | `false`                    | If true, animations only play when the component is hovered      |

## LogoItem Interface

| Prop                | Type                 | Default | Description                                                          |
| ------------------- | -------------------- | ------- | -------------------------------------------------------------------- |
| `label`             | `string`             | `-`     | Label text displayed next to the icon                                |
| `icon`              | `keyof typeof Icons` | `-`     | Icon key from the Icons object (e.g., "discord", "gitHub", "google") |
| `animationDelay`    | `number`             | `-`     | Animation delay in seconds (typically negative, e.g., -23)           |
| `animationDuration` | `number`             | `-`     | Animation duration in seconds (e.g., 30, 40, 45, 60)                 |
| `row`               | `number`             | `-`     | Row index (1-based) to group logos on the same timeline row          |

## Notes

- **Animation Timing**: Use negative `animationDelay` values to stagger logo animations and create a natural flow. The delay determines when each logo starts its animation cycle.
- **Row Organization**: Logos are grouped by their `row` property. Each row can have multiple logos with different animation timings to create a seamless, infinite scrolling effect.
- **Icon Selection**: Icons must be valid keys from the `Icons` object exported from `@/components/icons`. Common icons include: `react`, `ts`, `tailwind`, `gitHub`, `discord`, `openai`, `v0`, `radix`, and more.
- **Animation Duration**: Longer durations create slower, more elegant animations. Typical values range from 30-65 seconds. Logos on the same row should use the same duration for synchronized movement.
- **Responsive Design**: The component is fully responsive and works well on mobile devices. Use Tailwind's responsive height utilities (e.g., `h-[400px] sm:h-[600px]`) for optimal viewing across screen sizes.
- **Performance**: The component uses CSS animations for smooth, performant scrolling without JavaScript overhead.
upstream/apps/www/registry/example/logo-timeline-demo.tsx
파일 저장

import {
  IconBrandApple,
  IconBrandDiscord,
  IconBrandGithub,
  IconBrandGoogleDrive,
  IconBrandMessenger,
  IconBrandNotion,
  IconBrandOpenai,
  IconBrandPaypal,
  IconBrandReact,
  IconBrandTailwind,
  IconBrandTypescript,
  IconBrandWhatsapp,
  IconBrandX,
} from "@tabler/icons-react"

import { LogoTimeline } from "@/registry/gammaui/logo-timeline"
import type { LogoItem } from "@/registry/gammaui/logo-timeline"

const logos: LogoItem[] = [
  // Row 1 - Communication & Social (2 logos, 50s duration, spaced 25s apart)
  {
    label: "Discord",
    icon: <IconBrandDiscord />,
    animationDelay: -50,
    animationDuration: 50,
    row: 1,
  },
  {
    label: "X (Twitter)",
    icon: <IconBrandX />,
    animationDelay: -25,
    animationDuration: 50,
    row: 1,
  },
  // Row 2 - Development Tools (2 logos, 45s duration, spaced 22.5s apart)
  {
    label: "GitHub",
    icon: <IconBrandGithub />,
    animationDelay: -45,
    animationDuration: 45,
    row: 2,
  },
  {
    label: "React",
    icon: <IconBrandReact />,
    animationDelay: -22.5,
    animationDuration: 45,
    row: 2,
  },
  // Row 3 - Development Tools Continued (3 logos, 60s duration, spaced 20s apart)
  {
    label: "TypeScript",
    icon: <IconBrandTypescript />,
    animationDelay: -60,
    animationDuration: 60,
    row: 3,
  },
  {
    label: "Tailwind",
    icon: <IconBrandTailwind />,
    animationDelay: -40,
    animationDuration: 60,
    row: 3,
  },

  // Row 4 - Productivity & Cloud (2 logos, 55s duration, spaced 27.5s apart)
  {
    label: "Google Drive",
    icon: <IconBrandGoogleDrive />,
    animationDelay: -55,
    animationDuration: 55,
    row: 4,
  },
  {
    label: "Notion",
    icon: <IconBrandNotion />,
    animationDelay: -27.5,
    animationDuration: 55,
    row: 4,
  },
  // Row 5 - Messaging (2 logos, 50s duration, spaced 25s apart)
  {
    label: "WhatsApp",
    icon: <IconBrandWhatsapp />,
    animationDelay: -50,
    animationDuration: 50,
    row: 5,
  },
  {
    label: "Messenger",
    icon: <IconBrandMessenger />, // Placeholder icon
    animationDelay: -25,
    animationDuration: 50,
    row: 5,
  },
  // Row 6 - AI & Automation (3 logos, 65s duration, spaced ~21.5s apart)
  {
    label: "OpenAI",
    icon: <IconBrandOpenai />, // Placeholder icon
    animationDelay: -65,
    animationDuration: 65,
    row: 6,
  },

  // Row 7 - Payment & Services (2 logos, 50s duration, spaced 25s apart)
  {
    label: "PayPal",
    icon: <IconBrandPaypal />, // Placeholder icon
    animationDelay: -50,
    animationDuration: 50,
    row: 7,
  },
  {
    label: "Apple",
    icon: <IconBrandApple />, // Placeholder icon
    animationDelay: -25,
    animationDuration: 50,
    row: 7,
  },
]

export function LogoTimelineDemo() {
  return (
    <div className="w-full overflow-hidden">
      <LogoTimeline
        items={logos}
        title="Built with the best tools"
        height="h-[400px] sm:h-[400px]"
        iconSize={18}
        showRowSeparator={true}
      />
    </div>
  )
}
author/apps/www/registry/gammaui/logo-timeline.tsx
파일 저장

"use client"

import type React from "react"
import { useState } from "react"
import { motion } from "motion/react"

import { cn } from "@/lib/utils"

export interface LogoItem {
  /** The label text displayed next to the icon */
  label: string
  /** The icon name from the Icons object (e.g., "gitHub", "react", "tailwind") */
  icon: React.ReactNode
  /** Animation delay in seconds (use negative values for staggered effect) */
  animationDelay: number
  /** Animation duration in seconds */
  animationDuration: number
  /** The row number where this logo should appear (1-based) */
  row: number
}

export interface LogoTimelineProps {
  /** Array of logo items to display */
  items: LogoItem[]
  /** Optional title text to display in the center */
  title?: string
  /** Height of the timeline container */
  height?: string
  /** Additional className for the container */
  className?: string
  /** Icon size in pixels (default: 16) */
  iconSize?: number
  /** Whether to show separator lines between rows (default: true) */
  showRowSeparator?: boolean
  /** Whether to animate logos only on hover (default: false) */
  animateOnHover?: boolean
}

export function LogoTimeline({
  items,
  title,
  height = "h-[400px] sm:h-[800px]",
  className,
  iconSize = 16,
  showRowSeparator = true,
  animateOnHover = false,
}: LogoTimelineProps) {
  const [isHovered, setIsHovered] = useState(false)

  // Group items by row
  const rowsMap = new Map<number, LogoItem[]>()
  items.forEach((item) => {
    if (!rowsMap.has(item.row)) {
      rowsMap.set(item.row, [])
    }
    rowsMap.get(item.row)?.push(item)
  })

  // Convert map to sorted array
  const rows = Array.from(rowsMap.entries())
    .sort(([a], [b]) => a - b)
    .map(([, rowItems]) => rowItems)

  // Determine animation play state
  const animationPlayState = animateOnHover
    ? isHovered
      ? "running"
      : "paused"
    : "running"

  return (
    <section className={cn("w-full", height, className)}>
      <motion.div
        aria-hidden="true"
        className="bg-background relative h-full w-full overflow-hidden py-24 ring-inset sm:py-32"
        onMouseEnter={() => animateOnHover && setIsHovered(true)}
        onMouseLeave={() => animateOnHover && setIsHovered(false)}
      >
        {title && (
          <div className="absolute top-1/2 left-1/2 mx-auto w-full max-w-[90%] -translate-x-1/2 -translate-y-1/2 text-center">
            <div className="relative z-10">
              <p className="text-foreground/10 mx-auto mt-2 max-w-3xl text-4xl font-semibold tracking-tight text-pretty sm:text-5xl md:text-6xl">
                {title}
              </p>
            </div>
          </div>
        )}
        <div
          className="@container absolute inset-0 grid"
          style={{ gridTemplateRows: `repeat(${rows.length}, 1fr)` }}
        >
          {rows.map((rowItems, index) => (
            <div className="group relative flex items-center" key={index}>
              <div className="from-foreground/15 dark:from-foreground/15 absolute inset-x-0 top-1/2 h-0.5 bg-linear-to-r from-[2px] to-[2px] bg-size-[12px_100%]" />
              {showRowSeparator && (
                <div className="from-foreground/5 dark:from-foreground/5 absolute inset-x-0 bottom-0 h-0.5 bg-linear-to-r from-[2px] to-[2px] bg-size-[12px_100%] group-last:hidden" />
              )}
              {rowItems.map((logo) => {
                return (
                  <div
                    key={`${logo.row}-${logo.label}`}
                    className={cn(
                      "absolute top-1/2 flex -translate-y-1/2 items-center gap-2 px-3 py-1.5 whitespace-nowrap",
                      "ring-background/10 dark:ring-foreground/10 rounded-full bg-linear-to-t from-white/50 from-50% to-white/50 ring-1 backdrop-blur-sm ring-inset dark:from-neutral-900 dark:to-gray-900",
                      "repeat-[infinite] [--move-x-from:-100%] [--move-x-to:calc(100%+100cqw)] [animation-name:move-x] [animation-timing-function:linear]",
                      "shadow-[0_0_15px_rgba(0,0,0,0.1)] dark:shadow-none"
                    )}
                    style={{
                      animationDelay: `${logo.animationDelay}s`,
                      animationDuration: `${logo.animationDuration}s`,
                      animationPlayState: animationPlayState,
                    }}
                  >
                    {logo.icon}
                    <span className="text-foreground text-sm/6 font-medium">
                      {logo.label}
                    </span>
                  </div>
                )
              })}
            </div>
          ))}
        </div>
      </motion.div>
    </section>
  )
}
author/apps/www/lib/utils.ts
파일 저장

import process from "process"
import { Metadata } from "next"
import clsx, { ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

export const pluralize = (count: number, word: string) =>
  `${count} ${word}${count === 1 ? "" : "s"}`

export function humanize(name: string): string {
  return name
    .replace(/-/g, " ")
    .replace(/([A-Z])/g, " $1")
    .trim()
    .split(/\s+/)
    .map((word) => word[0].toUpperCase() + word.substring(1).toLowerCase())
    .join(" ")
}

export const truncate = (str: string | null, length: number) => {
  if (!str || str.length <= length) return str
  return `${str.slice(0, length - 3)}...`
}

export const fetcher = (...args: Parameters<typeof fetch>) =>
  fetch(...args).then((res) => res.json())

/**
 * Capitalizes first letters of words in string.
 * @param {string} str String to be modified
 * @param {boolean=false} lower Whether all other letters should be lowercased
 * @return {string}
 * @see https://stackoverflow.com/questions/2332811/capitalize-words-in-string/7592235#7592235
 * @usage
 *   capitalize('fix this string');     // -> 'Fix This String'
 *   capitalize('javaSCrIPT');          // -> 'JavaSCrIPT'
 *   capitalize('javaSCrIPT', true);    // -> 'Javascript'
 */
export const capitalize = (str: string, lower = false) =>
  (lower ? str.toLowerCase() : str).replace(/(?:^|\s|["'([{])+\S/g, (match) =>
    match.toUpperCase()
  )

export function formatDate(input: string | number): string {
  const date = new Date(input)
  return date.toLocaleDateString("en-US", {
    month: "long",
    day: "numeric",
    year: "numeric",
  })
}

export const calculateReadingTime = (content: string): number => {
  const words = content?.trim().split(/\s+/).filter(Boolean).length
  return Math.max(1, Math.ceil(words / 200))
}

export const normalizeTag = (tag: unknown): string[] => {
  if (!tag) return []
  return Array.isArray(tag)
    ? tag.filter((t): t is string => typeof t === "string")
    : [String(tag)]
}

export function absoluteUrl(path: string) {
  return `${process.env.NEXT_PUBLIC_APP_URL}${path}`
}

export function constructMetadata({
  title = "Gamma UI - Modern React + Tailwind CSS components & Templates",
  description = "Beautifully designed landing page components built with React & Tailwind CSS & Motion.",
  image = absoluteUrl("/og"),
  ...props
}: {
  title?: string
  description?: string
  image?: string
  [key: string]: Metadata[keyof Metadata]
}): Metadata {
  return {
    title,
    description,
    keywords: [
      "GammaUI",
      "UI Library",
      "Component Library",
      "React Components",
      "Next.js",
      "Tailwind CSS",
      "Open Source",
      "Frontend",
      "Design System",
      "Mazyar Kawa",
      "GammaUI by Aman",
      "Copy and Paste Components",
      "Developer Tools",
      "Frontend Engineer",
      "Beautiful UI",
      "React UI Kit",
      "Free React Components",
      "Open Source UI Kit",
      "Tailwind UI Components",
      "Headless UI",
      "Reusable React Components",
      "UI Templates",
      "Accessible React Components",
      "Copy-Paste UI",
      "Next.js Component Library",
      "Open Source Developer Tools",
      "Frontend Design System",
      "Minimal UI Kit",
      "Clean React UI",
    ],
    openGraph: {
      title,
      description,
      type: "website",
      images: [
        {
          url: image,
          width: 1200,
          height: 630,
        },
      ],
    },
    twitter: {
      card: "summary_large_image",
      title,
      description,
      images: [image],
      creator: "@mazyar_kawa",
    },
    icons: "/favicon.ico",
    metadataBase: new URL("https://gammaui.com"),
    authors: [
      {
        name: "mazyar_kawa",
        url: "https://twitter.com/mazyar_kawa",
      },
    ],
    creator: "mazyar_kawa",
    ...props,
  }
}
author/apps/www/app/globals.css
파일 저장

@import "tailwindcss";
@import "tw-animate-css";

@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: Open Sans, sans-serif;
  --font-mono: Menlo, monospace;
  --font-serif: Georgia, serif;
  --radius: 1.3rem;
  --tracking-tighter: calc(var(--tracking-normal) - 0.05em);
  --tracking-tight: calc(var(--tracking-normal) - 0.025em);
  --tracking-wide: calc(var(--tracking-normal) + 0.025em);
  --tracking-wider: calc(var(--tracking-normal) + 0.05em);
  --tracking-widest: calc(var(--tracking-normal) + 0.1em);
  --tracking-normal: var(--tracking-normal);
  --shadow-2xl: var(--shadow-2xl);
  --shadow-xl: var(--shadow-xl);
  --shadow-lg: var(--shadow-lg);
  --shadow-md: var(--shadow-md);
  --shadow: var(--shadow);
  --shadow-sm: var(--shadow-sm);
  --shadow-xs: var(--shadow-xs);
  --shadow-2xs: var(--shadow-2xs);
  --spacing: var(--spacing);
  --letter-spacing: var(--letter-spacing);
  --shadow-offset-y: var(--shadow-offset-y);
  --shadow-offset-x: var(--shadow-offset-x);
  --shadow-spread: var(--shadow-spread);
  --shadow-blur: var(--shadow-blur);
  --shadow-opacity: var(--shadow-opacity);
  --color-shadow-color: var(--shadow-color);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar: var(--sidebar);
  --color-chart-5: var(--chart-5);
  --color-chart-4: var(--chart-4);
  --color-chart-3: var(--chart-3);
  --color-chart-2: var(--chart-2);
  --color-chart-1: var(--chart-1);
  --color-ring: var(--ring);
  --color-input: var(--input);
  --color-border: var(--border);
  --color-destructive: var(--destructive);
  --color-accent-foreground: var(--accent-foreground);
  --color-accent: var(--accent);
  --color-muted-foreground: var(--muted-foreground);
  --color-muted: var(--muted);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-secondary: var(--secondary);
  --color-primary-foreground: var(--primary-foreground);
  --color-primary: var(--primary);
  --color-popover-foreground: var(--popover-foreground);
  --color-popover: var(--popover);
  --color-card-foreground: var(--card-foreground);
  --color-card: var(--card);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  --animate-shimmer-slide: shimmer-slide var(--speed) ease-in-out infinite
    alternate;
  --animate-spin-around: spin-around calc(var(--speed) * 2) infinite linear;
  @keyframes shimmer-slide {
    to {
      transform: translate(calc(100cqw - 100%), 0);
    }
  }
  @keyframes spin-around {
    0% {
      transform: translateZ(0) rotate(0);
    }
    15%,
    35% {
      transform: translateZ(0) rotate(90deg);
    }
    65%,
    85% {
      transform: translateZ(0) rotate(270deg);
    }
    100% {
      transform: translateZ(0) rotate(360deg);
    }
  }
}

:root {
  --header-height: 64px;
  --radius: 1.3rem;
  --card: oklch(0.9784 0.0011 197.1387);
  --card-foreground: oklch(0.1884 0.0128 248.5103);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.1884 0.0128 248.5103);
  --primary: oklab(67.23% -0.06787 -0.14566);
  --primary-foreground: oklch(1 0 0);
  --secondary: oklch(0.1884 0.0128 248.5103);
  --secondary-foreground: oklch(1 0 0);
  --muted: oklch(0.9222 0.0013 286.3737);
  --muted-foreground: oklch(0.1884 0.0128 248.5103);
  --accent: oklch(0.9392 0.0166 250.8453);
  --accent-foreground: oklch(0.6723 0.1606 244.9955);
  --destructive: oklch(0.6188 0.2376 25.7658);
  --border: oklch(0.9317 0.0118 231.6594);
  --input: oklch(0.9809 0.0025 228.7836);
  --ring: oklch(0.6818 0.1584 243.354);
  --chart-1: oklch(0.6723 0.1606 244.9955);
  --chart-2: oklch(0.6907 0.1554 160.3454);
  --chart-3: oklch(0.8214 0.16 82.5337);
  --chart-4: oklch(0.7064 0.1822 151.7125);
  --chart-5: oklch(0.5919 0.2186 10.5826);
  --sidebar: oklch(0.9784 0.0011 197.1387);
  --sidebar-foreground: oklch(0.1884 0.0128 248.5103);
  --sidebar-primary: oklch(0.6723 0.1606 244.9955);
  --sidebar-primary-foreground: oklch(1 0 0);
  --sidebar-accent: oklch(0.9392 0.0166 250.8453);
  --sidebar-accent-foreground: oklch(0.6723 0.1606 244.9955);
  --sidebar-border: oklch(0.9271 0.0101 238.5177);
  --sidebar-ring: oklch(0.6818 0.1584 243.354);
  --destructive-foreground: oklch(1 0 0);
  --font-sans: Open Sans, sans-serif;
  --font-serif: Georgia, serif;
  --font-mono: Menlo, monospace;
  --shadow-color: rgba(29, 161, 242, 0.15);
  --shadow-opacity: 0;
  --shadow-blur: 0px;
  --shadow-spread: 0px;
  --shadow-offset-x: 0px;
  --shadow-offset-y: 2px;
  --letter-spacing: 0em;
  --spacing: 0.25rem;
  --shadow-2xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-sm:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-md:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 2px 4px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-lg:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 4px 6px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-xl:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 8px 10px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-2xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --tracking-normal: 0em;
  --background: oklch(1 0 0);
  --foreground: oklch(0.1884 0.0128 248.5103);
}

.dark {
  --header-height: 64px;
  --background: oklch(0 0 0);
  --foreground: oklch(0.9328 0.0025 228.7857);
  --card: oklch(0.2097 0.008 274.5332);
  --card-foreground: oklch(0.8853 0 0);
  --popover: oklch(0 0 0);
  --popover-foreground: oklch(0.9328 0.0025 228.7857);
  --primary: oklch(0.6692 0.1607 245.011);
  --primary-foreground: oklch(1 0 0);
  --secondary: oklch(0.9622 0.0035 219.5331);
  --secondary-foreground: oklch(0.1884 0.0128 248.5103);
  --muted: oklch(0.209 0 0);
  --muted-foreground: oklch(0.5637 0.0078 247.9662);
  --accent: oklch(0.1928 0.0331 242.5459);
  --accent-foreground: oklch(0.6692 0.1607 245.011);
  --destructive: oklch(0.6188 0.2376 25.7658);
  --border: oklch(0.2674 0.0047 248.0045);
  --input: oklch(0.302 0.0288 244.8244);
  --ring: oklch(0.6818 0.1584 243.354);
  --chart-1: oklch(0.6723 0.1606 244.9955);
  --chart-2: oklch(0.6907 0.1554 160.3454);
  --chart-3: oklch(0.8214 0.16 82.5337);
  --chart-4: oklch(0.7064 0.1822 151.7125);
  --chart-5: oklch(0.5919 0.2186 10.5826);
  --sidebar: oklch(0.2097 0.008 274.5332);
  --sidebar-foreground: oklch(0.8853 0 0);
  --sidebar-primary: oklch(0.6818 0.1584 243.354);
  --sidebar-primary-foreground: oklch(1 0 0);
  --sidebar-accent: oklch(0.1928 0.0331 242.5459);
  --sidebar-accent-foreground: oklch(0.6692 0.1607 245.011);
  --sidebar-border: oklch(0.3795 0.022 240.5943);
  --sidebar-ring: oklch(0.6818 0.1584 243.354);
  --destructive-foreground: oklch(1 0 0);
  --radius: 1.3rem;
  --font-sans: Open Sans, sans-serif;
  --font-serif: Georgia, serif;
  --font-mono: Menlo, monospace;
  --shadow-color: rgba(29, 161, 242, 0.25);
  --shadow-opacity: 0;
  --shadow-blur: 0px;
  --shadow-spread: 0px;
  --shadow-offset-x: 0px;
  --shadow-offset-y: 2px;
  --letter-spacing: 0em;
  --spacing: 0.25rem;
  --shadow-2xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-sm:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-md:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 2px 4px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-lg:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 4px 6px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-xl:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 8px 10px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-2xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
    letter-spacing: var(--tracking-normal);
  }
}

.no-scrollbar {
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE and Edge */
}

.no-scrollbar::-webkit-scrollbar {
  display: none; /* Chrome, Safari, Opera */
}

.radial-gradient {
  background: radial-gradient(
      circle at 50% 0%,
      rgba(250, 250, 250, 0.05) 0%,
      transparent 60%
    )
    rgba(15, 15, 15, 1);
  transition: background 0.3s ease-in-out;
  width: fit-content;
}

.radial-gradient:hover {
  background: radial-gradient(
      circle at 50% 0%,
      rgba(250, 250, 250, 0.05) 0%,
      transparent 60%
    )
    rgba(15, 15, 15, 0.5);
}

.radial-gradient-border-button {
  background: radial-gradient(
      circle at 50% 0%,
      rgba(250, 250, 250, 0.05) 0%,
      transparent 60%
    )
    rgba(15, 15, 15, 1);
  transition: background 0.3s ease-in-out;
  width: fit-content;
}

.linear-mask {
  mask-image: linear-gradient(
    -75deg,
    white calc(var(--x) + 20%),
    transparent calc(var(--x) + 30%),
    white calc(var(--x) + 100%)
  );
  -webkit-mask-image: linear-gradient(
    -75deg,
    white calc(var(--x) + 20%),
    transparent calc(var(--x) + 30%),
    white calc(var(--x) + 100%)
  );
}

.linear-overlay {
  background-image: linear-gradient(
    -75deg,
    rgba(255, 255, 255, 0.1) calc(var(--x) + 20%),
    rgba(255, 255, 255, 0.5) calc(var(--x) + 25%),
    rgba(255, 255, 255, 0.1) calc(var(--x) + 100%)
  );
  mask:
    linear-gradient(black, black) content-box,
    linear-gradient(black, black);
  -webkit-mask:
    linear-gradient(black, black) content-box,
    linear-gradient(black, black);
  mask-composite: exclude;
  -webkit-mask-composite: xor;
  padding: 1px;
}

@keyframes move-x {
  from {
    transform: translateX(var(--move-x-from));
  }
  to {
    transform: translateX(var(--move-x-to));
  }
}

.cpu-architecture {
  offset-anchor: 10px 0px;
  animation: animation-path;
  animation-iteration-count: infinite;
  animation-timing-function: cubic-bezier(0.75, -0.01, 0, 0.99);
}

.cpu-line-1 {
  offset-path: path("M 10 20 h 79.5 q 5 0 5 5 v 30");
  animation-duration: 5s;
  animation-delay: 1s;
}

.cpu-line-2 {
  offset-path: path("M 180 10 h -69.7 q -5 0 -5 5 v 40");
  animation-delay: 6s;
  animation-duration: 2s;
}

.cpu-line-3 {
  offset-path: path("M 130 20 v 21.8 q 0 5 -5 5 h -25");
  animation-delay: 4s;
  animation-duration: 6s;
}

.cpu-line-4 {
  offset-path: path("M 170 80 v -21.8 q 0 -5 -5 -5 h -65");
  animation-delay: 3s;
  animation-duration: 3s;
}

.cpu-line-5 {
  offset-path: path(
    "M 135 65 h 15 q 5 0 5 5 v 10 q 0 5 -5 5 h -39.8 q -5 0 -5 -5 v -35"
  );
  animation-delay: 9s;
  animation-duration: 4s;
}

.cpu-line-6 {
  offset-path: path("M 94.8 95 v -46");
  animation-delay: 3s;
  animation-duration: 7s;
}

.cpu-line-7 {
  offset-path: path(
    "M 88 88 v -15 q 0 -5 -5 -5 h -10 q -5 0 -5 -5 v -5 q 0 -5 5 -5 h 28"
  );
  animation-delay: 4s;
  animation-duration: 4s;
}

.cpu-line-8 {
  offset-path: path("M 30 30 h 25 q 5 0 5 5 v 6.5 q 0 5 5 5 h 35");
  animation-delay: 3s;
  animation-duration: 3s;
}

@keyframes animation-path {
  0% {
    offset-distance: 0%;
  }
  100% {
    offset-distance: 100%;
  }
}

.shadcn {
  offset-anchor: 10px 0px;
  animation: shadcn-animation-path;
  animation-iteration-count: infinite;
  animation-timing-function: cubic-bezier(0.75, -0.01, 0, 0.99);
  animation-duration: 4s;
  animation-delay: 1.5s;
}

.shadcn-line-1 {
  offset-path: path("M 35 14 h 25 q 5 0 5 5 v 23 q 0 5 5 5 h 25");
}

.shadcn-line-2 {
  offset-path: path("M 30 50 h 65");
}

.shadcn-line-3 {
  offset-path: path("M 36.3 87 h 24 q 5 0 5 -5 v -24 q 0 -5 5 -5 h 25");
}

.compBox {
  animation: shadncn-animation-path-backwards;
  animation-iteration-count: infinite;
  animation-timing-function: cubic-bezier(0.57, 0.02, 0, 1);
  offset-path: path("M 167 36 h -17 q -5 0 -5 5 v 4 q 0 5 -5 5 h -32");
  animation-duration: 4s;
  opacity: 0;
  animation-delay: 5s;
}

@keyframes shadncn-animation-path-backwards {
  0% {
    offset-distance: 100%;
    opacity: 0;
  }
  90% {
    opacity: 1;
    offset-distance: 0%;
  }
  100% {
    opacity: 0;
  }
}

@keyframes shadcn-animation-path {
  0% {
    offset-distance: 0%;
  }
  100% {
    offset-distance: 100%;
  }
}

@keyframes landing-marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

.animate-landing-marquee {
  animation: landing-marquee var(--marquee-duration, 45s) linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .animate-landing-marquee {
    animation: none;
  }
}
author/apps/www/package.json
파일 저장

{
  "name": "www",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "preinstall": "npx only-allow bun",
    "postinstall": "fumadocs-mdx",
    "dev": "next dev",
    "prebuild": "bun run build:registry",
    "build": "next build",
    "build:registry": "npx tsx ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache || true",
    "shadcn:build": "npx shadcn build registry.json --output ../www/public/r",
    "typecheck": "tsc --noEmit",
    "start": "next start",
    "lint": "eslint .",
    "lint:fix": "eslint --fix .",
    "format:fix": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
    "format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
    "check": "bun run lint && bun run typecheck && bun run format:check"
  },
  "dependencies": {
    "@number-flow/react": "^0.5.10",
    "@radix-ui/react-accordion": "^1.2.12",
    "@radix-ui/react-collapsible": "^1.1.12",
    "@radix-ui/react-dialog": "^1.1.15",
    "@radix-ui/react-dropdown-menu": "^2.1.16",
    "@radix-ui/react-icons": "^1.3.2",
    "@radix-ui/react-popover": "^1.1.15",
    "@radix-ui/react-separator": "^1.1.7",
    "@radix-ui/react-slot": "^1.2.4",
    "@radix-ui/react-tabs": "^1.1.13",
    "@radix-ui/react-tooltip": "^1.2.8",
    "@react-three/fiber": "^9.6.1",
    "@react-three/postprocessing": "^3.0.4",
    "@tabler/icons-react": "^3.35.0",
    "@types/react-syntax-highlighter": "^15.5.13",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "front-matter": "^4.0.2",
    "fumadocs-core": "^16.2.5",
    "fumadocs-mdx": "^13.0.5",
    "jotai": "^2.15.1",
    "lucide-react": "^0.552.0",
    "motion": "^12.23.24",
    "next": "15.2.6",
    "next-themes": "^0.4.6",
    "postprocessing": "^6.39.2",
    "prettier-plugin-tailwindcss": "^0.7.1",
    "react": "^19.2.3",
    "react-dom": "^19.2.3",
    "react-syntax-highlighter": "^16.1.0",
    "rehype-pretty-code": "^0.14.1",
    "schema-dts": "^1.1.5",
    "sonner": "^2.0.7",
    "tailwind-merge": "^3.3.1",
    "three": "^0.185.1",
    "ts-morph": "^27.0.2",
    "zod": "^4.1.13"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3.3.1",
    "@eslint/js": "^9.39.1",
    "@ianvs/prettier-plugin-sort-imports": "^4.7.0",
    "@shikijs/transformers": "^3.20.0",
    "@tailwindcss/postcss": "^4.0.0",
    "@types/node": "^20.0.0",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "@types/three": "^0.185.0",
    "@typescript-eslint/eslint-plugin": "^8.47.0",
    "@typescript-eslint/parser": "^8.47.0",
    "eslint": "^9.39.2",
    "eslint-config-next": "^16.0.10",
    "eslint-config-prettier": "^10.1.8",
    "eslint-plugin-react": "^7.37.5",
    "eslint-plugin-react-hooks": "^7.0.1",
    "lefthook": "^2.0.4",
    "prettier": "^3.6.2",
    "rimraf": "^6.1.2",
    "shiki": "^3.20.0",
    "tailwindcss": "^4.0.0",
    "tw-animate-css": "^1.4.0",
    "typescript": "^5.0.0",
    "typescript-eslint": "^8.47.0"
  },
  "engines": {
    "bun": ">=1.1.43"
  },
  "prettier": {
    "endOfLine": "lf",
    "semi": false,
    "singleQuote": false,
    "tabWidth": 2,
    "trailingComma": "es5",
    "importOrder": [
      "^(react/(.*)$)|^(react$)",
      "^(next/(.*)$)|^(next$)",
      "<THIRD_PARTY_MODULES>",
      "",
      "^@workspace/(.*)$",
      "",
      "^types$",
      "^@/types/(.*)$",
      "^@/config/(.*)$",
      "^@/lib/(.*)$",
      "^@/hooks/(.*)$",
      "^@/components/ui/(.*)$",
      "^@/components/(.*)$",
      "^@/registry/(.*)$",
      "^@/styles/(.*)$",
      "^@/app/(.*)$",
      "^@/www/(.*)$",
      "",
      "^[./]"
    ],
    "importOrderParserPlugins": [
      "typescript",
      "jsx",
      "decorators-legacy"
    ],
    "plugins": [
      "@ianvs/prettier-plugin-sort-imports",
      "prettier-plugin-tailwindcss"
    ]
  }
}
Usage.tsx실행 안내·자료
파일 저장

// Local host for the unchanged exact baseline demonstration.
import {LogoTimelineDemo as OriginalDemo} from "./baseline-example.tsx";
export default function Demo() { return <><OriginalDemo /></>; }
runtime/author-mount.tsx실행 안내·자료
파일 저장

/** Local sandbox host. Ready is a mount observation, never a verification result. */
import React, { Component, useEffect } from 'react';
import { createRoot } from 'react-dom/client';

declare global {
  interface Window {
    __STYLEGALLERY_PREVIEW__: { id: string; status: string; errors: string[] };
  }
}

export function mount(Demo: React.ComponentType, id: string) {
  const state = window.__STYLEGALLERY_PREVIEW__ = { id, status: 'loading', errors: [] as string[] };
  const send = (message: object) => parent.postMessage({ ...message, id }, '*');
  const report = (error: unknown) => {
    const message = error instanceof Error ? error.message : String(error);
    if (!state.errors.includes(message)) state.errors.push(message);
    state.status = 'error';
    document.body.dataset.previewStatus = 'error';
    send({ type: 'sg-preview-error', message });
  };
  window.addEventListener('error', (event) => report(event.error ?? event.message));
  window.addEventListener('unhandledrejection', (event) => report(event.reason));
  window.addEventListener('securitypolicyviolation', (event) => report(`Blocked by preview policy: ${event.violatedDirective}`));
  const theme = new URLSearchParams(location.search).get('theme') === 'dark' ? 'dark' : 'light';
  document.documentElement.classList.toggle('dark', theme === 'dark');
  document.documentElement.dataset.theme = theme;
  // Keep original source bytes while ensuring imported attribution/navigation remains local text.
  const removeDestinations = () => document.querySelectorAll('a[href]').forEach((link) => {
    link.removeAttribute('href');
    link.removeAttribute('target');
  });
  document.addEventListener('click', (event) => {
    if ((event.target as Element)?.closest?.('a')) event.preventDefault();
  }, true);
  document.addEventListener('submit', (event) => event.preventDefault());
  new MutationObserver(removeDestinations).observe(document.getElementById('root')!, { childList: true, subtree: true });
  const observe = () => {
    const root = document.getElementById('root')!;
    const elements = Array.from(root.querySelectorAll('*'));
    const rect = root.getBoundingClientRect();
    const diagnostics = {
      textLength: (root.textContent ?? '').trim().length,
      elementCount: elements.length,
      visibleElementCount: elements.filter((element) => {
        const bounds = element.getBoundingClientRect();
        const style = getComputedStyle(element);
        return bounds.width > 0 && bounds.height > 0 && style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0;
      }).length,
      images: [],
      canvases: [],
      rootRect: { width: rect.width, height: rect.height },
      documentWidth: document.documentElement.scrollWidth,
      viewportWidth: innerWidth,
    };
    send({ type: 'sg-preview-observation', diagnostics });
  };
  class Boundary extends Component<{ children: React.ReactNode }, { error: string | null }> {
    state = { error: null as string | null };
    static getDerivedStateFromError(error: Error) { return { error: error.message }; }
    componentDidCatch(error: Error) { report(error); }
    render() { return this.state.error ? <p role="alert">This preview could not render: {this.state.error}</p> : this.props.children; }
  }
  function Ready() {
    useEffect(() => {
      const preference = matchMedia('(prefers-reduced-motion: reduce)');
      const syncSVG = () => document.querySelectorAll('svg').forEach((svg) => {
        if (preference.matches) svg.pauseAnimations?.();
        else svg.unpauseAnimations?.();
      });
      syncSVG();
      preference.addEventListener('change', syncSVG);
      let second = 0;
      let delayed = 0;
      const first = requestAnimationFrame(() => { second = requestAnimationFrame(() => {
        if (state.status !== 'error') {
          state.status = 'mounted';
          document.body.dataset.previewStatus = 'mounted';
          send({ type: 'sg-preview-ready' });
          observe();
          delayed = window.setTimeout(observe, 700);
        }
      }); });
      return () => {
        cancelAnimationFrame(first);
        cancelAnimationFrame(second);
        clearTimeout(delayed);
        preference.removeEventListener('change', syncSVG);
      };
    }, []);
    return <Demo />;
  }
  createRoot(document.getElementById('root')!).render(<Boundary><Ready /></Boundary>);
}
runtime/author-styles.css실행 안내·자료
파일 저장

@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--background);
  --color-card-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--muted);
  --color-secondary-foreground: var(--foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--muted);
  --color-accent-foreground: var(--foreground);
  --color-popover: var(--background);
  --color-popover-foreground: var(--foreground);
  --color-destructive: #dc2626;
  --color-destructive-foreground: #ffffff;
  --color-input: var(--border);
  --color-border: var(--border);
  --color-ring: var(--foreground);
  --radius-lg: 0.5rem;
  --radius-md: 0.375rem;
  --radius-sm: 0.25rem;
}

:root {
  --background: #ffffff;
  --border: #d4d4d8;
  --foreground: #18181b;
  --muted: #f4f4f5;
  --muted-foreground: #71717a;
  --primary: #18181b;
  --primary-foreground: #fafafa;
  background: var(--background);
  color: var(--foreground);
  color-scheme: light;
  font-family: Arial, sans-serif;
}

.dark {
  --background: #09090b;
  --border: #3f3f46;
  --foreground: #fafafa;
  --muted: #27272a;
  --muted-foreground: #a1a1aa;
  --primary: #fafafa;
  --primary-foreground: #18181b;
  color-scheme: dark;
}

body {
  margin: 0;
  min-height: 100vh;
}

#root {
  align-items: center;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
  width: 100%;
}

#root > * {
  max-width: 100%;
}

noscript {
  display: block;
  padding: 24px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-play-state: paused !important;
  }
}
runtime/applied-theme.css실행 안내·자료
파일 저장

@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: Open Sans, sans-serif;
  --font-mono: Menlo, monospace;
  --font-serif: Georgia, serif;
  --radius: 1.3rem;
  --tracking-tighter: calc(var(--tracking-normal) - 0.05em);
  --tracking-tight: calc(var(--tracking-normal) - 0.025em);
  --tracking-wide: calc(var(--tracking-normal) + 0.025em);
  --tracking-wider: calc(var(--tracking-normal) + 0.05em);
  --tracking-widest: calc(var(--tracking-normal) + 0.1em);
  --tracking-normal: var(--tracking-normal);
  --shadow-2xl: var(--shadow-2xl);
  --shadow-xl: var(--shadow-xl);
  --shadow-lg: var(--shadow-lg);
  --shadow-md: var(--shadow-md);
  --shadow: var(--shadow);
  --shadow-sm: var(--shadow-sm);
  --shadow-xs: var(--shadow-xs);
  --shadow-2xs: var(--shadow-2xs);
  --spacing: var(--spacing);
  --letter-spacing: var(--letter-spacing);
  --shadow-offset-y: var(--shadow-offset-y);
  --shadow-offset-x: var(--shadow-offset-x);
  --shadow-spread: var(--shadow-spread);
  --shadow-blur: var(--shadow-blur);
  --shadow-opacity: var(--shadow-opacity);
  --color-shadow-color: var(--shadow-color);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar: var(--sidebar);
  --color-chart-5: var(--chart-5);
  --color-chart-4: var(--chart-4);
  --color-chart-3: var(--chart-3);
  --color-chart-2: var(--chart-2);
  --color-chart-1: var(--chart-1);
  --color-ring: var(--ring);
  --color-input: var(--input);
  --color-border: var(--border);
  --color-destructive: var(--destructive);
  --color-accent-foreground: var(--accent-foreground);
  --color-accent: var(--accent);
  --color-muted-foreground: var(--muted-foreground);
  --color-muted: var(--muted);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-secondary: var(--secondary);
  --color-primary-foreground: var(--primary-foreground);
  --color-primary: var(--primary);
  --color-popover-foreground: var(--popover-foreground);
  --color-popover: var(--popover);
  --color-card-foreground: var(--card-foreground);
  --color-card: var(--card);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  --animate-shimmer-slide: shimmer-slide var(--speed) ease-in-out infinite
    alternate;
  --animate-spin-around: spin-around calc(var(--speed) * 2) infinite linear;
  @keyframes shimmer-slide {
    to {
      transform: translate(calc(100cqw - 100%), 0);
    }
  }
  @keyframes spin-around {
    0% {
      transform: translateZ(0) rotate(0);
    }
    15%,
    35% {
      transform: translateZ(0) rotate(90deg);
    }
    65%,
    85% {
      transform: translateZ(0) rotate(270deg);
    }
    100% {
      transform: translateZ(0) rotate(360deg);
    }
  }
}

:root {
  --header-height: 64px;
  --radius: 1.3rem;
  --card: oklch(0.9784 0.0011 197.1387);
  --card-foreground: oklch(0.1884 0.0128 248.5103);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.1884 0.0128 248.5103);
  --primary: oklab(67.23% -0.06787 -0.14566);
  --primary-foreground: oklch(1 0 0);
  --secondary: oklch(0.1884 0.0128 248.5103);
  --secondary-foreground: oklch(1 0 0);
  --muted: oklch(0.9222 0.0013 286.3737);
  --muted-foreground: oklch(0.1884 0.0128 248.5103);
  --accent: oklch(0.9392 0.0166 250.8453);
  --accent-foreground: oklch(0.6723 0.1606 244.9955);
  --destructive: oklch(0.6188 0.2376 25.7658);
  --border: oklch(0.9317 0.0118 231.6594);
  --input: oklch(0.9809 0.0025 228.7836);
  --ring: oklch(0.6818 0.1584 243.354);
  --chart-1: oklch(0.6723 0.1606 244.9955);
  --chart-2: oklch(0.6907 0.1554 160.3454);
  --chart-3: oklch(0.8214 0.16 82.5337);
  --chart-4: oklch(0.7064 0.1822 151.7125);
  --chart-5: oklch(0.5919 0.2186 10.5826);
  --sidebar: oklch(0.9784 0.0011 197.1387);
  --sidebar-foreground: oklch(0.1884 0.0128 248.5103);
  --sidebar-primary: oklch(0.6723 0.1606 244.9955);
  --sidebar-primary-foreground: oklch(1 0 0);
  --sidebar-accent: oklch(0.9392 0.0166 250.8453);
  --sidebar-accent-foreground: oklch(0.6723 0.1606 244.9955);
  --sidebar-border: oklch(0.9271 0.0101 238.5177);
  --sidebar-ring: oklch(0.6818 0.1584 243.354);
  --destructive-foreground: oklch(1 0 0);
  --font-sans: Open Sans, sans-serif;
  --font-serif: Georgia, serif;
  --font-mono: Menlo, monospace;
  --shadow-color: rgba(29, 161, 242, 0.15);
  --shadow-opacity: 0;
  --shadow-blur: 0px;
  --shadow-spread: 0px;
  --shadow-offset-x: 0px;
  --shadow-offset-y: 2px;
  --letter-spacing: 0em;
  --spacing: 0.25rem;
  --shadow-2xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-sm:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-md:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 2px 4px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-lg:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 4px 6px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-xl:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 8px 10px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-2xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --tracking-normal: 0em;
  --background: oklch(1 0 0);
  --foreground: oklch(0.1884 0.0128 248.5103);
}

.dark {
  --header-height: 64px;
  --background: oklch(0 0 0);
  --foreground: oklch(0.9328 0.0025 228.7857);
  --card: oklch(0.2097 0.008 274.5332);
  --card-foreground: oklch(0.8853 0 0);
  --popover: oklch(0 0 0);
  --popover-foreground: oklch(0.9328 0.0025 228.7857);
  --primary: oklch(0.6692 0.1607 245.011);
  --primary-foreground: oklch(1 0 0);
  --secondary: oklch(0.9622 0.0035 219.5331);
  --secondary-foreground: oklch(0.1884 0.0128 248.5103);
  --muted: oklch(0.209 0 0);
  --muted-foreground: oklch(0.5637 0.0078 247.9662);
  --accent: oklch(0.1928 0.0331 242.5459);
  --accent-foreground: oklch(0.6692 0.1607 245.011);
  --destructive: oklch(0.6188 0.2376 25.7658);
  --border: oklch(0.2674 0.0047 248.0045);
  --input: oklch(0.302 0.0288 244.8244);
  --ring: oklch(0.6818 0.1584 243.354);
  --chart-1: oklch(0.6723 0.1606 244.9955);
  --chart-2: oklch(0.6907 0.1554 160.3454);
  --chart-3: oklch(0.8214 0.16 82.5337);
  --chart-4: oklch(0.7064 0.1822 151.7125);
  --chart-5: oklch(0.5919 0.2186 10.5826);
  --sidebar: oklch(0.2097 0.008 274.5332);
  --sidebar-foreground: oklch(0.8853 0 0);
  --sidebar-primary: oklch(0.6818 0.1584 243.354);
  --sidebar-primary-foreground: oklch(1 0 0);
  --sidebar-accent: oklch(0.1928 0.0331 242.5459);
  --sidebar-accent-foreground: oklch(0.6692 0.1607 245.011);
  --sidebar-border: oklch(0.3795 0.022 240.5943);
  --sidebar-ring: oklch(0.6818 0.1584 243.354);
  --destructive-foreground: oklch(1 0 0);
  --radius: 1.3rem;
  --font-sans: Open Sans, sans-serif;
  --font-serif: Georgia, serif;
  --font-mono: Menlo, monospace;
  --shadow-color: rgba(29, 161, 242, 0.25);
  --shadow-opacity: 0;
  --shadow-blur: 0px;
  --shadow-spread: 0px;
  --shadow-offset-x: 0px;
  --shadow-offset-y: 2px;
  --letter-spacing: 0em;
  --spacing: 0.25rem;
  --shadow-2xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-sm:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-md:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 2px 4px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-lg:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 4px 6px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-xl:
    0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0),
    0px 8px 10px -1px hsl(202.8169 89.1213% 53.1373% / 0);
  --shadow-2xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
    letter-spacing: var(--tracking-normal);
  }
}

.no-scrollbar {
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE and Edge */
}

.no-scrollbar::-webkit-scrollbar {
  display: none; /* Chrome, Safari, Opera */
}

.radial-gradient {
  background: radial-gradient(
      circle at 50% 0%,
      rgba(250, 250, 250, 0.05) 0%,
      transparent 60%
    )
    rgba(15, 15, 15, 1);
  transition: background 0.3s ease-in-out;
  width: fit-content;
}

.radial-gradient:hover {
  background: radial-gradient(
      circle at 50% 0%,
      rgba(250, 250, 250, 0.05) 0%,
      transparent 60%
    )
    rgba(15, 15, 15, 0.5);
}

.radial-gradient-border-button {
  background: radial-gradient(
      circle at 50% 0%,
      rgba(250, 250, 250, 0.05) 0%,
      transparent 60%
    )
    rgba(15, 15, 15, 1);
  transition: background 0.3s ease-in-out;
  width: fit-content;
}

.linear-mask {
  mask-image: linear-gradient(
    -75deg,
    white calc(var(--x) + 20%),
    transparent calc(var(--x) + 30%),
    white calc(var(--x) + 100%)
  );
  -webkit-mask-image: linear-gradient(
    -75deg,
    white calc(var(--x) + 20%),
    transparent calc(var(--x) + 30%),
    white calc(var(--x) + 100%)
  );
}

.linear-overlay {
  background-image: linear-gradient(
    -75deg,
    rgba(255, 255, 255, 0.1) calc(var(--x) + 20%),
    rgba(255, 255, 255, 0.5) calc(var(--x) + 25%),
    rgba(255, 255, 255, 0.1) calc(var(--x) + 100%)
  );
  mask:
    linear-gradient(black, black) content-box,
    linear-gradient(black, black);
  -webkit-mask:
    linear-gradient(black, black) content-box,
    linear-gradient(black, black);
  mask-composite: exclude;
  -webkit-mask-composite: xor;
  padding: 1px;
}

@keyframes move-x {
  from {
    transform: translateX(var(--move-x-from));
  }
  to {
    transform: translateX(var(--move-x-to));
  }
}

.cpu-architecture {
  offset-anchor: 10px 0px;
  animation: animation-path;
  animation-iteration-count: infinite;
  animation-timing-function: cubic-bezier(0.75, -0.01, 0, 0.99);
}

.cpu-line-1 {
  offset-path: path("M 10 20 h 79.5 q 5 0 5 5 v 30");
  animation-duration: 5s;
  animation-delay: 1s;
}

.cpu-line-2 {
  offset-path: path("M 180 10 h -69.7 q -5 0 -5 5 v 40");
  animation-delay: 6s;
  animation-duration: 2s;
}

.cpu-line-3 {
  offset-path: path("M 130 20 v 21.8 q 0 5 -5 5 h -25");
  animation-delay: 4s;
  animation-duration: 6s;
}

.cpu-line-4 {
  offset-path: path("M 170 80 v -21.8 q 0 -5 -5 -5 h -65");
  animation-delay: 3s;
  animation-duration: 3s;
}

.cpu-line-5 {
  offset-path: path(
    "M 135 65 h 15 q 5 0 5 5 v 10 q 0 5 -5 5 h -39.8 q -5 0 -5 -5 v -35"
  );
  animation-delay: 9s;
  animation-duration: 4s;
}

.cpu-line-6 {
  offset-path: path("M 94.8 95 v -46");
  animation-delay: 3s;
  animation-duration: 7s;
}

.cpu-line-7 {
  offset-path: path(
    "M 88 88 v -15 q 0 -5 -5 -5 h -10 q -5 0 -5 -5 v -5 q 0 -5 5 -5 h 28"
  );
  animation-delay: 4s;
  animation-duration: 4s;
}

.cpu-line-8 {
  offset-path: path("M 30 30 h 25 q 5 0 5 5 v 6.5 q 0 5 5 5 h 35");
  animation-delay: 3s;
  animation-duration: 3s;
}

@keyframes animation-path {
  0% {
    offset-distance: 0%;
  }
  100% {
    offset-distance: 100%;
  }
}

.shadcn {
  offset-anchor: 10px 0px;
  animation: shadcn-animation-path;
  animation-iteration-count: infinite;
  animation-timing-function: cubic-bezier(0.75, -0.01, 0, 0.99);
  animation-duration: 4s;
  animation-delay: 1.5s;
}

.shadcn-line-1 {
  offset-path: path("M 35 14 h 25 q 5 0 5 5 v 23 q 0 5 5 5 h 25");
}

.shadcn-line-2 {
  offset-path: path("M 30 50 h 65");
}

.shadcn-line-3 {
  offset-path: path("M 36.3 87 h 24 q 5 0 5 -5 v -24 q 0 -5 5 -5 h 25");
}

.compBox {
  animation: shadncn-animation-path-backwards;
  animation-iteration-count: infinite;
  animation-timing-function: cubic-bezier(0.57, 0.02, 0, 1);
  offset-path: path("M 167 36 h -17 q -5 0 -5 5 v 4 q 0 5 -5 5 h -32");
  animation-duration: 4s;
  opacity: 0;
  animation-delay: 5s;
}

@keyframes shadncn-animation-path-backwards {
  0% {
    offset-distance: 100%;
    opacity: 0;
  }
  90% {
    opacity: 1;
    offset-distance: 0%;
  }
  100% {
    opacity: 0;
  }
}

@keyframes shadcn-animation-path {
  0% {
    offset-distance: 0%;
  }
  100% {
    offset-distance: 100%;
  }
}

@keyframes landing-marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

.animate-landing-marquee {
  animation: landing-marquee var(--marquee-duration, 45s) linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .animate-landing-marquee {
    animation: none;
  }
}
runtime/process.ts실행 안내·자료
파일 저장

// Browser host for a Node-only import in unused upstream documentation helpers.
// Actual components consume the unchanged cn export; no provider environment is exposed.
export default { env: {} };
THIRD-PARTY-LICENSES.txt실행 안내·자료
파일 저장

react 19.2.3 — LICENSE

MIT License

Copyright (c) Meta Platforms, Inc. and affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

@tabler/icons-react 3.35.0 — LICENSE

MIT License

Copyright (c) 2020-2025 Paweł Kuna

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

framer-motion 12.43.0 — LICENSE.md

The MIT License (MIT)

Copyright (c) 2018 Framer B.V.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

motion-utils 12.39.0 — LICENSE.md

The MIT License (MIT)

Copyright (c) 2024 [Motion](https://motion.dev) B.V.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

motion-dom 12.43.0 — LICENSE.md

The MIT License (MIT)

Copyright (c) 2024 [Motion](https://motion.dev) B.V.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

motion 12.23.25 — LICENSE.md

The MIT License (MIT)

Copyright (c) 2024 [Motion](https://motion.dev) B.V.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

clsx 2.1.1 — license

MIT License

Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


========================================================================

tailwind-merge 3.3.1 — LICENSE.md

MIT License

Copyright (c) 2021 Dany Castillo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

scheduler 0.27.0 — LICENSE

MIT License

Copyright (c) Meta Platforms, Inc. and affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

react-dom 19.2.3 — LICENSE

MIT License

Copyright (c) Meta Platforms, Inc. and affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

tailwindcss 4.1.13 — LICENSE

MIT License

Copyright (c) Tailwind Labs, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


========================================================================

tw-animate-css 1.3.8 — LICENSE

MIT License

Copyright (c) 2025 Wombosvideo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
provenance.json실행 안내·자료
파일 저장

{
  "id": "21st-0bc8f3db6cb0",
  "sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
  "demoIdentity": {
    "file": "baseline-example.tsx",
    "export": "LogoTimelineDemo",
    "source": "exact-public-demo"
  },
  "fidelity": {
    "preserved": "21st 공개 discovery의 고유 데모 ID와 미리보기 이미지가 가리키는 원래 데모 바이트 전체",
    "dependency_revision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
    "observed_differences": [
      "원래 CDN demo의 레이아웃 및 인수를 유지하고, 별도 component는 조사 시점 고정 작성자 revision에서 연결한다."
    ],
    "mapping": [
      "21st 설치 별칭 @/components/ui/logo-timeline → 작성자 registry/gammaui/logo-timeline.tsx; 문서 ComponentSource의 이름과 export/props 대조"
    ]
  },
  "acquisitionLimitations": [
    "CDN 데모는 SHA256으로, 작성자 원본과 의존 파일은 Git 커밋 및 blob SHA로 고정했다.",
    "현재 작성자 revision의 의존 파일이 과거 21st 내부 구현과 바이트 단위로 같다는 주장은 하지 않는다.",
    "다운로드한 코드를 실행하지 않았으며 브라우저·상호작용 검증은 별도 단계다."
  ],
  "files": [
    {
      "file": "baseline-example.tsx",
      "kind": "implementation",
      "sha256": "57287e0cee28a4439f2d482019fb7a5aa9ca9ad5767dc2188b86df3b0b57ba0a",
      "sourceRevision": "sha256:57287e0cee28a4439f2d482019fb7a5aa9ca9ad5767dc2188b86df3b0b57ba0a",
      "license": "MIT"
    },
    {
      "file": "LICENSE",
      "kind": "license",
      "sha256": "185f2cd440066381aa1fb70411e11e6c288a961a91bc5274e92a72ee245f83ed",
      "sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
      "license": "MIT"
    },
    {
      "file": "upstream/apps/www/content/docs/components/logo-timeline.mdx",
      "kind": "upstream-implementation",
      "sha256": "061a8cfbd65e80395e292e1d795079187b5a21c6ac5e351074a161b90b0e71b5",
      "sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
      "license": "MIT"
    },
    {
      "file": "upstream/apps/www/registry/example/logo-timeline-demo.tsx",
      "kind": "upstream-implementation",
      "sha256": "f94184a8373e9826bf701686d82c79060b6077ad7275a767c5ca6949951e0163",
      "sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
      "license": "MIT"
    },
    {
      "file": "author/apps/www/registry/gammaui/logo-timeline.tsx",
      "kind": "implementation-dependency",
      "sha256": "b3a2672638b6950e5fec804709c4b16569176c4a72c1d17ca37d2482e6040d1d",
      "sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
      "license": "MIT"
    },
    {
      "file": "author/apps/www/lib/utils.ts",
      "kind": "implementation-dependency",
      "sha256": "498451813b574b01a0eac9a1d5a96dfa261218ad55fff92ffc5afbf9fe785e22",
      "sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
      "license": "MIT"
    },
    {
      "file": "author/apps/www/app/globals.css",
      "kind": "theme-source",
      "sha256": "d48b78e343a4a866a9ccc1137ca3ac2418ebf221bf52ab79d70759426f091628",
      "sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
      "license": "MIT"
    },
    {
      "file": "author/apps/www/package.json",
      "kind": "dependency-manifest",
      "sha256": "f3307ab08f84659d3850ff0d9aa47e10999581e3536af88acef876b1cd1099fa",
      "sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
      "license": "MIT"
    }
  ],
  "importMap": {
    "@/components/ui/logo-timeline": "author/apps/www/registry/gammaui/logo-timeline.tsx",
    "@/lib/utils": "author/apps/www/lib/utils.ts"
  },
  "declaredDependencies": {
    "@tabler/icons-react": "^3.35.0",
    "clsx": "^2.1.1",
    "motion": "^12.23.24",
    "next": "15.2.6",
    "process": null,
    "react": "^19.2.3",
    "tailwind-merge": "^3.3.1"
  },
  "runtimeDependencies": {
    "react": "19.2.3",
    "@tabler/icons-react": "3.35.0",
    "framer-motion": "12.43.0",
    "motion-utils": "12.39.0",
    "motion-dom": "12.43.0",
    "motion": "12.23.25",
    "clsx": "2.1.1",
    "tailwind-merge": "3.3.1",
    "scheduler": "0.27.0",
    "react-dom": "19.2.3",
    "tailwindcss": "4.1.13",
    "tw-animate-css": "1.3.8"
  },
  "assets": [],
  "assetAdaptations": [],
  "adaptations": [
    "The exact original 13-logo array, negative delays, seven rows, and 400px demonstration height are retained.",
    "The original author CSS move-x keyframe is included; an empty browser process shim is provided for unused documentation helpers."
  ],
  "runtime_verified": false
}
README.md실행 안내·자료
파일 저장

# Logo Timeline · Gamma UI

The acquired original files are unchanged. Source revision: c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd. Each exact file hash and any demo content revision is recorded in provenance.json. Keep all included license notices.

- The exact original 13-logo array, negative delays, seven rows, and 400px demonstration height are retained.
- The original author CSS move-x keyframe is included; an empty browser process shim is provided for unused documentation helpers.
- Host spacing and fallback tokens come from runtime/author-styles.css. runtime/applied-theme.css contains the selected original author theme and overrides fallback tokens.

Open the standalone HTML to run with all JavaScript and CSS bundled locally. ?theme=light and ?theme=dark select the host theme. The parent gallery must use an opaque allow-scripts sandbox. No external requests are required.

For source reuse, start with Usage.tsx and the unchanged baseline demonstration. Resolve @/ imports to their included matching author files and install the exact package versions in provenance.json. The host mount and styles are supplied for reference.

Build success and the ready message do not prove runtime or accessibility behavior. Browser validation is recorded separately.