diff --git a/AGENTS.md b/AGENTS.md
index c153a9b..e454135 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -5,3 +5,9 @@
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
+
+## Styling
+
+- Use plain CSS and colocated CSS Modules (`*.module.css`) for component and layout styles.
+- Keep `app/globals.css` limited to global resets, shared design tokens, and document-level styles.
+- Do not add Tailwind CSS utilities or Tailwind-dependent styling packages.
diff --git a/app/Home.module.css b/app/Home.module.css
new file mode 100644
index 0000000..85a3b2f
--- /dev/null
+++ b/app/Home.module.css
@@ -0,0 +1,558 @@
+.homeContent {
+ --ocean-surface: oklch(0.31 0.105 238);
+ --ocean-mid: oklch(0.22 0.075 248);
+ --ocean-deep: oklch(0.145 0.052 258);
+ --ocean-abyss: oklch(0.075 0.032 265);
+ --sea-sand: oklch(0.55 0.07 78);
+ --ocean-text: oklch(0.96 0.018 210);
+ --ocean-muted: oklch(0.8 0.035 218);
+ --ocean-border: oklch(0.85 0.045 215 / 18%);
+ --ocean-panel: oklch(0.09 0.035 260 / 34%);
+ position: relative;
+ z-index: 3;
+ isolation: isolate;
+ overflow: hidden;
+ color: var(--ocean-text);
+ background:
+ radial-gradient(circle at 18% 4%, oklch(0.76 0.1 206 / 18%), transparent 28rem),
+ radial-gradient(circle at 84% 22%, oklch(0.5 0.12 226 / 12%), transparent 32rem),
+ linear-gradient(
+ 180deg,
+ var(--ocean-surface) 0%,
+ var(--ocean-mid) 25%,
+ var(--ocean-deep) 58%,
+ var(--ocean-abyss) 100%
+ );
+}
+
+.lightRaysLayer {
+ top: -2rem;
+ bottom: auto;
+ z-index: 0;
+ height: clamp(20rem, 46vw, 36rem);
+ mix-blend-mode: screen;
+ opacity: 0.62;
+}
+
+.band {
+ position: relative;
+ z-index: 1;
+ padding-block: clamp(3.5rem, 7vw, 6rem);
+ border-top: 1px solid var(--ocean-border);
+}
+
+.introBand {
+ background:
+ linear-gradient(90deg, oklch(0.42 0.095 220 / 18%), transparent 55%),
+ linear-gradient(180deg, oklch(0.73 0.07 205 / 9%), transparent 70%);
+}
+
+.projectsBand {
+ background: linear-gradient(90deg, transparent, oklch(0.26 0.075 248 / 24%) 50%, transparent);
+}
+
+.blogBand {
+ background:
+ radial-gradient(circle at 12% 12%, oklch(0.33 0.08 236 / 16%), transparent 22rem),
+ linear-gradient(180deg, oklch(0.08 0.03 262 / 6%), transparent 58%);
+}
+
+.contactBand {
+ background:
+ radial-gradient(ellipse at 52% 100%, oklch(0.14 0.04 250 / 26%), transparent 32rem),
+ linear-gradient(180deg, transparent, oklch(0.045 0.022 265 / 36%));
+}
+
+.split,
+.contactRow {
+ display: grid;
+ grid-template-columns: minmax(14rem, 0.75fr) minmax(0, 1.25fr);
+ gap: clamp(1.5rem, 5vw, 4rem);
+ align-items: start;
+}
+
+.sectionHeader {
+ display: flex;
+ align-items: end;
+ justify-content: space-between;
+ gap: 1.5rem;
+ margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
+}
+
+.kicker {
+ margin-bottom: 0.7rem;
+ color: oklch(0.82 0.12 190);
+ font-family: var(--font-geist-mono), monospace;
+ font-size: 0.76rem;
+ font-weight: 720;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+.band h2 {
+ max-width: 18ch;
+ margin-bottom: 0;
+ font-size: clamp(1.45rem, 2.8vw, 2.35rem);
+ line-height: 1.08;
+}
+
+.copy {
+ max-width: 44rem;
+ color: var(--ocean-muted);
+ font-size: clamp(1rem, 1.5vw, 1.12rem);
+ line-height: 1.8;
+}
+
+.copy p {
+ margin-bottom: 1rem;
+}
+
+.copy p:last-child {
+ margin-bottom: 0;
+}
+
+.textLink,
+.contactLink {
+ display: inline-flex;
+ align-items: center;
+ min-height: 2.65rem;
+ color: var(--ocean-text);
+ font-weight: 700;
+ text-decoration: none;
+ background: oklch(0.08 0.03 260 / 22%);
+ border: 1px solid var(--ocean-border);
+ transition:
+ border-color 160ms ease,
+ background-color 160ms ease,
+ transform 160ms ease;
+}
+
+.textLink {
+ flex: 0 0 auto;
+ padding-inline: 1rem;
+ border-radius: 999px;
+}
+
+.textLink:hover,
+.contactLink:hover {
+ background: oklch(0.15 0.05 240 / 48%);
+ border-color: oklch(0.85 0.05 205 / 32%);
+ transform: translateY(-1px);
+}
+
+.projectGrid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1rem;
+}
+
+.projectCard {
+ display: grid;
+ min-height: 15rem;
+ align-content: start;
+ padding: clamp(1rem, 2vw, 1.35rem);
+ background: linear-gradient(180deg, oklch(0.18 0.045 252 / 42%), oklch(0.07 0.03 264 / 34%)), var(--ocean-panel);
+ border: 1px solid var(--ocean-border);
+ border-radius: var(--radius-md);
+ box-shadow: 0 1rem 2.6rem oklch(0.03 0.025 265 / 30%);
+ backdrop-filter: blur(12px) saturate(120%);
+}
+
+.projectCard h3 {
+ margin-bottom: 0.75rem;
+ font-size: 1.05rem;
+}
+
+.projectCard p {
+ margin-bottom: 1.25rem;
+ color: var(--ocean-muted);
+ line-height: 1.65;
+}
+
+.tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ padding: 0;
+ margin: auto 0 0;
+ list-style: none;
+}
+
+.tags li {
+ padding: 0.42rem 0.6rem;
+ color: var(--ocean-muted);
+ font-family: var(--font-geist-mono), monospace;
+ font-size: 0.68rem;
+ background: oklch(0.1 0.03 260 / 42%);
+ border: 1px solid var(--ocean-border);
+ border-radius: 999px;
+}
+
+.anglerContact {
+ --lure-x: 51.5%;
+ --lure-y: 35.4%;
+ --ghost-x: 50%;
+ --ghost-y: 50%;
+ position: relative;
+ display: grid;
+ min-height: clamp(16rem, 26vw, 20rem);
+ place-items: center;
+ isolation: isolate;
+}
+
+.ghostCursor {
+ position: absolute;
+ top: var(--ghost-y);
+ left: var(--ghost-x);
+ z-index: 5;
+ display: grid;
+ width: 2.7rem;
+ height: 2.7rem;
+ place-items: center;
+ pointer-events: none;
+ opacity: 0;
+ mix-blend-mode: screen;
+ transform: translate(-50%, -50%);
+ transition: opacity 160ms ease;
+}
+
+.anglerContact:hover .ghostCursor {
+ opacity: 1;
+}
+
+.ghostCursor::before {
+ position: absolute;
+ inset: 0.58rem;
+ content: '';
+ background: radial-gradient(circle, oklch(0.96 0.05 205 / 82%), oklch(0.72 0.12 205 / 16%) 54%, transparent 72%);
+ border-radius: 999px;
+ filter: blur(0.1rem);
+}
+
+.ghostCursor::after {
+ position: absolute;
+ inset: -1rem;
+ content: '';
+ background:
+ radial-gradient(circle, oklch(0.78 0.13 205 / 20%), transparent 56%),
+ radial-gradient(circle at 40% 34%, oklch(0.95 0.06 205 / 24%), transparent 28%);
+ border-radius: 999px;
+ filter: blur(0.8rem);
+}
+
+.ghostCursorCore {
+ position: relative;
+ z-index: 1;
+ width: 0.46rem;
+ height: 0.46rem;
+ background: oklch(0.95 0.05 205);
+ border-radius: 999px;
+ box-shadow:
+ 0 0 0.55rem oklch(0.86 0.12 200 / 86%),
+ 0 0 1.4rem oklch(0.66 0.15 205 / 58%);
+}
+
+.ghostCursorTrail {
+ position: absolute;
+ inset: 0.35rem;
+ border: 1px solid oklch(0.82 0.1 205 / 34%);
+ border-radius: 54% 46% 56% 44%;
+ animation: ghostCursorPulse 1.65s ease-out infinite;
+}
+
+.ghostCursorTrail:last-child {
+ inset: 0.05rem;
+ animation-delay: 0.48s;
+}
+
+.anglerReveal {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.anglerImageFrame {
+ position: relative;
+ width: min(14rem, 48vw);
+ aspect-ratio: 1086 / 1448;
+ transform: translate(calc(50% - var(--lure-x)), calc(50% - var(--lure-y)));
+ transition: transform 180ms ease;
+}
+
+.anglerImageFrame::before,
+.anglerImageFrame::after {
+ position: absolute;
+ top: var(--lure-y);
+ left: var(--lure-x);
+ z-index: 1;
+ width: clamp(3.8rem, 13vw, 7rem);
+ height: clamp(3.8rem, 13vw, 7rem);
+ pointer-events: none;
+ content: '';
+ border-radius: 999px;
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(0.18);
+ transform-origin: center;
+}
+
+.anglerImageFrame::before {
+ background:
+ radial-gradient(circle, oklch(0.98 0.04 205 / 82%) 0 7%, oklch(0.86 0.1 205 / 32%) 18%, transparent 48%),
+ radial-gradient(circle, oklch(0.82 0.12 205 / 18%), transparent 62%);
+ filter: blur(0.16rem);
+ mix-blend-mode: screen;
+ transition: opacity 180ms ease;
+}
+
+.anglerImageFrame::after {
+ border: 1px solid oklch(0.82 0.1 205 / 26%);
+ box-shadow:
+ inset 0 0 1.2rem oklch(0.72 0.12 205 / 16%),
+ 0 0 1.5rem oklch(0.72 0.12 205 / 16%);
+ mix-blend-mode: screen;
+}
+
+.anglerContact:hover .anglerImageFrame::before,
+.anglerContact:focus-within .anglerImageFrame::before {
+ opacity: 1;
+ animation: lureLightBloom 2.4s ease-out infinite;
+}
+
+.anglerContact:hover .anglerImageFrame::after,
+.anglerContact:focus-within .anglerImageFrame::after {
+ opacity: 1;
+ animation: lureLightRing 2.4s ease-out infinite;
+}
+
+.anglerImage {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ filter: drop-shadow(0 1.2rem 1.8rem oklch(0.015 0.018 265 / 70%)) saturate(0.94) contrast(1.08);
+ opacity: 0;
+ pointer-events: none;
+ transition:
+ opacity 180ms ease,
+ filter 180ms ease;
+}
+
+.anglerContact:hover .anglerImage,
+.anglerContact:focus-within .anglerImage {
+ opacity: 0.72;
+}
+
+.lureButton {
+ position: absolute;
+ top: var(--lure-y);
+ left: var(--lure-x);
+ z-index: 2;
+ display: grid;
+ width: 3rem;
+ height: 3rem;
+ padding: 0;
+ place-items: center;
+ color: oklch(0.78 0.24 29);
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ border-radius: 999px;
+ transform: translate(-50%, -50%);
+}
+
+.lureButton::before,
+.lureButton::after {
+ position: absolute;
+ inset: 0.52rem;
+ content: '';
+ border: 1px solid oklch(0.68 0.26 29 / 58%);
+ border-radius: inherit;
+ box-shadow: 0 0 1rem oklch(0.68 0.26 29 / 24%);
+ animation: lurePing 1.9s ease-out infinite;
+}
+
+.lureButton::after {
+ animation-delay: 0.82s;
+}
+
+.lureButton:hover::before,
+.lureButton:hover::after,
+.lureButton:focus-visible::before,
+.lureButton:focus-visible::after {
+ border-color: oklch(0.74 0.27 29 / 76%);
+}
+
+.lureButton:focus-visible {
+ outline: 2px solid oklch(0.82 0.12 190);
+ outline-offset: 0.3rem;
+}
+
+.lureCore {
+ position: relative;
+ z-index: 1;
+ width: 0.78rem;
+ height: 0.78rem;
+ background: radial-gradient(circle, oklch(0.98 0.06 65) 0 18%, oklch(0.82 0.23 32) 44%, oklch(0.58 0.26 28) 100%);
+ border-radius: 999px;
+ box-shadow:
+ 0 0 0.35rem oklch(0.94 0.12 45),
+ 0 0 1.05rem oklch(0.72 0.26 29 / 88%),
+ 0 0 2rem oklch(0.55 0.24 25 / 52%);
+}
+
+.contactLinks {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ justify-content: end;
+}
+
+.anglerReveal .contactLinks {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ justify-content: center;
+ visibility: hidden;
+ opacity: 0;
+ transform: translateY(0.75rem);
+ transition:
+ opacity 180ms ease,
+ transform 180ms ease,
+ visibility 180ms ease;
+}
+
+.anglerContact:hover .contactLinks,
+.anglerContact:focus-within .contactLinks {
+ visibility: visible;
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.contactLink {
+ gap: 0.5rem;
+ justify-content: center;
+ min-width: 6.75rem;
+ padding-inline: 0.85rem;
+ border-radius: var(--radius-sm);
+}
+
+.contactLink svg {
+ width: 1.05rem;
+ height: 1.05rem;
+ flex: 0 0 auto;
+ stroke-width: 1.9;
+}
+
+.contactLink span {
+ line-height: 1;
+}
+
+@keyframes lurePing {
+ 0% {
+ opacity: 0.86;
+ transform: scale(0.28);
+ }
+
+ 72%,
+ 100% {
+ opacity: 0;
+ transform: scale(2.35);
+ }
+}
+
+@keyframes lureLightBloom {
+ 0% {
+ opacity: 0.88;
+ transform: translate(-50%, -50%) scale(0.22);
+ }
+
+ 58% {
+ opacity: 0.42;
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(1.55);
+ }
+}
+
+@keyframes lureLightRing {
+ 0% {
+ opacity: 0.62;
+ transform: translate(-50%, -50%) scale(0.2);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(1.85);
+ }
+}
+
+@keyframes ghostCursorPulse {
+ 0% {
+ opacity: 0.58;
+ transform: rotate(0deg) scale(0.42);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: rotate(18deg) scale(1.45);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .anglerContact:hover .anglerImageFrame::before,
+ .anglerContact:hover .anglerImageFrame::after,
+ .anglerContact:focus-within .anglerImageFrame::before,
+ .anglerContact:focus-within .anglerImageFrame::after,
+ .ghostCursorTrail,
+ .lureButton::before,
+ .lureButton::after {
+ animation: none;
+ }
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .anglerContact,
+ .anglerContact a,
+ .anglerContact button {
+ cursor: none;
+ }
+}
+
+@media (max-width: 56rem) {
+ .split,
+ .contactRow {
+ grid-template-columns: 1fr;
+ }
+
+ .sectionHeader {
+ align-items: start;
+ flex-direction: column;
+ }
+
+ .projectGrid {
+ grid-template-columns: 1fr;
+ }
+
+ .projectCard {
+ min-height: 0;
+ }
+
+ .contactLinks {
+ justify-content: start;
+ }
+}
+
+@media (max-width: 34rem) {
+ .band {
+ padding-block: 3rem;
+ }
+
+ .contactLink {
+ min-width: 0;
+ }
+}
diff --git a/app/about/page.tsx b/app/about/page.tsx
new file mode 100644
index 0000000..ac45c76
--- /dev/null
+++ b/app/about/page.tsx
@@ -0,0 +1,10 @@
+import type { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: 'About',
+ description: 'About Alper.',
+}
+
+export default function AboutPage() {
+ return null
+}
diff --git a/app/blog/page.tsx b/app/blog/page.tsx
new file mode 100644
index 0000000..d9fcab2
--- /dev/null
+++ b/app/blog/page.tsx
@@ -0,0 +1,10 @@
+import type { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: 'Blog',
+ description: 'Writing by Alper.',
+}
+
+export default function BlogPage() {
+ return null
+}
diff --git a/app/components/AlperLoader.module.css b/app/components/AlperLoader.module.css
new file mode 100644
index 0000000..596ecf3
--- /dev/null
+++ b/app/components/AlperLoader.module.css
@@ -0,0 +1,111 @@
+.loader {
+ display: grid;
+ min-height: 100svh;
+ color: var(--primary);
+ background:
+ radial-gradient(circle, color-mix(in oklch, var(--primary) 16%, transparent), transparent 28rem), var(--background);
+ place-items: center;
+ overflow: hidden;
+}
+
+.mark {
+ width: min(88vw, 34rem);
+ overflow: visible;
+}
+
+.letter {
+ fill: none;
+ stroke: currentColor;
+ stroke-dasharray: 1;
+ stroke-dashoffset: 1;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-width: 7;
+ animation: draw-letter 3.2s cubic-bezier(0.65, 0, 0.35, 1) infinite;
+ filter: drop-shadow(0 0 0.45rem color-mix(in oklch, currentColor 45%, transparent));
+ transform-box: fill-box;
+ transform-origin: center;
+}
+
+.letter:nth-of-type(2) {
+ animation-delay: 100ms;
+}
+
+.letter:nth-of-type(3) {
+ animation-delay: 200ms;
+}
+
+.letter:nth-of-type(4) {
+ animation-delay: 300ms;
+}
+
+.letter:nth-of-type(5) {
+ animation-delay: 400ms;
+}
+
+.letter:nth-of-type(6) {
+ animation-delay: 500ms;
+}
+
+.letter:nth-of-type(7) {
+ animation-delay: 600ms;
+}
+
+.letter:nth-of-type(8) {
+ animation-delay: 700ms;
+}
+
+.letter:nth-of-type(9) {
+ animation-delay: 800ms;
+}
+
+.once .letter {
+ animation-iteration-count: 1;
+}
+
+.dot {
+ stroke-width: 10;
+}
+
+.screenReaderText {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+@keyframes draw-letter {
+ 0%,
+ 12% {
+ opacity: 0;
+ stroke-dashoffset: 1;
+ transform: translateY(0.75rem);
+ }
+
+ 42%,
+ 68% {
+ opacity: 1;
+ stroke-dashoffset: 0;
+ transform: translateY(0);
+ }
+
+ 92%,
+ 100% {
+ opacity: 0;
+ stroke-dashoffset: -1;
+ transform: translateY(-0.5rem);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .letter {
+ animation: none;
+ opacity: 1;
+ stroke-dashoffset: 0;
+ transform: none;
+ }
+}
diff --git a/app/components/AlperLoader.tsx b/app/components/AlperLoader.tsx
new file mode 100644
index 0000000..07260c6
--- /dev/null
+++ b/app/components/AlperLoader.tsx
@@ -0,0 +1,47 @@
+'use client'
+
+import { useEffect } from 'react'
+import styles from './AlperLoader.module.css'
+
+type AlperLoaderProps = {
+ once?: boolean
+ onComplete?: () => void
+}
+
+export default function AlperLoader({ once = false, onComplete }: AlperLoaderProps) {
+ useEffect(() => {
+ if (once && globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches) {
+ onComplete?.()
+ }
+ }, [once, onComplete])
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading alper.dev
+
+ )
+}
diff --git a/app/components/AnglerSocialReveal.tsx b/app/components/AnglerSocialReveal.tsx
new file mode 100644
index 0000000..b576ac8
--- /dev/null
+++ b/app/components/AnglerSocialReveal.tsx
@@ -0,0 +1,114 @@
+'use client'
+
+import { FileText, Mail } from 'lucide-react'
+import Image from 'next/image'
+import Link from 'next/link'
+import type { PointerEvent } from 'react'
+import { useRef } from 'react'
+import { socialLinks } from '../data/site'
+import styles from '../Home.module.css'
+
+type ContactIconName = 'github' | 'linkedin' | 'email' | 'cv'
+
+const contactLinks = [
+ ...socialLinks.map((link) => ({ ...link, icon: getContactIconName(link.label) })),
+ { label: 'CV', href: '/contact', icon: 'cv' as const },
+]
+
+function getContactIconName(label: string): ContactIconName {
+ const normalizedLabel = label.toLowerCase()
+
+ if (normalizedLabel === 'github') {
+ return 'github'
+ }
+
+ if (normalizedLabel === 'linkedin') {
+ return 'linkedin'
+ }
+
+ if (normalizedLabel === 'email') {
+ return 'email'
+ }
+
+ return 'cv'
+}
+
+function ContactIcon({ icon }: { icon: ContactIconName }) {
+ if (icon === 'github') {
+ return (
+
+
+
+ )
+ }
+
+ if (icon === 'linkedin') {
+ return (
+
+
+
+ )
+ }
+
+ if (icon === 'email') {
+ return
+ }
+
+ return
+}
+
+export default function AnglerSocialReveal() {
+ const rootRef = useRef(null)
+
+ function handlePointerMove(event: PointerEvent) {
+ const root = rootRef.current
+
+ if (!root) {
+ return
+ }
+
+ const rect = root.getBoundingClientRect()
+ root.style.setProperty('--ghost-x', `${event.clientX - rect.left}px`)
+ root.style.setProperty('--ghost-y', `${event.clientY - rect.top}px`)
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {contactLinks.map((link) => (
+
+
+ {link.label}
+
+ ))}
+
+
+
+ )
+}
diff --git a/app/components/Footer.module.css b/app/components/Footer.module.css
new file mode 100644
index 0000000..90a3e68
--- /dev/null
+++ b/app/components/Footer.module.css
@@ -0,0 +1,141 @@
+.footer {
+ --footer-ocean-abyss: oklch(0.075 0.032 265);
+ --footer-sea-sand: oklch(0.55 0.07 78);
+ position: relative;
+ z-index: 3;
+ grid-row: 3;
+ isolation: isolate;
+ overflow: hidden;
+ color: oklch(0.9 0.025 215);
+ background:
+ radial-gradient(circle at 18% 8%, oklch(0.28 0.08 230 / 18%), transparent 24rem),
+ linear-gradient(
+ 180deg,
+ var(--footer-ocean-abyss) 0%,
+ oklch(0.055 0.025 265) 74%,
+ color-mix(in oklch, oklch(0.055 0.025 265) 88%, var(--footer-sea-sand)) 100%
+ );
+}
+
+.footer::after {
+ position: absolute;
+ right: 0;
+ bottom: -2rem;
+ left: 0;
+ z-index: -1;
+ height: clamp(4.5rem, 10vw, 7rem);
+ content: '';
+ pointer-events: none;
+ background:
+ radial-gradient(ellipse at 20% 92%, oklch(0.58 0.065 78 / 34%), transparent 38%),
+ radial-gradient(ellipse at 74% 94%, oklch(0.43 0.05 70 / 32%), transparent 42%),
+ radial-gradient(circle at 14% 64%, oklch(0.72 0.07 84 / 18%) 0 0.06rem, transparent 0.08rem),
+ radial-gradient(circle at 42% 78%, oklch(0.75 0.055 82 / 14%) 0 0.05rem, transparent 0.07rem),
+ radial-gradient(circle at 79% 66%, oklch(0.7 0.06 75 / 16%) 0 0.06rem, transparent 0.08rem),
+ linear-gradient(180deg, transparent 0%, oklch(0.29 0.04 72 / 10%) 34%, oklch(0.42 0.055 76 / 46%) 100%);
+ background-size:
+ auto,
+ auto,
+ 5.5rem 4rem,
+ 7rem 4.5rem,
+ 6.5rem 4.25rem,
+ auto;
+ opacity: 0.58;
+}
+
+.content {
+ position: relative;
+ z-index: 1;
+ display: grid;
+ grid-template-columns: minmax(15rem, 2fr) repeat(2, minmax(8rem, 1fr));
+ gap: clamp(2rem, 6vw, 5rem);
+ padding-block: clamp(4rem, 9vw, 6.5rem) 2rem;
+}
+
+.intro {
+ max-width: 26rem;
+}
+
+.intro p {
+ margin-top: 1.15rem;
+ margin-bottom: 0;
+ color: oklch(0.7 0.035 220);
+ line-height: 1.75;
+}
+
+.column {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.75rem;
+}
+
+.column h2 {
+ margin-bottom: 0.35rem;
+ color: oklch(0.98 0.015 210);
+ font-family: var(--font-geist-mono), monospace;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+}
+
+.column a {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ color: oklch(0.7 0.035 220);
+ font-size: 0.92rem;
+ text-decoration: none;
+}
+
+.column a:hover {
+ color: oklch(0.9 0.1 205);
+}
+
+.column span {
+ padding: 0.12rem 0.35rem;
+ color: oklch(0.82 0.12 65);
+ font-family: var(--font-geist-mono), monospace;
+ font-size: 0.58rem;
+ border: 1px solid oklch(0.82 0.12 65 / 35%);
+ border-radius: 999px;
+}
+
+.bottom {
+ display: flex;
+ grid-column: 1 / -1;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding-top: 1.5rem;
+ color: oklch(0.57 0.03 225);
+ font-size: 0.78rem;
+ border-top: 1px solid oklch(1 0 0 / 10%);
+}
+
+@media (max-width: 48rem) {
+ .content {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .intro,
+ .bottom {
+ grid-column: 1 / -1;
+ }
+}
+
+@media (max-width: 30rem) {
+ .content {
+ grid-template-columns: 1fr;
+ }
+
+ .intro,
+ .bottom {
+ grid-column: auto;
+ }
+
+ .bottom {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+}
diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx
index 2cbe4f2..888342d 100644
--- a/app/components/Footer.tsx
+++ b/app/components/Footer.tsx
@@ -1,3 +1,42 @@
+import Link from 'next/link'
+import { navigation, socialLinks } from '../data/site'
+import PageContainer from './PageContainer'
+import Wordmark from './Wordmark'
+import styles from './Footer.module.css'
+
export default function Footer() {
- return Footer
+ return (
+
+ )
}
diff --git a/app/components/Header.module.css b/app/components/Header.module.css
new file mode 100644
index 0000000..028f107
--- /dev/null
+++ b/app/components/Header.module.css
@@ -0,0 +1,165 @@
+.header {
+ position: sticky;
+ top: 0;
+ z-index: 50;
+ grid-row: 1;
+ width: 100%;
+ background: color-mix(in oklch, var(--background) 72%, transparent);
+ border-bottom: 1px solid color-mix(in oklch, var(--border) 70%, transparent);
+ backdrop-filter: blur(18px) saturate(150%);
+ animation: header-enter 480ms cubic-bezier(0.22, 1, 0.36, 1) both;
+}
+
+.inner {
+ display: grid;
+ width: min(100%, calc(var(--page-width) + var(--space-page) * 2));
+ min-height: var(--header-height);
+ grid-template-columns: 1fr auto 1fr;
+ align-items: center;
+ gap: 1rem;
+ padding-inline: var(--space-page);
+ margin-inline: auto;
+}
+
+.nav {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.35rem;
+}
+
+.link {
+ position: relative;
+ padding: 0.65rem 0.8rem;
+ color: var(--muted-foreground);
+ font-size: 0.9rem;
+ font-weight: 620;
+ text-decoration: none;
+ border-radius: 999px;
+ transition:
+ color 160ms ease,
+ background-color 160ms ease,
+ transform 160ms ease;
+}
+
+.link:hover {
+ color: var(--foreground);
+ background: color-mix(in oklch, var(--primary) 10%, transparent);
+ transform: translateY(-1px);
+}
+
+.active {
+ color: var(--primary-strong);
+}
+
+.active::after {
+ position: absolute;
+ right: 0.8rem;
+ bottom: 0.28rem;
+ left: 0.8rem;
+ height: 2px;
+ content: '';
+ background: linear-gradient(90deg, var(--primary), var(--accent));
+ border-radius: 999px;
+}
+
+.actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 0.5rem;
+}
+
+.menuButton {
+ display: none;
+ width: 2.65rem;
+ height: 2.65rem;
+ padding: 0;
+ color: var(--foreground);
+ cursor: pointer;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: 50%;
+ place-items: center;
+}
+
+.menuButton svg {
+ width: 1.2rem;
+ height: 1.2rem;
+}
+
+.mobileNavWrap {
+ display: none;
+}
+
+:global(html.intro-pending) .header {
+ opacity: 0;
+ animation: none;
+ transform: translateY(-1rem);
+}
+
+:global(html.intro-revealed) .header {
+ animation: header-reveal 480ms cubic-bezier(0.22, 1, 0.36, 1) both;
+}
+
+@keyframes header-enter {
+ from {
+ opacity: 0;
+ transform: translateY(-1rem);
+ }
+}
+
+@keyframes header-reveal {
+ from {
+ opacity: 0;
+ transform: translateY(-1rem);
+ }
+}
+
+@media (max-width: 48rem) {
+ .inner {
+ display: flex;
+ justify-content: space-between;
+ }
+
+ .nav {
+ display: none;
+ }
+
+ .menuButton {
+ display: grid;
+ }
+
+ .mobileNavWrap {
+ display: grid;
+ grid-template-rows: 0fr;
+ overflow: hidden;
+ transition: grid-template-rows 220ms ease;
+ }
+
+ .mobileNavWrap[data-open='true'] {
+ grid-template-rows: 1fr;
+ }
+
+ .mobileNav {
+ display: grid;
+ min-height: 0;
+ padding-inline: var(--space-page);
+ }
+
+ .mobileLink {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.95rem 0;
+ color: var(--muted-foreground);
+ font-size: 1rem;
+ font-weight: 650;
+ text-decoration: none;
+ border-top: 1px solid var(--border);
+ }
+
+ .mobileLink:last-child {
+ padding-bottom: 1.25rem;
+ }
+}
diff --git a/app/components/Header.tsx b/app/components/Header.tsx
index 4bfacc8..6fcf7e9 100644
--- a/app/components/Header.tsx
+++ b/app/components/Header.tsx
@@ -1,3 +1,75 @@
+'use client'
+
+import { Menu, X } from 'lucide-react'
+import Link from 'next/link'
+import { usePathname } from 'next/navigation'
+import { useState } from 'react'
+import { cn } from '@/lib/utils'
+import routes from '../routes'
+import ThemeToggle from './ThemeToggle'
+import Wordmark from './Wordmark'
+import styles from './Header.module.css'
+
export default function Header() {
- return Header
+ const pathname = usePathname()
+ const [menuOpen, setMenuOpen] = useState(false)
+
+ return (
+
+
+
+
+
+ {routes.map((route) => {
+ const isActive = route.href === '/' ? pathname === route.href : pathname.startsWith(route.href)
+
+ return (
+
+ {route.title}
+
+ )
+ })}
+
+
+
+
+ setMenuOpen((open) => !open)}
+ >
+ {menuOpen ? : }
+
+
+
+
+
+
+ {routes.map((route) => {
+ const isActive = route.href === '/' ? pathname === route.href : pathname.startsWith(route.href)
+ return (
+ setMenuOpen(false)}
+ >
+ {route.title}
+ ↗
+
+ )
+ })}
+
+
+
+ )
}
diff --git a/app/components/Hero.module.css b/app/components/Hero.module.css
new file mode 100644
index 0000000..15a9637
--- /dev/null
+++ b/app/components/Hero.module.css
@@ -0,0 +1,219 @@
+.hero {
+ position: relative;
+ z-index: 2;
+ display: grid;
+ min-height: min(31rem, calc((100dvh - var(--header-height)) * 0.63));
+ grid-template-columns: minmax(13rem, 0.7fr) minmax(20rem, 1fr);
+ align-content: start;
+ align-items: start;
+ gap: clamp(1rem, 3.5vw, 3rem);
+ padding-block: clamp(1rem, 2.5vh, 2rem) 0;
+}
+
+.portraitCard,
+.introCard {
+ border: 1px solid oklch(1 0 0 / 18%);
+ box-shadow: 0 1.5rem 4rem oklch(0.04 0.03 260 / 34%);
+ backdrop-filter: blur(18px) saturate(130%);
+}
+
+.portraitCard {
+ width: min(100%, 21rem);
+ padding: clamp(0.75rem, 2vw, 1rem);
+ justify-self: center;
+ background: oklch(0.15 0.035 258 / 62%);
+ border-radius: var(--radius-xl);
+ transform: rotate(-1.5deg);
+}
+
+.portrait {
+ position: relative;
+ display: grid;
+ aspect-ratio: 4 / 5;
+ overflow: hidden;
+ color: oklch(0.94 0.025 210 / 84%);
+ background-color: oklch(0.22 0.05 255);
+ background-image:
+ radial-gradient(circle at 50% 32%, oklch(0.78 0.12 210 / 60%), transparent 18%),
+ linear-gradient(145deg, oklch(0.32 0.12 225), oklch(0.18 0.06 275) 58%, oklch(0.27 0.1 335));
+ background-position: center;
+ background-size: cover;
+ border-radius: calc(var(--radius-xl) - 0.45rem);
+ place-items: center;
+}
+
+.portraitImage {
+ z-index: 1;
+ object-fit: cover;
+}
+
+.portrait span {
+ font-family: var(--font-geist-mono), monospace;
+ font-size: clamp(3.25rem, 7vw, 5.75rem);
+ font-weight: 700;
+ letter-spacing: -0.12em;
+ text-shadow: 0 0.25rem 2rem oklch(0.05 0.03 260 / 45%);
+}
+
+.portraitCaption {
+ padding: 0.65rem 0.3rem 0.1rem;
+ margin: 0;
+ color: oklch(0.88 0.035 215);
+ font-family: var(--font-geist-mono), monospace;
+ font-size: 0.73rem;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+}
+
+.introCard {
+ max-width: 42rem;
+ padding: clamp(1.35rem, 3vw, 2.75rem);
+ color: oklch(0.97 0.015 210);
+ background:
+ linear-gradient(145deg, oklch(0.18 0.045 260 / 82%), oklch(0.12 0.035 265 / 68%)),
+ radial-gradient(circle at 100% 0, oklch(0.72 0.16 165 / 20%), transparent 22rem);
+ border-radius: var(--radius-xl);
+}
+
+.eyebrow {
+ margin-bottom: 0.85rem;
+ color: oklch(0.82 0.15 195);
+ font-family: var(--font-geist-mono), monospace;
+ font-size: 0.78rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+}
+
+.introCard h1 {
+ max-width: 12ch;
+ margin-bottom: 1.15rem;
+ font-size: clamp(2.25rem, 4.3vw, 4rem);
+ line-height: 0.98;
+ letter-spacing: -0.065em;
+ text-wrap: balance;
+}
+
+.intro,
+.detail {
+ max-width: 57ch;
+ color: oklch(0.83 0.035 218);
+ line-height: 1.75;
+}
+
+.intro {
+ margin-bottom: 0.65rem;
+ font-size: clamp(0.95rem, 1.25vw, 1.05rem);
+}
+
+.detail {
+ margin-bottom: 1.25rem;
+ font-size: 0.9rem;
+}
+
+.focusList {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.65rem;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+
+.focusList li {
+ padding: 0.55rem 0.8rem;
+ color: oklch(0.9 0.045 210);
+ font-family: var(--font-geist-mono), monospace;
+ font-size: 0.72rem;
+ background: oklch(1 0 0 / 7%);
+ border: 1px solid oklch(1 0 0 / 14%);
+ border-radius: 999px;
+}
+
+@media (max-width: 48rem) {
+ .hero {
+ min-height: min(32rem, calc((100dvh - var(--header-height)) * 0.63));
+ grid-template-columns: minmax(8rem, 0.42fr) minmax(0, 1fr);
+ gap: 1rem;
+ padding-block: clamp(0.85rem, 2vh, 1.5rem) 0;
+ }
+
+ .portraitCard {
+ width: min(100%, 11rem);
+ transform: rotate(-1.5deg);
+ }
+
+ .portraitCaption {
+ font-size: 0.55rem;
+ line-height: 1.35;
+ }
+
+ .introCard {
+ padding: clamp(1rem, 3vw, 1.5rem);
+ }
+
+ .introCard h1 {
+ max-width: 12ch;
+ font-size: clamp(2rem, 8vw, 3.1rem);
+ }
+
+ .intro,
+ .detail {
+ line-height: 1.55;
+ }
+
+ .focusList {
+ gap: 0.45rem;
+ }
+
+ .focusList li {
+ padding: 0.45rem 0.65rem;
+ }
+}
+
+@media (max-width: 30rem) {
+ .hero {
+ grid-template-columns: minmax(5.5rem, 0.32fr) minmax(0, 1fr);
+ gap: 0.75rem;
+ }
+
+ .portraitCard {
+ width: min(100%, 7.75rem);
+ padding: 0.55rem;
+ transform: none;
+ }
+
+ .portraitCaption {
+ display: none;
+ }
+
+ .introCard {
+ padding: 0.9rem;
+ }
+
+ .eyebrow {
+ margin-bottom: 0.55rem;
+ font-size: 0.65rem;
+ }
+
+ .introCard h1 {
+ margin-bottom: 0.75rem;
+ font-size: clamp(1.65rem, 7vw, 2.15rem);
+ letter-spacing: -0.045em;
+ }
+
+ .intro {
+ margin-bottom: 0.55rem;
+ font-size: 0.82rem;
+ }
+
+ .detail {
+ margin-bottom: 0.85rem;
+ font-size: 0.78rem;
+ }
+
+ .focusList li {
+ font-size: 0.62rem;
+ }
+}
diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx
new file mode 100644
index 0000000..d573734
--- /dev/null
+++ b/app/components/Hero.tsx
@@ -0,0 +1,51 @@
+'use client'
+
+import Image from 'next/image'
+import PageContainer from './PageContainer'
+import styles from './Hero.module.css'
+
+export default function Hero() {
+ return (
+
+
+
+ AM
+ {
+ event.currentTarget.style.display = 'none'
+ }}
+ />
+
+
Based in Istanbul · Building for the web
+
+
+
+
Hello, I'm Alper
+
I turn thoughtful ideas into dependable software.
+
+ I'm a software engineer focused on building clear, useful digital experiences. I enjoy working across
+ product, interface, and engineering to take ideas from an early sketch to polished, maintainable software.
+
+
+ This is where I share selected projects, engineering notes, and the experiments that keep me curious.
+
+
+
+ Product engineering
+ Web interfaces
+ Cloud systems
+
+
+
+ )
+}
diff --git a/app/components/IntroGate.module.css b/app/components/IntroGate.module.css
new file mode 100644
index 0000000..d4e98be
--- /dev/null
+++ b/app/components/IntroGate.module.css
@@ -0,0 +1,35 @@
+.overlay {
+ position: fixed;
+ z-index: 100;
+ inset: 0;
+ opacity: 1;
+ transform: scale(1);
+ transition:
+ opacity 320ms ease,
+ transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
+}
+
+.exiting {
+ pointer-events: none;
+ opacity: 0;
+ transform: scale(1.025);
+}
+
+.application {
+ min-height: 100svh;
+}
+
+:global(html:not(.intro-pending)) .overlay {
+ display: none;
+}
+
+:global(html.intro-pending) .application {
+ visibility: hidden;
+ opacity: 0;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .overlay {
+ transition: none;
+ }
+}
diff --git a/app/components/IntroGate.tsx b/app/components/IntroGate.tsx
new file mode 100644
index 0000000..74d8fb5
--- /dev/null
+++ b/app/components/IntroGate.tsx
@@ -0,0 +1,41 @@
+'use client'
+
+import { useState } from 'react'
+import { INTRO_EXIT_DURATION_MS, INTRO_STORAGE_KEY } from '../intro'
+import AlperLoader from './AlperLoader'
+import styles from './IntroGate.module.css'
+
+export default function IntroGate({ children }: Readonly<{ children: React.ReactNode }>) {
+ const [exiting, setExiting] = useState(false)
+
+ function completeIntro() {
+ const root = document.documentElement
+
+ if (!root.classList.contains('intro-pending')) {
+ return
+ }
+
+ globalThis.localStorage.setItem(INTRO_STORAGE_KEY, 'complete')
+ setExiting(true)
+
+ const reducedMotion = globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches
+
+ globalThis.setTimeout(
+ () => {
+ root.classList.remove('intro-pending')
+ root.classList.add('intro-revealed')
+ setExiting(false)
+ },
+ reducedMotion ? 0 : INTRO_EXIT_DURATION_MS,
+ )
+ }
+
+ return (
+ <>
+
+ {children}
+ >
+ )
+}
diff --git a/app/components/LightRays.module.css b/app/components/LightRays.module.css
new file mode 100644
index 0000000..bc6e0b8
--- /dev/null
+++ b/app/components/LightRays.module.css
@@ -0,0 +1,27 @@
+.rays {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ animation: rays-fade-in 900ms ease both;
+ pointer-events: none;
+}
+
+.rays canvas {
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+
+@keyframes rays-fade-in {
+ from {
+ opacity: 0;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .rays {
+ animation: none;
+ }
+}
diff --git a/app/components/LightRays.tsx b/app/components/LightRays.tsx
new file mode 100644
index 0000000..50ed101
--- /dev/null
+++ b/app/components/LightRays.tsx
@@ -0,0 +1,365 @@
+'use client'
+
+import { useEffect, useRef, useState } from 'react'
+import { Mesh, Program, Renderer, Triangle } from 'ogl'
+import styles from './LightRays.module.css'
+
+type RaysOrigin =
+ | 'top-center'
+ | 'top-left'
+ | 'top-right'
+ | 'left'
+ | 'right'
+ | 'bottom-left'
+ | 'bottom-center'
+ | 'bottom-right'
+
+type LightRaysProps = {
+ className?: string
+ raysOrigin?: RaysOrigin
+ raysColor?: string
+ raysSpeed?: number
+ lightSpread?: number
+ rayLength?: number
+ pulsating?: boolean
+ fadeDistance?: number
+ saturation?: number
+ followMouse?: boolean
+ mouseInfluence?: number
+ noiseAmount?: number
+ distortion?: number
+}
+
+type UniformValue = number | [number, number] | [number, number, number]
+type Uniforms = Record
+
+const DEFAULT_COLOR = '#ffffff'
+
+const vertexShader = `
+attribute vec2 position;
+varying vec2 vUv;
+
+void main() {
+ vUv = position * 0.5 + 0.5;
+ gl_Position = vec4(position, 0.0, 1.0);
+}
+`
+
+const fragmentShader = `
+precision highp float;
+
+uniform float iTime;
+uniform vec2 iResolution;
+uniform vec2 rayPos;
+uniform vec2 rayDir;
+uniform vec3 raysColor;
+uniform float raysSpeed;
+uniform float lightSpread;
+uniform float rayLength;
+uniform float pulsating;
+uniform float fadeDistance;
+uniform float saturation;
+uniform vec2 mousePos;
+uniform float mouseInfluence;
+uniform float noiseAmount;
+uniform float distortion;
+
+varying vec2 vUv;
+
+float noise(vec2 st) {
+ return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
+}
+
+float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord, float seedA, float seedB, float speed) {
+ vec2 sourceToCoord = coord - raySource;
+ vec2 dirNorm = normalize(sourceToCoord);
+ float cosAngle = dot(dirNorm, rayRefDirection);
+ float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2;
+ float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001));
+ float distance = length(sourceToCoord);
+ float maxDistance = iResolution.x * rayLength;
+ float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0);
+ float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0);
+ float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0;
+ float baseStrength = clamp(
+ (0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) +
+ (0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)),
+ 0.0,
+ 1.0
+ );
+
+ return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);
+ vec2 finalRayDir = rayDir;
+
+ if (mouseInfluence > 0.0) {
+ vec2 mouseScreenPos = mousePos * iResolution.xy;
+ vec2 mouseDirection = normalize(mouseScreenPos - rayPos);
+ finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence));
+ }
+
+ vec4 rays1 = vec4(1.0) * rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349, 1.5 * raysSpeed);
+ vec4 rays2 = vec4(1.0) * rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234, 1.1 * raysSpeed);
+
+ fragColor = rays1 * 0.5 + rays2 * 0.4;
+
+ if (noiseAmount > 0.0) {
+ float n = noise(coord * 0.01 + iTime * 0.1);
+ fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n);
+ }
+
+ float brightness = 1.0 - (coord.y / iResolution.y);
+ fragColor.x *= 0.1 + brightness * 0.8;
+ fragColor.y *= 0.3 + brightness * 0.6;
+ fragColor.z *= 0.5 + brightness * 0.5;
+
+ if (saturation != 1.0) {
+ float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));
+ fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation);
+ }
+
+ fragColor.rgb *= raysColor;
+}
+
+void main() {
+ vec4 color;
+ mainImage(color, gl_FragCoord.xy);
+ gl_FragColor = color;
+}
+`
+
+function hexToRgb(hex: string): [number, number, number] {
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
+
+ return result
+ ? [Number.parseInt(result[1], 16) / 255, Number.parseInt(result[2], 16) / 255, Number.parseInt(result[3], 16) / 255]
+ : [1, 1, 1]
+}
+
+function getRayAnchor(origin: RaysOrigin, width: number, height: number) {
+ switch (origin) {
+ case 'top-left':
+ return { anchor: [0, -0.2 * height] as [number, number], direction: [0, 1] as [number, number] }
+ case 'top-right':
+ return { anchor: [width, -0.2 * height] as [number, number], direction: [0, 1] as [number, number] }
+ case 'left':
+ return { anchor: [-0.2 * width, 0.5 * height] as [number, number], direction: [1, 0] as [number, number] }
+ case 'right':
+ return { anchor: [1.2 * width, 0.5 * height] as [number, number], direction: [-1, 0] as [number, number] }
+ case 'bottom-left':
+ return { anchor: [0, 1.2 * height] as [number, number], direction: [0, -1] as [number, number] }
+ case 'bottom-center':
+ return { anchor: [0.5 * width, 1.2 * height] as [number, number], direction: [0, -1] as [number, number] }
+ case 'bottom-right':
+ return { anchor: [width, 1.2 * height] as [number, number], direction: [0, -1] as [number, number] }
+ default:
+ return { anchor: [0.5 * width, -0.2 * height] as [number, number], direction: [0, 1] as [number, number] }
+ }
+}
+
+function usePrefersReducedMotion() {
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(false)
+
+ useEffect(() => {
+ const media = window.matchMedia('(prefers-reduced-motion: reduce)')
+ const update = () => setPrefersReducedMotion(media.matches)
+
+ update()
+ media.addEventListener('change', update)
+
+ return () => media.removeEventListener('change', update)
+ }, [])
+
+ return prefersReducedMotion
+}
+
+export default function LightRays({
+ className,
+ raysOrigin = 'top-center',
+ raysColor = DEFAULT_COLOR,
+ raysSpeed = 1,
+ lightSpread = 1,
+ rayLength = 2,
+ pulsating = false,
+ fadeDistance = 1,
+ saturation = 1,
+ followMouse = true,
+ mouseInfluence = 0.1,
+ noiseAmount = 0,
+ distortion = 0,
+}: Readonly) {
+ const containerRef = useRef(null)
+ const rendererRef = useRef(null)
+ const uniformsRef = useRef(null)
+ const meshRef = useRef(null)
+ const animationRef = useRef(null)
+ const mouseRef = useRef({ x: 0.5, y: 0.5 })
+ const smoothMouseRef = useRef({ x: 0.5, y: 0.5 })
+ const prefersReducedMotion = usePrefersReducedMotion()
+
+ useEffect(() => {
+ if (prefersReducedMotion || !containerRef.current) return
+
+ const container = containerRef.current
+ const renderer = new Renderer({ dpr: Math.min(window.devicePixelRatio, 2), alpha: true })
+ rendererRef.current = renderer
+
+ const gl = renderer.gl
+ gl.canvas.style.width = '100%'
+ gl.canvas.style.height = '100%'
+
+ while (container.firstChild) container.removeChild(container.firstChild)
+ container.appendChild(gl.canvas)
+
+ const uniforms: Uniforms = {
+ iTime: { value: 0 },
+ iResolution: { value: [1, 1] },
+ rayPos: { value: [0, 0] },
+ rayDir: { value: [0, 1] },
+ raysColor: { value: hexToRgb(raysColor) },
+ raysSpeed: { value: raysSpeed },
+ lightSpread: { value: lightSpread },
+ rayLength: { value: rayLength },
+ pulsating: { value: pulsating ? 1 : 0 },
+ fadeDistance: { value: fadeDistance },
+ saturation: { value: saturation },
+ mousePos: { value: [0.5, 0.5] },
+ mouseInfluence: { value: mouseInfluence },
+ noiseAmount: { value: noiseAmount },
+ distortion: { value: distortion },
+ }
+ uniformsRef.current = uniforms
+
+ const geometry = new Triangle(gl)
+ const program = new Program(gl, { vertex: vertexShader, fragment: fragmentShader, uniforms })
+ const mesh = new Mesh(gl, { geometry, program })
+ meshRef.current = mesh
+
+ const resize = () => {
+ const { clientWidth, clientHeight } = container
+ const dpr = Math.min(window.devicePixelRatio, 2)
+ renderer.dpr = dpr
+ renderer.setSize(clientWidth, clientHeight)
+
+ const width = clientWidth * dpr
+ const height = clientHeight * dpr
+ uniforms.iResolution.value = [width, height]
+
+ const { anchor, direction } = getRayAnchor(raysOrigin, width, height)
+ uniforms.rayPos.value = anchor
+ uniforms.rayDir.value = direction
+ }
+
+ const render = (time: number) => {
+ uniforms.iTime.value = time * 0.001
+
+ if (followMouse && mouseInfluence > 0) {
+ smoothMouseRef.current.x = smoothMouseRef.current.x * 0.92 + mouseRef.current.x * 0.08
+ smoothMouseRef.current.y = smoothMouseRef.current.y * 0.92 + mouseRef.current.y * 0.08
+ uniforms.mousePos.value = [smoothMouseRef.current.x, smoothMouseRef.current.y]
+ }
+
+ renderer.render({ scene: mesh })
+ animationRef.current = requestAnimationFrame(render)
+ }
+
+ resize()
+ window.addEventListener('resize', resize)
+ animationRef.current = requestAnimationFrame(render)
+
+ return () => {
+ if (animationRef.current) cancelAnimationFrame(animationRef.current)
+ window.removeEventListener('resize', resize)
+
+ try {
+ renderer.gl.getExtension('WEBGL_lose_context')?.loseContext()
+ renderer.gl.canvas.parentNode?.removeChild(renderer.gl.canvas)
+ } catch {
+ // WebGL context cleanup can fail during fast route transitions.
+ }
+
+ rendererRef.current = null
+ uniformsRef.current = null
+ meshRef.current = null
+ animationRef.current = null
+ }
+ }, [
+ distortion,
+ fadeDistance,
+ followMouse,
+ lightSpread,
+ mouseInfluence,
+ noiseAmount,
+ prefersReducedMotion,
+ pulsating,
+ rayLength,
+ raysColor,
+ raysOrigin,
+ raysSpeed,
+ saturation,
+ ])
+
+ useEffect(() => {
+ const uniforms = uniformsRef.current
+ const renderer = rendererRef.current
+ const container = containerRef.current
+
+ if (!uniforms || !renderer || !container) return
+
+ uniforms.raysColor.value = hexToRgb(raysColor)
+ uniforms.raysSpeed.value = raysSpeed
+ uniforms.lightSpread.value = lightSpread
+ uniforms.rayLength.value = rayLength
+ uniforms.pulsating.value = pulsating ? 1 : 0
+ uniforms.fadeDistance.value = fadeDistance
+ uniforms.saturation.value = saturation
+ uniforms.mouseInfluence.value = mouseInfluence
+ uniforms.noiseAmount.value = noiseAmount
+ uniforms.distortion.value = distortion
+
+ const width = container.clientWidth * renderer.dpr
+ const height = container.clientHeight * renderer.dpr
+ const { anchor, direction } = getRayAnchor(raysOrigin, width, height)
+ uniforms.rayPos.value = anchor
+ uniforms.rayDir.value = direction
+ }, [
+ distortion,
+ fadeDistance,
+ lightSpread,
+ mouseInfluence,
+ noiseAmount,
+ pulsating,
+ rayLength,
+ raysColor,
+ raysOrigin,
+ raysSpeed,
+ saturation,
+ ])
+
+ useEffect(() => {
+ if (!followMouse) return
+
+ const onMouseMove = (event: MouseEvent) => {
+ const container = containerRef.current
+
+ if (!container) return
+
+ const bounds = container.getBoundingClientRect()
+ mouseRef.current = {
+ x: (event.clientX - bounds.left) / bounds.width,
+ y: (event.clientY - bounds.top) / bounds.height,
+ }
+ }
+
+ window.addEventListener('mousemove', onMouseMove)
+
+ return () => window.removeEventListener('mousemove', onMouseMove)
+ }, [followMouse])
+
+ return (
+
+ )
+}
diff --git a/app/components/MainWrapper.module.css b/app/components/MainWrapper.module.css
new file mode 100644
index 0000000..309cef5
--- /dev/null
+++ b/app/components/MainWrapper.module.css
@@ -0,0 +1,29 @@
+.main {
+ grid-row: 2;
+ min-width: 0;
+ animation: page-enter 520ms 70ms cubic-bezier(0.22, 1, 0.36, 1) both;
+}
+
+:global(html.intro-pending) .main {
+ opacity: 0;
+ animation: none;
+ transform: translateY(1rem);
+}
+
+:global(html.intro-revealed) .main {
+ animation: page-reveal 520ms 70ms cubic-bezier(0.22, 1, 0.36, 1) both;
+}
+
+@keyframes page-enter {
+ from {
+ opacity: 0;
+ transform: translateY(1rem);
+ }
+}
+
+@keyframes page-reveal {
+ from {
+ opacity: 0;
+ transform: translateY(1rem);
+ }
+}
diff --git a/app/components/MainWrapper.tsx b/app/components/MainWrapper.tsx
index ecafb16..60cb056 100644
--- a/app/components/MainWrapper.tsx
+++ b/app/components/MainWrapper.tsx
@@ -1,11 +1,9 @@
+import styles from './MainWrapper.module.css'
+
export default function MainWrapper({
children,
}: Readonly<{
children: React.ReactNode
}>) {
- return (
-
- {children}
-
- )
+ return {children}
}
diff --git a/app/components/PageContainer.module.css b/app/components/PageContainer.module.css
new file mode 100644
index 0000000..982319f
--- /dev/null
+++ b/app/components/PageContainer.module.css
@@ -0,0 +1,9 @@
+.container {
+ width: min(100%, calc(var(--page-width) + var(--space-page) * 2));
+ padding-inline: var(--space-page);
+ margin-inline: auto;
+}
+
+.reading {
+ width: min(100%, calc(var(--reading-width) + var(--space-page) * 2));
+}
diff --git a/app/components/PageContainer.tsx b/app/components/PageContainer.tsx
new file mode 100644
index 0000000..9bae2a6
--- /dev/null
+++ b/app/components/PageContainer.tsx
@@ -0,0 +1,10 @@
+import { cn } from '@/lib/utils'
+import styles from './PageContainer.module.css'
+
+type PageContainerProps = React.ComponentPropsWithoutRef<'div'> & {
+ reading?: boolean
+}
+
+export default function PageContainer({ className, reading = false, ...props }: PageContainerProps) {
+ return
+}
diff --git a/app/components/Parallax.module.css b/app/components/Parallax.module.css
new file mode 100644
index 0000000..5ac053c
--- /dev/null
+++ b/app/components/Parallax.module.css
@@ -0,0 +1,56 @@
+.wrapper {
+ position: relative;
+ z-index: 0;
+ isolation: isolate;
+ width: 100%;
+ min-height: 100dvh;
+ overflow: hidden;
+ background-image: url('/images/parallax/layer-1.jpeg');
+ background-repeat: no-repeat;
+ background-position: top center;
+ background-size: 100% auto;
+}
+
+.wrapper::after {
+ position: absolute;
+ z-index: 1;
+ inset: 0;
+ content: '';
+ pointer-events: none;
+ background:
+ linear-gradient(180deg, oklch(0.08 0.025 260 / 10%), oklch(0.08 0.025 260 / 48%)),
+ radial-gradient(circle at 70% 45%, transparent 0, oklch(0.08 0.025 260 / 20%) 65%);
+}
+
+.layer {
+ position: absolute;
+ z-index: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ background-repeat: no-repeat;
+ background-position: top center;
+ background-size: 100% auto;
+ bottom: -10rem;
+}
+
+.layer:nth-child(1) {
+ background-image: url('/images/parallax/layer-2.svg');
+}
+
+.layer:nth-child(2) {
+ background-image: url('/images/parallax/layer-3.svg');
+}
+
+@media (max-width: 71.875rem) {
+ .wrapper {
+ min-height: clamp(34rem, 58vw, 42rem);
+ }
+}
+
+@media (max-width: 48rem) {
+ .wrapper,
+ .layer {
+ background-size: auto 100%;
+ }
+}
diff --git a/app/components/Parallax.tsx b/app/components/Parallax.tsx
new file mode 100644
index 0000000..8551a8b
--- /dev/null
+++ b/app/components/Parallax.tsx
@@ -0,0 +1,20 @@
+'use client'
+
+import styles from './Parallax.module.css'
+
+type Props = {
+ scrollSpeed: number
+ tabletScrollSpeed?: number
+ mobileScrollSpeed?: number
+}
+
+export default function Parallax({ scrollSpeed, tabletScrollSpeed, mobileScrollSpeed }: Readonly) {
+ return (
+
+ )
+}
diff --git a/app/components/ParallaxScene.tsx b/app/components/ParallaxScene.tsx
new file mode 100644
index 0000000..475b8bf
--- /dev/null
+++ b/app/components/ParallaxScene.tsx
@@ -0,0 +1,75 @@
+'use client'
+
+import { useLenis } from 'lenis/react'
+import { useEffect, useRef } from 'react'
+import gsap from 'gsap'
+import { ScrollTrigger } from 'gsap/ScrollTrigger'
+import Hero from './Hero'
+import Parallax from './Parallax'
+import styles from './Parallax.module.css'
+
+gsap.registerPlugin(ScrollTrigger)
+
+export default function ParallaxScene() {
+ const wrapperRef = useRef(null)
+
+ useLenis(() => ScrollTrigger.update())
+
+ useEffect(() => {
+ const wrapper = wrapperRef.current
+
+ if (!wrapper) return
+
+ const context = gsap.context(() => {
+ const layers = gsap.utils.toArray(`.${styles.layer}`)
+
+ const timeline = gsap.timeline({
+ scrollTrigger: {
+ trigger: wrapper,
+ start: 'top top',
+ scrub: true,
+ invalidateOnRefresh: true,
+ },
+ })
+
+ layers.forEach((layer) => {
+ timeline.to(
+ layer,
+ {
+ y: () => {
+ const desktopSpeed = Number(layer.dataset.speed ?? 1)
+ const tabletSpeed = Number(layer.dataset.tabletSpeed ?? desktopSpeed)
+ const mobileSpeed = Number(layer.dataset.mobileSpeed ?? desktopSpeed)
+ const isMobile = globalThis.matchMedia('(max-width: 48rem)').matches
+ const isTablet = globalThis.matchMedia('(max-width: 71.875rem)').matches
+ let speed = desktopSpeed
+
+ if (isTablet) {
+ speed = tabletSpeed
+ }
+
+ if (isMobile) {
+ speed = mobileSpeed
+ }
+
+ return 100 * speed
+ },
+ duration: 2,
+ ease: 'none',
+ },
+ 0,
+ )
+ })
+ }, wrapper)
+
+ return () => context.revert()
+ }, [])
+
+ return (
+
+ )
+}
diff --git a/app/components/ScrollReveal.module.css b/app/components/ScrollReveal.module.css
new file mode 100644
index 0000000..28de9d2
--- /dev/null
+++ b/app/components/ScrollReveal.module.css
@@ -0,0 +1,19 @@
+.reveal {
+ margin: 0;
+}
+
+.text {
+ display: block;
+}
+
+.word {
+ display: inline-block;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .word {
+ opacity: 1 !important;
+ filter: none !important;
+ transform: none !important;
+ }
+}
diff --git a/app/components/ScrollReveal.tsx b/app/components/ScrollReveal.tsx
new file mode 100644
index 0000000..2ef3e41
--- /dev/null
+++ b/app/components/ScrollReveal.tsx
@@ -0,0 +1,137 @@
+'use client'
+
+import { useEffect, useMemo, useRef } from 'react'
+import gsap from 'gsap'
+import { ScrollTrigger } from 'gsap/ScrollTrigger'
+import styles from './ScrollReveal.module.css'
+
+gsap.registerPlugin(ScrollTrigger)
+
+type ScrollRevealProps = {
+ children: string
+ as?: 'h2' | 'p'
+ id?: string
+ className?: string
+ enableBlur?: boolean
+ baseOpacity?: number
+ baseRotation?: number
+ blurStrength?: number
+ rotationEnd?: string
+ wordAnimationEnd?: string
+}
+
+function usePrefersReducedMotion() {
+ const prefersReducedMotionRef = useRef(false)
+
+ useEffect(() => {
+ const media = window.matchMedia('(prefers-reduced-motion: reduce)')
+ const update = () => {
+ prefersReducedMotionRef.current = media.matches
+ }
+
+ update()
+ media.addEventListener('change', update)
+
+ return () => media.removeEventListener('change', update)
+ }, [])
+
+ return prefersReducedMotionRef
+}
+
+export default function ScrollReveal({
+ children,
+ as = 'h2',
+ id,
+ className,
+ enableBlur = true,
+ baseOpacity = 0.18,
+ baseRotation = 2,
+ blurStrength = 5,
+ rotationEnd = 'bottom 72%',
+ wordAnimationEnd = 'bottom 68%',
+}: Readonly) {
+ const containerRef = useRef(null)
+ const prefersReducedMotionRef = usePrefersReducedMotion()
+ const Component = as
+
+ const splitText = useMemo(
+ () =>
+ children.split(/(\s+)/).map((word, index) => {
+ if (word.match(/^\s+$/)) return word
+
+ return (
+
+ {word}
+
+ )
+ }),
+ [children],
+ )
+
+ useEffect(() => {
+ const element = containerRef.current
+
+ if (!element || prefersReducedMotionRef.current) return
+
+ const context = gsap.context(() => {
+ gsap.fromTo(
+ element,
+ { rotate: baseRotation, transformOrigin: '0% 50%' },
+ {
+ ease: 'none',
+ rotate: 0,
+ scrollTrigger: {
+ trigger: element,
+ start: 'top bottom',
+ end: rotationEnd,
+ scrub: true,
+ },
+ },
+ )
+
+ const words = element.querySelectorAll(`.${styles.word}`)
+
+ gsap.fromTo(
+ words,
+ { opacity: baseOpacity, willChange: 'opacity, filter' },
+ {
+ ease: 'none',
+ opacity: 1,
+ stagger: 0.05,
+ scrollTrigger: {
+ trigger: element,
+ start: 'top bottom-=20%',
+ end: wordAnimationEnd,
+ scrub: true,
+ },
+ },
+ )
+
+ if (enableBlur) {
+ gsap.fromTo(
+ words,
+ { filter: `blur(${blurStrength}px)` },
+ {
+ ease: 'none',
+ filter: 'blur(0px)',
+ stagger: 0.05,
+ scrollTrigger: {
+ trigger: element,
+ start: 'top bottom-=20%',
+ end: wordAnimationEnd,
+ scrub: true,
+ },
+ },
+ )
+ }
+ }, element)
+
+ return () => context.revert()
+ }, [baseOpacity, baseRotation, blurStrength, enableBlur, prefersReducedMotionRef, rotationEnd, wordAnimationEnd])
+
+ return (
+
+ {splitText}
+
+ )
+}
diff --git a/app/components/Sidebar.tsx b/app/components/Sidebar.tsx
deleted file mode 100644
index ea5d76d..0000000
--- a/app/components/Sidebar.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-export default function Sidebar() {
- return (
- Sidebar
- )
-}
diff --git a/app/components/SmoothScroll.tsx b/app/components/SmoothScroll.tsx
new file mode 100644
index 0000000..cb601bd
--- /dev/null
+++ b/app/components/SmoothScroll.tsx
@@ -0,0 +1,36 @@
+'use client'
+
+import { ReactLenis } from 'lenis/react'
+import { useEffect, useState } from 'react'
+
+export default function SmoothScroll({ children }: Readonly<{ children: React.ReactNode }>) {
+ const [reducedMotion, setReducedMotion] = useState(false)
+
+ useEffect(() => {
+ const mediaQuery = globalThis.matchMedia('(prefers-reduced-motion: reduce)')
+ const updateMotionPreference = () => setReducedMotion(mediaQuery.matches)
+
+ updateMotionPreference()
+ mediaQuery.addEventListener('change', updateMotionPreference)
+
+ return () => mediaQuery.removeEventListener('change', updateMotionPreference)
+ }, [])
+
+ if (reducedMotion) {
+ return children
+ }
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/app/components/ThemeToggle.module.css b/app/components/ThemeToggle.module.css
new file mode 100644
index 0000000..268cd60
--- /dev/null
+++ b/app/components/ThemeToggle.module.css
@@ -0,0 +1,55 @@
+.button {
+ position: relative;
+ display: grid;
+ width: 2.65rem;
+ height: 2.65rem;
+ flex: 0 0 auto;
+ padding: 0;
+ color: var(--foreground);
+ cursor: pointer;
+ background: color-mix(in oklch, var(--surface-raised) 78%, transparent);
+ border: 1px solid var(--border);
+ border-radius: 50%;
+ box-shadow: 0 0.5rem 1.5rem var(--shadow-color);
+ place-items: center;
+ transition:
+ color 180ms ease,
+ transform 180ms ease,
+ background-color 180ms ease;
+}
+
+.button:hover {
+ color: var(--accent-warm);
+ background: var(--surface-raised);
+ transform: translateY(-2px) rotate(-4deg);
+}
+
+.sun,
+.moon {
+ position: absolute;
+ width: 1.15rem;
+ height: 1.15rem;
+ transition:
+ opacity 180ms ease,
+ transform 220ms ease;
+}
+
+.sun {
+ opacity: 0;
+ transform: scale(0.5) rotate(-45deg);
+}
+
+.moon {
+ opacity: 1;
+ transform: scale(1) rotate(0);
+}
+
+:global(html.dark) .sun {
+ opacity: 1;
+ transform: scale(1) rotate(0);
+}
+
+:global(html.dark) .moon {
+ opacity: 0;
+ transform: scale(0.5) rotate(45deg);
+}
diff --git a/app/components/ThemeToggle.tsx b/app/components/ThemeToggle.tsx
new file mode 100644
index 0000000..e82bba7
--- /dev/null
+++ b/app/components/ThemeToggle.tsx
@@ -0,0 +1,75 @@
+'use client'
+
+import { Moon, Sun } from 'lucide-react'
+import styles from './ThemeToggle.module.css'
+
+type Theme = 'light' | 'dark'
+let themeTransitionActive = false
+
+function getTheme(): Theme {
+ const storedTheme = globalThis.localStorage?.getItem('theme')
+
+ if (storedTheme === 'light' || storedTheme === 'dark') {
+ return storedTheme
+ }
+
+ return globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
+}
+
+export default function ThemeToggle() {
+ function toggleTheme() {
+ if (themeTransitionActive) {
+ return
+ }
+
+ const nextTheme = getTheme() === 'dark' ? 'light' : 'dark'
+
+ function applyTheme() {
+ document.documentElement.classList.toggle('dark', nextTheme === 'dark')
+ document.documentElement.dataset.theme = nextTheme
+ globalThis.localStorage.setItem('theme', nextTheme)
+ }
+
+ if (globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches) {
+ applyTheme()
+ return
+ }
+
+ themeTransitionActive = true
+
+ if (document.startViewTransition) {
+ const transition = document.startViewTransition(applyTheme)
+
+ transition.finished.finally(() => {
+ themeTransitionActive = false
+ })
+ return
+ }
+
+ const fallbackAnimation = document.documentElement.animate(
+ [{ opacity: 1 }, { opacity: 0.45, offset: 0.48 }, { opacity: 1 }],
+ {
+ duration: 360,
+ easing: 'ease-in-out',
+ },
+ )
+
+ globalThis.setTimeout(applyTheme, 170)
+ fallbackAnimation.finished.finally(() => {
+ themeTransitionActive = false
+ })
+ }
+
+ return (
+
+
+
+
+ )
+}
diff --git a/app/components/Wordmark.module.css b/app/components/Wordmark.module.css
new file mode 100644
index 0000000..ae59a96
--- /dev/null
+++ b/app/components/Wordmark.module.css
@@ -0,0 +1,38 @@
+.wordmark {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.15rem;
+ color: var(--foreground);
+ font-weight: 760;
+ text-decoration: none;
+ letter-spacing: -0.04em;
+}
+
+.mark {
+ width: 4.75rem;
+ color: var(--primary);
+ overflow: visible;
+}
+
+.mark path {
+ fill: none;
+ stroke: currentColor;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-width: 3.1;
+}
+
+.text {
+ margin-left: -1.5rem;
+ font-size: 1.05rem;
+}
+
+.text span {
+ color: var(--primary);
+}
+
+@media (max-width: 27rem) {
+ .text {
+ display: none;
+ }
+}
diff --git a/app/components/Wordmark.tsx b/app/components/Wordmark.tsx
new file mode 100644
index 0000000..48c98b1
--- /dev/null
+++ b/app/components/Wordmark.tsx
@@ -0,0 +1,19 @@
+import Link from 'next/link'
+import styles from './Wordmark.module.css'
+
+export default function Wordmark() {
+ return (
+
+
+
+
+
+
+ alper.dev
+
+
+ )
+}
diff --git a/app/components/ui/button.module.css b/app/components/ui/button.module.css
new file mode 100644
index 0000000..1cdf4d7
--- /dev/null
+++ b/app/components/ui/button.module.css
@@ -0,0 +1,176 @@
+.button {
+ display: inline-flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.875rem;
+ font-weight: 500;
+ white-space: nowrap;
+ user-select: none;
+ background-clip: padding-box;
+ border: 1px solid transparent;
+ border-radius: 0.5rem;
+ outline: none;
+ transition:
+ color 150ms ease,
+ background-color 150ms ease,
+ border-color 150ms ease,
+ box-shadow 150ms ease,
+ transform 150ms ease;
+}
+
+.button:focus-visible {
+ border-color: var(--ring);
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
+}
+
+.button:active:not([aria-haspopup]) {
+ transform: translateY(1px);
+}
+
+.button:disabled {
+ pointer-events: none;
+ opacity: 0.5;
+}
+
+.button[aria-invalid='true'] {
+ border-color: var(--destructive);
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
+}
+
+.button svg {
+ flex-shrink: 0;
+ pointer-events: none;
+}
+
+.button svg:not([class*='size-']) {
+ width: 1rem;
+ height: 1rem;
+}
+
+.default {
+ color: var(--primary-foreground);
+ background: var(--primary);
+}
+
+.default:hover {
+ background: color-mix(in oklch, var(--primary) 80%, transparent);
+}
+
+.outline {
+ color: var(--foreground);
+ background: var(--background);
+ border-color: var(--border);
+}
+
+.outline:hover,
+.outline[aria-expanded='true'] {
+ color: var(--foreground);
+ background: var(--muted);
+}
+
+.secondary {
+ color: var(--secondary-foreground);
+ background: var(--secondary);
+}
+
+.secondary:hover {
+ background: color-mix(in oklch, var(--secondary), var(--foreground) 5%);
+}
+
+.ghost {
+ color: var(--foreground);
+ background: transparent;
+}
+
+.ghost:hover,
+.ghost[aria-expanded='true'] {
+ color: var(--foreground);
+ background: var(--muted);
+}
+
+.destructive {
+ color: var(--destructive);
+ background: color-mix(in oklch, var(--destructive) 10%, transparent);
+}
+
+.destructive:hover {
+ background: color-mix(in oklch, var(--destructive) 20%, transparent);
+}
+
+.link {
+ color: var(--primary);
+ background: transparent;
+ text-underline-offset: 0.25rem;
+}
+
+.link:hover {
+ text-decoration: underline;
+}
+
+.sizeDefault,
+.sizeLarge {
+ height: 2rem;
+ gap: 0.375rem;
+ padding-inline: 0.625rem;
+}
+
+.sizeExtraSmall {
+ height: 1.5rem;
+ gap: 0.25rem;
+ padding-inline: 0.5rem;
+ font-size: 0.75rem;
+ border-radius: min(var(--radius-md), 10px);
+}
+
+.sizeSmall {
+ height: 1.75rem;
+ gap: 0.25rem;
+ padding-inline: 0.625rem;
+ font-size: 0.8rem;
+ border-radius: min(var(--radius-md), 12px);
+}
+
+.sizeLarge {
+ height: 2.25rem;
+}
+
+.sizeIcon,
+.sizeIconExtraSmall,
+.sizeIconSmall,
+.sizeIconLarge {
+ padding: 0;
+}
+
+.sizeIcon {
+ width: 2rem;
+ height: 2rem;
+}
+
+.sizeIconExtraSmall {
+ width: 1.5rem;
+ height: 1.5rem;
+ border-radius: min(var(--radius-md), 10px);
+}
+
+.sizeIconSmall {
+ width: 1.75rem;
+ height: 1.75rem;
+ border-radius: min(var(--radius-md), 12px);
+}
+
+.sizeIconLarge {
+ width: 2.25rem;
+ height: 2.25rem;
+}
+
+.sizeExtraSmall svg:not([class*='size-']),
+.sizeIconExtraSmall svg:not([class*='size-']) {
+ width: 0.75rem;
+ height: 0.75rem;
+}
+
+.sizeSmall svg:not([class*='size-']) {
+ width: 0.875rem;
+ height: 0.875rem;
+}
diff --git a/app/components/ui/button.tsx b/app/components/ui/button.tsx
new file mode 100644
index 0000000..9d83f97
--- /dev/null
+++ b/app/components/ui/button.tsx
@@ -0,0 +1,66 @@
+import * as React from 'react'
+import { Slot } from 'radix-ui'
+
+import { cn } from '@/lib/utils'
+import styles from './button.module.css'
+
+type ButtonVariant = 'default' | 'outline' | 'secondary' | 'ghost' | 'destructive' | 'link'
+type ButtonSize = 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'icon-xs' | 'icon-sm' | 'icon-lg'
+
+const variantClasses: Record = {
+ default: styles.default,
+ outline: styles.outline,
+ secondary: styles.secondary,
+ ghost: styles.ghost,
+ destructive: styles.destructive,
+ link: styles.link,
+}
+
+const sizeClasses: Record = {
+ default: styles.sizeDefault,
+ xs: styles.sizeExtraSmall,
+ sm: styles.sizeSmall,
+ lg: styles.sizeLarge,
+ icon: styles.sizeIcon,
+ 'icon-xs': styles.sizeIconExtraSmall,
+ 'icon-sm': styles.sizeIconSmall,
+ 'icon-lg': styles.sizeIconLarge,
+}
+
+function buttonVariants({
+ variant = 'default',
+ size = 'default',
+ className,
+}: {
+ variant?: ButtonVariant | null
+ size?: ButtonSize | null
+ className?: string
+} = {}) {
+ return cn(styles.button, variantClasses[variant ?? 'default'], sizeClasses[size ?? 'default'], className)
+}
+
+function Button({
+ className,
+ variant = 'default',
+ size = 'default',
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> & {
+ variant?: ButtonVariant
+ size?: ButtonSize
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot.Root : 'button'
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/app/components/ui/navigation-menu.module.css b/app/components/ui/navigation-menu.module.css
new file mode 100644
index 0000000..8e1a072
--- /dev/null
+++ b/app/components/ui/navigation-menu.module.css
@@ -0,0 +1,257 @@
+.root {
+ position: relative;
+ display: flex;
+ max-width: max-content;
+ flex: 1;
+ align-items: center;
+ justify-content: center;
+}
+
+.list {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ justify-content: center;
+ gap: 0;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+
+.item {
+ position: relative;
+}
+
+.trigger {
+ display: inline-flex;
+ width: max-content;
+ height: 2.25rem;
+ align-items: center;
+ justify-content: center;
+ padding: 0.375rem 0.625rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ background: transparent;
+ border: 0;
+ border-radius: 0.5rem;
+ outline: none;
+ transition:
+ background-color 150ms ease,
+ box-shadow 150ms ease;
+}
+
+.trigger:hover,
+.trigger:focus,
+.trigger[data-state='open'] {
+ background: var(--muted);
+}
+
+.trigger:focus-visible {
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
+}
+
+.trigger:disabled {
+ pointer-events: none;
+ opacity: 0.5;
+}
+
+.chevron {
+ position: relative;
+ top: 1px;
+ width: 0.75rem;
+ height: 0.75rem;
+ margin-left: 0.25rem;
+ transition: transform 300ms ease;
+}
+
+.trigger[data-state='open'] .chevron {
+ transform: rotate(180deg);
+}
+
+.content {
+ top: 0;
+ left: 0;
+ width: 100%;
+ padding: 0.25rem;
+ animation-duration: 300ms;
+ animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
+}
+
+.content[data-motion^='from-'] {
+ animation-name: navigation-fade-in;
+}
+
+.content[data-motion^='to-'] {
+ animation-name: navigation-fade-out;
+}
+
+.content[data-motion='from-start'] {
+ --navigation-translate: -13rem;
+}
+
+.content[data-motion='from-end'] {
+ --navigation-translate: 13rem;
+}
+
+.content[data-motion='to-start'] {
+ --navigation-translate: -13rem;
+}
+
+.content[data-motion='to-end'] {
+ --navigation-translate: 13rem;
+}
+
+.root[data-viewport='false'] .content {
+ position: absolute;
+ top: 100%;
+ width: auto;
+ margin-top: 0.375rem;
+ overflow: hidden;
+ color: var(--popover-foreground);
+ background: var(--popover);
+ border-radius: 0.5rem;
+ box-shadow: 0 1px 3px oklch(0 0 0 / 18%);
+ outline: 1px solid color-mix(in oklch, var(--foreground) 10%, transparent);
+}
+
+.viewportPosition {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 50;
+ isolation: isolate;
+ display: flex;
+ justify-content: center;
+}
+
+.viewport {
+ position: relative;
+ width: 100%;
+ height: var(--radix-navigation-menu-viewport-height);
+ margin-top: 0.375rem;
+ overflow: hidden;
+ color: var(--popover-foreground);
+ background: var(--popover);
+ border-radius: 0.5rem;
+ box-shadow: 0 1px 3px oklch(0 0 0 / 18%);
+ outline: 1px solid color-mix(in oklch, var(--foreground) 10%, transparent);
+ transform-origin: top center;
+}
+
+.viewport[data-state='open'] {
+ animation: navigation-zoom-in 100ms ease-out;
+}
+
+.viewport[data-state='closed'] {
+ animation: navigation-zoom-out 100ms ease-in;
+}
+
+.link {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem;
+ color: inherit;
+ font-size: 0.875rem;
+ text-decoration: none;
+ border-radius: 0.5rem;
+ outline: none;
+ transition:
+ background-color 150ms ease,
+ box-shadow 150ms ease;
+}
+
+.link:hover,
+.link:focus,
+.link[data-active] {
+ background: var(--muted);
+}
+
+.link:focus-visible {
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
+}
+
+.link svg:not([class*='size-']) {
+ width: 1rem;
+ height: 1rem;
+}
+
+.indicator {
+ top: 100%;
+ z-index: 1;
+ display: flex;
+ height: 0.375rem;
+ align-items: flex-end;
+ justify-content: center;
+ overflow: hidden;
+}
+
+.indicator[data-state='visible'] {
+ animation: navigation-opacity-in 150ms ease-out;
+}
+
+.indicator[data-state='hidden'] {
+ animation: navigation-opacity-out 150ms ease-in;
+}
+
+.indicatorArrow {
+ position: relative;
+ top: 60%;
+ width: 0.5rem;
+ height: 0.5rem;
+ background: var(--border);
+ border-radius: 0.125rem 0 0;
+ box-shadow: 0 4px 6px oklch(0 0 0 / 15%);
+ transform: rotate(45deg);
+}
+
+@media (min-width: 48rem) {
+ .content {
+ position: absolute;
+ width: auto;
+ }
+
+ .viewport {
+ width: var(--radix-navigation-menu-viewport-width);
+ }
+}
+
+@keyframes navigation-fade-in {
+ from {
+ opacity: 0;
+ transform: translateX(var(--navigation-translate, 0));
+ }
+}
+
+@keyframes navigation-fade-out {
+ to {
+ opacity: 0;
+ transform: translateX(var(--navigation-translate, 0));
+ }
+}
+
+@keyframes navigation-zoom-in {
+ from {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+}
+
+@keyframes navigation-zoom-out {
+ to {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+}
+
+@keyframes navigation-opacity-in {
+ from {
+ opacity: 0;
+ }
+}
+
+@keyframes navigation-opacity-out {
+ to {
+ opacity: 0;
+ }
+}
diff --git a/app/components/ui/navigation-menu.tsx b/app/components/ui/navigation-menu.tsx
new file mode 100644
index 0000000..cf000fb
--- /dev/null
+++ b/app/components/ui/navigation-menu.tsx
@@ -0,0 +1,115 @@
+import * as React from 'react'
+import { NavigationMenu as NavigationMenuPrimitive } from 'radix-ui'
+
+import { cn } from '@/lib/utils'
+import { ChevronDownIcon } from 'lucide-react'
+import styles from './navigation-menu.module.css'
+
+function NavigationMenu({
+ className,
+ children,
+ viewport = true,
+ ...props
+}: React.ComponentProps & {
+ viewport?: boolean
+}) {
+ return (
+
+ {children}
+ {viewport && }
+
+ )
+}
+
+function NavigationMenuList({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function NavigationMenuItem({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+const navigationMenuTriggerStyle = () => styles.trigger
+
+function NavigationMenuTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+function NavigationMenuContent({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function NavigationMenuViewport({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function NavigationMenuLink({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function NavigationMenuIndicator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ NavigationMenu,
+ NavigationMenuList,
+ NavigationMenuItem,
+ NavigationMenuContent,
+ NavigationMenuTrigger,
+ NavigationMenuLink,
+ NavigationMenuIndicator,
+ NavigationMenuViewport,
+ navigationMenuTriggerStyle,
+}
diff --git a/app/contact/page.tsx b/app/contact/page.tsx
new file mode 100644
index 0000000..7aa548a
--- /dev/null
+++ b/app/contact/page.tsx
@@ -0,0 +1,10 @@
+import type { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: 'Contact',
+ description: 'Contact Alper.',
+}
+
+export default function ContactPage() {
+ return null
+}
diff --git a/app/data/site.ts b/app/data/site.ts
new file mode 100644
index 0000000..09f905c
--- /dev/null
+++ b/app/data/site.ts
@@ -0,0 +1,26 @@
+type NavItem = {
+ readonly id: number
+ readonly title: string
+ readonly href: string
+ readonly description: string
+}
+
+type SocialLink = {
+ readonly label: string
+ readonly href: string
+ readonly draft?: boolean
+}
+
+export const navigation: readonly NavItem[] = [
+ { id: 0, title: 'Home', href: '/', description: 'Home' },
+ { id: 1, title: 'About', href: '/about', description: 'About Alper' },
+ { id: 2, title: 'Projects', href: '/projects', description: 'Selected projects' },
+ { id: 3, title: 'Blog', href: '/blog', description: 'Writing and notes' },
+ { id: 4, title: 'Contact', href: '/contact', description: 'Contact Alper' },
+]
+
+export const socialLinks: readonly SocialLink[] = [
+ { label: 'Email', href: 'mailto:alper.makcan@gmail.com', draft: true },
+ { label: 'GitHub', href: 'https://github.com/replakcan', draft: true },
+ { label: 'LinkedIn', href: 'https://www.linkedin.com/in/alpermutluakcan/', draft: true },
+]
diff --git a/app/globals.css b/app/globals.css
index 82c14fe..3e7784a 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,30 +1,196 @@
-@import 'tailwindcss';
-
:root {
- --background: #ffffff;
- --foreground: #171717;
+ color-scheme: light;
+ --background: oklch(0.985 0.01 205);
+ --background-deep: oklch(0.955 0.025 218);
+ --surface: oklch(1 0 0 / 84%);
+ --surface-solid: oklch(0.995 0.005 210);
+ --surface-raised: oklch(1 0 0);
+ --foreground: oklch(0.225 0.035 258);
+ --muted-foreground: oklch(0.5 0.035 250);
+ --faint-foreground: oklch(0.625 0.03 245);
+ --primary: oklch(0.61 0.19 224);
+ --primary-strong: oklch(0.51 0.2 236);
+ --primary-foreground: oklch(0.99 0.005 210);
+ --accent: oklch(0.72 0.16 165);
+ --accent-warm: oklch(0.74 0.16 48);
+ --accent-pink: oklch(0.68 0.2 335);
+ --border: oklch(0.86 0.035 225);
+ --border-strong: oklch(0.76 0.065 225);
+ --focus: oklch(0.64 0.2 225);
+ --shadow-color: oklch(0.3 0.08 245 / 16%);
+ --code-background: oklch(0.94 0.025 225);
+ --radius-sm: 0.5rem;
+ --radius-md: 0.85rem;
+ --radius-lg: 1.3rem;
+ --radius-xl: 2rem;
+ --page-width: 74rem;
+ --reading-width: 44rem;
+ --header-height: 4.75rem;
+ --space-page: clamp(1rem, 4vw, 2rem);
+ --section-space: clamp(4.5rem, 10vw, 8rem);
}
-@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
+html.dark {
+ color-scheme: dark;
+ --background: oklch(0.16 0.035 262);
+ --background-deep: oklch(0.115 0.032 266);
+ --surface: oklch(0.205 0.04 260 / 82%);
+ --surface-solid: oklch(0.205 0.04 260);
+ --surface-raised: oklch(0.245 0.045 258);
+ --foreground: oklch(0.965 0.018 210);
+ --muted-foreground: oklch(0.75 0.035 220);
+ --faint-foreground: oklch(0.61 0.035 230);
+ --primary: oklch(0.77 0.15 210);
+ --primary-strong: oklch(0.7 0.18 222);
+ --primary-foreground: oklch(0.13 0.035 260);
+ --accent: oklch(0.78 0.16 165);
+ --accent-warm: oklch(0.82 0.15 65);
+ --accent-pink: oklch(0.76 0.17 335);
+ --border: oklch(0.36 0.05 255);
+ --border-strong: oklch(0.48 0.075 245);
+ --focus: oklch(0.79 0.15 210);
+ --shadow-color: oklch(0.04 0.03 260 / 42%);
+ --code-background: oklch(0.115 0.025 262);
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ border-color: var(--border);
+}
+
+html {
+ min-height: 100%;
+ scroll-behavior: smooth;
+ scrollbar-gutter: stable;
+}
+
+html.lenis {
+ scroll-behavior: auto;
}
-* {
- border: 1px solid purple;
+html.intro-pending,
+html.intro-pending body {
+ overflow: hidden;
}
body {
- background: var(--background);
+ min-width: 20rem;
+ min-height: 100%;
+ margin: 0;
+ overflow-x: hidden;
color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
+ background:
+ radial-gradient(circle at 18% -10%, color-mix(in oklch, var(--primary) 18%, transparent), transparent 30rem),
+ radial-gradient(circle at 88% 12%, color-mix(in oklch, var(--accent-pink) 12%, transparent), transparent 26rem),
+ var(--background);
+}
+
+body,
+button,
+input,
+textarea,
+select {
+ font: inherit;
+}
+
+button,
+input,
+textarea,
+select {
+ color: inherit;
+}
+
+button,
+a {
+ -webkit-tap-highlight-color: transparent;
+}
+
+a {
+ color: inherit;
+}
+
+button {
+ border: 0;
+}
+
+img,
+svg {
+ display: block;
+ max-width: 100%;
+}
+
+h1,
+h2,
+h3,
+p {
+ margin-top: 0;
+}
+
+::selection {
+ color: var(--primary-foreground);
+ background: var(--primary-strong);
+}
+
+::view-transition-old(root),
+::view-transition-new(root) {
+ animation: none;
+ mix-blend-mode: normal;
+}
+
+::view-transition-old(root) {
+ z-index: 1;
+}
+
+::view-transition-new(root) {
+ z-index: 2;
+ animation: theme-wipe-up 650ms cubic-bezier(0.76, 0, 0.24, 1) both;
+}
+
+@keyframes theme-wipe-up {
+ from {
+ clip-path: inset(100% 0 0 0);
+ }
+
+ to {
+ clip-path: inset(0);
+ }
+}
+
+:focus-visible {
+ outline: 3px solid var(--focus);
+ outline-offset: 4px;
+}
+
+.screen-reader-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+
+ ::view-transition-old(root),
+ ::view-transition-new(root) {
+ animation: none !important;
+ }
}
diff --git a/app/intro.ts b/app/intro.ts
new file mode 100644
index 0000000..e0a38fc
--- /dev/null
+++ b/app/intro.ts
@@ -0,0 +1,2 @@
+export const INTRO_STORAGE_KEY = 'alper-intro-v1'
+export const INTRO_EXIT_DURATION_MS = 320
diff --git a/app/layout.module.css b/app/layout.module.css
new file mode 100644
index 0000000..ccb90db
--- /dev/null
+++ b/app/layout.module.css
@@ -0,0 +1,16 @@
+.root {
+ min-height: 100%;
+ font-family: var(--font-geist-sans), sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.body {
+ min-height: 100%;
+}
+
+.appShell {
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ min-height: 100svh;
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index d02129b..3a681ea 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,10 +1,14 @@
-import type { Metadata } from 'next'
+import type { Metadata, Viewport } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import Footer from './components/Footer'
import Header from './components/Header'
+import IntroGate from './components/IntroGate'
import MainWrapper from './components/MainWrapper'
-import Sidebar from './components/Sidebar'
+import SmoothScroll from './components/SmoothScroll'
+import { INTRO_STORAGE_KEY } from './intro'
import './globals.css'
+import styles from './layout.module.css'
+import 'lenis/dist/lenis.css'
const geistSans = Geist({
variable: '--font-geist-sans',
@@ -17,24 +21,74 @@ const geistMono = Geist_Mono({
})
export const metadata: Metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
+ metadataBase: new URL('https://alper.dev'),
+ title: {
+ default: 'Alper — Software Engineer',
+ template: '%s · Alper',
+ },
+ description: 'Selected software projects, engineering notes, and thoughtful interface experiments from Alper.',
+ openGraph: {
+ title: 'Alper — Software Engineer',
+ description: 'Selected software projects, engineering notes, and thoughtful interface experiments.',
+ type: 'website',
+ url: 'https://alper.dev',
+ siteName: 'alper.dev',
+ },
}
+export const viewport: Viewport = {
+ colorScheme: 'dark light',
+ themeColor: [
+ { media: '(prefers-color-scheme: light)', color: '#f4fbfc' },
+ { media: '(prefers-color-scheme: dark)', color: '#101523' },
+ ],
+}
+
+const themeScript = `
+ (() => {
+ try {
+ const storedTheme = localStorage.getItem('theme');
+ const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
+ const theme = storedTheme === 'light' || storedTheme === 'dark'
+ ? storedTheme
+ : prefersDark ? 'dark' : 'light';
+
+ document.documentElement.classList.toggle('dark', theme === 'dark');
+ document.documentElement.dataset.theme = theme;
+
+ if (localStorage.getItem('${INTRO_STORAGE_KEY}') !== 'complete') {
+ document.documentElement.classList.add('intro-pending');
+ }
+ } catch {
+ document.documentElement.classList.add('dark');
+ document.documentElement.dataset.theme = 'dark';
+ document.documentElement.classList.add('intro-pending');
+ }
+ })();
+`
+
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
-
-
-
-
- {children}
-
-
-
+
+
+
+
+
+
+
+ {children}
+
+
+
+
)
diff --git a/app/loading.tsx b/app/loading.tsx
new file mode 100644
index 0000000..8b05fd1
--- /dev/null
+++ b/app/loading.tsx
@@ -0,0 +1,5 @@
+import AlperLoader from './components/AlperLoader'
+
+export default function Loading() {
+ return
+}
diff --git a/app/page.tsx b/app/page.tsx
index 35d3c07..ad309dc 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,3 +1,152 @@
+import type { Metadata } from 'next'
+import Link from 'next/link'
+import AnglerSocialReveal from './components/AnglerSocialReveal'
+import LightRays from './components/LightRays'
+import ParallaxScene from './components/ParallaxScene'
+import PageContainer from './components/PageContainer'
+import ScrollReveal from './components/ScrollReveal'
+import styles from './Home.module.css'
+
+export const metadata: Metadata = {
+ title: 'Home',
+ description: "Alper Makcan's personal website.",
+}
+
+const featuredProjects = [
+ {
+ title: 'Personal site',
+ description: 'A Next.js site with a layered ocean scene, simple navigation, and room for project notes.',
+ tags: ['Next.js', 'CSS Modules', 'Interaction'],
+ },
+ {
+ title: 'Interface experiments',
+ description: 'Small UI ideas focused on readable states, useful motion, and maintainable component structure.',
+ tags: ['React', 'Design systems', 'Frontend'],
+ },
+ {
+ title: 'Backend practice',
+ description: 'API and deployment work for learning how product features hold together beyond the screen.',
+ tags: ['APIs', 'Cloud', 'Tooling'],
+ },
+]
+
export default function Home() {
- return Coming very very s00n
+ return (
+ <>
+
+
+
+
+
+
+
About
+
+ Software, interfaces, and the parts between them.
+
+
+
+
+ I am Alper Makcan, a software engineer based in Istanbul. I work mostly on web interfaces and product
+ systems, with care for clear code, steady delivery, and screens that are easy to use.
+
+
+ This site collects the projects, notes, and contact points I want to keep in one place while the rest of
+ the site grows.
+
+
+
+
+
+
+
+
+
+
Projects
+
A few things in progress.
+
+
+ View projects
+
+
+
+
+ {featuredProjects.map((project) => (
+
+ {project.title}
+ {project.description}
+
+ {project.tags.map((tag) => (
+ {tag}
+ ))}
+
+
+ ))}
+
+
+
+
+
+
+
+
Blog
+
Notes will live here.
+
+
+
+ Blog data is not wired up yet. Once posts exist, this section can show the latest notes from the blog
+ without changing the page structure.
+
+
+ Visit blog
+
+
+
+
+
+
+
+ >
+ )
}
diff --git a/app/projects/page.tsx b/app/projects/page.tsx
new file mode 100644
index 0000000..8b04dae
--- /dev/null
+++ b/app/projects/page.tsx
@@ -0,0 +1,10 @@
+import type { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: 'Projects',
+ description: 'Projects by Alper.',
+}
+
+export default function ProjectsPage() {
+ return null
+}
diff --git a/app/routes.ts b/app/routes.ts
new file mode 100644
index 0000000..226e188
--- /dev/null
+++ b/app/routes.ts
@@ -0,0 +1,3 @@
+import { navigation } from './data/site'
+
+export default navigation
diff --git a/docs/architecture.md b/docs/architecture.md
index 3b426fc..534b213 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -2,10 +2,12 @@
## Overview
-This project is an MVP personal website built as a static site. The application is authored with Next.js, React, TypeScript, and Tailwind CSS, then exported to static files and served from AWS.
+This project is an MVP personal website built as a static site. The application is authored with Next.js, React, TypeScript, and plain CSS Modules, then exported to static files and served from AWS.
The architecture intentionally avoids a runtime backend, database, authentication system, CMS, or server-rendered request handling. The MVP content is small, mostly public, and can be generated at build time.
+The Blog route is a static preview index backed by repository-authored data. Individual post routes, a CMS, and runtime content APIs are intentionally deferred.
+
## Runtime Architecture
At runtime, visitors access the site through CloudFront. CloudFront serves cached static assets and fetches missing files from a private S3 bucket through Origin Access Control (OAC).
diff --git a/docs/mvp.md b/docs/mvp.md
index 8b99b30..12b0c1b 100644
--- a/docs/mvp.md
+++ b/docs/mvp.md
@@ -18,11 +18,12 @@ The MVP is a static website with a small, focused set of pages and content areas
- About section or page covering background, interests, and current focus.
- Projects section or page listing selected work with concise descriptions and relevant links.
- Contact section or page with external profile links and a contact path.
+- Static Blog index with structured draft previews for future writing.
- Shared layout, styling, and reusable UI structure for future content.
## Out of Scope
-- Blog, CMS, admin panel, or dynamic content editing.
+- CMS, admin panel, dynamic content editing, or individual blog post publishing.
- Authentication, user accounts, comments, or form submissions that require a backend.
- API routes, Server Actions, runtime server-side rendering, or request-time personalization.
- Full design system documentation or long-term roadmap.
@@ -32,7 +33,7 @@ The MVP is a static website with a small, focused set of pages and content areas
- Next.js App Router with static export enabled.
- React and TypeScript for application code.
-- Tailwind CSS for styling.
+- Plain CSS with colocated CSS Modules for styling.
- ESLint, Prettier, TypeScript checks, and GitHub Actions for quality checks.
- Static assets served from the exported build output.
diff --git a/lib/utils.ts b/lib/utils.ts
new file mode 100644
index 0000000..40ed99b
--- /dev/null
+++ b/lib/utils.ts
@@ -0,0 +1,5 @@
+import { clsx, type ClassValue } from 'clsx'
+
+export function cn(...inputs: ClassValue[]) {
+ return clsx(inputs)
+}
diff --git a/package-lock.json b/package-lock.json
index 7434565..22f0d39 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,12 +8,17 @@
"name": "alper-dev",
"version": "0.1.0",
"dependencies": {
+ "clsx": "^2.1.1",
+ "gsap": "^3.15.0",
+ "lenis": "^1.3.24",
+ "lucide-react": "^1.21.0",
"next": "16.2.7",
+ "ogl": "^1.0.11",
+ "radix-ui": "^1.6.0",
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
- "@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
@@ -22,23 +27,9 @@
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"prettier": "^3.8.3",
- "tailwindcss": "^4",
"typescript": "^5"
}
},
- "node_modules/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@babel/code-frame": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
@@ -456,6 +447,44 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz",
+ "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
+ "license": "MIT"
+ },
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
@@ -1249,291 +1278,1513 @@
"node": ">=12.4.0"
}
},
- "node_modules/@rtsao/scc": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
- "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
- "dev": true,
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz",
+ "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==",
"license": "MIT"
},
- "node_modules/@swc/helpers": {
- "version": "0.5.15",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
- "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz",
+ "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-accessible-icon": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.10.tgz",
+ "integrity": "sha512-TraSwZUqTcVbiDV2/RXzAXC7aeVVXchq0daPFZE7zAxYFaMzjOUggLOfQH9KFLgRizuwVKZO/crveV1eeO3/ZQ==",
+ "license": "MIT",
"dependencies": {
- "tslib": "^2.8.0"
+ "@radix-ui/react-visually-hidden": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/node": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz",
- "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==",
- "dev": true,
+ "node_modules/@radix-ui/react-accordion": {
+ "version": "1.2.14",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.14.tgz",
+ "integrity": "sha512-iE8YB9nmTBH8zd73ofBISZ8JCzgMoMkATJr7qDwa6u5F1+7mTM81V6fa71jgZ65rpjVpecDf1vSnwIFP9Ly1zw==",
"license": "MIT",
"dependencies": {
- "@jridgewell/remapping": "^2.3.5",
- "enhanced-resolve": "^5.21.0",
- "jiti": "^2.6.1",
- "lightningcss": "1.32.0",
- "magic-string": "^0.30.21",
- "source-map-js": "^1.2.1",
- "tailwindcss": "4.3.0"
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-collapsible": "1.1.14",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz",
- "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==",
- "dev": true,
+ "node_modules/@radix-ui/react-alert-dialog": {
+ "version": "1.1.17",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.17.tgz",
+ "integrity": "sha512-563ygGeyWPrxyVCNp7OV4rE2aIXhFPknpFyo4wbDlcyMMPZ6ySh+zC5WTvY0ZFLgPTg/QB6tA8PyDQyJ2b4cPg==",
"license": "MIT",
- "engines": {
- "node": ">= 20"
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-dialog": "1.1.17",
+ "@radix-ui/react-primitive": "2.1.6"
},
- "optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.3.0",
- "@tailwindcss/oxide-darwin-arm64": "4.3.0",
- "@tailwindcss/oxide-darwin-x64": "4.3.0",
- "@tailwindcss/oxide-freebsd-x64": "4.3.0",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0",
- "@tailwindcss/oxide-linux-arm64-musl": "4.3.0",
- "@tailwindcss/oxide-linux-x64-gnu": "4.3.0",
- "@tailwindcss/oxide-linux-x64-musl": "4.3.0",
- "@tailwindcss/oxide-wasm32-wasi": "4.3.0",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0",
- "@tailwindcss/oxide-win32-x64-msvc": "4.3.0"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz",
- "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.10.tgz",
+ "integrity": "sha512-j2VTDz1vgCsmuG0k5lBfOcM8n5JPFqZBcMryasFjHYMhwxYL5SRUV5lMSUpRdNtw3D/Sv8pzJtrlAgkssYSsQQ==",
"license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 20"
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz",
- "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-aspect-ratio": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.10.tgz",
+ "integrity": "sha512-kbI7NrqhDeuytYrq7JjAsoXczvL8wgj2tc1MyaYWm+50bMKHCHQtVWCryslx4cCpmCTTkBcwQckE4CmmGV2haQ==",
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 20"
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz",
- "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-avatar": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.0.tgz",
+ "integrity": "sha512-am/CwltXtmtdtP+5FbYblYDnMa/zuKcMJP1i3/SJMDXXfj2mG+BTqLH2wucqeyyiQMursUtg/5cK+Nh2pCaSOA==",
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 20"
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-is-hydrated": "0.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz",
- "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.5.tgz",
+ "integrity": "sha512-pREzrmNnVwGvYaBoM64huTRK7B3lrTRuwj8A9nwhPiEtMb+yudiWh6zWAqEtP0Dzd5+iBa1Ki7V1pCxV8ExMdA==",
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 20"
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz",
- "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-collapsible": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.14.tgz",
+ "integrity": "sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz",
- "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.10.tgz",
+ "integrity": "sha512-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-slot": "1.3.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz",
- "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz",
+ "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz",
- "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz",
+ "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz",
- "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-context-menu": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.1.tgz",
+ "integrity": "sha512-XbrxS68W5dyiE4fAb96yvJwSVU5x66B20A99sD5Mk3xSWK/LqeOnx6TZnim1KieMjXS/CTFq8reOAjWxas2G8Q==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-menu": "2.1.18",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz",
- "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==",
- "bundleDependencies": [
- "@napi-rs/wasm-runtime",
- "@emnapi/core",
- "@emnapi/runtime",
- "@tybys/wasm-util",
- "@emnapi/wasi-threads",
- "tslib"
- ],
- "cpu": [
- "wasm32"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.17",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.17.tgz",
+ "integrity": "sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.13",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.10",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-portal": "1.1.12",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz",
+ "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.13.tgz",
+ "integrity": "sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-escape-keydown": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.18.tgz",
+ "integrity": "sha512-PZGV82gFk0WltDRI//SsG28ZIjlo9ANTmoNYg0jLNzXXiDsAy5PkOOYQaVD1pPxY6t7gxffb1QMD6qaUvsBZdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-menu": "2.1.18",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz",
+ "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.10.tgz",
+ "integrity": "sha512-Fas/lXQqhVvqwAb64s5RFeHiHYElZ6SUQbZaNd6EkfhP/Al7wTIQ9WIR4QVX475tlu5yFCEdDcJH6/UwsZjMWw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-callback-ref": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-form": {
+ "version": "0.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.10.tgz",
+ "integrity": "sha512-1NfuvctVtX4sU3Mmq/IdrR8UunxiCMiVg3A5UENKhFzxUBeOyaQQ+lmaQaV7Tc8cqvBKsJL3/KGBsixK0D8WFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-label": "2.1.10",
+ "@radix-ui/react-primitive": "2.1.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-hover-card": {
+ "version": "1.1.17",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.17.tgz",
+ "integrity": "sha512-GjZQIEANVkuuWeztlKz6QEHe31ZX2iDfHzcTMCQVZXC0JyQrgfKWSC+LOOEw6aVV64zyjzobIzSA4AU4eKWrHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.13",
+ "@radix-ui/react-popper": "1.3.1",
+ "@radix-ui/react-portal": "1.1.12",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz",
+ "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.10.tgz",
+ "integrity": "sha512-ib0zvq2ZsAqKm5tRnqGJn3vOxSgIts5ToxsXT0q1S/GfLD1Zj7UOEnkw8u2w6sRmn47djpQWuSU1DCL1R29/yw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.18.tgz",
+ "integrity": "sha512-lj8Rxjtn6zJq1oSbE/uDtAwCbB9BnxgHD+8MwJMuTh6u1dPamYhW9iuELr/Z8d0D/UysFblYYHeBPwi7T4k0YQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.13",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.10",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-popper": "1.3.1",
+ "@radix-ui/react-portal": "1.1.12",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-roving-focus": "1.1.13",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menubar": {
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.18.tgz",
+ "integrity": "sha512-hX7EGx/oFq6DPY27GQuP/2wP48GHf5LG6r06VgNJlG+znmDS8OfopZcRcGly3L4lsB9FqpmLx6JQSE9P3BUpyw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-menu": "2.1.18",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-roving-focus": "1.1.13",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-navigation-menu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.16.tgz",
+ "integrity": "sha512-nJ0SkrSQgudyYhMiYeHA1ayLVuduEJCFLan1RZZN7c9kqzzCFLaU9kuy81uNtqzweM9YaQPgWzxi9MwQ9jZ04g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.13",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-visually-hidden": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-one-time-password-field": {
+ "version": "0.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.10.tgz",
+ "integrity": "sha512-GHkcJ+WVj91At+OvUVTD4R3W0/wxw9t/sG5xFUBYXaCbtWiooZX5Md376QjJqgH4VsVyXrbVNHO2O4NYcmjfVg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.2",
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-roving-focus": "1.1.13",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-effect-event": "0.0.3",
+ "@radix-ui/react-use-is-hydrated": "0.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-password-toggle-field": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.5.tgz",
+ "integrity": "sha512-fVuA82u0b/fClpbEJv8yp1nU9eSvoSEOERsU/hhf3FXGPIvkmE7oEaHEu8poowoXO39/Va7zq2E0TUcYr1dBRg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-effect-event": "0.0.3",
+ "@radix-ui/react-use-is-hydrated": "0.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.17",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.17.tgz",
+ "integrity": "sha512-/YSAOdJ7YJvdn7bn5sdSx2egW+SKY+u7O5RyAVs94Ymrg2fg5QTSFPMRkzvhGyFuE4/qsmPBdrwYoZMZh/4f+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.13",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.10",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-popper": "1.3.1",
+ "@radix-ui/react-portal": "1.1.12",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.1.tgz",
+ "integrity": "sha512-bhnq/0DEPTi2lsOD3J5rTL65qUKHbKbhqHsmN9TMiclSXpipi651ooUKPPp6G5lF/WiHBdn1s0Wuqsn+myVAvw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-rect": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2",
+ "@radix-ui/rect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.12.tgz",
+ "integrity": "sha512-m309havGzsjLHHaIX50G5PlvRs3xkgPCsGk/5PTvYm8D5q33yG0J7w/712PTOhid7NTaFETtnSXjngHQavvhVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz",
+ "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.6.tgz",
+ "integrity": "sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.3.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.10.tgz",
+ "integrity": "sha512-JYzEg60lk79PwKM27WZyKd7PW8O4OM5jOaFfRPfOyeXmMw7tLJh5kSj+CEjVTehszuwml/AdCzPGMXBTGf4BBw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.1.tgz",
+ "integrity": "sha512-/SSxZdKEo2Eo29FFRKd06EfFDYp8HryKg0WYg7QLXaydPzl52YfSvCH2a3QDBRdtcuwACroJT8UVjQVgOJ7P9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-roving-focus": "1.1.13",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.13.tgz",
+ "integrity": "sha512-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.12.tgz",
+ "integrity": "sha512-xuafVzQiTCLsyEjakowTdG3OgTXsmO7IdCiO77otIa+z44xoLNs9Do5eg7POFumIOCjtG6djfm6RKUKpUa/csA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.2",
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.1.tgz",
+ "integrity": "sha512-w6eDvY78LE9ZUiNnXCA1QVK8RYN7k9galFv09kjVydJqBAgHd7Y9A6h0UJ/6DCZNGZMZrB2ohcSW1Bo9d8+wWA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.2",
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.13",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.10",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-popper": "1.3.1",
+ "@radix-ui/react-portal": "1.1.12",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-visually-hidden": "1.2.6",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.10.tgz",
+ "integrity": "sha512-Y6K6jLQCVfCnTL2MEtGxDLffkhNfEfHsEg3Wa8JU+IWdn3EWbLXd3OuOfQRN7p/W/cUce1WyTk3QeuAoDBzN9g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slider": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.1.tgz",
+ "integrity": "sha512-r91WSpQucNGFKAIxT8FT0H0zyjd5tJlqObLp7LOMV4z49KoDCwjy01w3vDOU4e1wxhF9IgjYco7SB6byOW7Buw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.2",
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz",
+ "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.1.tgz",
+ "integrity": "sha512-55bQtCnOB0BohomSHi6qvQXpJEEqUGDm6hRrM0Bph5OXwhSegqkd8IqgBAQkM1IlgUlWZIxpxRcpOEfRIgimyw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.15.tgz",
+ "integrity": "sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-roving-focus": "1.1.13",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.17",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.17.tgz",
+ "integrity": "sha512-uL4kyyWy000pPL43fGGCV5qT6ZchCWEQZOSlkYiPwPt8Hy1iW38RjeptIvz1/SZesrW6Vn58Ct3sV7tfEfiAbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.13",
+ "@radix-ui/react-portal": "1.1.12",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-visually-hidden": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.12.tgz",
+ "integrity": "sha512-AsAVsYNZIlRBsci7BhE+QyQeKd1h6TffJYt+lF0QQkd5OpQ3klfIByPsCb4G0h/Fq6PJwh1FYNluzBFYzhk4+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle-group": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.13.tgz",
+ "integrity": "sha512-Xb9PLtlvU66F36LiKba6dFswu6V2mDkgidO4fNSbQHQwmZ9ObxMIO17MN/LJ4aWJecVuSVLAHPZjyeMzJrgeiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-roving-focus": "1.1.13",
+ "@radix-ui/react-toggle": "1.1.12",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toolbar": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.13.tgz",
+ "integrity": "sha512-Za1l4f6fzTkGgz/iynAMN8iaqiKff2wm2/QwiLmHPtDQreWEBrvSimgQFIekxMUdRPhILM7xdIXxuS/o/DGZag==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-roving-focus": "1.1.13",
+ "@radix-ui/react-separator": "1.1.10",
+ "@radix-ui/react-toggle-group": "1.1.13"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.10.tgz",
+ "integrity": "sha512-NlNe8D0dWEpVfXFli90IO6X07Josx/b1iu98tDnx9Xv0HT4wLIL+m2VOheMHhK7qbp2HoTBqALEFzGyZs/levw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.13",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-popper": "1.3.1",
+ "@radix-ui/react-portal": "1.1.12",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-visually-hidden": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz",
+ "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz",
+ "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz",
+ "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.2.tgz",
+ "integrity": "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-is-hydrated": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz",
+ "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz",
+ "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz",
+ "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz",
+ "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==",
"license": "MIT",
- "optional": true,
"dependencies": {
- "@emnapi/core": "^1.10.0",
- "@emnapi/runtime": "^1.10.0",
- "@emnapi/wasi-threads": "^1.2.1",
- "@napi-rs/wasm-runtime": "^1.1.4",
- "@tybys/wasm-util": "^0.10.1",
- "tslib": "^2.8.1"
+ "@radix-ui/react-use-layout-effect": "1.1.2"
},
- "engines": {
- "node": ">=14.0.0"
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz",
- "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.6.tgz",
+ "integrity": "sha512-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ==",
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 20"
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz",
- "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 20"
- }
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz",
+ "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==",
+ "license": "MIT"
},
- "node_modules/@tailwindcss/postcss": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz",
- "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==",
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
"dev": true,
- "license": "MIT",
+ "license": "MIT"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
"dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "@tailwindcss/node": "4.3.0",
- "@tailwindcss/oxide": "4.3.0",
- "postcss": "^8.5.10",
- "tailwindcss": "4.3.0"
+ "tslib": "^2.8.0"
}
},
"node_modules/@tybys/wasm-util": {
@@ -1582,7 +2833,7 @@
"version": "19.2.16",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz",
"integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -1592,7 +2843,7 @@
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.2.0"
@@ -2298,6 +3549,18 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
@@ -2734,6 +3997,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2804,7 +4076,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
@@ -2933,12 +4205,18 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "devOptional": true,
"license": "Apache-2.0",
+ "optional": true,
"engines": {
"node": ">=8"
}
},
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
"node_modules/doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -2981,20 +4259,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/enhanced-resolve": {
- "version": "5.22.2",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.2.tgz",
- "integrity": "sha512-0rxICaFZ7NQho/sHely2bvOPRP0Eu2B0NZ9zM54YvRvWMn7jfz3DmnOZDR9LlXDdDcqntAVc6Hfy4gr/tdH/Ag==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.3.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
@@ -3861,6 +5125,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -3962,12 +5235,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
+ "node_modules/gsap": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz",
+ "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
+ "license": "Standard 'no charge' license: https://gsap.com/standard-license."
},
"node_modules/has-bigints": {
"version": "1.1.0",
@@ -4617,6 +5889,8 @@
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
"dev": true,
"license": "MIT",
+ "optional": true,
+ "peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@@ -4690,333 +5964,103 @@
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/jsx-ast-utils": {
- "version": "3.3.5",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
- "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-includes": "^3.1.6",
- "array.prototype.flat": "^1.3.1",
- "object.assign": "^4.1.4",
- "object.values": "^1.1.6"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/language-subtag-registry": {
- "version": "0.3.23",
- "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
- "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/language-tags": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
- "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "language-subtag-registry": "^0.3.20"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/lightningcss": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
- "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
- "dev": true,
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-android-arm64": "1.32.0",
- "lightningcss-darwin-arm64": "1.32.0",
- "lightningcss-darwin-x64": "1.32.0",
- "lightningcss-freebsd-x64": "1.32.0",
- "lightningcss-linux-arm-gnueabihf": "1.32.0",
- "lightningcss-linux-arm64-gnu": "1.32.0",
- "lightningcss-linux-arm64-musl": "1.32.0",
- "lightningcss-linux-x64-gnu": "1.32.0",
- "lightningcss-linux-x64-musl": "1.32.0",
- "lightningcss-win32-arm64-msvc": "1.32.0",
- "lightningcss-win32-x64-msvc": "1.32.0"
- }
- },
- "node_modules/lightningcss-android-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
- "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
- "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
- "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
- "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
- "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "engines": {
+ "node": ">=6"
}
},
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
- "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
"dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "engines": {
+ "node": ">=4.0"
}
},
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
- "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
}
},
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
- "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/language-subtag-registry": {
+ "version": "0.3.23",
+ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
+ "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
"dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
+ "license": "CC0-1.0"
},
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
- "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
- "cpu": [
- "x64"
- ],
+ "node_modules/language-tags": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
+ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
"dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
+ "license": "MIT",
+ "dependencies": {
+ "language-subtag-registry": "^0.3.20"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "engines": {
+ "node": ">=0.10"
}
},
- "node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
- "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
+ "node_modules/lenis": {
+ "version": "1.3.24",
+ "resolved": "https://registry.npmjs.org/lenis/-/lenis-1.3.24.tgz",
+ "integrity": "sha512-UdWz0Bx/Z9dDgRdWe+N1dPmPWXGCF4VOTX3mugyX7lRpdjXG6ACfLI/cVmDOEMeQXf+j5Km/uKrvvf1X0k+u6g==",
+ "license": "MIT",
+ "workspaces": [
+ "packages/*",
+ "playground",
+ "playground/*"
],
- "engines": {
- "node": ">= 12.0.0"
- },
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "type": "github",
+ "url": "https://github.com/sponsors/darkroomengineering"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": ">=3.0.0",
+ "react": ">=17.0.0",
+ "vue": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "vue": {
+ "optional": true
+ }
}
},
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
- "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
- "cpu": [
- "x64"
- ],
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "engines": {
+ "node": ">= 0.8.0"
}
},
"node_modules/lint-staged": {
@@ -5170,14 +6214,13 @@
"yallist": "^3.0.2"
}
},
- "node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
+ "node_modules/lucide-react": {
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.21.0.tgz",
+ "integrity": "sha512-reEZMXq8Qdd5jg5XYkQ5TR1fB/GiQ7ih4vcrthYDtgjSDwh0i6/YLiGjsWsIwgN49gpAnd4J2elSNzncMEEUUQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/math-intrinsics": {
@@ -5531,6 +6574,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/ogl": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ogl/-/ogl-1.0.11.tgz",
+ "integrity": "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==",
+ "license": "Unlicense"
+ },
"node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
@@ -5684,35 +6733,6 @@
"node": ">= 0.4"
}
},
- "node_modules/postcss": {
- "version": "8.5.15",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
- "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.12",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5782,6 +6802,83 @@
],
"license": "MIT"
},
+ "node_modules/radix-ui": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.0.tgz",
+ "integrity": "sha512-EUEC70O03EgxWMP5aoqfBZ6iLC5bczFagGy7zhSYRt8o5DP7IWNiP3ywetse3L9b8843ExB0OGWZvgbYVJuNeg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.4",
+ "@radix-ui/react-accessible-icon": "1.1.10",
+ "@radix-ui/react-accordion": "1.2.14",
+ "@radix-ui/react-alert-dialog": "1.1.17",
+ "@radix-ui/react-arrow": "1.1.10",
+ "@radix-ui/react-aspect-ratio": "1.1.10",
+ "@radix-ui/react-avatar": "1.2.0",
+ "@radix-ui/react-checkbox": "1.3.5",
+ "@radix-ui/react-collapsible": "1.1.14",
+ "@radix-ui/react-collection": "1.1.10",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.1.4",
+ "@radix-ui/react-context-menu": "2.3.1",
+ "@radix-ui/react-dialog": "1.1.17",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.13",
+ "@radix-ui/react-dropdown-menu": "2.1.18",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.10",
+ "@radix-ui/react-form": "0.1.10",
+ "@radix-ui/react-hover-card": "1.1.17",
+ "@radix-ui/react-label": "2.1.10",
+ "@radix-ui/react-menu": "2.1.18",
+ "@radix-ui/react-menubar": "1.1.18",
+ "@radix-ui/react-navigation-menu": "1.2.16",
+ "@radix-ui/react-one-time-password-field": "0.1.10",
+ "@radix-ui/react-password-toggle-field": "0.1.5",
+ "@radix-ui/react-popover": "1.1.17",
+ "@radix-ui/react-popper": "1.3.1",
+ "@radix-ui/react-portal": "1.1.12",
+ "@radix-ui/react-presence": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.6",
+ "@radix-ui/react-progress": "1.1.10",
+ "@radix-ui/react-radio-group": "1.4.1",
+ "@radix-ui/react-roving-focus": "1.1.13",
+ "@radix-ui/react-scroll-area": "1.2.12",
+ "@radix-ui/react-select": "2.3.1",
+ "@radix-ui/react-separator": "1.1.10",
+ "@radix-ui/react-slider": "1.4.1",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-switch": "1.3.1",
+ "@radix-ui/react-tabs": "1.1.15",
+ "@radix-ui/react-toast": "1.2.17",
+ "@radix-ui/react-toggle": "1.1.12",
+ "@radix-ui/react-toggle-group": "1.1.13",
+ "@radix-ui/react-toolbar": "1.1.13",
+ "@radix-ui/react-tooltip": "1.2.10",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-effect-event": "0.0.3",
+ "@radix-ui/react-use-escape-keydown": "1.1.2",
+ "@radix-ui/react-use-is-hydrated": "0.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2",
+ "@radix-ui/react-visually-hidden": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
@@ -5810,6 +6907,75 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -6535,27 +7701,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/tailwindcss": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
- "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tapable": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
- "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
"node_modules/tinyexec": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
@@ -6906,6 +8051,49 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 214cc76..47e9126 100644
--- a/package.json
+++ b/package.json
@@ -14,12 +14,17 @@
"format": "prettier --write ."
},
"dependencies": {
+ "clsx": "^2.1.1",
+ "gsap": "^3.15.0",
+ "lenis": "^1.3.24",
+ "lucide-react": "^1.21.0",
"next": "16.2.7",
+ "ogl": "^1.0.11",
+ "radix-ui": "^1.6.0",
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
- "@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
@@ -28,7 +33,6 @@
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"prettier": "^3.8.3",
- "tailwindcss": "^4",
"typescript": "^5"
},
"lint-staged": {
diff --git a/postcss.config.mjs b/postcss.config.mjs
deleted file mode 100644
index ae85b2f..0000000
--- a/postcss.config.mjs
+++ /dev/null
@@ -1,7 +0,0 @@
-const config = {
- plugins: {
- '@tailwindcss/postcss': {},
- },
-}
-
-export default config
diff --git a/public/contact/angler_fish.jpeg b/public/contact/angler_fish.jpeg
new file mode 100644
index 0000000..a8b2e72
Binary files /dev/null and b/public/contact/angler_fish.jpeg differ
diff --git a/public/file.svg b/public/file.svg
deleted file mode 100644
index 004145c..0000000
--- a/public/file.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/globe.svg b/public/globe.svg
deleted file mode 100644
index 567f17b..0000000
--- a/public/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/images/parallax/layer-1.jpeg b/public/images/parallax/layer-1.jpeg
new file mode 100644
index 0000000..1466917
Binary files /dev/null and b/public/images/parallax/layer-1.jpeg differ
diff --git a/public/images/parallax/layer-2.svg b/public/images/parallax/layer-2.svg
new file mode 100644
index 0000000..2d35d97
--- /dev/null
+++ b/public/images/parallax/layer-2.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/public/images/parallax/layer-3.svg b/public/images/parallax/layer-3.svg
new file mode 100644
index 0000000..e134871
--- /dev/null
+++ b/public/images/parallax/layer-3.svg
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/public/next.svg b/public/next.svg
deleted file mode 100644
index 5174b28..0000000
--- a/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
deleted file mode 100644
index 7705396..0000000
--- a/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/window.svg b/public/window.svg
deleted file mode 100644
index b2b2a44..0000000
--- a/public/window.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file