diff --git a/apps/khix/next.config.js b/apps/khix/next.config.js index 722f52089..2d8b0083f 100644 --- a/apps/khix/next.config.js +++ b/apps/khix/next.config.js @@ -1,16 +1,7 @@ /** @type {import("next").NextConfig} */ const config = { reactStrictMode: true, - images: { - formats: ["image/avif", "image/webp"], - minimumCacheTTL: 60 * 60 * 24 * 30, - remotePatterns: [ - { - protocol: "https", - hostname: "assets.knighthacks.org", - }, - ], - }, + allowedDevOrigins: ["127.0.0.1"], /** We already do linting and typechecking as separate tasks in CI */ typescript: { ignoreBuildErrors: true }, diff --git a/apps/khix/package.json b/apps/khix/package.json index 764488ec8..d5977394a 100644 --- a/apps/khix/package.json +++ b/apps/khix/package.json @@ -15,12 +15,12 @@ "with-env": "dotenv -e ../../.env --" }, "dependencies": { + "@gsap/react": "^2.1.2", "framer-motion": "^12.34.3", - "lucide-react": "^0.575.0", + "gsap": "^3.14.2", "next": "^16.2.7", "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-icons": "^5.5.0" + "react-dom": "^19.2.4" }, "devDependencies": { "@forge/eslint-config": "workspace:*", @@ -35,7 +35,6 @@ "eslint": "catalog:", "prettier": "catalog:", "tailwindcss": "catalog:", - "tw-animate-css": "^1.4.0", "typescript": "catalog:" }, "prettier": "@forge/prettier-config" diff --git a/apps/khix/public/KHIXHero/optimized/1_front.webp b/apps/khix/public/KHIXHero/optimized/1_front.webp new file mode 100644 index 000000000..17718d112 Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/1_front.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/2_tk.webp b/apps/khix/public/KHIXHero/optimized/2_tk.webp new file mode 100644 index 000000000..8170f3162 Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/2_tk.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/3_tree_and_waterfall.webp b/apps/khix/public/KHIXHero/optimized/3_tree_and_waterfall.webp new file mode 100644 index 000000000..9d3544b26 Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/3_tree_and_waterfall.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/4_bg.webp b/apps/khix/public/KHIXHero/optimized/4_bg.webp new file mode 100644 index 000000000..2a567505d Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/4_bg.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/5_bg.webp b/apps/khix/public/KHIXHero/optimized/5_bg.webp new file mode 100644 index 000000000..fdd74e3da Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/5_bg.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/6_bg.webp b/apps/khix/public/KHIXHero/optimized/6_bg.webp new file mode 100644 index 000000000..4932c84e5 Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/6_bg.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/7_bg.webp b/apps/khix/public/KHIXHero/optimized/7_bg.webp new file mode 100644 index 000000000..7b32ccf8b Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/7_bg.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/pond-frames/pond-animated.webp b/apps/khix/public/KHIXHero/optimized/pond-frames/pond-animated.webp new file mode 100644 index 000000000..575bd097b Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/pond-frames/pond-animated.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/pond.webp b/apps/khix/public/KHIXHero/optimized/pond.webp new file mode 100644 index 000000000..8bd04b3a2 Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/pond.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/tk_glow_alone.webp b/apps/khix/public/KHIXHero/optimized/tk_glow_alone.webp new file mode 100644 index 000000000..9fa1e1043 Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/tk_glow_alone.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/waterfall-frames/waterfall-animated.webp b/apps/khix/public/KHIXHero/optimized/waterfall-frames/waterfall-animated.webp new file mode 100644 index 000000000..fa0ce93db Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/waterfall-frames/waterfall-animated.webp differ diff --git a/apps/khix/public/KHIXHero/optimized/waterfall.webp b/apps/khix/public/KHIXHero/optimized/waterfall.webp new file mode 100644 index 000000000..391c25972 Binary files /dev/null and b/apps/khix/public/KHIXHero/optimized/waterfall.webp differ diff --git a/apps/khix/public/assets/FAQ cave ceiling.webp b/apps/khix/public/assets/FAQ cave ceiling.webp new file mode 100644 index 000000000..e05c0a662 Binary files /dev/null and b/apps/khix/public/assets/FAQ cave ceiling.webp differ diff --git a/apps/khix/public/assets/FAQ crystal cluster.webp b/apps/khix/public/assets/FAQ crystal cluster.webp new file mode 100644 index 000000000..c92ab1406 Binary files /dev/null and b/apps/khix/public/assets/FAQ crystal cluster.webp differ diff --git a/apps/khix/public/assets/FAQ crystal rock 1.webp b/apps/khix/public/assets/FAQ crystal rock 1.webp new file mode 100644 index 000000000..855f042d0 Binary files /dev/null and b/apps/khix/public/assets/FAQ crystal rock 1.webp differ diff --git a/apps/khix/public/assets/FAQ crystal rock 2.webp b/apps/khix/public/assets/FAQ crystal rock 2.webp new file mode 100644 index 000000000..ceaa6edb6 Binary files /dev/null and b/apps/khix/public/assets/FAQ crystal rock 2.webp differ diff --git a/apps/khix/public/assets/FAQ crystal rock 3.webp b/apps/khix/public/assets/FAQ crystal rock 3.webp new file mode 100644 index 000000000..2d50b8c04 Binary files /dev/null and b/apps/khix/public/assets/FAQ crystal rock 3.webp differ diff --git a/apps/khix/public/assets/FAQ large crystal.webp b/apps/khix/public/assets/FAQ large crystal.webp new file mode 100644 index 000000000..658a47e3c Binary files /dev/null and b/apps/khix/public/assets/FAQ large crystal.webp differ diff --git a/apps/khix/public/assets/FAQ large mushroom.webp b/apps/khix/public/assets/FAQ large mushroom.webp new file mode 100644 index 000000000..6d527fe67 Binary files /dev/null and b/apps/khix/public/assets/FAQ large mushroom.webp differ diff --git a/apps/khix/public/assets/FAQ misc rock 1.webp b/apps/khix/public/assets/FAQ misc rock 1.webp new file mode 100644 index 000000000..f20554cdc Binary files /dev/null and b/apps/khix/public/assets/FAQ misc rock 1.webp differ diff --git a/apps/khix/public/assets/FAQ misc rock 2.webp b/apps/khix/public/assets/FAQ misc rock 2.webp new file mode 100644 index 000000000..69b5dbd1e Binary files /dev/null and b/apps/khix/public/assets/FAQ misc rock 2.webp differ diff --git a/apps/khix/public/assets/FAQ mushroom rock 1.webp b/apps/khix/public/assets/FAQ mushroom rock 1.webp new file mode 100644 index 000000000..3d63b8553 Binary files /dev/null and b/apps/khix/public/assets/FAQ mushroom rock 1.webp differ diff --git a/apps/khix/public/assets/FAQ mushroom rock 2.webp b/apps/khix/public/assets/FAQ mushroom rock 2.webp new file mode 100644 index 000000000..2b976560b Binary files /dev/null and b/apps/khix/public/assets/FAQ mushroom rock 2.webp differ diff --git a/apps/khix/public/assets/IMG_7680.webp b/apps/khix/public/assets/IMG_7680.webp new file mode 100644 index 000000000..013e739c1 Binary files /dev/null and b/apps/khix/public/assets/IMG_7680.webp differ diff --git a/apps/khix/public/assets/IMG_7681.webp b/apps/khix/public/assets/IMG_7681.webp new file mode 100644 index 000000000..1c6ac9e3e Binary files /dev/null and b/apps/khix/public/assets/IMG_7681.webp differ diff --git a/apps/khix/public/assets/IMG_7682.webp b/apps/khix/public/assets/IMG_7682.webp new file mode 100644 index 000000000..54f87e575 Binary files /dev/null and b/apps/khix/public/assets/IMG_7682.webp differ diff --git a/apps/khix/public/assets/IMG_7683.webp b/apps/khix/public/assets/IMG_7683.webp new file mode 100644 index 000000000..ff837fb18 Binary files /dev/null and b/apps/khix/public/assets/IMG_7683.webp differ diff --git a/apps/khix/public/assets/IMG_7684.webp b/apps/khix/public/assets/IMG_7684.webp new file mode 100644 index 000000000..ece91938d Binary files /dev/null and b/apps/khix/public/assets/IMG_7684.webp differ diff --git a/apps/khix/public/assets/IMG_7685.webp b/apps/khix/public/assets/IMG_7685.webp new file mode 100644 index 000000000..f09f6cba4 Binary files /dev/null and b/apps/khix/public/assets/IMG_7685.webp differ diff --git a/apps/khix/public/assets/IMG_7687.webp b/apps/khix/public/assets/IMG_7687.webp new file mode 100644 index 000000000..766f9dd61 Binary files /dev/null and b/apps/khix/public/assets/IMG_7687.webp differ diff --git a/apps/khix/public/assets/IMG_7688.webp b/apps/khix/public/assets/IMG_7688.webp new file mode 100644 index 000000000..e1804f1a5 Binary files /dev/null and b/apps/khix/public/assets/IMG_7688.webp differ diff --git a/apps/khix/public/assets/IMG_7689.webp b/apps/khix/public/assets/IMG_7689.webp new file mode 100644 index 000000000..6047df2d5 Binary files /dev/null and b/apps/khix/public/assets/IMG_7689.webp differ diff --git a/apps/khix/public/assets/LennyAnimFooter/lenny-animated.webp b/apps/khix/public/assets/LennyAnimFooter/lenny-animated.webp new file mode 100644 index 000000000..d202159de Binary files /dev/null and b/apps/khix/public/assets/LennyAnimFooter/lenny-animated.webp differ diff --git a/apps/khix/public/assets/_ignore-delete.webp b/apps/khix/public/assets/_ignore-delete.webp new file mode 100644 index 000000000..176a18ab5 Binary files /dev/null and b/apps/khix/public/assets/_ignore-delete.webp differ diff --git a/apps/khix/public/assets/_ignore-delete1.webp b/apps/khix/public/assets/_ignore-delete1.webp new file mode 100644 index 000000000..cd2bc9251 Binary files /dev/null and b/apps/khix/public/assets/_ignore-delete1.webp differ diff --git a/apps/khix/public/assets/branch-1.webp b/apps/khix/public/assets/branch-1.webp new file mode 100644 index 000000000..4d06ab69e Binary files /dev/null and b/apps/khix/public/assets/branch-1.webp differ diff --git a/apps/khix/public/assets/branch-2.webp b/apps/khix/public/assets/branch-2.webp new file mode 100644 index 000000000..248a8c184 Binary files /dev/null and b/apps/khix/public/assets/branch-2.webp differ diff --git a/apps/khix/public/assets/branch-3.webp b/apps/khix/public/assets/branch-3.webp new file mode 100644 index 000000000..351a47db0 Binary files /dev/null and b/apps/khix/public/assets/branch-3.webp differ diff --git a/apps/khix/public/assets/branch-4.webp b/apps/khix/public/assets/branch-4.webp new file mode 100644 index 000000000..fa9ecb496 Binary files /dev/null and b/apps/khix/public/assets/branch-4.webp differ diff --git a/apps/khix/public/assets/branch-5.webp b/apps/khix/public/assets/branch-5.webp new file mode 100644 index 000000000..66eec4c17 Binary files /dev/null and b/apps/khix/public/assets/branch-5.webp differ diff --git a/apps/khix/public/assets/leaf-1.webp b/apps/khix/public/assets/leaf-1.webp new file mode 100644 index 000000000..370aecda0 Binary files /dev/null and b/apps/khix/public/assets/leaf-1.webp differ diff --git a/apps/khix/public/assets/leaf-2.webp b/apps/khix/public/assets/leaf-2.webp new file mode 100644 index 000000000..675f5cf88 Binary files /dev/null and b/apps/khix/public/assets/leaf-2.webp differ diff --git a/apps/khix/public/assets/leaf-3.webp b/apps/khix/public/assets/leaf-3.webp new file mode 100644 index 000000000..5d353946c Binary files /dev/null and b/apps/khix/public/assets/leaf-3.webp differ diff --git a/apps/khix/public/assets/lights-1.webp b/apps/khix/public/assets/lights-1.webp new file mode 100644 index 000000000..7d29b4da3 Binary files /dev/null and b/apps/khix/public/assets/lights-1.webp differ diff --git a/apps/khix/public/assets/lights-2.webp b/apps/khix/public/assets/lights-2.webp new file mode 100644 index 000000000..0cab50130 Binary files /dev/null and b/apps/khix/public/assets/lights-2.webp differ diff --git a/apps/khix/public/assets/lights-3.webp b/apps/khix/public/assets/lights-3.webp new file mode 100644 index 000000000..e8af7ef1b Binary files /dev/null and b/apps/khix/public/assets/lights-3.webp differ diff --git a/apps/khix/public/assets/lights-4.webp b/apps/khix/public/assets/lights-4.webp new file mode 100644 index 000000000..5456fae79 Binary files /dev/null and b/apps/khix/public/assets/lights-4.webp differ diff --git a/apps/khix/public/assets/pile-1.webp b/apps/khix/public/assets/pile-1.webp new file mode 100644 index 000000000..d7340d0ae Binary files /dev/null and b/apps/khix/public/assets/pile-1.webp differ diff --git a/apps/khix/public/assets/pile-2.webp b/apps/khix/public/assets/pile-2.webp new file mode 100644 index 000000000..ab425b9ea Binary files /dev/null and b/apps/khix/public/assets/pile-2.webp differ diff --git a/apps/khix/public/assets/pile-3.webp b/apps/khix/public/assets/pile-3.webp new file mode 100644 index 000000000..7b41402b8 Binary files /dev/null and b/apps/khix/public/assets/pile-3.webp differ diff --git a/apps/khix/public/assets/pile-4.webp b/apps/khix/public/assets/pile-4.webp new file mode 100644 index 000000000..578561c12 Binary files /dev/null and b/apps/khix/public/assets/pile-4.webp differ diff --git a/apps/khix/public/assets/pile-5.webp b/apps/khix/public/assets/pile-5.webp new file mode 100644 index 000000000..b7fb8f76e Binary files /dev/null and b/apps/khix/public/assets/pile-5.webp differ diff --git a/apps/khix/public/assets/waterfall.webp b/apps/khix/public/assets/waterfall.webp new file mode 100644 index 000000000..a7c9e45e0 Binary files /dev/null and b/apps/khix/public/assets/waterfall.webp differ diff --git a/apps/khix/public/birds.mp3 b/apps/khix/public/birds.mp3 deleted file mode 100644 index bbd85cfcb..000000000 Binary files a/apps/khix/public/birds.mp3 and /dev/null differ diff --git a/apps/khix/public/favicon.svg b/apps/khix/public/favicon.svg new file mode 100644 index 000000000..c960d1aff --- /dev/null +++ b/apps/khix/public/favicon.svg @@ -0,0 +1,7 @@ + + + + diff --git a/apps/khix/public/flower.png b/apps/khix/public/flower.png deleted file mode 100644 index 18a25716d..000000000 Binary files a/apps/khix/public/flower.png and /dev/null differ diff --git a/apps/khix/public/khix-gem-shard-mask.png b/apps/khix/public/khix-gem-shard-mask.png deleted file mode 100644 index d04e30254..000000000 Binary files a/apps/khix/public/khix-gem-shard-mask.png and /dev/null differ diff --git a/apps/khix/public/khix-logo-white.svg b/apps/khix/public/khix-logo-white.svg deleted file mode 100644 index af0cc9ef8..000000000 --- a/apps/khix/public/khix-logo-white.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apps/khix/public/khix-sigil.svg b/apps/khix/public/khix-sigil.svg deleted file mode 100644 index 460ecf049..000000000 --- a/apps/khix/public/khix-sigil.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apps/khix/public/khlogo.svg b/apps/khix/public/khlogo.svg new file mode 100644 index 000000000..0e352e2ad --- /dev/null +++ b/apps/khix/public/khlogo.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/khix/public/ladybug.png b/apps/khix/public/ladybug.png deleted file mode 100644 index 7b8bcc7ff..000000000 Binary files a/apps/khix/public/ladybug.png and /dev/null differ diff --git a/apps/khix/public/mushroom.png b/apps/khix/public/mushroom.png deleted file mode 100644 index 38839ac62..000000000 Binary files a/apps/khix/public/mushroom.png and /dev/null differ diff --git a/apps/khix/public/music.mp3 b/apps/khix/public/music.mp3 deleted file mode 100644 index 4c62cc364..000000000 Binary files a/apps/khix/public/music.mp3 and /dev/null differ diff --git a/apps/khix/src/app/_components/WispCursor.tsx b/apps/khix/src/app/_components/WispCursor.tsx deleted file mode 100644 index 3b866da16..000000000 --- a/apps/khix/src/app/_components/WispCursor.tsx +++ /dev/null @@ -1,155 +0,0 @@ -"use client"; - -import type { CSSProperties } from "react"; -import { useEffect, useRef, useState } from "react"; - -const WISP_COLORS = [ - { - core: "rgba(246, 255, 255, 0.92)", - glow: "rgba(119, 238, 255, 0.76)", - edge: "rgba(45, 167, 255, 0.34)", - }, - { - core: "rgba(235, 251, 255, 0.9)", - glow: "rgba(100, 201, 255, 0.72)", - edge: "rgba(131, 118, 255, 0.3)", - }, - { - core: "rgba(244, 255, 239, 0.88)", - glow: "rgba(142, 255, 218, 0.68)", - edge: "rgba(55, 211, 255, 0.3)", - }, -] as const; - -interface CursorWisp { - id: number; - x: number; - y: number; - size: number; - driftX: number; - driftY: number; - rotate: number; - core: string; - glow: string; - edge: string; -} - -type WispCursorStyle = CSSProperties & { - "--wisp-width": string; - "--wisp-height": string; - "--wisp-drift-x": string; - "--wisp-drift-y": string; - "--wisp-drift-x-mid": string; - "--wisp-drift-y-mid": string; - "--wisp-rotate": string; - "--wisp-rotate-mid": string; - "--wisp-tail-rotate": string; - "--wisp-tail-rotate-mid": string; - "--wisp-tail-rotate-end": string; - "--wisp-fog-drift-x-mid": string; - "--wisp-fog-drift-x-end": string; - "--wisp-core": string; - "--wisp-glow": string; - "--wisp-edge": string; -}; - -export default function WispCursor() { - const [wisps, setWisps] = useState([]); - const counterRef = useRef(0); - const lastTrailPos = useRef({ x: -200, y: -200 }); - const cleanupTimers = useRef([]); - - useEffect(() => { - const prefersReducedMotion = window.matchMedia( - "(prefers-reduced-motion: reduce)", - ).matches; - const isCoarsePointer = window.matchMedia("(pointer: coarse)").matches; - - if (prefersReducedMotion || isCoarsePointer) { - return; - } - - const onMove = (event: MouseEvent) => { - const dx = event.clientX - lastTrailPos.current.x; - const dy = event.clientY - lastTrailPos.current.y; - const distance = Math.sqrt(dx * dx + dy * dy); - - if (distance <= 42) { - return; - } - - lastTrailPos.current = { x: event.clientX, y: event.clientY }; - - const id = counterRef.current++; - const color = WISP_COLORS[id % WISP_COLORS.length] ?? WISP_COLORS[0]; - const driftDirection = id % 2 === 0 ? 1 : -1; - const wisp: CursorWisp = { - id, - x: event.clientX, - y: event.clientY, - size: 26 + (id % 4) * 3, - driftX: driftDirection * (10 + (id % 5) * 3), - driftY: -54 - (id % 4) * 10, - rotate: driftDirection * (5 + (id % 4) * 5), - ...color, - }; - - setWisps((currentWisps) => [...currentWisps.slice(-9), wisp]); - - const cleanupTimer = window.setTimeout(() => { - setWisps((currentWisps) => - currentWisps.filter((currentWisp) => currentWisp.id !== id), - ); - }, 1420); - cleanupTimers.current.push(cleanupTimer); - }; - - window.addEventListener("mousemove", onMove); - return () => { - window.removeEventListener("mousemove", onMove); - cleanupTimers.current.forEach((cleanupTimer) => { - window.clearTimeout(cleanupTimer); - }); - cleanupTimers.current = []; - }; - }, []); - - return ( - <> - {wisps.map((wisp) => { - const style: WispCursorStyle = { - left: wisp.x, - top: wisp.y, - "--wisp-width": `${wisp.size * 1.25}px`, - "--wisp-height": `${wisp.size * 1.55}px`, - "--wisp-drift-x": `${wisp.driftX}px`, - "--wisp-drift-y": `${wisp.driftY}px`, - "--wisp-drift-x-mid": `${wisp.driftX * 0.48}px`, - "--wisp-drift-y-mid": `${wisp.driftY * 0.52}px`, - "--wisp-rotate": `${wisp.rotate}deg`, - "--wisp-rotate-mid": `${wisp.rotate * 0.55}deg`, - "--wisp-tail-rotate": `${wisp.rotate * -1}deg`, - "--wisp-tail-rotate-mid": `${wisp.rotate * -0.72}deg`, - "--wisp-tail-rotate-end": `${wisp.rotate * -0.42}deg`, - "--wisp-fog-drift-x-mid": `${wisp.driftX * -0.28}px`, - "--wisp-fog-drift-x-end": `${wisp.driftX * -0.36}px`, - "--wisp-core": wisp.core, - "--wisp-glow": wisp.glow, - "--wisp-edge": wisp.edge, - }; - - return ( - - - - - ); - })} - > - ); -} diff --git a/apps/khix/src/app/_components/assets/AssetCredit.module.css b/apps/khix/src/app/_components/assets/AssetCredit.module.css new file mode 100644 index 000000000..eb5355e91 --- /dev/null +++ b/apps/khix/src/app/_components/assets/AssetCredit.module.css @@ -0,0 +1,76 @@ +.assetCredit { + position: relative; + display: block; + width: 100%; + max-width: 100%; + pointer-events: auto; +} + +.creditText { + position: absolute; + right: 0; + bottom: -0.15rem; + z-index: 2; + max-width: min(24rem, calc(100vw - 2rem)); + border: 0; + color: rgba(255, 255, 255, 0.68); + background: transparent; + box-shadow: none; + font-size: clamp(0.62rem, 1.2vw, 0.72rem); + font-weight: 700; + letter-spacing: 0; + line-height: 1.15; + text-align: right; + text-shadow: + 0 1px 0.45rem rgba(0, 0, 0, 0.34), + 0 0 0.7rem rgba(255, 255, 255, 0.1); + opacity: 0; + pointer-events: none; + transform: translate3d(0, 0.16rem, 0); + transform-origin: 100% 100%; + transition: + opacity 180ms ease, + transform 220ms ease; +} + +.creditLabel { + color: rgba(255, 255, 255, 0.44); + font-weight: 600; +} + +.creditName, +.creditLink { + color: inherit; +} + +.creditLink { + text-decoration: none; + text-underline-offset: 0.18em; + transition: color 160ms ease; +} + +.creditLink:hover, +.creditLink:focus-visible { + color: rgba(255, 255, 255, 0.9); + text-decoration: underline; + outline: none; +} + +.creditSeparator { + margin: 0 0.32rem; + color: rgba(255, 255, 255, 0.34); +} + +.assetCredit:hover .creditText, +.assetCredit:focus-within .creditText { + opacity: 1; + pointer-events: auto; + transform: translate3d(0, 0, 0); +} + +@media (prefers-reduced-motion: reduce) { + .creditText, + .creditLink { + transition: none; + } +} diff --git a/apps/khix/src/app/_components/assets/AssetCredit.tsx b/apps/khix/src/app/_components/assets/AssetCredit.tsx new file mode 100644 index 000000000..56e50caeb --- /dev/null +++ b/apps/khix/src/app/_components/assets/AssetCredit.tsx @@ -0,0 +1,62 @@ +import type { ReactNode } from "react"; +import { Fragment } from "react"; + +import styles from "./AssetCredit.module.css"; + +export interface AssetCreditEntry { + name: string; + href?: string; + newTab?: boolean; +} + +interface AssetCreditProps { + credits: readonly AssetCreditEntry[]; + children: ReactNode; + className?: string; + label?: string; +} + +export function AssetCredit({ + credits, + children, + className, + label = "Made by", +}: AssetCreditProps) { + const rootClassName = className + ? `${styles.assetCredit} ${className}` + : styles.assetCredit; + const visibleCredits = credits.filter((credit) => credit.name.trim()); + + if (visibleCredits.length === 0) { + return {children}; + } + + return ( + + {children} + + {label}{" "} + {visibleCredits.map((credit, index) => ( + + {index > 0 ? ( + / + ) : null} + {credit.href ? ( + + {credit.name} + + ) : ( + {credit.name} + )} + + ))} + + + ); +} diff --git a/apps/khix/src/app/_components/assets/index.ts b/apps/khix/src/app/_components/assets/index.ts new file mode 100644 index 000000000..903321c89 --- /dev/null +++ b/apps/khix/src/app/_components/assets/index.ts @@ -0,0 +1 @@ +export { AssetCredit } from "./AssetCredit"; diff --git a/apps/khix/src/app/_components/navbar/Navbar.module.css b/apps/khix/src/app/_components/navbar/Navbar.module.css new file mode 100644 index 000000000..f07fdce00 --- /dev/null +++ b/apps/khix/src/app/_components/navbar/Navbar.module.css @@ -0,0 +1,201 @@ +.navbar { + position: fixed; + top: max(0.75rem, env(safe-area-inset-top)); + left: 0; + z-index: 80; + width: 100%; + padding: 0 clamp(0.75rem, 3vw, 1.5rem); + color: #ffffff; + pointer-events: none; + transform: translate3d(0, 0, 0); + transition: + transform 220ms ease, + opacity 180ms ease; +} + +.navbar[data-hidden="true"] { + opacity: 0; + transform: translate3d(0, calc(-100% - 1rem), 0); +} + +.inner { + display: flex; + align-items: center; + justify-content: center; + width: fit-content; + max-width: 100%; + min-height: 3.35rem; + margin: 0 auto; + gap: clamp(0.8rem, 2vw, 1.35rem); + padding: 0.48rem clamp(0.7rem, 2vw, 1.2rem); + border: 0; + border-radius: 999px; + background: transparent; + box-shadow: none; + pointer-events: auto; +} + +.brand { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + min-width: 0; + color: inherit; + text-decoration: none; +} + +.logoSlot { + display: block; + width: 2.1rem; + height: 2.1rem; + border: 1.5px solid rgba(255, 255, 255, 0.62); + border-radius: 999px; + background: transparent; + box-shadow: inset 0 0 0 0.35rem rgba(255, 255, 255, 0.08); + transition: + border-color 180ms ease, + background 180ms ease, + box-shadow 180ms ease; +} + +.brand:hover .logoSlot, +.brand:focus-visible .logoSlot { + border-color: rgba(255, 255, 255, 0.95); + background: rgba(255, 255, 255, 0.08); + box-shadow: + inset 0 0 0 0.35rem rgba(255, 255, 255, 0.1), + 0 0.5rem 1.2rem rgba(0, 0, 0, 0.12); +} + +.links { + display: flex; + flex: 0 1 auto; + align-items: center; + justify-content: center; + gap: clamp(0.85rem, 1.7vw, 1.45rem); + min-width: 0; + max-width: 100%; + overflow-x: auto; + scrollbar-width: none; +} + +.links::-webkit-scrollbar { + display: none; +} + +.link { + position: relative; + display: inline-flex; + flex: 0 0 auto; + align-items: center; + padding: 0.55rem 0.05rem; + color: rgba(255, 255, 255, 0.9); + font-size: 0.82rem; + font-weight: 800; + letter-spacing: 0; + line-height: 1; + text-decoration: none; + text-shadow: 0 1px 0.85rem rgba(0, 0, 0, 0.26); + transform: translateY(0) scale(1); + transform-origin: center; + white-space: nowrap; + transition: + color 180ms ease, + text-shadow 180ms ease, + transform 180ms ease; +} + +.link:hover, +.link:focus-visible { + color: #ffffff; + outline: none; + transform: translateY(-1px) scale(1.08); + text-shadow: + 0 0 0.16rem rgba(255, 255, 255, 0.88), + 0 0 0.62rem rgba(225, 255, 238, 0.78), + 0 0 1.1rem rgba(150, 255, 198, 0.36), + 0 1px 0.85rem rgba(0, 0, 0, 0.24); +} + +.socialLinks { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: 0.65rem; + padding-left: clamp(0.2rem, 1vw, 0.5rem); +} + +.socialLink { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.55rem; + height: 1.55rem; + color: rgba(255, 255, 255, 0.84); + font-size: 0.7rem; + font-weight: 900; + letter-spacing: 0; + line-height: 1; + text-decoration: none; + text-transform: uppercase; + transition: + color 180ms ease, + transform 180ms ease; +} + +.socialLink:hover, +.socialLink:focus-visible { + color: #ffffff; + outline: none; + transform: translateY(-1px); +} + +@media (max-width: 840px) { + .navbar { + top: max(0.5rem, env(safe-area-inset-top)); + padding-right: 0.65rem; + padding-left: 0.65rem; + } + + .inner { + justify-content: flex-start; + gap: 0.7rem; + min-height: 3rem; + padding: 0.38rem 0.58rem; + } + + .logoSlot { + width: 1.95rem; + height: 1.95rem; + } + + .links { + justify-content: flex-start; + gap: 0.85rem; + padding-right: 0.2rem; + } + + .link { + font-size: 0.74rem; + } + + .socialLinks { + gap: 0.45rem; + padding-left: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .navbar, + .logoSlot, + .link, + .socialLink { + animation: none; + transition: none; + } + + .link:hover, + .link:focus-visible { + transform: none; + } +} diff --git a/apps/khix/src/app/_components/navbar/Navbar.tsx b/apps/khix/src/app/_components/navbar/Navbar.tsx new file mode 100644 index 000000000..9d82cb87b --- /dev/null +++ b/apps/khix/src/app/_components/navbar/Navbar.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import styles from "./Navbar.module.css"; + +export interface NavbarLink { + label: string; + href: string; + external?: boolean; +} + +export interface NavbarSocialLink { + label: string; + href: string; + shortLabel: string; +} + +interface NavbarProps { + links: readonly NavbarLink[]; + socialLinks?: readonly NavbarSocialLink[]; + homeHref?: string; +} + +const HIDE_SCROLL_Y = 80; +const SCROLL_DELTA = 6; + +export function Navbar({ + links, + socialLinks = [], + homeHref = "#home", +}: NavbarProps) { + const [isHidden, setIsHidden] = useState(false); + + useEffect(() => { + let lastScrollY = window.scrollY; + let ticking = false; + + const updateVisibility = () => { + const currentScrollY = window.scrollY; + const scrollDelta = currentScrollY - lastScrollY; + + if (currentScrollY <= HIDE_SCROLL_Y) { + setIsHidden(false); + lastScrollY = currentScrollY; + ticking = false; + return; + } + + if (Math.abs(scrollDelta) >= SCROLL_DELTA) { + setIsHidden(scrollDelta > 0); + lastScrollY = currentScrollY; + } + + ticking = false; + }; + + const handleScroll = () => { + if (ticking) return; + + ticking = true; + window.requestAnimationFrame(updateVisibility); + }; + + handleScroll(); + + window.addEventListener("scroll", handleScroll, { passive: true }); + + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + return ( + + + + + + + + {links.map((link) => ( + + {link.label} + + ))} + + + {socialLinks.length > 0 ? ( + + {socialLinks.map((link) => ( + + {link.shortLabel} + + ))} + + ) : null} + + + ); +} diff --git a/apps/khix/src/app/_components/navbar/index.ts b/apps/khix/src/app/_components/navbar/index.ts new file mode 100644 index 000000000..385c652e5 --- /dev/null +++ b/apps/khix/src/app/_components/navbar/index.ts @@ -0,0 +1,2 @@ +export { Navbar } from "./Navbar"; +export type { NavbarLink, NavbarSocialLink } from "./Navbar"; diff --git a/apps/khix/src/app/_components/sections/hero/FallingLeaves.tsx b/apps/khix/src/app/_components/sections/hero/FallingLeaves.tsx new file mode 100644 index 000000000..c1c5abdab --- /dev/null +++ b/apps/khix/src/app/_components/sections/hero/FallingLeaves.tsx @@ -0,0 +1,142 @@ +import type { CSSProperties } from "react"; + +import styles from "./Hero.module.css"; + +interface FallingLeaf { + delay: string; + drift: string; + duration: string; + fill: string; + opacity: number; + rotate: string; + size: string; + x: string; +} + +const FALLING_LEAVES: FallingLeaf[] = [ + { + x: "52%", + size: "22px", + duration: "9.6s", + delay: "-1.8s", + opacity: 0.5, + fill: "#b987ff", + drift: "-38px", + rotate: "280deg", + }, + { + x: "61%", + size: "18px", + duration: "11.2s", + delay: "-6.4s", + opacity: 0.42, + fill: "#9b6dff", + drift: "51px", + rotate: "340deg", + }, + { + x: "70%", + size: "25px", + duration: "10.4s", + delay: "-3.2s", + opacity: 0.46, + fill: "#d19aff", + drift: "-61px", + rotate: "390deg", + }, + { + x: "79%", + size: "20px", + duration: "12.8s", + delay: "-8.5s", + opacity: 0.38, + fill: "#8752f0", + drift: "45px", + rotate: "320deg", + }, + { + x: "86%", + size: "18px", + duration: "8.9s", + delay: "-4.9s", + opacity: 0.44, + fill: "#c08cff", + drift: "-29px", + rotate: "250deg", + }, + { + x: "47%", + size: "19px", + duration: "10.8s", + delay: "-7.2s", + opacity: 0.4, + fill: "#a66cff", + drift: "35px", + rotate: "310deg", + }, + { + x: "57%", + size: "26px", + duration: "13.4s", + delay: "-10.6s", + opacity: 0.34, + fill: "#cf95ff", + drift: "-48px", + rotate: "420deg", + }, + { + x: "66%", + size: "18px", + duration: "9.2s", + delay: "-0.9s", + opacity: 0.46, + fill: "#8f5bf5", + drift: "59px", + rotate: "270deg", + }, + { + x: "74%", + size: "23px", + duration: "12.1s", + delay: "-5.7s", + opacity: 0.37, + fill: "#b47aff", + drift: "-43px", + rotate: "365deg", + }, + { + x: "91%", + size: "21px", + duration: "10s", + delay: "-2.6s", + opacity: 0.43, + fill: "#d3a2ff", + drift: "30px", + rotate: "295deg", + }, +]; + +export function FallingLeaves() { + return ( + + {FALLING_LEAVES.map((leaf, index) => ( + + ))} + + ); +} diff --git a/apps/khix/src/app/_components/sections/hero/Hero.module.css b/apps/khix/src/app/_components/sections/hero/Hero.module.css new file mode 100644 index 000000000..9507f299c --- /dev/null +++ b/apps/khix/src/app/_components/sections/hero/Hero.module.css @@ -0,0 +1,384 @@ +@property --khix-scroll-progress { + syntax: ""; + inherits: true; + initial-value: 0; +} + +.hero { + position: relative; + z-index: 1; + min-height: 100vh; + min-height: 100svh; + overflow-anchor: none; + background: #eef6cf; +} + +.stage { + --khix-pointer-x: var(--khix-hero-pointer-x, 0); + --khix-pointer-y: var(--khix-hero-pointer-y, 0); + --khix-scroll-progress: var(--khix-hero-scroll-progress, 0); + + position: relative; + width: 100%; + height: 100vh; + height: 100svh; + overflow: hidden; + overflow-anchor: none; + isolation: isolate; + background: + linear-gradient( + 180deg, + rgba(247, 253, 213, 0.96), + rgba(219, 240, 226, 0.9) + ), + #eef6cf; +} + +.art { + --khix-art-motion-scale: 0.64; + position: absolute; + inset: -6.32svh -3.76vw; + z-index: 1; + pointer-events: auto; + overflow-anchor: none; +} + +.introVeil { + position: absolute; + inset: 0; + z-index: 60; + pointer-events: none; + background: + linear-gradient( + 180deg, + rgba(228, 238, 214, 0.58), + rgba(191, 215, 211, 0.34) + ), + rgba(42, 56, 70, 0.08); + backdrop-filter: blur(26px) saturate(1.04); + animation: introVeilRelease 1.45s ease-out forwards; +} + +.layer { + --khix-layer-depth-x: 0; + --khix-layer-depth-y: 0; + --khix-layer-scale: 1; + --khix-layer-scroll-y: 0px; + + position: absolute; + inset: 0; + display: block; + overflow: hidden; + overflow-anchor: none; + transform: translate3d( + calc( + var(--khix-pointer-x) * var(--khix-layer-depth-x) * + var(--khix-art-motion-scale) * 1px + ), + calc( + ( + var(--khix-pointer-y) * var(--khix-layer-depth-y) * + var(--khix-art-motion-scale) * 1px + ) + + ( + var(--khix-scroll-progress) * var(--khix-layer-scroll-y) * + var(--khix-art-motion-scale) + ) + ), + 0 + ) + scale(var(--khix-layer-scale)); + backface-visibility: hidden; + will-change: transform; +} + +.layerImage { + object-fit: cover; + object-position: 24% 50%; + user-select: none; + pointer-events: none; +} + +.distantLayer .layerImage { + opacity: 0.92; +} + +.foregroundLayer { + inset: -6.32svh -3.76vw; + z-index: 12; + overflow: visible; + pointer-events: none; + transform: none; + will-change: auto; +} + +.foregroundImage { + position: absolute; + top: clamp(14rem, 36svh, 24rem); + left: 0; + width: 100%; + height: auto; + filter: saturate(0.92) brightness(0.86); +} + +.glowLayer { + --khix-hero-glow-opacity: 0.14; + + opacity: var(--khix-hero-glow-opacity); +} + +.waterLayer { + overflow: hidden; +} + +.pondLayer .layerImage { + transform-origin: 52% 78%; +} + +.pondSourceImage { + opacity: 1; +} + +.pondFrameSequence { + position: absolute; + inset: 0; + display: block; + pointer-events: none; + background-image: url("/KHIXHero/optimized/pond-frames/pond-animated.webp"); + background-repeat: no-repeat; + background-position: 24% 50%; + background-size: cover; + opacity: 0.5; + transform-origin: 52% 78%; +} + +.waterfallLayer .layerImage { + z-index: 1; + transform-origin: 4% 56%; +} + +.waterfallSourceImage { + opacity: 0; +} + +.waterfallFrameSequence { + position: absolute; + inset: 0; + z-index: 1; + display: block; + pointer-events: none; + background-image: url("/KHIXHero/optimized/waterfall-frames/waterfall-animated.webp"); + background-repeat: no-repeat; + background-position: 24% 50%; + background-size: cover; + transform-origin: 4% 56%; +} + +.leafField { + position: absolute; + inset: 0; + z-index: 40; + overflow: hidden; + pointer-events: none; +} + +.leaf { + position: absolute; + top: -9svh; + left: var(--leaf-x); + width: var(--leaf-size); + aspect-ratio: 0.62; + border-radius: 100% 0 100% 0; + background: + linear-gradient(135deg, rgba(239, 217, 255, 0.44), transparent 38%), + var(--leaf-fill); + box-shadow: inset -1px -2px 3px rgba(58, 31, 93, 0.22); + opacity: 0; + transform: translate3d(0, -8svh, 0) rotate(-18deg); + animation: leafFall var(--leaf-duration) linear var(--leaf-delay) infinite; +} + +.leaf::after { + position: absolute; + inset: 13% 46% 8% auto; + width: 1px; + content: ""; + background: rgba(64, 38, 102, 0.3); + transform: rotate(10deg); + transform-origin: bottom; +} + +.shade { + position: absolute; + inset: 0; + z-index: 10; + pointer-events: none; + background: + linear-gradient( + 180deg, + rgba(12, 20, 35, 0.15), + rgba(35, 50, 58, 0.04) 46%, + rgba(5, 18, 24, 0.1) + ), + radial-gradient( + ellipse at 60% 18%, + rgba(208, 224, 236, 0.1), + transparent 42% + ); + box-shadow: + inset 0 -24svh 34svh rgba(7, 28, 23, 0.2), + inset 0 18svh 30svh rgba(247, 253, 213, 0.16); +} + +.titleLockup { + --khix-title-aura-opacity: 0.42; + --khix-title-glow-opacity: 0.62; + + position: absolute; + top: 29%; + left: 14%; + z-index: 30; + width: min(78vw, 90rem); + pointer-events: auto; + opacity: 1; + transform: translate3d(0, 0, 0) scale(1); + transform-origin: 50% 55%; +} + +.titleLockup::before { + position: absolute; + inset: -18% -8% -10%; + z-index: 0; + pointer-events: none; + content: ""; + background: + radial-gradient( + ellipse at 42% 52%, + rgb(203 255 224 / calc(var(--khix-title-aura-opacity) * 0.38)), + transparent 48% + ), + radial-gradient( + ellipse at 68% 48%, + rgb(133 234 255 / calc(var(--khix-title-aura-opacity) * 0.2)), + transparent 54% + ); + filter: blur(18px); + opacity: var(--khix-title-aura-opacity); +} + +.titleLogo { + position: relative; + z-index: 1; + display: block; + width: 100%; + height: auto; + user-select: none; + filter: brightness(1.06) + drop-shadow(0 0 0.1rem rgb(255 255 255 / var(--khix-title-glow-opacity))) + drop-shadow( + 0 0 0.42rem rgb(235 249 255 / calc(var(--khix-title-glow-opacity) * 0.58)) + ) + drop-shadow( + 0 0 0.9rem rgb(103 228 255 / calc(var(--khix-title-glow-opacity) * 0.3)) + ) + drop-shadow( + 0 0 1.65rem rgb(103 228 255 / calc(var(--khix-title-glow-opacity) * 0.13)) + ); +} + +@media (max-width: 700px) { + .layerImage { + object-position: 94% 50%; + } + + .foregroundLayer { + inset: -6.32svh -3.76vw; + } + + .foregroundImage { + top: clamp(16rem, 42svh, 25rem); + left: 0; + width: 100%; + max-width: none; + transform: none; + } + + .pondFrameSequence, + .waterfallFrameSequence { + background-position: 94% 50%; + } + + .titleLockup { + top: 20%; + left: 6%; + width: 88vw; + } +} + +@media (prefers-reduced-motion: reduce) { + .hero { + min-height: 100svh; + } + + .stage { + position: relative; + } + + .introVeil { + display: none; + } + + .layer { + transform: none !important; + } + + .leaf { + display: none; + } + + .pondFrameSequence, + .waterfallFrameSequence { + display: none; + } + + .pondSourceImage, + .waterfallSourceImage { + opacity: 1; + } +} + +@keyframes introVeilRelease { + 0% { + opacity: 1; + backdrop-filter: blur(26px) saturate(1.04); + } + + 100% { + opacity: 0; + backdrop-filter: blur(0) saturate(1); + visibility: hidden; + } +} + +@keyframes leafFall { + 0% { + opacity: 0; + transform: translate3d(0, -8svh, 0) rotate(-18deg) scale(0.86); + } + + 12% { + opacity: var(--leaf-opacity); + } + + 58% { + opacity: var(--leaf-opacity); + transform: translate3d(var(--leaf-drift), 54svh, 0) + rotate(var(--leaf-rotate)) scale(1); + } + + 100% { + opacity: 0; + transform: translate3d(var(--leaf-drift), 106svh, 0) + rotate(var(--leaf-rotate)) scale(0.9); + } +} diff --git a/apps/khix/src/app/_components/sections/hero/Hero.tsx b/apps/khix/src/app/_components/sections/hero/Hero.tsx new file mode 100644 index 000000000..11ccecf23 --- /dev/null +++ b/apps/khix/src/app/_components/sections/hero/Hero.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { FallingLeaves } from "./FallingLeaves"; +import styles from "./Hero.module.css"; +import { HeroLayerImage } from "./HeroLayerImage"; +import { HeroTitle } from "./HeroTitle"; +import { HERO_LAYERS } from "./layers"; +import { useHeroMotion } from "./useHeroMotion"; + +export default function Hero() { + const foregroundLayerIndex = HERO_LAYERS.length - 1; + const foregroundLayer = HERO_LAYERS[foregroundLayerIndex]; + const backgroundLayers = HERO_LAYERS.slice(0, foregroundLayerIndex); + const { sectionRef, stageRef, handlePointerMove, handlePointerLeave } = + useHeroMotion(); + + if (!foregroundLayer) { + return null; + } + + return ( + + + + Knight Hacks IX + + + {backgroundLayers.map((layer, index) => ( + + ))} + + + + + + + + + ); +} diff --git a/apps/khix/src/app/_components/sections/hero/HeroLayerImage.tsx b/apps/khix/src/app/_components/sections/hero/HeroLayerImage.tsx new file mode 100644 index 000000000..ba4d87545 --- /dev/null +++ b/apps/khix/src/app/_components/sections/hero/HeroLayerImage.tsx @@ -0,0 +1,114 @@ +import type { CSSProperties } from "react"; +import Image from "next/image"; + +import type { HeroLayer } from "./layers"; +import styles from "./Hero.module.css"; +import { HERO_ASSET_BASE_PATH } from "./layers"; + +interface HeroLayerImageProps { + layer: HeroLayer; + index: number; + zIndex?: number; +} + +export function HeroLayerImage({ layer, index, zIndex }: HeroLayerImageProps) { + const isGlow = layer.motionRole === "glow"; + const isPond = layer.motionRole === "pond"; + const isTk = layer.motionRole === "tk"; + const isWaterfall = layer.motionRole === "waterfall"; + const isForeground = layer.motionRole === "foreground"; + const isTreeWaterfall = layer.filename.includes("3_tree_and_waterfall"); + const isDistant = index < 4; + const hasAmbientImageMotion = !( + isForeground || + isGlow || + isPond || + isTk || + isWaterfall + ); + const imageClassName = [ + styles.layerImage, + isForeground ? styles.foregroundImage : "", + isPond ? styles.pondSourceImage : "", + isWaterfall ? styles.waterfallSourceImage : "", + ] + .filter(Boolean) + .join(" "); + const layerClassName = [ + styles.layer, + isForeground ? styles.foregroundLayer : "", + isDistant ? styles.distantLayer : "", + isGlow ? styles.glowLayer : "", + isPond || isWaterfall ? styles.waterLayer : "", + isPond ? styles.pondLayer : "", + isTreeWaterfall ? styles.treeWaterfallLayer : "", + isTk ? styles.tkLayer : "", + isWaterfall ? styles.waterfallLayer : "", + ] + .filter(Boolean) + .join(" "); + + const layerStyle = { + "--khix-layer-depth-x": layer.depthX, + "--khix-layer-depth-y": layer.depthY, + "--khix-layer-scale": layer.scale, + "--khix-layer-scroll-y": `${layer.scrollY}px`, + zIndex: zIndex ?? index + 1, + } as CSSProperties; + const imageDataAttributes = { + "data-hero-layer-image": !isWaterfall ? true : undefined, + "data-hero-ambient-image": hasAmbientImageMotion ? true : undefined, + }; + + return ( + + {isForeground ? ( + + ) : ( + + )} + {isPond ? ( + + ) : null} + {isWaterfall ? ( + + ) : null} + + ); +} diff --git a/apps/khix/src/app/_components/sections/hero/HeroTitle.tsx b/apps/khix/src/app/_components/sections/hero/HeroTitle.tsx new file mode 100644 index 000000000..40ed773de --- /dev/null +++ b/apps/khix/src/app/_components/sections/hero/HeroTitle.tsx @@ -0,0 +1,25 @@ +import Image from "next/image"; + +import { AssetCredit } from "../../assets"; +import styles from "./Hero.module.css"; + +export function HeroTitle() { + return ( + + + + + + ); +} diff --git a/apps/khix/src/app/_components/sections/hero/index.ts b/apps/khix/src/app/_components/sections/hero/index.ts new file mode 100644 index 000000000..eada8b792 --- /dev/null +++ b/apps/khix/src/app/_components/sections/hero/index.ts @@ -0,0 +1 @@ +export { default } from "./Hero"; diff --git a/apps/khix/src/app/_components/sections/hero/layers.ts b/apps/khix/src/app/_components/sections/hero/layers.ts new file mode 100644 index 000000000..7cdcdcce4 --- /dev/null +++ b/apps/khix/src/app/_components/sections/hero/layers.ts @@ -0,0 +1,88 @@ +export const HERO_ASSET_BASE_PATH = "/KHIXHero"; + +export interface HeroLayer { + filename: string; + depthX: number; + depthY: number; + scrollY: number; + scale: number; + motionRole?: "foreground" | "glow" | "pond" | "tk" | "waterfall"; +} + +export const HERO_LAYERS: HeroLayer[] = [ + { + filename: "optimized/7_bg.webp", + depthX: -10, + depthY: -4, + scrollY: -16, + scale: 1.002, + }, + { + filename: "optimized/6_bg.webp", + depthX: -8, + depthY: -3.2, + scrollY: -14, + scale: 1.002, + }, + { + filename: "optimized/5_bg.webp", + depthX: -5.5, + depthY: -2.4, + scrollY: -10, + scale: 1.002, + }, + { + filename: "optimized/4_bg.webp", + depthX: 5, + depthY: 2.4, + scrollY: -6, + scale: 1.001, + }, + { + filename: "optimized/pond.webp", + depthX: 14, + depthY: 7, + scrollY: 12, + scale: 1.001, + motionRole: "pond", + }, + { + filename: "optimized/3_tree_and_waterfall.webp", + depthX: 18, + depthY: 9, + scrollY: 16, + scale: 1.001, + }, + { + filename: "optimized/waterfall.webp", + depthX: 18, + depthY: 9, + scrollY: 16, + scale: 1.001, + motionRole: "waterfall", + }, + { + filename: "optimized/2_tk.webp", + depthX: 24, + depthY: 12, + scrollY: 20, + scale: 1.001, + motionRole: "tk", + }, + { + filename: "optimized/tk_glow_alone.webp", + depthX: 24, + depthY: 12, + scrollY: 20, + scale: 1.001, + motionRole: "glow", + }, + { + filename: "optimized/1_front.png", + depthX: 0, + depthY: 0, + scrollY: 0, + scale: 1, + motionRole: "foreground", + }, +]; diff --git a/apps/khix/src/app/_components/sections/hero/useHeroMotion.ts b/apps/khix/src/app/_components/sections/hero/useHeroMotion.ts new file mode 100644 index 000000000..329959a7e --- /dev/null +++ b/apps/khix/src/app/_components/sections/hero/useHeroMotion.ts @@ -0,0 +1,191 @@ +import type { PointerEvent, RefObject } from "react"; +import { useEffect, useRef } from "react"; + +interface HeroMotion { + sectionRef: RefObject; + stageRef: RefObject; + handlePointerMove: (event: PointerEvent) => void; + handlePointerLeave: () => void; +} + +const clamp = (value: number, min = 0, max = 1) => + Math.min(max, Math.max(min, value)); + +const getViewportHeight = () => + window.visualViewport?.height ?? window.innerHeight; + +const setStageMotionNumber = ( + stage: HTMLDivElement | null, + property: string, + value: number, +) => { + stage?.style.setProperty(property, value.toFixed(4)); +}; + +export function useHeroMotion(): HeroMotion { + const sectionRef = useRef(null); + const stageRef = useRef(null); + const pointerFrameRef = useRef(0); + const pendingPointerRef = useRef({ x: 0, y: 0 }); + const shouldReduceMotionRef = useRef(false); + const motionValuesRef = useRef({ + pointerX: 0, + pointerY: 0, + scrollProgress: 0, + }); + + const setPointerVars = (x: number, y: number) => { + const stage = stageRef.current; + const motionValues = motionValuesRef.current; + + if ( + Math.abs(motionValues.pointerX - x) < 0.001 && + Math.abs(motionValues.pointerY - y) < 0.001 + ) { + return; + } + + motionValues.pointerX = x; + motionValues.pointerY = y; + setStageMotionNumber(stage, "--khix-hero-pointer-x", x); + setStageMotionNumber(stage, "--khix-hero-pointer-y", y); + }; + + const cancelPointerFrame = () => { + if (!pointerFrameRef.current) { + return; + } + + window.cancelAnimationFrame(pointerFrameRef.current); + pointerFrameRef.current = 0; + }; + + const schedulePointerVars = (x: number, y: number) => { + pendingPointerRef.current = { x, y }; + + if (pointerFrameRef.current) { + return; + } + + pointerFrameRef.current = window.requestAnimationFrame(() => { + pointerFrameRef.current = 0; + setPointerVars(pendingPointerRef.current.x, pendingPointerRef.current.y); + }); + }; + + useEffect(() => { + const reduceMotionQuery = window.matchMedia( + "(prefers-reduced-motion: reduce)", + ); + let frame = 0; + + const setScrollProgressVar = (progress: number) => { + const motionValues = motionValuesRef.current; + + if (Math.abs(motionValues.scrollProgress - progress) < 0.001) { + return; + } + + motionValues.scrollProgress = progress; + setStageMotionNumber( + stageRef.current, + "--khix-hero-scroll-progress", + progress, + ); + }; + + const updateScrollProgress = () => { + const section = sectionRef.current; + + if (!section || shouldReduceMotionRef.current) { + setScrollProgressVar(0); + return; + } + + const viewportHeight = getViewportHeight(); + const scrollableDistance = Math.max(1, viewportHeight); + const progress = clamp( + -section.getBoundingClientRect().top / scrollableDistance, + ); + + setScrollProgressVar(progress); + }; + + const scheduleScrollProgress = () => { + if (frame) { + return; + } + + frame = window.requestAnimationFrame(() => { + frame = 0; + updateScrollProgress(); + }); + }; + + shouldReduceMotionRef.current = reduceMotionQuery.matches; + setPointerVars(0, 0); + updateScrollProgress(); + + const handleReducedMotionChange = () => { + shouldReduceMotionRef.current = reduceMotionQuery.matches; + + if (reduceMotionQuery.matches) { + cancelPointerFrame(); + setPointerVars(0, 0); + } + + updateScrollProgress(); + }; + + reduceMotionQuery.addEventListener("change", handleReducedMotionChange); + window.addEventListener("scroll", scheduleScrollProgress, { + passive: true, + }); + window.addEventListener("resize", scheduleScrollProgress); + window.visualViewport?.addEventListener("resize", scheduleScrollProgress); + + return () => { + reduceMotionQuery.removeEventListener( + "change", + handleReducedMotionChange, + ); + window.removeEventListener("scroll", scheduleScrollProgress); + window.removeEventListener("resize", scheduleScrollProgress); + window.visualViewport?.removeEventListener( + "resize", + scheduleScrollProgress, + ); + if (frame) { + window.cancelAnimationFrame(frame); + } + cancelPointerFrame(); + setPointerVars(0, 0); + setScrollProgressVar(0); + }; + }, []); + + const handlePointerMove = (event: PointerEvent) => { + if (shouldReduceMotionRef.current) { + cancelPointerFrame(); + setPointerVars(0, 0); + return; + } + + const bounds = event.currentTarget.getBoundingClientRect(); + const nextX = ((event.clientX - bounds.left) / bounds.width - 0.5) * 2; + const nextY = ((event.clientY - bounds.top) / bounds.height - 0.5) * 2; + + schedulePointerVars(nextX, nextY); + }; + + const handlePointerLeave = () => { + schedulePointerVars(0, 0); + }; + + return { + sectionRef, + stageRef, + handlePointerMove, + handlePointerLeave, + }; +} diff --git a/apps/khix/src/app/globals.css b/apps/khix/src/app/globals.css index f02df20d2..b469d5808 100644 --- a/apps/khix/src/app/globals.css +++ b/apps/khix/src/app/globals.css @@ -1,5 +1,4 @@ @import "tailwindcss"; -@import "tw-animate-css"; @source "../../../../packages/ui/src"; @config "../../tailwind.config.ts"; @custom-variant dark (&:is(.dark *)); @@ -57,1487 +56,26 @@ --ring: 263.4 70% 50.4%; } + html, body { - @apply bg-background text-foreground; - } -} - -html, -body { - width: 100%; - min-width: 100%; - min-height: 100%; - overflow-x: hidden; - background: #070c10; -} - -.khix-site-background { - position: relative; - isolation: isolate; - width: 100%; - min-width: 100%; - min-height: 100vh; - min-height: 100svh; - overflow: visible; - color: #fbfff2; - background: #070c10; -} - -.khix-experience-stage { - position: absolute; - inset: 0; - z-index: 1; - overflow: hidden; - isolation: isolate; - transition: - filter 1.35s cubic-bezier(0.16, 1, 0.3, 1), - opacity 1.15s ease; - will-change: filter; -} - -.khix-experience-awaiting .khix-experience-stage { - filter: blur(1.15rem) saturate(1.22) brightness(0.78); -} - -.khix-experience-entered .khix-experience-stage { - filter: blur(0) saturate(1) brightness(1); -} - -.khix-background-layer { - position: absolute; - inset: -1px; - z-index: -2; - pointer-events: none; - background-color: #111827; - background-image: url("https://assets.knighthacks.org/ixtempbg.webp"); - background-image: image-set( - url("https://assets.knighthacks.org/ixtempbg.avif") type("image/avif"), - url("https://assets.knighthacks.org/ixtempbg.webp") type("image/webp") - ); - background-position: center center; - background-repeat: no-repeat; - background-size: cover; - transform: scale(1.035); - transform-origin: center; - transition: transform 1.35s cubic-bezier(0.16, 1, 0.3, 1); - will-change: transform; -} - -.khix-experience-entered .khix-background-layer { - transform: scale(1); -} - -.khix-edge-gloom { - position: absolute; - inset: -1px; - z-index: 1; - pointer-events: none; - background: - radial-gradient( - ellipse at 50% 48%, - transparent 50%, - rgba(7, 18, 22, 0.18) 64%, - rgba(1, 7, 10, 0.55) 82%, - rgba(0, 2, 5, 0.82) 100% - ), - linear-gradient( - 90deg, - rgba(2, 11, 15, 0.54), - transparent 16% 84%, - rgba(2, 11, 15, 0.58) - ), - linear-gradient( - 0deg, - rgba(0, 4, 8, 0.64), - transparent 20% 82%, - rgba(5, 15, 18, 0.38) - ); - opacity: 0.75; -} - -.khix-edge-gloom::before { - position: absolute; - inset: 0; - display: block; - content: ""; - pointer-events: none; - background: - radial-gradient(ellipse at 4% 12%, rgba(11, 27, 32, 0.42), transparent 33%), - radial-gradient(ellipse at 91% 22%, rgba(8, 28, 25, 0.38), transparent 34%), - radial-gradient(ellipse at 50% 98%, rgba(5, 17, 24, 0.58), transparent 45%); - opacity: 0.5; - animation: khix-edge-vignette-pulse 7.4s ease-in-out infinite alternate; -} - -.khix-mlh-badge { - position: fixed; - top: max(0px, env(safe-area-inset-top)); - right: 1.25rem; - z-index: 50; - display: block; - width: 74px; - min-width: 60px; - max-width: 100px; - filter: drop-shadow(0 0.7rem 1.1rem rgba(0, 0, 0, 0.44)) - drop-shadow(0 0 0.75rem rgba(255, 255, 255, 0.16)); -} - -.khix-mlh-badge img { - display: block; - width: 100%; - height: auto; -} - -.khix-hero { - position: relative; - z-index: 2; - display: flex; - align-items: flex-start; - justify-content: center; - width: 100%; - height: 100%; - min-height: 100%; - overflow: hidden; - padding: clamp(2.25rem, 4.5vh, 3.25rem) 1.25rem 2rem; - text-align: center; -} - -.khix-hero-content { - position: relative; - z-index: 4; - display: flex; - flex-direction: column; - align-items: center; - width: min(48rem, 100%); - max-width: calc(100vw - 2.5rem); -} - -.khix-logo-lockup { - position: relative; - isolation: isolate; - width: min(60vw, 40rem); -} - -.khix-logo-image { - width: 100%; - height: auto; -} - -.khix-event-date-lockup { - width: min(40vw, 26rem); - margin: -1.5rem auto 0; - display: flex; - flex-direction: column; - align-items: center; - color: #fffff6; - font-family: var(--font-khix), system-ui, sans-serif; - font-weight: 900; - font-synthesis-weight: auto; - letter-spacing: 0; - text-align: center; - text-transform: uppercase; -} - -.khix-event-date-text { - margin: 0; - font-size: clamp(1.45rem, 2.55vw, 2.3rem); - line-height: 0.94; - white-space: nowrap; - -webkit-text-stroke: 0.035em rgba(255, 255, 246, 0.9); - paint-order: stroke fill; - text-shadow: - 0 0 0.2rem rgba(255, 255, 255, 0.8), - 0 0 0.6rem rgba(214, 255, 86, 0.3), - 0 0.5rem 1rem rgba(0, 0, 0, 0.5); -} - -.khix-event-location-text { - margin: clamp(0.25rem, 0.55vh, 0.42rem) 0 0; - font-size: clamp(0.72rem, 1.28vw, 1.2rem); - line-height: 1; - opacity: 1; - white-space: nowrap; - -webkit-text-stroke: 0.024em rgba(255, 255, 246, 0.86); - paint-order: stroke fill; - text-shadow: - 0 0 0.16rem rgba(255, 255, 255, 0.7), - 0 0 0.5rem rgba(214, 255, 86, 0.24), - 0 0.45rem 0.85rem rgba(0, 0, 0, 0.42); -} - -.khix-gem-aura { - position: absolute; - top: 50.8%; - left: 50.8%; - z-index: 2; - width: clamp(10rem, 25vw, 24rem); - height: clamp(4.7rem, 10.5vw, 10rem); - pointer-events: none; - border-radius: 999px; - background: - radial-gradient( - circle at 50% 45%, - rgba(255, 245, 224, 0.34), - transparent 14% - ), - radial-gradient( - ellipse at 48% 52%, - rgba(255, 44, 105, 0.34), - rgba(127, 46, 184, 0.24) 44%, - transparent 68% - ); - filter: blur(0.95rem); - mix-blend-mode: plus-lighter; - opacity: 0.78; - translate: -50% -50%; -} - -.khix-gem-magic { - position: absolute; - top: 50.8%; - left: 50.8%; - z-index: 3; - width: clamp(12rem, 27vw, 30rem); - height: clamp(5rem, 10.8vw, 11rem); - pointer-events: none; - mix-blend-mode: normal; - opacity: 0.92; - translate: -50% -50%; -} - -.khix-gem-magic::before, -.khix-gem-magic::after, -.khix-gem-magic-wisp, -.khix-gem-magic-thread { - position: absolute; - display: block; - content: ""; - pointer-events: none; -} - -.khix-gem-magic::before { - inset: 18% 13% 14%; - border-radius: 999px; - background: radial-gradient( - ellipse at 48% 52%, - rgba(255, 62, 121, 0.26), - rgba(155, 58, 216, 0.2) 38%, - transparent 70% - ); - filter: blur(0.95rem); - opacity: 0.58; - animation: khix-gem-magic-breathe 7.4s ease-in-out infinite alternate; -} - -.khix-gem-magic::after { - top: 38%; - left: 25%; - width: 52%; - height: 20%; - border-radius: 999px; - background: linear-gradient( - 102deg, - transparent 0%, - rgba(160, 55, 219, 0.2) 24%, - rgba(255, 70, 116, 0.22) 50%, - rgba(171, 68, 232, 0.16) 70%, - transparent 100% - ); - filter: blur(0.35rem); - opacity: 0.7; - animation: khix-gem-magic-drift-mid 8.6s ease-in-out infinite alternate; -} - -.khix-gem-magic-wisp { - height: clamp(0.75rem, 1.1vw, 1.3rem); - border-radius: 999px; - background: radial-gradient( - ellipse at 50% 50%, - rgba(255, 76, 126, 0.34), - rgba(143, 55, 213, 0.26) 46%, - transparent 74% - ); - filter: blur(0.22rem); -} - -.khix-gem-magic-wisp-left { - top: 53%; - left: 11%; - width: 34%; - opacity: 0.42; - transform: rotate(-13deg); - animation: khix-gem-magic-drift-left 8.2s ease-in-out infinite alternate; -} - -.khix-gem-magic-wisp-mid { - top: 31%; - left: 35%; - width: 28%; - opacity: 0.34; - transform: rotate(-8deg); - animation: khix-gem-magic-drift-mid 9.8s ease-in-out -1.8s infinite alternate; -} - -.khix-gem-magic-wisp-right { - top: 47%; - right: 12%; - width: 32%; - opacity: 0.4; - transform: rotate(10deg); - animation: khix-gem-magic-drift-right 7.8s ease-in-out -2.6s infinite - alternate; -} - -.khix-gem-magic-thread { - width: clamp(3.2rem, 7vw, 7.4rem); - height: clamp(0.5rem, 0.85vw, 0.9rem); - border-radius: 999px; - background: linear-gradient( - 90deg, - transparent, - rgba(128, 42, 192, 0.34) 24%, - rgba(255, 58, 119, 0.46) 52%, - rgba(255, 160, 192, 0.22) 68%, - transparent - ); - filter: blur(0.16rem); - mix-blend-mode: screen; - opacity: 0.66; - will-change: opacity, transform; -} - -.khix-gem-magic-thread-left { - top: 53%; - left: 20%; - transform: rotate(-18deg); - animation: khix-gem-magic-thread-left 4.9s ease-in-out -0.6s infinite - alternate; -} - -.khix-gem-magic-thread-right { - top: 43%; - right: 20%; - transform: rotate(12deg); - animation: khix-gem-magic-thread-right 5.6s ease-in-out -1.7s infinite - alternate; -} - -.khix-gem-shard-shimmer { - position: absolute; - inset: 0; - z-index: 4; - pointer-events: none; - background: - radial-gradient( - ellipse at 51% 51%, - rgba(255, 53, 109, 0.24), - rgba(141, 47, 204, 0.16) 28%, - transparent 47% - ), - linear-gradient( - 112deg, - transparent 0 42%, - rgba(255, 70, 124, 0.16) 46%, - rgba(255, 235, 211, 0.38) 50%, - rgba(174, 64, 220, 0.2) 55%, - transparent 62% 100% - ); - background-position: - center center, - -32vw center; - background-repeat: no-repeat; - background-size: - cover, - 42vw 100%; - filter: blur(0.035rem); - mix-blend-mode: screen; - opacity: 0.68; - -webkit-mask-image: url("/khix-gem-shard-mask.png"); - mask-image: url("/khix-gem-shard-mask.png"); - -webkit-mask-position: center center; - mask-position: center center; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-size: cover; - mask-size: cover; - animation: khix-gem-shard-mask-shimmer 4.8s ease-in-out infinite alternate; - will-change: background-position, opacity; -} - -.khix-gem-shard-glint { - position: absolute; - top: 4%; - left: 27%; - z-index: 2; - width: 56%; - height: 38%; - border-radius: 999px; - background: linear-gradient( - 111deg, - transparent 0%, - rgba(255, 143, 152, 0.12) 23%, - rgba(255, 245, 219, 0.56) 41%, - rgba(255, 255, 248, 0.86) 49%, - rgba(255, 94, 124, 0.2) 58%, - transparent 76% - ); - filter: blur(0.06rem); - mix-blend-mode: screen; - opacity: 0.72; - transform: translate3d(0, 0, 0) skewX(-21deg) rotate(-6deg) scaleX(0.96); - animation: khix-gem-shard-glint 4.6s ease-in-out infinite alternate; - will-change: opacity, transform; -} - -.khix-gem-shard-spark { - position: absolute; - z-index: 3; - display: block; - width: clamp(0.28rem, 0.45vw, 0.52rem); - height: clamp(0.28rem, 0.45vw, 0.52rem); - border-radius: 999px; - background: rgba(255, 250, 219, 0.92); - box-shadow: - 0 0 0.45rem rgba(255, 236, 184, 0.62), - 0 0 0.95rem rgba(255, 71, 111, 0.38); - mix-blend-mode: screen; -} - -.khix-gem-shard-spark-top { - top: 12%; - left: 56%; - opacity: 0.42; - animation: khix-gem-shard-spark-top 5.2s ease-in-out -1.2s infinite alternate; -} - -.khix-gem-shard-spark-low { - top: 43%; - left: 82%; - width: clamp(0.18rem, 0.3vw, 0.36rem); - height: clamp(0.18rem, 0.3vw, 0.36rem); - opacity: 0.32; - animation: khix-gem-shard-spark-low 6.4s ease-in-out -2.4s infinite alternate; -} - -.khix-hero-actions { - position: absolute; - bottom: clamp(4.35rem, 6.3vh, 5.45rem); - left: 50%; - z-index: 5; - display: flex; - flex-wrap: wrap; - gap: 0.8rem; - align-items: center; - justify-content: center; - width: min(18.75rem, calc(100vw - 2rem)); - max-width: calc(100vw - 2rem); - margin-top: clamp(1rem, 2.4vh, 1.5rem); - opacity: 0; - translate: -50% 0; - animation: khix-control-fade 0.58s ease-out 0.42s forwards; -} - -.khix-social-links { - position: absolute; - bottom: clamp(0.78rem, 1.35vh, 1.1rem); - left: 50%; - z-index: 5; - display: flex; - gap: 0.58rem; - align-items: center; - justify-content: center; - width: max-content; - max-width: calc(100vw - 2rem); - translate: -50% 0; - opacity: 0; - animation: khix-control-fade 0.58s ease-out 0.58s forwards; -} - -.khix-social-link { - display: inline-flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - color: rgba(255, 255, 246, 0.76); - text-decoration: none; - filter: drop-shadow(0 0.48rem 0.72rem rgba(0, 0, 0, 0.44)) - drop-shadow(0 0 0.5rem rgba(214, 255, 86, 0.12)); - opacity: 0.84; - transition: - color 0.18s ease, - filter 0.18s ease, - opacity 0.18s ease; -} - -.khix-social-link:hover { - color: #fff7cf; - filter: drop-shadow(0 0.5rem 0.8rem rgba(0, 0, 0, 0.44)) - drop-shadow(0 0 0.72rem rgba(214, 255, 86, 0.28)); - opacity: 1; -} - -.khix-button { - position: relative; - isolation: isolate; - display: inline-flex; - flex: 1 1 0; - align-items: center; - justify-content: center; - min-width: 0; - min-height: 2.72rem; - padding: 0.68rem 1.8rem; - font-family: var(--font-khix), serif; - font-size: 0.85rem; - font-weight: 800; - letter-spacing: 0.05em; - line-height: 1; - text-decoration: none; - text-transform: uppercase; - white-space: nowrap; - background: transparent; - border: none; - outline: none; - z-index: 1; - transition: - filter 0.2s ease, - color 0.2s ease, - transform 0.2s ease; -} - -.khix-button::before { - content: ""; - position: absolute; - inset: 0; - z-index: 0; - pointer-events: none; - -webkit-clip-path: polygon( - 1rem 0, - calc(100% - 1rem) 0, - 100% 50%, - calc(100% - 1rem) 100%, - 1rem 100%, - 0 50% - ); - clip-path: polygon( - 1rem 0, - calc(100% - 1rem) 0, - 100% 50%, - calc(100% - 1rem) 100%, - 1rem 100%, - 0 50% - ); - transition: - background 0.2s ease, - opacity 0.2s ease; -} - -.khix-button:focus-visible { - outline: 2px solid rgba(255, 242, 163, 0.96); - outline-offset: 0.35rem; -} - -.khix-button-primary { - color: #1d090a; - filter: drop-shadow(0 0.5rem 0.8rem rgba(210, 255, 69, 0.3)); -} - -.khix-button-primary::before { - background: linear-gradient(135deg, #fff2a3, #d8ff5f 62%, #ff4867); -} - -.khix-button-label { - position: relative; - z-index: 2; - display: block; - width: 100%; - color: inherit; - text-align: center; -} - -.khix-button-secondary { - color: #fff7d2; - filter: drop-shadow(0 0 0.4rem rgba(255, 211, 106, 0.2)); -} - -.khix-button-secondary::before { - background: rgba(255, 211, 106, 0.48); -} - -.khix-button-secondary::after { - content: ""; - position: absolute; - inset: 1px; - z-index: 1; - pointer-events: none; - -webkit-clip-path: polygon( - calc(1rem - 1px) 0, - calc(100% - 1rem + 1px) 0, - 100% 50%, - calc(100% - 1rem + 1px) 100%, - calc(1rem - 1px) 100%, - 0 50% - ); - clip-path: polygon( - calc(1rem - 1px) 0, - calc(100% - 1rem + 1px) 0, - 100% 50%, - calc(100% - 1rem + 1px) 100%, - calc(1rem - 1px) 100%, - 0 50% - ); - background: rgba(18, 13, 22, 0.8); - backdrop-filter: blur(12px); - transition: background 0.2s ease; -} - -.khix-button-primary:hover { - filter: drop-shadow(0 0.8rem 1.2rem rgba(255, 83, 108, 0.4)); -} - -.khix-button-secondary:hover::before { - background: rgba(255, 211, 106, 0.78); -} - -.khix-button-secondary:hover::after { - background: rgba(42, 21, 29, 0.72); -} - -.khix-code-link { - position: absolute; - bottom: 2.75rem; - left: 50%; - z-index: 5; - color: rgba(255, 253, 241, 0.72); - font-family: - ui-monospace, "SFMono-Regular", "Cascadia Code", "Liberation Mono", Menlo, - Monaco, Consolas, monospace; - font-size: 0.68rem; - font-weight: 800; - letter-spacing: 0; - text-decoration: none; - text-shadow: 0 0.65rem 1.2rem rgba(0, 0, 0, 0.55); - text-transform: uppercase; - translate: -50% 0; - width: max-content; - max-width: calc(100vw - 2rem); - opacity: 0; - animation: khix-control-fade 0.58s ease-out 0.52s forwards; -} - -.khix-code-link:hover { - color: #fff2a3; -} - -.khix-enter-gate { - position: fixed; - inset: 0; - z-index: 70; - display: grid; - place-items: center; - padding: clamp(1rem, 4vw, 2rem); - border: 0; - color: #fffff6; - cursor: pointer; - appearance: none; - -webkit-appearance: none; - -webkit-tap-highlight-color: transparent; - touch-action: manipulation; - background: - radial-gradient( - ellipse at 50% 47%, - rgba(214, 255, 86, 0.24), - rgba(255, 69, 121, 0.11) 22%, - rgba(104, 48, 150, 0.08) 39%, - transparent 58% - ), - radial-gradient( - ellipse at 50% 57%, - rgba(255, 70, 120, 0.1), - transparent 46% - ), - rgba(5, 8, 10, 0.28); - text-align: center; - transition: - opacity 0.8s ease, - transform 1s cubic-bezier(0.16, 1, 0.3, 1), - visibility 0.8s ease; -} - -.khix-enter-gate::before { - position: absolute; - inset: 0; - content: ""; - pointer-events: none; - background: - radial-gradient( - ellipse at 50% 48%, - rgba(255, 255, 230, 0.16), - transparent 31% - ), - linear-gradient( - 115deg, - transparent 0%, - rgba(216, 255, 86, 0.08) 44%, - rgba(255, 72, 122, 0.1) 51%, - transparent 62% 100% - ); - filter: blur(0.22rem); - mix-blend-mode: screen; - opacity: 0.72; - animation: khix-enter-haze 5.8s ease-in-out infinite alternate; -} - -.khix-experience-entered .khix-enter-gate { - visibility: hidden; - pointer-events: none; - opacity: 0; - transform: scale(1.04); -} - -.khix-enter-gate-aura { - position: absolute; - pointer-events: none; - width: clamp(18rem, 48vw, 38rem); - aspect-ratio: 1.75; - border-radius: 999px; - background: radial-gradient( - ellipse at center, - rgba(213, 255, 82, 0.26), - rgba(255, 73, 122, 0.16) 33%, - rgba(117, 48, 160, 0.1) 51%, - transparent 76% - ); - filter: blur(2.35rem); - opacity: 0.7; - animation: khix-enter-aura 3.4s ease-in-out infinite alternate; -} - -.khix-enter-gate-content { - position: relative; - pointer-events: none; - display: inline-flex; - align-items: center; - justify-content: center; - width: min(32rem, calc(100vw - 2rem)); - font-family: - ui-monospace, "SFMono-Regular", "Cascadia Code", "Liberation Mono", Menlo, - Monaco, Consolas, monospace; - font-size: clamp(0.76rem, 1.16vw, 1.02rem); - font-weight: 900; - letter-spacing: 0; - line-height: 1.25; - text-transform: uppercase; - text-wrap: balance; - text-shadow: - 0 0 0.28rem rgba(255, 255, 246, 0.5), - 0 0 0.82rem rgba(214, 255, 86, 0.52), - 0 0 1.7rem rgba(255, 71, 119, 0.36), - 0 0.75rem 1.2rem rgba(0, 0, 0, 0.58); - filter: drop-shadow(0 0 0.7rem rgba(214, 255, 86, 0.34)); - animation: khix-enter-text-float 2.9s ease-in-out infinite alternate; -} - -.khix-enter-gate-content::after { - position: absolute; - inset: -0.75rem -1.5rem; - z-index: -1; - content: ""; - border-radius: 999px; - background: radial-gradient( - ellipse at center, - rgba(255, 255, 238, 0.08), - rgba(214, 255, 86, 0.08) 38%, - transparent 74% - ); - filter: blur(0.65rem); -} - -.khix-audio-toggle { - position: static; - flex: 0 0 2rem; - right: auto; - bottom: auto; - left: auto; - z-index: 80; - display: inline-flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - padding: 0; - border: 0 !important; - border-radius: 999px; - outline: 0 !important; - color: rgba(255, 255, 246, 0.76); - cursor: pointer; - appearance: none; - background: transparent !important; - box-shadow: none !important; - opacity: 0.84; - -webkit-tap-highlight-color: transparent; -} - -.khix-audio-toggle svg { - filter: drop-shadow(0 0.45rem 0.55rem rgba(0, 0, 0, 0.36)) - drop-shadow(0 0 0.42rem rgba(204, 255, 75, 0.16)); -} - -.khix-audio-toggle:hover, -.khix-audio-toggle:focus, -.khix-audio-toggle:focus-visible, -.khix-audio-toggle:active { - opacity: 0.9; - border: 0 !important; - outline: 0 !important; - background: transparent !important; - box-shadow: none !important; -} - -.khix-social-links .khix-audio-toggle-row { - position: static !important; - flex: 0 0 2rem; - right: auto !important; - bottom: auto !important; - left: auto !important; - width: 2rem; - height: 2rem; - color: rgba(255, 255, 246, 0.76); - line-height: 1; -} - -@keyframes khix-control-fade { - to { - opacity: 1; - } -} - -.khix-cursor-wisp { - position: fixed; - z-index: 95; - display: block; - width: var(--wisp-width); - height: var(--wisp-height); - pointer-events: none; - user-select: none; - transform: translate(-50%, -50%); - transform-origin: center; - animation: khix-cursor-wisp-drift 1.42s cubic-bezier(0.19, 1, 0.22, 1) - forwards; - filter: drop-shadow(0 0 0.7rem var(--wisp-glow)) - drop-shadow(0 0 1.55rem var(--wisp-edge)); - mix-blend-mode: normal; - will-change: opacity, transform; -} - -.khix-cursor-wisp::before { - position: absolute; - inset: -145% -175%; - content: ""; - border-radius: 999px; - background: radial-gradient( - ellipse at 50% 58%, - var(--wisp-glow) 0%, - var(--wisp-edge) 24%, - transparent 66% - ); - filter: blur(1.35rem); - opacity: 0.42; - animation: khix-cursor-wisp-aura 1.42s ease-out forwards; -} - -.khix-cursor-wisp::after { - position: absolute; - inset: 18% -68% -18%; - content: ""; - border-radius: 999px; - background: - radial-gradient(ellipse at 35% 55%, var(--wisp-edge) 0%, transparent 48%), - radial-gradient(ellipse at 70% 44%, var(--wisp-glow) 0%, transparent 42%); - filter: blur(0.95rem); - opacity: 0.34; - transform: rotate(var(--wisp-tail-rotate-end)); - animation: khix-cursor-wisp-fog 1.42s ease-out forwards; -} - -.khix-cursor-wisp-tail { - position: absolute; - right: 24%; - bottom: -4%; - width: 156%; - height: 72%; - border-radius: 999px; - background: - linear-gradient(96deg, transparent, var(--wisp-edge) 32%, transparent 74%), - radial-gradient(ellipse at 76% 50%, var(--wisp-glow), transparent 58%); - filter: blur(0.62rem); - opacity: 0.38; - transform: rotate(var(--wisp-tail-rotate)) scaleX(0.84); - transform-origin: 88% 50%; - animation: khix-cursor-wisp-tail 1.42s ease-out forwards; -} - -.khix-cursor-wisp-core { - position: absolute; - inset: 0 4% 4%; - border-radius: 999px; - background: - radial-gradient(ellipse at 52% 68%, var(--wisp-glow) 0%, transparent 54%), - radial-gradient(ellipse at 50% 88%, var(--wisp-edge) 0%, transparent 62%); - box-shadow: 0 0 1.15rem var(--wisp-glow); - opacity: 0.9; - transform: rotate(2deg) skewX(-10deg) scale(0.92, 1); - animation: khix-cursor-wisp-flicker 0.58s ease-in-out infinite alternate; -} - -.khix-cursor-wisp-core::before, -.khix-cursor-wisp-core::after { - position: absolute; - bottom: 10%; - content: ""; - border-radius: 999px; - pointer-events: none; - transform-origin: 50% 92%; -} - -.khix-cursor-wisp-core::before { - left: 34%; - width: 34%; - height: 88%; - background: - radial-gradient(ellipse at 50% 12%, var(--wisp-core) 0%, transparent 20%), - linear-gradient( - 180deg, - transparent 0%, - var(--wisp-glow) 24%, - var(--wisp-edge) 63%, - transparent 100% - ); - filter: blur(0.12rem); - opacity: 0.86; - transform: rotate(8deg) scaleY(0.94); - animation: khix-cursor-wisp-thread-main 0.68s ease-in-out infinite alternate; -} - -.khix-cursor-wisp-core::after { - left: 18%; - width: 54%; - height: 72%; - background: - radial-gradient(ellipse at 42% 20%, var(--wisp-core) 0%, transparent 18%), - linear-gradient( - 178deg, - transparent 0%, - var(--wisp-glow) 28%, - var(--wisp-edge) 66%, - transparent 100% - ); - filter: blur(0.22rem); - opacity: 0.56; - transform: rotate(-14deg) scaleY(0.86); - animation: khix-cursor-wisp-thread-side 0.78s ease-in-out infinite alternate; -} - -@keyframes khix-cursor-wisp-drift { - 0% { - opacity: 0; - transform: translate(-50%, -50%) scale(0.78) rotate(0deg); - } - - 16% { - opacity: 0.72; - } - - 54% { - opacity: 0.48; - transform: translate( - calc(-50% + var(--wisp-drift-x-mid)), - calc(-50% + var(--wisp-drift-y-mid)) - ) - scale(1.05) rotate(var(--wisp-rotate-mid)); - } - - 100% { - opacity: 0; - transform: translate( - calc(-50% + var(--wisp-drift-x)), - calc(-50% + var(--wisp-drift-y)) - ) - scale(0.42) rotate(var(--wisp-rotate)); - } -} - -@keyframes khix-cursor-wisp-aura { - 0% { - opacity: 0.28; - transform: scale(0.8); - } - - 38% { - opacity: 0.46; - transform: scale(1.16); - } - - 100% { - opacity: 0; - transform: scale(1.62); - } -} - -@keyframes khix-cursor-wisp-tail { - 0% { - opacity: 0.36; - transform: rotate(var(--wisp-tail-rotate)) scaleX(0.55); - } - - 52% { - opacity: 0.32; - transform: rotate(var(--wisp-tail-rotate-mid)) scaleX(1.26); - } - - 100% { - opacity: 0; - transform: rotate(var(--wisp-tail-rotate-end)) scaleX(0.2); - } -} - -@keyframes khix-cursor-wisp-fog { - 0% { - opacity: 0.2; - transform: translate3d(0, 0.12rem, 0) rotate(var(--wisp-tail-rotate)); - } - - 45% { - opacity: 0.34; - transform: translate3d(var(--wisp-fog-drift-x-mid), -0.18rem, 0) - rotate(var(--wisp-tail-rotate-mid)) scale(1.18); - } - - 100% { - opacity: 0; - transform: translate3d(var(--wisp-fog-drift-x-end), -0.5rem, 0) - rotate(var(--wisp-tail-rotate-end)) scale(1.42); - } -} - -@keyframes khix-cursor-wisp-flicker { - from { - filter: blur(0.08rem); - transform: rotate(-3deg) skewX(-12deg) scale(0.86, 0.94); - } - - to { - filter: blur(0.16rem); - transform: rotate(5deg) skewX(8deg) scale(1.04, 1.12); - } -} - -@keyframes khix-cursor-wisp-thread-main { - from { - opacity: 0.58; - transform: translate3d(-0.04rem, 0.08rem, 0) rotate(5deg) scale(0.82, 0.9); - } - - to { - opacity: 0.9; - transform: translate3d(0.08rem, -0.08rem, 0) rotate(13deg) scale(1, 1.12); - } -} - -@keyframes khix-cursor-wisp-thread-side { - from { - opacity: 0.36; - transform: translate3d(0.06rem, 0.08rem, 0) rotate(-18deg) scale(0.84, 0.88); - } - - to { - opacity: 0.68; - transform: translate3d(-0.08rem, -0.1rem, 0) rotate(-8deg) scale(1.05, 1.08); - } -} - -@keyframes khix-enter-haze { - from { - opacity: 0.52; - transform: translate3d(-0.3rem, 0.1rem, 0) scale(0.98); - } - - to { - opacity: 0.86; - transform: translate3d(0.35rem, -0.2rem, 0) scale(1.03); - } -} - -@keyframes khix-enter-aura { - from { - opacity: 0.62; - transform: scale(0.92); - } - - to { - opacity: 0.98; - transform: scale(1.07); - } -} - -@keyframes khix-enter-text-float { - from { - opacity: 0.82; - transform: translate3d(0, 0.12rem, 0); - } - - to { - opacity: 1; - transform: translate3d(0, -0.12rem, 0); - } -} - -@keyframes khix-edge-vignette-pulse { - from { - opacity: 0.38; - } - - to { - opacity: 0.62; - } -} - -@keyframes khix-gem-magic-breathe { - from { - opacity: 0.42; - transform: translate3d(0, 0, 0) scale(0.96); - } - - to { - opacity: 0.64; - transform: translate3d(0.25rem, -0.16rem, 0) scale(1.04); - } -} - -@keyframes khix-gem-magic-drift-left { - from { - opacity: 0.3; - transform: translate3d(0.2rem, 0.1rem, 0) rotate(-13deg) scaleX(0.92); - } - - to { - opacity: 0.5; - transform: translate3d(-1.1rem, -0.45rem, 0) rotate(-18deg) scaleX(1.08); - } -} - -@keyframes khix-gem-magic-drift-mid { - from { - opacity: 0.26; - transform: translate3d(-0.25rem, 0.08rem, 0) rotate(-8deg) scaleX(0.96); - } - - to { - opacity: 0.44; - transform: translate3d(0.45rem, -0.35rem, 0) rotate(-5deg) scaleX(1.08); - } -} - -@keyframes khix-gem-magic-drift-right { - from { - opacity: 0.28; - transform: translate3d(-0.1rem, 0.08rem, 0) rotate(10deg) scaleX(0.94); - } - - to { - opacity: 0.48; - transform: translate3d(1rem, -0.48rem, 0) rotate(15deg) scaleX(1.08); - } -} - -@keyframes khix-gem-magic-thread-left { - from { - opacity: 0.36; - transform: translate3d(0.35rem, 0.12rem, 0) rotate(-18deg) scaleX(0.78); - } - - to { - opacity: 0.72; - transform: translate3d(-0.45rem, -0.26rem, 0) rotate(-24deg) scaleX(1.05); - } -} - -@keyframes khix-gem-magic-thread-right { - from { - opacity: 0.32; - transform: translate3d(-0.2rem, 0.08rem, 0) rotate(12deg) scaleX(0.82); - } - - to { - opacity: 0.7; - transform: translate3d(0.55rem, -0.24rem, 0) rotate(18deg) scaleX(1.08); - } -} - -@keyframes khix-gem-shard-mask-shimmer { - from { - opacity: 0.48; - background-position: - center center, - -28vw center; - } - - to { - opacity: 0.74; - background-position: - center center, - 28vw center; - } -} - -@keyframes khix-gem-shard-glint { - from { - opacity: 0.52; - transform: translate3d(-0.18rem, 0.04rem, 0) skewX(-21deg) rotate(-6deg) - scaleX(0.9); - } - - to { - opacity: 0.86; - transform: translate3d(0.22rem, -0.1rem, 0) skewX(-21deg) rotate(-6deg) - scaleX(1.06); - } -} - -@keyframes khix-gem-shard-spark-top { - from { - opacity: 0.36; - transform: scale(0.72); - } - - to { - opacity: 0.74; - transform: scale(1.08); + width: 100%; + min-width: 100%; + min-height: 100%; + background: hsl(var(--background)); } -} -@keyframes khix-gem-shard-spark-low { - from { - opacity: 0.26; - transform: scale(0.7); + html { + scroll-behavior: smooth; + scroll-padding-top: 5.5rem; } - to { - opacity: 0.56; - transform: scale(1.04); + body { + @apply bg-background text-foreground; } } -.khix-experience-awaiting .khix-experience-stage *, -.khix-experience-awaiting .khix-experience-stage *::before, -.khix-experience-awaiting .khix-experience-stage *::after { - animation: none !important; -} - -.khix-experience-awaiting .khix-enter-gate::before, -.khix-experience-awaiting .khix-enter-gate-aura, -.khix-experience-awaiting .khix-enter-gate-content { - animation: none !important; -} - @media (prefers-reduced-motion: reduce) { - .khix-gem-magic::before, - .khix-gem-magic::after, - .khix-gem-magic-wisp, - .khix-gem-magic-thread, - .khix-gem-shard-shimmer, - .khix-gem-shard-glint, - .khix-gem-shard-spark, - .khix-edge-gloom::before, - .khix-enter-gate::before, - .khix-enter-gate-aura, - .khix-enter-gate-content, - .khix-cursor-wisp, - .khix-cursor-wisp::before, - .khix-cursor-wisp-tail, - .khix-cursor-wisp-core { - animation: none; - } - - .khix-cursor-wisp { - display: none; - } -} - -@media (min-width: 1024px) { - .khix-mlh-badge { - right: 3.125rem; - width: 92px; - } -} - -@media (max-width: 900px) { - .khix-background-layer { - background-position: center center; - } - - .khix-logo-lockup { - width: min(75vw, 30rem); - } - - .khix-event-date-lockup { - width: min(56vw, 20rem); - } - - .khix-event-date-text { - font-size: clamp(1.25rem, 2.45vw, 1.75rem); - } - - .khix-gem-aura { - top: 51%; - width: 15rem; - height: 8rem; - } -} - -@media (max-width: 520px) { - .khix-mlh-badge { - right: 0.75rem; - width: 60px; - } - - .khix-audio-toggle { - position: static; - flex: 0 0 2.75rem; - right: auto; - bottom: auto; - left: auto; - width: 2.75rem; - height: 2.75rem; - color: rgba(255, 255, 246, 0.76); - opacity: 0.84; - line-height: 1; - } - - .khix-social-links .khix-audio-toggle-row { - position: static !important; - flex: 0 0 2.75rem; - right: auto !important; - bottom: auto !important; - left: auto !important; - width: 2.75rem; - height: 2.75rem; - color: rgba(255, 255, 246, 0.76); - line-height: 1; - } - - .khix-hero { - padding: 5.35rem 1rem 1.25rem; - } - - .khix-background-layer { - background-position: center center; - } - - .khix-stage-accent-mushroom { - --khix-mushroom-left: calc(50% - clamp(7.3rem, 31vw, 10.2rem)); - } - - .khix-stage-accent-ladybug { - --khix-ladybug-width: clamp(1.05rem, 3.2vw, 1.35rem); - } - - .khix-logo-lockup { - width: min(85vw, 24rem); - } - - .khix-event-date-lockup { - width: min(62vw, 15.5rem); - margin-top: -0.75rem; - } - - .khix-event-date-text { - font-size: clamp(0.95rem, 4.2vw, 1.15rem); - text-shadow: - 0 0 0.1rem rgba(255, 255, 255, 0.4), - 0 0 0.3rem rgba(214, 255, 86, 0.15), - 0 0.5rem 1rem rgba(0, 0, 0, 0.5); - } - - .khix-event-location-text { - text-shadow: - 0 0 0.08rem rgba(255, 255, 255, 0.35), - 0 0 0.25rem rgba(214, 255, 86, 0.12), - 0 0.45rem 0.85rem rgba(0, 0, 0, 0.42); - } - - .khix-hero-actions { - bottom: calc(max(0.75rem, env(safe-area-inset-bottom)) + 5.65rem); - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - width: min(18rem, calc(100vw - 2.5rem)); - gap: 0.55rem; - } - - .khix-social-links { - bottom: max(0.75rem, env(safe-area-inset-bottom)); - gap: clamp(0.25rem, 1.8vw, 0.55rem); - } - - .khix-social-link { - width: 2.75rem; - height: 2.75rem; - } - - .khix-code-link { - bottom: calc(max(0.75rem, env(safe-area-inset-bottom)) + 2.9rem); - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 1.9rem; - padding: 0 0.45rem; - line-height: 1; - } - - .khix-button { - width: 100%; - min-height: 2.5rem; - padding: 0.58rem 0.65rem; - font-size: 0.62rem; - } - - .khix-code-link { - font-size: 0.62rem; - } -} - -@media (max-width: 520px) and (max-height: 720px) { - .khix-hero { - padding-top: 4.35rem; - } - - .khix-logo-lockup { - width: min(80vw, 20rem); - } - - .khix-event-date-lockup { - width: min(56vw, 14.5rem); - margin-top: -0.5rem; - } - - .khix-event-date-text { - font-size: clamp(0.9rem, 4vw, 1.08rem); - } - - .khix-hero-actions { - bottom: calc(max(0.62rem, env(safe-area-inset-bottom)) + 5.35rem); - gap: 0.5rem; - } - - .khix-social-links { - bottom: max(0.5rem, env(safe-area-inset-bottom)); - } - - .khix-code-link { - bottom: calc(max(0.5rem, env(safe-area-inset-bottom)) + 2.65rem); - } - - .khix-audio-toggle { - bottom: auto; + html { + scroll-behavior: auto; } } diff --git a/apps/khix/src/app/layout.tsx b/apps/khix/src/app/layout.tsx index 006ca4a26..ba3c33c12 100644 --- a/apps/khix/src/app/layout.tsx +++ b/apps/khix/src/app/layout.tsx @@ -1,84 +1,31 @@ import "./globals.css"; import type { Metadata, Viewport } from "next"; -import { Faculty_Glyphic } from "next/font/google"; -import WispCursor from "./_components/WispCursor"; -import { - eventJsonLd, - OG_IMAGE_ALT, - OG_IMAGE_HEIGHT, - OG_IMAGE_URL, - OG_IMAGE_WIDTH, - SEO_DESCRIPTION, - SEO_KEYWORDS, - SEO_TITLE, - SITE_URL, -} from "./seo"; - -const font = Faculty_Glyphic({ - weight: "400", - subsets: ["latin"], - variable: "--font-khix", -}); +import { SEO_DESCRIPTION, SEO_TITLE, SITE_URL } from "./seo"; export const metadata: Metadata = { metadataBase: new URL(SITE_URL), title: SEO_TITLE, description: SEO_DESCRIPTION, applicationName: "Knight Hacks IX", - keywords: SEO_KEYWORDS, alternates: { canonical: "/", }, robots: { index: true, follow: true, - googleBot: { - index: true, - follow: true, - "max-image-preview": "large", - "max-snippet": -1, - "max-video-preview": -1, - }, }, icons: { - icon: [{ url: "/khix-sigil.svg", type: "image/svg+xml" }], - shortcut: [{ url: "/khix-sigil.svg", type: "image/svg+xml" }], - }, - openGraph: { - type: "website", - locale: "en_US", - title: SEO_TITLE, - description: SEO_DESCRIPTION, - url: SITE_URL, - siteName: "Knight Hacks IX", - images: [ - { - url: OG_IMAGE_URL, - width: OG_IMAGE_WIDTH, - height: OG_IMAGE_HEIGHT, - alt: OG_IMAGE_ALT, - }, - ], - }, - twitter: { - card: "summary_large_image", - title: SEO_TITLE, - description: SEO_DESCRIPTION, - images: [ - { - url: OG_IMAGE_URL, - alt: OG_IMAGE_ALT, - }, - ], + icon: [{ url: "/favicon.svg", type: "image/svg+xml" }], + shortcut: [{ url: "/favicon.svg", type: "image/svg+xml" }], }, }; export const viewport: Viewport = { width: "device-width", initialScale: 1, - themeColor: "#070c10", + themeColor: "#eef6cf", viewportFit: "cover", }; @@ -89,16 +36,7 @@ export default function RootLayout({ }) { return ( - - {children} - - - + {children} ); } diff --git a/apps/khix/src/app/page.module.css b/apps/khix/src/app/page.module.css new file mode 100644 index 000000000..5991cbb0b --- /dev/null +++ b/apps/khix/src/app/page.module.css @@ -0,0 +1,32 @@ +.about { + position: relative; + z-index: 0; + min-height: 100vh; + min-height: 100svh; + overflow: hidden; + isolation: isolate; + background: + url("/KHIXHero/optimized/1_front.webp") center / cover no-repeat, + #050713; +} + +.aboutTitle { + position: relative; + z-index: 1; + margin: 0; + padding: clamp(9rem, 24svh, 16rem) 0 0 clamp(2rem, 12vw, 14rem); + color: rgba(255, 252, 252, 0.96); + font-family: var(--font-cormorant-garamond, Georgia), Georgia, serif; + font-size: clamp(2.5rem, 4.6vw, 4.4rem); + font-weight: 400; + line-height: 0.9; + letter-spacing: 0; +} + +@media (max-width: 760px) { + .aboutTitle { + padding-top: clamp(8rem, 24svh, 13rem); + padding-left: clamp(1.75rem, 12vw, 4.2rem); + font-size: clamp(2.2rem, 10vw, 3.4rem); + } +} diff --git a/apps/khix/src/app/page.tsx b/apps/khix/src/app/page.tsx index 57d1638b0..ec02dac48 100644 --- a/apps/khix/src/app/page.tsx +++ b/apps/khix/src/app/page.tsx @@ -1,499 +1,42 @@ -"use client"; - -import type { Variants } from "framer-motion"; -import { useCallback, useEffect, useReducer, useRef } from "react"; -import Image from "next/image"; -import { motion } from "framer-motion"; -import { - FaDiscord, - FaGithub, - FaInstagram, - FaLink, - FaLinkedin, - FaVolumeMute, - FaVolumeUp, -} from "react-icons/fa"; - -import { - APPLICATION_URL, - DISCORD_URL, - GITHUB_URL, - INSTAGRAM_URL, - LINKEDIN_URL, - LINKTREE_URL, - SEO_DESCRIPTION, - SPONSOR_URL, -} from "./seo"; - -const mlhCodeOfConductUrl = "https://mlh.io/code-of-conduct"; -const mlhTrustBadgeUrl = - "https://mlh.io/na?utm_source=na-hackathon&utm_medium=TrustBadge&utm_campaign=2027-season&utm_content=white"; -const mlhTrustBadgeImage = - "https://logged-assets.s3.amazonaws.com/trust-badge/2027/mlh-trust-badge-2027-white.svg"; - -const reveal: Variants = { - hidden: { opacity: 0, y: 20 }, - show: { - opacity: 1, - y: 0, - transition: { type: "tween", duration: 0.58, ease: "easeOut" }, - }, -}; - -const fadeReveal: Variants = { - hidden: { opacity: 0 }, - show: { - opacity: 1, - transition: { type: "tween", duration: 0.5, ease: "easeOut" }, - }, -}; - -const musicVolume = 0.16; -const birdsVolume = 0.055; - -const socialLinks = [ - { - label: "Discord", - href: DISCORD_URL, - Icon: FaDiscord, - }, +import type { NavbarLink, NavbarSocialLink } from "./_components/navbar"; +import { Navbar } from "./_components/navbar"; +import Hero from "./_components/sections/hero"; +import styles from "./page.module.css"; + +const NAV_LINKS = [ + { label: "Home", href: "#home" }, + { label: "About Us", href: "#about" }, + { label: "Tracks", href: "#tracks" }, + { label: "Speakers", href: "#speakers" }, + { label: "Sponsors", href: "#sponsors" }, + { label: "Team", href: "#team" }, + { label: "FAQ", href: "#faq" }, +] satisfies NavbarLink[]; + +const SOCIAL_LINKS = [ { - label: "Instagram", - href: INSTAGRAM_URL, - Icon: FaInstagram, + label: "Knight Hacks on Instagram", + href: "https://www.instagram.com/knighthacks/", + shortLabel: "ig", }, - { - label: "LinkedIn", - href: LINKEDIN_URL, - Icon: FaLinkedin, - }, - { - label: "GitHub", - href: GITHUB_URL, - Icon: FaGithub, - }, - { - label: "Linktree", - href: LINKTREE_URL, - Icon: FaLink, - }, -]; - -interface ExperienceState { - hasEntered: boolean; - music: "idle" | "playing" | "paused"; -} - -type ExperienceEvent = - | { type: "ENTER" } - | { type: "MUSIC_STARTED" } - | { type: "MUSIC_PAUSED" }; - -const initialExperienceState: ExperienceState = { - hasEntered: false, - music: "idle", -}; - -function prepareAudioTrack( - audio: HTMLAudioElement | null, - volume: number, -): HTMLAudioElement | null { - if (!audio) { - return null; - } - - audio.volume = volume; - audio.loop = true; - return audio; -} - -async function playAudioTracks( - primaryTrack: HTMLAudioElement | null, - secondaryTracks: HTMLAudioElement[] = [], -) { - if (!primaryTrack) { - return false; - } - - const tracks = [primaryTrack, ...secondaryTracks]; - const results = await Promise.allSettled(tracks.map((track) => track.play())); - const didStartPrimaryTrack = - results[0]?.status === "fulfilled" && !primaryTrack.paused; - - if (!didStartPrimaryTrack) { - tracks.forEach((track) => track.pause()); - } - - return didStartPrimaryTrack; -} - -function experienceReducer( - state: ExperienceState, - event: ExperienceEvent, -): ExperienceState { - switch (event.type) { - case "ENTER": - return { ...state, hasEntered: true }; - case "MUSIC_STARTED": - return { hasEntered: true, music: "playing" }; - case "MUSIC_PAUSED": - return { ...state, music: state.hasEntered ? "paused" : "idle" }; - default: - return state; - } -} +] satisfies NavbarSocialLink[]; export default function Page() { - const audioRef = useRef(null); - const birdsAudioRef = useRef(null); - const hasStartedExperienceRef = useRef(false); - const [experienceState, sendExperienceEvent] = useReducer( - experienceReducer, - initialExperienceState, - ); - const hasEntered = experienceState.hasEntered; - const isMusicPlaying = experienceState.music === "playing"; - - const startExperience = useCallback(async () => { - if (hasEntered || hasStartedExperienceRef.current) { - return; - } - - hasStartedExperienceRef.current = true; - sendExperienceEvent({ type: "ENTER" }); - - const musicTrack = prepareAudioTrack(audioRef.current, musicVolume); - const secondaryTracks = [ - prepareAudioTrack(birdsAudioRef.current, birdsVolume), - ].filter((track): track is HTMLAudioElement => Boolean(track)); - - const didStartAudio = await playAudioTracks(musicTrack, secondaryTracks); - if (didStartAudio) { - sendExperienceEvent({ type: "MUSIC_STARTED" }); - } else { - sendExperienceEvent({ type: "MUSIC_PAUSED" }); - } - }, [hasEntered]); - - useEffect(() => { - if (hasEntered) { - return; - } - - const handleKeyDown = (event: KeyboardEvent) => { - if (event.code !== "Space") { - return; - } - - event.preventDefault(); - void startExperience(); - }; - - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [hasEntered, startExperience]); - - const toggleMusic = async () => { - const musicTrack = prepareAudioTrack(audioRef.current, musicVolume); - const secondaryTracks = [ - prepareAudioTrack(birdsAudioRef.current, birdsVolume), - ].filter((track): track is HTMLAudioElement => Boolean(track)); - const tracks = musicTrack - ? [musicTrack, ...secondaryTracks] - : [...secondaryTracks]; - - if (tracks.length === 0) { - return; - } - - if (isMusicPlaying) { - tracks.forEach((track) => track.pause()); - sendExperienceEvent({ type: "MUSIC_PAUSED" }); - return; - } - - const didStartAudio = await playAudioTracks(musicTrack, secondaryTracks); - if (didStartAudio) { - sendExperienceEvent({ type: "MUSIC_STARTED" }); - } else { - sendExperienceEvent({ type: "MUSIC_PAUSED" }); - } - }; - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Knight Hacks IX - {SEO_DESCRIPTION} - - October 9-11th, 2026 - - University of Central Florida - - - - - - - Join Blade - - - Sponsor - - - - MLH Code of Conduct - - - {socialLinks.map(({ label, href, Icon }) => ( - - - - ))} - {hasEntered ? ( - - {isMusicPlaying ? ( - - ) : ( - - )} - - ) : null} - + <> + + + + + + About + - - - void startExperience()} - aria-label="Enter Knight Hacks IX" - > - - - Tap or press Space to enter the Knight Hacks experience - - - - ); -} - -function MlhTrustBadge({ hasEntered }: { hasEntered: boolean }) { - return ( - - {/* eslint-disable-next-line @next/next/no-img-element -- MLH serves the required trust badge as a remote SVG. */} - - + + > ); } diff --git a/apps/khix/src/app/seo.ts b/apps/khix/src/app/seo.ts index 2dd1552d3..2bae6c6eb 100644 --- a/apps/khix/src/app/seo.ts +++ b/apps/khix/src/app/seo.ts @@ -1,93 +1,3 @@ export const SITE_URL = "https://2026.knighthacks.org"; -export const EVENT_NAME = "Knight Hacks IX"; -export const EVENT_DATE_LABEL = "October 9-11, 2026"; -export const EVENT_START_DATE = "2026-10-09"; -export const EVENT_END_DATE = "2026-10-11"; -export const ORGANIZER_NAME = "Knight Hacks"; -export const ORGANIZER_URL = "https://knighthacks.org"; -export const CONTACT_EMAIL = "hack@knighthacks.org"; -export const APPLICATION_URL = "https://blade.knighthacks.org/"; -export const SPONSOR_URL = "https://blade.knighthacks.org/sponsor"; -export const DISCORD_URL = "https://discord.knighthacks.org/"; -export const INSTAGRAM_URL = "https://www.instagram.com/knighthacks/"; -export const LINKEDIN_URL = "https://www.linkedin.com/company/knight-hacks"; -export const GITHUB_URL = "https://github.com/knighthacks"; -export const LINKTREE_URL = "https://linktr.ee/knighthacks"; - -export const SEO_TITLE = - "Knight Hacks IX | 36-Hour UCF Hackathon in Orlando, Florida"; - -export const SEO_DESCRIPTION = - "Knight Hacks IX is a 36-hour in-person hackathon hosted by Knight Hacks at the University of Central Florida from October 9-11, 2026. Build projects with teams of up to four, attend workshops, meet mentors, join social events, and compete for prizes in Orlando, Florida."; - -export const OG_IMAGE_URL = "https://assets.knighthacks.org/IXSEO.webp"; -export const OG_IMAGE_WIDTH = 1200; -export const OG_IMAGE_HEIGHT = 630; -export const OG_IMAGE_ALT = - "Knight Hacks IX, a 36-hour UCF hackathon in Orlando, Florida"; - -export const SEO_KEYWORDS = [ - "Knight Hacks IX", - "Knight Hacks", - "UCF hackathon", - "University of Central Florida hackathon", - "Orlando hackathon", - "Florida hackathon", - "student hackathon Florida", - "college hackathon", - "beginner friendly hackathon", - "36 hour hackathon", - "MLH hackathon", - "coding competition Orlando", - "student tech event Orlando", - "hackathon workshops", - "hackathon prizes", - "hackathon mentors", -]; - -export const SOCIAL_PROFILE_URLS = [ - DISCORD_URL, - INSTAGRAM_URL, - LINKEDIN_URL, - GITHUB_URL, - LINKTREE_URL, -]; - -export const eventJsonLd = { - "@context": "https://schema.org", - "@type": "Event", - name: EVENT_NAME, - alternateName: "KH IX", - description: SEO_DESCRIPTION, - url: SITE_URL, - image: OG_IMAGE_URL, - startDate: EVENT_START_DATE, - endDate: EVENT_END_DATE, - eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode", - eventStatus: "https://schema.org/EventScheduled", - keywords: SEO_KEYWORDS.join(", "), - location: { - "@type": "Place", - name: "University of Central Florida", - address: { - "@type": "PostalAddress", - addressLocality: "Orlando", - addressRegion: "FL", - addressCountry: "US", - }, - }, - organizer: { - "@type": "Organization", - name: ORGANIZER_NAME, - url: ORGANIZER_URL, - email: CONTACT_EMAIL, - sameAs: SOCIAL_PROFILE_URLS, - }, - offers: { - "@type": "Offer", - url: APPLICATION_URL, - price: "0", - priceCurrency: "USD", - availability: "https://schema.org/InStock", - }, -}; +export const SEO_TITLE = "Knight Hacks IX"; +export const SEO_DESCRIPTION = "Knight Hacks IX"; diff --git a/apps/khix/tailwind.config.ts b/apps/khix/tailwind.config.ts index 721482e66..5100b51d6 100644 --- a/apps/khix/tailwind.config.ts +++ b/apps/khix/tailwind.config.ts @@ -1,39 +1,15 @@ import type { Config } from "tailwindcss"; -import defaultTheme from "tailwindcss/defaultTheme"; - -const { fontFamily } = defaultTheme; export default { darkMode: ["class", ".dark"], theme: { extend: { - backgroundImage: { - "custom-radial": `radial-gradient(121.83% 96.39% at 50.3% 9.28%, - rgba(216, 179, 254, 0.7) 0%, - rgba(216, 179, 254, 0.7) 0.01%, - rgba(216, 179, 254, 0.7) 5.55%, - rgba(216, 179, 254, 0.7) 14.99%, - rgba(133, 87, 180, 0.598491) 42.5%, - rgba(46, 22, 71, 0.7) 86.9%)`, - }, - filter: { - "blur-20": "blur(20px)", - "blur-25": "blur(25px)", - }, - brightness: { - 150: "1.5", - }, - transitionTimingFunction: { - "minor-spring": "cubic-bezier(0.18,0.89,0.82,1.04)", - }, colors: { background: "hsl(var(--background))", - 950: "#10182B", - cream: "#F4F4ED", + foreground: "hsl(var(--foreground))", border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", - foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", @@ -63,103 +39,6 @@ export default { foreground: "hsl(var(--card-foreground))", }, }, - boxShadow: { - impact: ` - 0px 0px 1879.49px #D8B5FE, - 0px 0px 1073.99px #D8B5FE, - 0px 0px 626.495px #D8B5FE, - 0px 0px 313.248px #D8B5FE, - 0px 0px 89.4993px #D8B5FE, - 0px 0px 44.7496px #D8B5FE`, - glow: "0 0 20px rgba(255, 204, 112, 0.7), 0 0 40px rgba(200, 80, 192, 0.5), 0 0 60px rgba(65, 88, 208, 0.3)", - glow2: - "0 0 20px rgba(50, 255, 50, 0.7), 0 0 40px rgba(20, 200, 20, 0.5), 0 0 60px rgba(5, 150, 5, 0.3)", - }, - fontFamily: { - sans: ["var(--font-khix)", ...fontFamily.sans], - mono: [...fontFamily.mono], - }, - animation: { - "infinite-scroll": "infinite-scroll 160s linear infinite", - "accordion-down": "accordion-down 0.5s cubic-bezier(0.87, 0, 0.13, 1)", - "accordion-up": "accordion-up 0.5s cubic-bezier(0.87, 0, 0.13, 1)", - "fade-up": "fade-up 2.8s ease-out forwards", - "fade-up-delay": "fade-up 2.8s ease-out forwards 0.4s", - first: "moveVertical 30s ease infinite", - second: "moveInCircle 20s reverse infinite", - third: "moveInCircle 40s linear infinite", - fourth: "moveHorizontal 40s ease infinite", - fifth: "moveInCircle 20s ease infinite", - }, - keyframes: { - "infinite-scroll": { - from: { transform: "translateX(0)" }, - to: { transform: "translateX(-180%)" }, - }, - "reveal-up": { - "0%": { opacity: "0", transform: "translateY(80%)" }, - "100%": { opacity: "1", transform: "translateY(0)" }, - }, - "reveal-down": { - "0%": { opacity: "0", transform: "translateY(-80%)" }, - "100%": { opacity: "1", transform: "translateY(0)" }, - }, - "content-blur": { - "0%": { filter: "blur(0.3rem)" }, - "100%": { filter: "blur(0)" }, - }, - "rotate-full": { - "0%": { transform: "rotate(0deg)" }, - "100%": { transform: "rotate(360deg)" }, - }, - "fade-up": { - "0%": { opacity: "0", transform: "translateY(20px)" }, - "100%": { opacity: "1", transform: "translateY(0)" }, - }, - "accordion-down": { - from: { - height: "0", - opacity: "0", - padding: "0", - margin: "0", - }, - to: { - height: "var(--radix-accordion-content-height)", - opacity: "1", - padding: "1rem", - margin: "0.5rem 0", - }, - }, - "accordion-up": { - from: { - height: "var(--radix-accordion-content-height)", - opacity: "1", - padding: "1rem", - margin: "0.5rem 0", - }, - to: { - height: "0", - opacity: "0", - padding: "0", - margin: "0", - }, - }, - moveHorizontal: { - "0%": { transform: "translateX(-50%) translateY(-10%)" }, - "50%": { transform: "translateX(50%) translateY(10%)" }, - "100%": { transform: "translateX(-50%) translateY(-10%)" }, - }, - moveInCircle: { - "0%": { transform: "rotate(0deg)" }, - "50%": { transform: "rotate(180deg)" }, - "100%": { transform: "rotate(360deg)" }, - }, - moveVertical: { - "0%": { transform: "translateY(-50%)" }, - "50%": { transform: "translateY(50%)" }, - "100%": { transform: "translateY(-50%)" }, - }, - }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", diff --git a/docs/PARALLAX.md b/docs/PARALLAX.md new file mode 100644 index 000000000..fdb1b9040 --- /dev/null +++ b/docs/PARALLAX.md @@ -0,0 +1,77 @@ +# Parallax Implementation Notes + +Temporary handoff for agents working on KHIX or future Forge parallax sections. +This documents the Chromium fix from Carlos's `39fc6795` commit so it can be +reused without rediscovering the same failure mode. + +## What Broke + +The KHIX hero parallax broke when the art stack was treated like a page +background instead of a normal hero section. The problematic shape was: + +- a long or sticky scroll zone that made the hero behave like a background; +- a large transformed `.art` container using `scale(...)`; +- per-layer parallax values that did not account for that container scale; +- unthrottled pointer writes fighting Chromium's rendering pipeline. + +The visible symptoms were rapid inline style churn in DevTools, unstable +Chromium parallax, and the hero reading as the entire page background instead +of a section that can scroll away. + +## Carlos's Fix + +Keep the hero itself as a normal section: + +- `section#home` owns the hero and stays in document flow. +- `.stage` is `position: relative`, not sticky, for a normal hero. +- The hero is `100vh` / `100svh`; extra page scroll should come from following + sections, not from a fake hero scroll zone. + +Keep the artwork full-bleed inside the hero without scaling the whole `.art` +container: + +- Store the old visual scale as `--khix-art-motion-scale: 0.64`. +- Use a smaller art inset, currently `inset: -6.32svh -3.76vw`. +- Do not put `transform: scale(...)` on `.art`. +- Apply `--khix-art-motion-scale` inside each layer's parallax transform math. + +Scope motion variables to the stage and throttle writes: + +- Write `--khix-hero-pointer-x`, `--khix-hero-pointer-y`, and + `--khix-hero-scroll-progress` on the hero stage. +- Coalesce pointer movement with `requestAnimationFrame`. +- Only write motion values when they changed by a meaningful epsilon. +- Cancel pending pointer frames during cleanup and when reduced motion is + enabled. + +## Reusable Pattern + +For future parallax components: + +1. Make the section normal page flow first. Avoid sticky or oversized scroll + zones unless the design explicitly calls for pinned storytelling. +2. Keep the art stack visually oversized with insets, not by scaling the whole + container. +3. If the art needs a visual scale, put that number in a CSS variable and + multiply the pointer and scroll offsets by it. +4. Put all layer movement on `transform: translate3d(...)` and keep depth + values as CSS vars on each layer. +5. Use one local stage for motion vars. Do not inject global `:root` motion + state for a component-specific parallax. +6. Throttle pointer writes with `requestAnimationFrame`; throttle scroll writes + too, and skip writes when the value barely changed. +7. Respect `prefers-reduced-motion` by resetting pointer and scroll progress to + `0` and disabling nonessential animated layers. + +## Verification Checklist + +Before handing off a parallax change: + +- The hero/section scrolls away like a normal section when content follows it. +- The stage is not sticky unless pinned scroll storytelling is the requirement. +- Chrome/Chromium DevTools does not show unrelated title/glow/art nodes + constantly changing inline `style` attributes. +- Pointer movement still changes layer transforms differently by depth. +- Scroll changes `--khix-hero-scroll-progress` without causing layout shifts. +- `pnpm --filter=@forge/khix lint` and + `pnpm --filter=@forge/khix typecheck` pass for KHIX changes. diff --git a/package.json b/package.json index 3c04489a0..c8a994c0a 100644 --- a/package.json +++ b/package.json @@ -47,5 +47,783 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3" } - } + }, + "version": "1.0.0", + "description": "\r \r \r \r Forge\r ", + "main": "index.js", + "directories": { + "doc": "docs" + }, + "dependencies": { + "abort-controller": "^3.0.0", + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "afinn-165": "^1.0.4", + "afinn-165-financialmarketnews": "^3.0.0", + "agent-base": "^7.1.4", + "aggregate-error": "^3.1.0", + "ajv": "^6.14.0", + "ansi-escapes": "^4.3.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^4.3.0", + "any-base": "^1.1.0", + "apparatus": "^0.0.10", + "argparse": "^2.0.1", + "aria-hidden": "^1.2.6", + "aria-query": "^5.3.2", + "array-buffer-byte-length": "^1.0.2", + "array-includes": "^3.1.9", + "array-union": "^2.1.0", + "array.prototype.findlast": "^1.2.5", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "arraybuffer.prototype.slice": "^1.0.4", + "ast-types-flow": "^0.0.8", + "async": "^3.2.6", + "async-function": "^1.0.0", + "available-typed-arrays": "^1.0.7", + "await-to-js": "^3.0.0", + "axe-core": "^4.11.1", + "axobject-query": "^4.1.0", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "bail": "^2.0.2", + "balanced-match": "^1.0.2", + "barcode-detector": "^3.0.8", + "base64-js": "^1.5.1", + "baseline-browser-mapping": "^2.10.0", + "basic-auth": "^2.0.1", + "better-auth": "^1.4.19", + "better-call": "^1.1.8", + "bignumber.js": "^9.3.1", + "block-stream2": "^2.1.0", + "bmp-ts": "^1.0.9", + "boolbase": "^1.0.0", + "bowser": "^2.14.1", + "brace-expansion": "^5.0.3", + "braces": "^3.0.3", + "browser-or-node": "^2.1.1", + "browserslist": "^4.28.1", + "bson": "^6.10.4", + "buffer": "^6.0.3", + "buffer-crc32": "^1.0.0", + "buffer-equal-constant-time": "^1.0.1", + "buffer-from": "^1.1.2", + "call-bind": "^1.0.8", + "call-bind-apply-helpers": "^1.0.2", + "call-bound": "^1.0.4", + "callsites": "^3.1.0", + "camel-case": "^3.0.0", + "camelcase": "^6.3.0", + "caniuse-lite": "^1.0.30001774", + "canvas-confetti": "^1.9.4", + "ccount": "^2.0.1", + "chalk": "^4.1.2", + "change-case": "^3.1.0", + "character-entities": "^2.0.2", + "character-entities-html4": "^2.1.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.1", + "chardet": "^2.1.1", + "class-variance-authority": "^0.7.1", + "classnames": "^2.5.1", + "clean-stack": "^2.2.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "client-only": "^0.0.1", + "cliui": "^6.0.0", + "clsx": "^2.1.1", + "cluster-key-slot": "^1.1.2", + "cmdk": "^1.1.1", + "color-convert": "^2.0.1", + "color-name": "^1.1.4", + "comma-separated-tokens": "^2.0.3", + "commander": "^7.2.0", + "concat-map": "^0.0.1", + "constant-case": "^2.0.0", + "convert-source-map": "^2.0.0", + "cookie": "^0.6.0", + "cookie-es": "^1.2.2", + "copy-anything": "^4.0.5", + "core-js-compat": "^3.48.0", + "core-js-pure": "^3.48.0", + "corser": "^2.0.1", + "cosmiconfig": "^8.3.6", + "cross-spawn": "^7.0.6", + "crossws": "^0.3.5", + "css-select": "^5.2.2", + "css-tree": "^2.3.1", + "css-what": "^6.2.2", + "csso": "^5.0.5", + "csstype": "^3.2.3", + "csv-parse": "^6.1.0", + "csv-stringify": "^6.6.0", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-ease": "^3.0.1", + "d3-format": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "d3-time-format": "^4.1.0", + "d3-timer": "^3.0.1", + "damerau-levenshtein": "^1.0.8", + "data-uri-to-buffer": "^4.0.1", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0", + "debug": "^4.4.3", + "decamelize": "^1.2.0", + "decimal.js-light": "^2.5.1", + "decode-named-character-reference": "^1.3.0", + "decode-uri-component": "^0.2.2", + "deep-is": "^0.1.4", + "deepmerge": "^4.3.1", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "defu": "^6.1.4", + "del": "^5.1.0", + "dequal": "^2.0.3", + "destr": "^2.0.5", + "detect-libc": "^2.1.2", + "detect-node-es": "^1.1.0", + "devlop": "^1.1.0", + "dijkstrajs": "^1.0.3", + "dir-glob": "^3.0.1", + "discord-api-types": "^0.38.40", + "discord.js": "^14.25.1", + "do-not-zip": "^1.0.0", + "doctrine": "^2.1.0", + "dom-lib": "^3.3.2", + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "dot-case": "^2.1.1", + "dotenv": "^16.6.1", + "dotenv-cli": "^11.0.0", + "dotenv-expand": "^12.0.3", + "drizzle-kit": "^0.31.9", + "drizzle-orm": "^0.45.1", + "drizzle-zod": "^0.8.3", + "dunder-proto": "^1.0.1", + "eastasianwidth": "^0.2.0", + "ecdsa-sig-formatter": "^1.0.11", + "electron-to-chromium": "^1.5.302", + "emoji-regex": "^9.2.2", + "enhanced-resolve": "^5.19.0", + "entities": "^4.5.0", + "error-ex": "^1.3.4", + "es-abstract": "^1.24.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-iterator-helpers": "^1.2.2", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-shim-unscopables": "^1.1.0", + "es-to-primitive": "^1.3.0", + "es-toolkit": "^1.44.0", + "esbuild": "^0.25.12", + "esbuild-register": "^3.6.0", + "escalade": "^3.2.0", + "escape-string-regexp": "^1.0.5", + "eslint": "^10.0.2", + "eslint-config-next": "^16.2.7", + "eslint-import-resolver-node": "^0.3.9", + "eslint-import-resolver-typescript": "^3.10.1", + "eslint-module-utils": "^2.12.1", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-turbo": "^2.8.11", + "eslint-scope": "^9.1.1", + "eslint-visitor-keys": "^5.0.1", + "espree": "^10.4.0", + "esquery": "^1.7.0", + "esrecurse": "^4.3.0", + "estraverse": "^5.3.0", + "estree-util-is-identifier-name": "^3.0.0", + "esutils": "^2.0.3", + "event-target-shim": "^5.0.1", + "eventemitter3": "^5.0.4", + "events": "^3.3.0", + "exif-parser": "^0.1.12", + "extend": "^3.0.2", + "external-editor": "^3.1.0", + "fast-deep-equal": "^3.1.3", + "fast-glob": "^3.3.1", + "fast-json-stable-stringify": "^2.1.0", + "fast-levenshtein": "^2.0.6", + "fast-sha256": "^1.3.0", + "fast-xml-parser": "^5.3.6", + "fastq": "^1.20.1", + "fdir": "^6.5.0", + "fetch-blob": "^3.2.0", + "figures": "^3.2.0", + "file-entry-cache": "^8.0.0", + "file-type": "^16.5.4", + "fill-range": "^7.1.1", + "filter-obj": "^1.1.0", + "find-up": "^5.0.0", + "flat-cache": "^4.0.1", + "flatted": "^3.3.3", + "follow-redirects": "^1.15.11", + "for-each": "^0.3.5", + "foreground-child": "^3.3.1", + "formdata-polyfill": "^4.0.10", + "framer-motion": "^12.34.3", + "fs.realpath": "^1.0.0", + "function-bind": "^1.1.2", + "function.prototype.name": "^1.1.8", + "functions-have-names": "^1.2.3", + "gaxios": "^7.1.3", + "gcp-metadata": "^8.1.2", + "geist": "^1.7.0", + "generator-function": "^2.0.1", + "generic-pool": "^3.9.0", + "gensync": "^1.0.0-beta.2", + "get-caller-file": "^2.0.5", + "get-intrinsic": "^1.3.0", + "get-nonce": "^1.0.1", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "get-tsconfig": "^4.13.6", + "gifwrap": "^0.10.1", + "glob": "^7.2.3", + "glob-parent": "^5.1.2", + "globals": "^14.0.0", + "globalthis": "^1.0.4", + "globby": "^10.0.2", + "google-auth-library": "^10.6.1", + "google-logging-utils": "^1.1.3", + "googleapis": "^171.4.0", + "googleapis-common": "^8.0.1", + "gopd": "^1.2.0", + "graceful-fs": "^4.2.11", + "gsap": "^3.14.2", + "h3": "^1.15.5", + "handlebars": "^4.7.8", + "has-bigints": "^1.1.0", + "has-flag": "^4.0.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2", + "hast-util-to-jsx-runtime": "^2.3.6", + "hast-util-whitespace": "^3.0.0", + "he": "^1.2.0", + "header-case": "^1.0.1", + "hermes-estree": "^0.25.1", + "hermes-parser": "^0.25.1", + "html-encoding-sniffer": "^3.0.0", + "html-to-text": "^9.0.5", + "html-url-attributes": "^3.0.1", + "htmlparser2": "^8.0.2", + "http-proxy": "^1.18.1", + "http-server": "^14.1.1", + "https-proxy-agent": "^7.0.6", + "iconv-lite": "^0.7.2", + "ieee754": "^1.2.1", + "ignore": "^5.3.2", + "image-q": "^4.0.0", + "immer": "^11.1.4", + "import-fresh": "^3.3.1", + "imurmurhash": "^0.1.4", + "indent-string": "^4.0.0", + "inflight": "^1.0.6", + "inherits": "^2.0.4", + "inline-style-parser": "^0.2.7", + "inquirer": "^7.3.3", + "internal-slot": "^1.1.0", + "internmap": "^2.0.3", + "ip-address": "^10.1.0", + "ipaddr.js": "^2.3.0", + "iron-webcrypto": "^1.2.1", + "is-alphabetical": "^2.0.1", + "is-alphanumerical": "^2.0.1", + "is-arguments": "^1.2.0", + "is-array-buffer": "^3.0.5", + "is-arrayish": "^0.2.1", + "is-async-function": "^2.1.1", + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.2", + "is-bun-module": "^2.0.0", + "is-callable": "^1.2.7", + "is-core-module": "^2.16.1", + "is-data-view": "^1.0.2", + "is-date-object": "^1.1.0", + "is-decimal": "^2.0.1", + "is-extglob": "^2.1.1", + "is-finalizationregistry": "^1.1.1", + "is-fullwidth-code-point": "^3.0.0", + "is-generator-function": "^1.1.2", + "is-glob": "^4.0.3", + "is-hexadecimal": "^2.0.1", + "is-lower-case": "^1.1.3", + "is-map": "^2.0.3", + "is-negative-zero": "^2.0.3", + "is-number": "^7.0.0", + "is-number-object": "^1.1.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.3", + "is-plain-obj": "^4.1.0", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-upper-case": "^1.1.2", + "is-weakmap": "^2.0.2", + "is-weakref": "^1.1.1", + "is-weakset": "^2.0.4", + "is-what": "^5.5.0", + "isarray": "^2.0.5", + "isbinaryfile": "^4.0.10", + "isexe": "^2.0.0", + "iterator.prototype": "^1.1.5", + "jackspeak": "^3.4.3", + "jimp": "^1.6.0", + "jiti": "^2.6.1", + "joi": "^17.4.2", + "jose": "^6.1.3", + "jpeg-js": "^0.4.4", + "js-base64": "^3.7.8", + "js-tokens": "^4.0.0", + "js-yaml": "^4.1.1", + "jsesc": "^3.1.0", + "json-bigint": "^1.0.0", + "json-buffer": "^3.0.1", + "json-parse-even-better-errors": "^2.3.1", + "json-schema-to-zod": "^2.7.0", + "json-schema-traverse": "^0.4.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "json5": "^2.2.3", + "jsx-ast-utils": "^3.3.5", + "jwa": "^2.0.1", + "jws": "^4.0.1", + "kareem": "^2.6.3", + "keyv": "^4.5.4", + "kysely": "^0.28.11", + "language-subtag-registry": "^0.3.23", + "language-tags": "^1.0.9", + "leac": "^0.6.0", + "levn": "^0.4.1", + "lightningcss": "^1.31.1", + "lightningcss-win32-x64-msvc": "^1.31.1", + "lines-and-columns": "^1.2.4", + "locate-path": "^6.0.0", + "lodash": "^4.17.23", + "lodash.debounce": "^4.0.8", + "lodash.get": "^4.4.2", + "lodash.snakecase": "^4.1.1", + "longest-streak": "^3.1.0", + "loose-envify": "^1.4.0", + "lower-case": "^1.1.4", + "lower-case-first": "^1.0.2", + "lru-cache": "^5.1.1", + "lucide-react": "^0.575.0", + "magic-bytes.js": "^1.13.0", + "magic-string": "^0.30.21", + "math-intrinsics": "^1.1.0", + "mdast-util-from-markdown": "^2.0.3", + "mdast-util-mdx-expression": "^2.0.1", + "mdast-util-mdx-jsx": "^3.2.0", + "mdast-util-mdxjs-esm": "^2.0.1", + "mdast-util-phrasing": "^4.1.0", + "mdast-util-to-hast": "^13.2.1", + "mdast-util-to-markdown": "^2.1.2", + "mdast-util-to-string": "^4.0.0", + "mdn-data": "^2.0.30", + "memjs": "^1.3.2", + "memoize-one": "^5.2.1", + "memory-pager": "^1.5.0", + "merge2": "^1.4.1", + "micromark": "^4.0.2", + "micromark-core-commonmark": "^2.0.3", + "micromark-factory-destination": "^2.0.1", + "micromark-factory-label": "^2.0.1", + "micromark-factory-space": "^2.0.1", + "micromark-factory-title": "^2.0.1", + "micromark-factory-whitespace": "^2.0.1", + "micromark-util-character": "^2.1.1", + "micromark-util-chunked": "^2.0.1", + "micromark-util-classify-character": "^2.0.1", + "micromark-util-combine-extensions": "^2.0.1", + "micromark-util-decode-numeric-character-reference": "^2.0.2", + "micromark-util-decode-string": "^2.0.1", + "micromark-util-encode": "^2.0.1", + "micromark-util-html-tag-name": "^2.0.1", + "micromark-util-normalize-identifier": "^2.0.1", + "micromark-util-resolve-all": "^2.0.1", + "micromark-util-sanitize-uri": "^2.0.1", + "micromark-util-subtokenize": "^2.1.0", + "micromark-util-symbol": "^2.0.1", + "micromark-util-types": "^2.0.2", + "micromatch": "^4.0.8", + "mime": "^3.0.0", + "mime-db": "^1.52.0", + "mime-types": "^2.1.35", + "mimic-fn": "^2.1.0", + "minimatch": "^10.2.4", + "minimist": "^1.2.8", + "minio": "^8.0.6", + "minipass": "^7.1.3", + "mkdirp": "^0.5.6", + "mongodb": "^6.20.0", + "mongodb-connection-string-url": "^3.0.2", + "mongoose": "^8.23.0", + "motion-dom": "^12.34.3", + "motion-utils": "^12.29.2", + "mpath": "^0.9.0", + "mquery": "^5.0.0", + "ms": "^2.1.3", + "mute-stream": "^2.0.0", + "nanoid": "^3.3.11", + "nanostores": "^1.1.1", + "napi-postinstall": "^0.3.4", + "natural": "^8.1.0", + "natural-compare": "^1.4.0", + "neo-async": "^2.6.2", + "next": "^16.2.7", + "next-themes": "^0.4.6", + "no-case": "^2.3.2", + "node-cron": "^4.2.1", + "node-domexception": "^1.0.0", + "node-exports-info": "^1.6.0", + "node-fetch": "^3.3.2", + "node-forge": "^1.3.3", + "node-mock-http": "^1.0.4", + "node-plop": "^0.26.3", + "node-releases": "^2.0.27", + "nth-check": "^2.1.1", + "oauth4webapi": "^2.17.0", + "object-assign": "^4.1.1", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "omggif": "^1.0.10", + "once": "^1.4.0", + "onetime": "^5.1.2", + "opener": "^1.5.2", + "optionator": "^0.9.4", + "os-tmpdir": "^1.0.2", + "own-keys": "^1.0.1", + "p-limit": "^3.1.0", + "p-locate": "^5.0.0", + "p-map": "^3.0.0", + "p-try": "^2.2.0", + "package-json-from-dist": "^1.0.1", + "pako": "^1.0.11", + "param-case": "^2.1.1", + "parent-module": "^1.0.1", + "parse-bmfont-ascii": "^1.0.6", + "parse-bmfont-binary": "^1.0.6", + "parse-bmfont-xml": "^1.1.6", + "parse-entities": "^4.0.2", + "parse-json": "^5.2.0", + "parseley": "^0.12.1", + "pascal-case": "^2.0.1", + "passkit-generator": "^3.5.7", + "path-case": "^2.1.1", + "path-exists": "^4.0.0", + "path-is-absolute": "^1.0.1", + "path-key": "^3.1.1", + "path-parse": "^1.0.7", + "path-scurry": "^1.11.1", + "path-type": "^4.0.0", + "peberminta": "^0.9.0", + "peek-readable": "^4.1.0", + "pg": "^8.19.0", + "pg-cloudflare": "^1.3.0", + "pg-connection-string": "^2.11.0", + "pg-int8": "^1.0.1", + "pg-pool": "^3.12.0", + "pg-protocol": "^1.12.0", + "pg-types": "^2.2.0", + "pgpass": "^1.0.5", + "picocolors": "^1.1.1", + "picomatch": "^4.0.3", + "pixelmatch": "^5.3.0", + "pngjs": "^6.0.0", + "portfinder": "^1.0.38", + "possible-typed-array-names": "^1.1.0", + "postal-mime": "^2.7.3", + "postcss": "^8.5.6", + "postgres": "^3.4.8", + "postgres-array": "^2.0.0", + "postgres-bytea": "^1.0.1", + "postgres-date": "^1.0.7", + "postgres-interval": "^1.2.0", + "preact": "^10.11.3", + "preact-render-to-string": "^5.2.3", + "prelude-ls": "^1.2.1", + "prettier-plugin-tailwindcss": "^0.7.2", + "pretty-format": "^3.8.0", + "process": "^0.11.10", + "prop-types": "^15.8.1", + "property-information": "^7.1.0", + "punycode": "^2.3.1", + "qr.js": "^0.0.0", + "qrcode": "^1.5.4", + "qs": "^6.15.0", + "query-string": "^7.1.3", + "queue-microtask": "^1.2.3", + "radix3": "^1.1.2", + "react": "^19.2.4", + "react-day-picker": "^9.14.0", + "react-dom": "^19.2.4", + "react-hook-form": "^7.71.2", + "react-icons": "^5.5.0", + "react-is": "^16.13.1", + "react-markdown": "^10.1.0", + "react-qr-code": "^2.0.18", + "react-qr-reader": "^3.0.0-beta-1", + "react-redux": "^9.2.0", + "react-remove-scroll": "^2.7.2", + "react-remove-scroll-bar": "^2.3.8", + "react-style-singleton": "^2.2.3", + "react-textarea-autosize": "^8.5.9", + "react-use-set": "^1.0.0", + "react-window": "^1.8.11", + "readable-stream": "^3.6.2", + "readable-web-to-node-stream": "^3.0.4", + "recharts": "^3.7.0", + "redis": "^4.7.1", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reflect.getprototypeof": "^1.0.10", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regexp.prototype.flags": "^1.5.4", + "regexpu-core": "^6.4.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "requires-port": "^1.0.0", + "reselect": "^5.1.1", + "resend": "^6.9.2", + "resolve": "^1.22.11", + "resolve-from": "^4.0.0", + "resolve-pkg-maps": "^1.0.0", + "restore-cursor": "^3.1.0", + "reusify": "^1.1.0", + "rimraf": "^3.0.2", + "rollup": "^2.80.0", + "rou3": "^0.7.12", + "rsuite": "^6.1.2", + "rsuite-table": "^5.19.2", + "run-async": "^2.4.1", + "run-parallel": "^1.2.0", + "rxjs": "^6.6.7", + "safe-array-concat": "^1.1.3", + "safe-buffer": "^5.2.1", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "safe-stable-stringify": "^2.5.0", + "safer-buffer": "^2.1.2", + "sax": "^1.4.4", + "scheduler": "^0.27.0", + "schema-typed": "^2.4.2", + "sdp": "^3.2.1", + "secure-compare": "^3.0.1", + "selderee": "^0.11.0", + "semver": "^6.3.1", + "sentence-case": "^2.1.1", + "server-only": "^0.0.1", + "set-blocking": "^2.0.0", + "set-cookie-parser": "^2.7.2", + "set-function-length": "^1.2.2", + "set-function-name": "^2.0.2", + "set-proto": "^1.0.0", + "sharp": "^0.34.5", + "shebang-command": "^2.0.0", + "shebang-regex": "^3.0.0", + "side-channel": "^1.1.0", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2", + "sift": "^17.1.3", + "signal-exit": "^4.1.0", + "simple-xml-to-json": "^1.2.3", + "slash": "^3.0.0", + "smart-buffer": "^4.2.0", + "snake-case": "^2.1.0", + "socks": "^2.8.7", + "sonner": "^2.0.7", + "source-map": "^0.6.1", + "source-map-js": "^1.2.1", + "source-map-support": "^0.5.21", + "space-separated-tokens": "^2.0.2", + "sparse-bitfield": "^3.0.3", + "split-on-first": "^1.1.0", + "split2": "^4.2.0", + "stable-hash": "^0.0.5", + "standardwebhooks": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "stopwords-iso": "^1.1.0", + "stream-chain": "^2.2.5", + "stream-json": "^1.9.1", + "strict-uri-encode": "^2.0.0", + "string-width": "^4.2.3", + "string-width-cjs": "^4.2.3", + "string.prototype.includes": "^2.0.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "stringify-entities": "^4.0.4", + "string_decoder": "^1.3.0", + "strip-ansi": "^6.0.1", + "strip-ansi-cjs": "^6.0.1", + "strip-bom": "^3.0.0", + "strip-json-comments": "^3.1.1", + "stripe": "^20.4.0", + "strnum": "^2.1.2", + "strtok3": "^6.3.0", + "style-to-js": "^1.1.21", + "style-to-object": "^1.0.14", + "styled-jsx": "^5.1.6", + "superjson": "^2.2.6", + "supports-color": "^7.2.0", + "supports-preserve-symlinks-flag": "^1.0.0", + "svg-parser": "^2.0.4", + "svgo": "^3.3.2", + "svix": "^1.84.1", + "swap-case": "^1.1.2", + "sylvester": "^0.0.12", + "tabs": "^0.2.0", + "tagged-tag": "^1.0.0", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.1", + "tapable": "^2.3.0", + "through": "^2.3.8", + "through2": "^4.0.2", + "tiny-invariant": "^1.3.3", + "tinycolor2": "^1.6.0", + "tinyglobby": "^0.2.15", + "title-case": "^2.1.1", + "tmp": "^0.0.33", + "to-regex-range": "^5.0.1", + "token-types": "^4.2.1", + "tr46": "^5.1.1", + "trim-lines": "^3.0.1", + "trough": "^2.2.0", + "ts-api-utils": "^2.4.0", + "ts-custom-error": "^3.3.1", + "ts-mixer": "^6.0.4", + "tsconfig-paths": "^3.15.0", + "tslib": "^2.8.1", + "tsx": "^4.21.0", + "turbo-windows-64": "^2.8.11", + "tw-animate-css": "^1.4.0", + "type-check": "^0.4.0", + "type-fest": "^0.21.3", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "typescript-eslint": "^8.56.1", + "ufo": "^1.6.3", + "uglify-js": "^3.19.3", + "unbox-primitive": "^1.1.0", + "uncrypto": "^0.1.3", + "underscore": "^1.13.8", + "undici": "^6.21.3", + "undici-types": "^7.18.2", + "unicode-canonical-property-names-ecmascript": "^2.0.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1", + "unicode-property-aliases-ecmascript": "^2.2.0", + "unified": "^11.0.5", + "union": "^0.5.0", + "unist-util-is": "^6.0.1", + "unist-util-position": "^5.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "unrs-resolver": "^1.11.1", + "update-browserslist-db": "^1.2.3", + "upper-case": "^1.1.3", + "upper-case-first": "^1.1.2", + "uri-js": "^4.4.1", + "url-join": "^4.0.1", + "url-template": "^2.0.8", + "use-callback-ref": "^1.3.3", + "use-composed-ref": "^1.4.0", + "use-isomorphic-layout-effect": "^1.2.1", + "use-latest": "^1.3.0", + "use-sidecar": "^1.1.3", + "use-sync-external-store": "^1.6.0", + "utif2": "^4.1.0", + "util": "^0.12.5", + "util-deprecate": "^1.0.2", + "uuid": "^9.0.1", + "vaul": "^1.1.2", + "vfile": "^6.0.3", + "vfile-message": "^4.0.3", + "victory-vendor": "^37.3.6", + "web-encoding": "^1.1.5", + "web-streams-polyfill": "^3.3.3", + "webidl-conversions": "^7.0.0", + "webrtc-adapter": "^9.0.3", + "whatwg-encoding": "^2.0.0", + "whatwg-url": "^14.2.0", + "which": "^2.0.2", + "which-boxed-primitive": "^1.1.1", + "which-builtin-type": "^1.2.1", + "which-collection": "^1.0.2", + "which-module": "^2.0.1", + "which-typed-array": "^1.1.20", + "word-wrap": "^1.2.5", + "wordnet-db": "^3.1.14", + "wordwrap": "^1.0.0", + "wrap-ansi": "^6.2.0", + "wrap-ansi-cjs": "^7.0.0", + "wrappy": "^1.0.2", + "ws": "^8.19.0", + "xml-parse-from-string": "^1.0.1", + "xml2js": "^0.6.2", + "xmlbuilder": "^11.0.1", + "xtend": "^4.0.2", + "y18n": "^4.0.3", + "yallist": "^3.1.1", + "yargs": "^15.4.1", + "yargs-parser": "^18.1.3", + "yocto-queue": "^0.1.0", + "yoctocolors-cjs": "^2.1.3", + "zod": "^4.3.6", + "zod-validation-error": "^4.0.2", + "zustand": "^4.5.7", + "zwitch": "^2.0.4", + "zxing-wasm": "^2.2.4" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/KnightHacks/forge.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/KnightHacks/forge/issues" + }, + "homepage": "https://github.com/KnightHacks/forge#readme" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25879b92f..7304daf0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -141,7 +141,7 @@ importers: version: 10.0.2(jiti@2.6.1) eslint-config-next: specifier: ^16.2.7 - version: 16.2.7(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) + version: 16.2.7(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) postcss: specifier: ^8.5.6 version: 8.5.6 @@ -255,7 +255,7 @@ importers: version: 6.6.0 geist: specifier: ^1.7.0 - version: 1.7.0(next@16.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + version: 1.7.0(next@16.2.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) google-auth-library: specifier: ^10.6.1 version: 10.6.1 @@ -391,7 +391,7 @@ importers: version: 12.34.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) geist: specifier: ^1.7.0 - version: 1.7.0(next@16.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + version: 1.7.0(next@16.2.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) gsap: specifier: ^3.14.2 version: 3.14.2 @@ -655,7 +655,7 @@ importers: version: 12.34.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) geist: specifier: ^1.7.0 - version: 1.7.0(next@16.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + version: 1.7.0(next@16.2.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) gsap: specifier: ^3.14.2 version: 3.14.2 @@ -749,7 +749,7 @@ importers: version: 12.34.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) geist: specifier: ^1.7.0 - version: 1.7.0(next@16.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + version: 1.7.0(next@16.2.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) gsap: specifier: ^3.14.2 version: 3.14.2 @@ -814,12 +814,15 @@ importers: apps/khix: dependencies: + '@gsap/react': + specifier: ^2.1.2 + version: 2.1.2(gsap@3.14.2)(react@19.2.4) framer-motion: specifier: ^12.34.3 version: 12.34.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - lucide-react: - specifier: ^0.575.0 - version: 0.575.0(react@19.2.4) + gsap: + specifier: ^3.14.2 + version: 3.14.2 next: specifier: ^16.2.7 version: 16.2.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -829,9 +832,6 @@ importers: react-dom: specifier: ^19.2.4 version: 19.2.4(react@19.2.4) - react-icons: - specifier: ^5.5.0 - version: 5.5.0(react@19.2.4) devDependencies: '@forge/eslint-config': specifier: workspace:* @@ -869,9 +869,6 @@ importers: tailwindcss: specifier: 'catalog:' version: 4.2.1 - tw-animate-css: - specifier: ^1.4.0 - version: 1.4.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -1438,7 +1435,7 @@ importers: version: 16.1.6 eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(eslint@10.0.2(jiti@2.6.1)) + version: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: specifier: ^6.10.2 version: 6.10.2(eslint@10.0.2(jiti@2.6.1)) @@ -13269,13 +13266,13 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@16.2.7(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3): + eslint-config-next@16.2.7(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 16.2.7 eslint: 10.0.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@10.0.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@10.0.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@10.0.2(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@10.0.2(jiti@2.6.1)) @@ -13297,7 +13294,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@10.0.2(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -13308,22 +13305,31 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.0.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) eslint: 10.0.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@10.0.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1)): + dependencies: + debug: 3.2.7 + optionalDependencies: + eslint: 10.0.2(jiti@2.6.1) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13334,7 +13340,7 @@ snapshots: doctrine: 2.1.0 eslint: 10.0.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.0.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -13352,7 +13358,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(eslint@10.0.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13363,7 +13369,7 @@ snapshots: doctrine: 2.1.0 eslint: 10.0.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1)))(eslint@10.0.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -13672,7 +13678,7 @@ snapshots: transitivePeerDependencies: - supports-color - geist@1.7.0(next@16.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): + geist@1.7.0(next@16.2.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): dependencies: next: 16.2.7(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
{SEO_DESCRIPTION}
October 9-11th, 2026
- University of Central Florida -