21st.dev 원본
Location Map · Gamma UI
데이터 · MIT
갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.
저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.
SOURCE FILES
원본 코드 읽기
수집한 원본 소스와 실행 안내를 함께 제공합니다. 실행 HTML에는 미리보기를 위한 실행 환경이 들어 있습니다.
baseline-example.tsx
import { LocationMap } from "@/components/ui/location-map"
export default function LocationMapDemo() {
return (
<main className="flex w-full items-center justify-center">
<div className="relative z-10 flex flex-col items-center gap-8">
{/* Optional subtle label */}
<p className="text-xs font-medium tracking-[0.2em] text-neutral-600 uppercase">
Current Location
</p>
<LocationMap
location="Berlin, Germany"
coordinates="52.5200° N, 13.4050° E"
/>
</div>
</main>
)
}
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.
함께 쓰는 파일 12개 보기
upstream/apps/www/content/docs/components/location-map.mdx
---
title: Location Map
date: 2025-12-04
description: An interactive 3D-tilting location card with expandable animated map, live indicator, and motion-based hover effects.
author: mazyar
published: true
---
<ComponentPreview name="location-map-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/location-map
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the component into your project.</Step>
<ComponentSource
name="location-map"
title="components/gammaui/location-map.tsx"
/>
<Step>
Make sure you have <code>motion</code> installed.
</Step>
```bash
npm install motion
# or
pnpm add motion
# or
bun add motion
```
</Steps>
</TabsContent>
</Tabs>
---
## Usage
```tsx showLineNumbers
import { LocationMap } from "@/components/gammaui/location-map"
export default function Page() {
return (
<div className="p-10">
<LocationMap
location="San Francisco, CA"
coordinates="37.7749° N, 122.4194° W"
/>
</div>
)
}
```
---
## Examples
### Default
```tsx showLineNumbers
<LocationMap />
```
### Custom Location
```tsx showLineNumbers
<LocationMap location="Berlin, Germany" coordinates="52.5200° N, 13.4050° E" />
```
### With Custom Styling
```tsx showLineNumbers
<LocationMap
className="max-w-sm"
location="Tokyo, Japan"
coordinates="35.6762° N, 139.6503° E"
/>
```
---
## Props
| Prop | Type | Default | Description |
| ------------- | -------- | --------------------------- | ------------------------------- |
| `location` | `string` | `"San Francisco, CA"` | Displayed location name |
| `coordinates` | `string` | `"37.7749° N, 122.4194° W"` | Coordinates shown when expanded |
| `className` | `string` | `-` | Optional wrapper classes |
---
## Features
- **3D Hover Tilt** powered by motion values & springs
- **Expandable Map View** with animated roads & buildings
- **Live Status Indicator**
- **Animated SVG Streets & Buildings**
- **Smooth Layout Transitions**
- **Click-to-Expand Interaction**
- **Fully Theme-Aware** (light & dark mode)
- **No external map APIs required**
---
## Behavior
- Hover to tilt the card in 3D space
- Click to expand and reveal the animated map
- Hover effects enhance depth, glow, and motion
- Coordinates fade in only when expanded
---
## Use Cases
- Location previews
- Event cards
- Office & company locations
- Dashboard widgets
- Interactive UI showcases
- Landing page highlights
upstream/apps/www/registry/example/location-map-demo.tsx
import { LocationMap } from "@/registry/gammaui/location-map"
export default function LocationMapDemo() {
return (
<main className="flex w-full items-center justify-center">
<div className="relative z-10 flex flex-col items-center gap-8">
{/* Optional subtle label */}
<p className="text-xs font-medium tracking-[0.2em] text-neutral-600 uppercase">
Current Location
</p>
<LocationMap
location="Berlin, Germany"
coordinates="52.5200° N, 13.4050° E"
/>
</div>
</main>
)
}
author/apps/www/registry/gammaui/location-map.tsx
"use client"
import type React from "react"
import { useRef, useState } from "react"
import {
AnimatePresence,
motion,
useMotionValue,
useSpring,
useTransform,
} from "motion/react"
interface LocationMapProps {
location?: string
coordinates?: string
className?: string
}
export function LocationMap({
location = "San Francisco, CA",
coordinates = "37.7749° N, 122.4194° W",
className,
}: LocationMapProps) {
const [isHovered, setIsHovered] = useState(false)
const [isExpanded, setIsExpanded] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)
const mouseX = useMotionValue(0)
const mouseY = useMotionValue(0)
const rotateX = useTransform(mouseY, [-50, 50], [8, -8])
const rotateY = useTransform(mouseX, [-50, 50], [-8, 8])
const springRotateX = useSpring(rotateX, { stiffness: 300, damping: 30 })
const springRotateY = useSpring(rotateY, { stiffness: 300, damping: 30 })
const handleMouseMove = (e: React.MouseEvent) => {
if (!containerRef.current) return
const rect = containerRef.current.getBoundingClientRect()
const centerX = rect.left + rect.width / 2
const centerY = rect.top + rect.height / 2
mouseX.set(e.clientX - centerX)
mouseY.set(e.clientY - centerY)
}
const handleMouseLeave = () => {
mouseX.set(0)
mouseY.set(0)
setIsHovered(false)
}
const handleClick = () => {
setIsExpanded(!isExpanded)
}
return (
<motion.div
ref={containerRef}
className={`relative cursor-pointer select-none ${className}`}
style={{
perspective: 1000,
}}
onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
>
<motion.div
className="bg-background border-border relative overflow-hidden rounded-2xl border"
style={{
rotateX: springRotateX,
rotateY: springRotateY,
transformStyle: "preserve-3d",
}}
animate={{
width: isExpanded ? 360 : 240,
height: isExpanded ? 280 : 140,
}}
transition={{
type: "spring",
stiffness: 400,
damping: 35,
}}
>
{/* Subtle gradient overlay */}
<div className="from-muted/20 to-muted/40 absolute inset-0 bg-linear-to-br via-transparent" />
<AnimatePresence>
{isExpanded && (
<motion.div
className="pointer-events-none absolute inset-0"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.4, delay: 0.1 }}
>
<div className="bg-muted absolute inset-0 rounded-2xl" />
<svg
className="absolute inset-0 h-full w-full"
preserveAspectRatio="none"
>
{/* Main roads - using foreground with opacity */}
<motion.line
x1="0%"
y1="35%"
x2="100%"
y2="35%"
className="stroke-foreground/25"
strokeWidth="4"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.8, delay: 0.2 }}
/>
<motion.line
x1="0%"
y1="65%"
x2="100%"
y2="65%"
className="stroke-foreground/25"
strokeWidth="4"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.8, delay: 0.3 }}
/>
{/* Vertical main roads */}
<motion.line
x1="30%"
y1="0%"
x2="30%"
y2="100%"
className="stroke-foreground/20"
strokeWidth="3"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.6, delay: 0.4 }}
/>
<motion.line
x1="70%"
y1="0%"
x2="70%"
y2="100%"
className="stroke-foreground/20"
strokeWidth="3"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.6, delay: 0.5 }}
/>
{/* Secondary streets */}
{[20, 50, 80].map((y, i) => (
<motion.line
key={`h-${i}`}
x1="0%"
y1={`${y}%`}
x2="100%"
y2={`${y}%`}
className="stroke-foreground/10"
strokeWidth="1.5"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.5, delay: 0.6 + i * 0.1 }}
/>
))}
{[15, 45, 55, 85].map((x, i) => (
<motion.line
key={`v-${i}`}
x1={`${x}%`}
y1="0%"
x2={`${x}%`}
y2="100%"
className="stroke-foreground/10"
strokeWidth="1.5"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.5, delay: 0.7 + i * 0.1 }}
/>
))}
</svg>
{/* Buildings - using muted-foreground */}
<motion.div
className="bg-muted-foreground/30 border-muted-foreground/20 absolute top-[40%] left-[10%] h-[20%] w-[15%] rounded-sm border"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.5 }}
/>
<motion.div
className="bg-muted-foreground/25 border-muted-foreground/15 absolute top-[15%] left-[35%] h-[15%] w-[12%] rounded-sm border"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.6 }}
/>
<motion.div
className="bg-muted-foreground/28 border-muted-foreground/18 absolute top-[70%] left-[75%] h-[18%] w-[18%] rounded-sm border"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.7 }}
/>
<motion.div
className="bg-muted-foreground/22 border-muted-foreground/15 absolute top-[20%] right-[10%] h-[25%] w-[10%] rounded-sm border"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.55 }}
/>
<motion.div
className="bg-muted-foreground/20 border-muted-foreground/12 absolute top-[55%] left-[5%] h-[12%] w-[8%] rounded-sm border"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.65 }}
/>
<motion.div
className="bg-muted-foreground/22 border-muted-foreground/15 absolute top-[8%] left-[75%] h-[10%] w-[14%] rounded-sm border"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.75 }}
/>
<motion.div
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
initial={{ scale: 0, y: -20 }}
animate={{ scale: 1, y: 0 }}
transition={{
type: "spring",
stiffness: 400,
damping: 20,
delay: 0.3,
}}
>
<svg
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
className="drop-shadow-lg"
style={{
filter: "drop-shadow(0 0 10px rgba(52, 211, 153, 0.5))",
}}
>
<path
d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"
fill="#34D399"
/>
<circle cx="12" cy="9" r="2.5" className="fill-background" />
</svg>
</motion.div>
<div className="from-background absolute inset-0 bg-linear-to-t via-transparent to-transparent opacity-60" />
</motion.div>
)}
</AnimatePresence>
{/* Grid pattern - only show when collapsed */}
<motion.div
className="absolute inset-0 opacity-[0.03]"
animate={{ opacity: isExpanded ? 0 : 0.03 }}
transition={{ duration: 0.3 }}
>
<svg width="100%" height="100%" className="absolute inset-0">
<defs>
<pattern
id="grid"
width="20"
height="20"
patternUnits="userSpaceOnUse"
>
<path
d="M 20 0 L 0 0 0 20"
fill="none"
className="stroke-foreground"
strokeWidth="0.5"
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</motion.div>
{/* Content */}
<div className="relative z-10 flex h-full flex-col justify-between p-5">
{/* Top section */}
<div className="flex items-start justify-between">
<div className="relative">
<motion.div
className="relative"
animate={{
opacity: isExpanded ? 0 : 1,
}}
transition={{ duration: 0.3 }}
>
{/* Map Icon SVG */}
<motion.svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-emerald-400"
animate={{
filter: isHovered
? "drop-shadow(0 0 8px rgba(52, 211, 153, 0.6))"
: "drop-shadow(0 0 4px rgba(52, 211, 153, 0.3))",
}}
transition={{ duration: 0.3 }}
>
<polygon points="3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21" />
<line x1="9" x2="9" y1="3" y2="18" />
<line x1="15" x2="15" y1="6" y2="21" />
</motion.svg>
</motion.div>
</div>
{/* Status indicator */}
<motion.div
className="bg-foreground/5 flex items-center gap-1.5 rounded-full px-2 py-1 backdrop-blur-sm"
animate={{
scale: isHovered ? 1.05 : 1,
backgroundColor: isHovered
? "hsl(var(--foreground) / 0.08)"
: "hsl(var(--foreground) / 0.05)",
}}
transition={{ duration: 0.2 }}
>
<div className="h-1.5 w-1.5 rounded-full bg-emerald-400" />
<span className="text-muted-foreground text-[10px] font-medium tracking-wide uppercase">
Live
</span>
</motion.div>
</div>
{/* Bottom section */}
<div className="space-y-1">
<motion.h3
className="text-foreground text-sm font-medium tracking-tight"
animate={{
x: isHovered ? 4 : 0,
}}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
>
{location}
</motion.h3>
<AnimatePresence>
{isExpanded && (
<motion.p
className="text-muted-foreground font-mono text-xs"
initial={{ opacity: 0, y: -10, height: 0 }}
animate={{ opacity: 1, y: 0, height: "auto" }}
exit={{ opacity: 0, y: -10, height: 0 }}
transition={{ duration: 0.25 }}
>
{coordinates}
</motion.p>
)}
</AnimatePresence>
{/* Animated underline */}
<motion.div
className="h-px bg-linear-to-r from-emerald-500/50 via-emerald-400/30 to-transparent"
initial={{ scaleX: 0, originX: 0 }}
animate={{
scaleX: isHovered || isExpanded ? 1 : 0.3,
}}
transition={{ duration: 0.4, ease: "easeOut" }}
/>
</div>
</div>
</motion.div>
{/* Click hint */}
<motion.p
className="text-muted-foreground absolute -bottom-6 left-1/2 text-[10px] whitespace-nowrap"
style={{ x: "-50%" }}
initial={{ opacity: 0 }}
animate={{
opacity: isHovered && !isExpanded ? 1 : 0,
y: isHovered ? 0 : 4,
}}
transition={{ duration: 0.2 }}
>
Click to expand
</motion.p>
</motion.div>
)
}
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 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;
}
}
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.
========================================================================
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.
========================================================================
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-fa77f17f6963",
"sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
"demoIdentity": {
"file": "baseline-example.tsx",
"export": "default",
"source": "exact-public-demo"
},
"fidelity": {
"preserved": "21st 공개 discovery의 고유 데모 ID와 미리보기 이미지가 가리키는 원래 데모 바이트 전체",
"dependency_revision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
"observed_differences": [
"원래 CDN demo의 레이아웃 및 인수를 유지하고, 별도 component는 조사 시점 고정 작성자 revision에서 연결한다."
],
"mapping": [
"21st 설치 별칭 @/components/ui/location-map → 작성자 registry/gammaui/location-map.tsx; 문서 ComponentSource의 이름과 export/props 대조"
]
},
"acquisitionLimitations": [
"CDN 데모는 SHA256으로, 작성자 원본과 의존 파일은 Git 커밋 및 blob SHA로 고정했다.",
"현재 작성자 revision의 의존 파일이 과거 21st 내부 구현과 바이트 단위로 같다는 주장은 하지 않는다.",
"다운로드한 코드를 실행하지 않았으며 브라우저·상호작용 검증은 별도 단계다."
],
"files": [
{
"file": "baseline-example.tsx",
"kind": "implementation",
"sha256": "c5dae95f57365c55d4eaed954cdcc3fe44804aa37ba6e5f1b77565071fe839b2",
"sourceRevision": "sha256:c5dae95f57365c55d4eaed954cdcc3fe44804aa37ba6e5f1b77565071fe839b2",
"license": "MIT"
},
{
"file": "LICENSE",
"kind": "license",
"sha256": "185f2cd440066381aa1fb70411e11e6c288a961a91bc5274e92a72ee245f83ed",
"sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
"license": "MIT"
},
{
"file": "upstream/apps/www/content/docs/components/location-map.mdx",
"kind": "upstream-implementation",
"sha256": "7dad896f7fd9704c3a52947b6e88aa4db6138064974348c462c7152e3d9b54bf",
"sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
"license": "MIT"
},
{
"file": "upstream/apps/www/registry/example/location-map-demo.tsx",
"kind": "upstream-implementation",
"sha256": "493ff6bd24c03e3ae329009cbeaec65a3554f0becb50c220bb4404d83cdc0d91",
"sourceRevision": "c79ba85279e4ff05a85b5ea4c2a8282c3467b6bd",
"license": "MIT"
},
{
"file": "author/apps/www/registry/gammaui/location-map.tsx",
"kind": "implementation-dependency",
"sha256": "e24a88042ed3915b4845f947fe5378102a53ffbf9836ab0a0f12ee5fc3763d40",
"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/location-map": "author/apps/www/registry/gammaui/location-map.tsx"
},
"declaredDependencies": {
"motion": "^12.23.24",
"react": "^19.2.3"
},
"runtimeDependencies": {
"react": "19.2.3",
"framer-motion": "12.43.0",
"motion-utils": "12.39.0",
"motion-dom": "12.43.0",
"motion": "12.23.25",
"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 Berlin label and coordinates are retained. No geolocation or remote map service is connected."
],
"runtime_verified": false
}
README.md실행 안내·자료
# Location Map · 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 Berlin label and coordinates are retained. No geolocation or remote map service is connected.
- 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.
BEHIND THE EXAMPLE
이 장면을 만드는 원리
Live 표시와 실제 위치 데이터의 경계
현재 상태를 말하는 문구는 어떤 정보 갱신을 근거로 하는지 구분해야 합니다.
- 이 예제에서는
- baseline은 Berlin, Germany와 52.5200° N, 13.4050° E를 문자열 prop으로 전달합니다. LocationMap은 고정된 도로·건물 도형을 그리며 Live 문구를 항상 표시합니다. 위치 조회, 좌표로 도형을 계산하는 함수, 네트워크 갱신은 이 소스에 없습니다.
코드와 함께 확인하기
코드에서 찾기
location="Berlin, Germany"baseline-example.tsx갤러리가 유지하는 원본 데모 위치입니다.
coordinates = "37.7749° N, 122.4194° W"location-map.tsx위치와 좌표는 선택적 표시 문자열이며 기본값도 고정되어 있습니다.
Livelocation-map.tsx표시 문구를 실제 갱신 상태에서 계산하지 않습니다.
직접 해보기
별도 소비 코드에서 위치·좌표 문자열만 다른 장소로 바꿉니다.
살펴볼 변화텍스트와 고정 도형의 변화 범위를 구분합니다. 이 그림이 좌표에 대응하는 실제 지도를 생성한다고 소개하지 않습니다.
네트워크 요청 없이 데모를 열어 두고 Live 문구와 좌표를 관찰합니다.
살펴볼 변화고정 데이터 예제라는 경계를 유지하고 위치 추적·실시간 갱신을 검증한 것으로 보고하지 않습니다.
좌표를 여는 입력과 확장 크기
추가 정보의 노출은 입력 가능한 컨트롤, 상태 의미, 부모 제약까지 포함합니다.
- 이 예제에서는
- 카드의 motion.div는 onClick으로 isExpanded를 토글하고 좌표 문단은 펼쳤을 때만 렌더링합니다. 크기는 240×140에서 360×280으로 바뀝니다. 트리거 버튼·키보드 처리·aria-expanded가 없고 Click to expand 안내는 hover 중 접힌 상태에서만 보이도록 되어 있습니다.
코드와 함께 확인하기
코드에서 찾기
onClick={handleClick}location-map.tsx카드 전체가 클릭 입력을 받습니다.
width: isExpanded ? 360 : 240,location-map.tsx확장 폭은 부모 비율이 아닌 고정 수치입니다.
opacity: isHovered && !isExpanded ? 1 : 0,location-map.tsx사용 안내의 가시성은 hover와 접힘 상태에서 결정됩니다.
직접 해보기
포인터를 쓰지 않고 카드를 찾아 좌표를 열기를 시도합니다.
살펴볼 변화실제 키보드 진입·활성화 가능 여부와 펼침 상태의 접근성 노출을 기록합니다. 클릭 동작만으로 좌표 접근을 보장하지 않습니다.
320px 부모에서 카드를 펼치고 좌표·카드 경계를 확인합니다.
살펴볼 변화360px 확장 폭의 넘침이나 잘림을 확인해야 합니다. 원본의 고정 폭을 부모에 맞춰 자동 축소되는 계약으로 설명하지 않습니다.
기울기와 펼침이 사용하는 서로 다른 상태
한 상호작용을 끝냈을 때 어떤 상태만 초기화할지 분명히 하고 반복 입력을 확인합니다.
- 이 예제에서는
- 포인터 위치는 카드 중심에서의 거리로 계산하고 ±50 범위를 ±8도 회전에 매핑한 뒤 spring을 적용합니다. mouseleave는 두 위치 값을 0으로 돌리고 isHovered만 끕니다. isExpanded는 유지되며 카드 크기, 도로, 좌표 문단은 별도의 펼침 애니메이션을 사용합니다.
코드와 함께 확인하기
코드에서 찾기
const rotateX = useTransform(mouseY, [-50, 50], [8, -8])location-map.tsx포인터 거리에서 회전 목표를 계산합니다.
const handleMouseLeave = () => {location-map.tsx포인터 이탈은 기울기와 hover를 초기화하고 펼침 값은 바꾸지 않습니다.
<AnimatePresence>location-map.tsx조건부 내용의 등장·퇴장 경로를 사용합니다.
직접 해보기
기울어진 상태에서 펼치고 즉시 포인터를 카드 밖으로 이동합니다.
살펴볼 변화기울기는 중앙으로 돌아가되 펼침과 좌표는 유지되는지 확인합니다.
크기가 바뀌는 중 다시 접고 펼치며 움직임 감소 선호도 바꿉니다.
살펴볼 변화최신 펼침 상태와 좌표 문단이 일치해야 합니다. 원본에 없는 정적 대체 경로를 있다고 가정하지 말고 남는 효과를 별도로 기록합니다.
