diff --git a/package-lock.json b/package-lock.json index 6bd8e40..f8f19ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.1.0", "dependencies": { "@prisma/client": "^6.16.2", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/postcss": "^4.1.13", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -1058,6 +1060,152 @@ "@prisma/debug": "6.16.2" } }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1394,7 +1542,7 @@ "version": "19.1.13", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1404,7 +1552,7 @@ "version": "19.1.9", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -2535,7 +2683,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -6477,6 +6625,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index bf0e1fb..d7b7027 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ }, "dependencies": { "@prisma/client": "^6.16.2", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/postcss": "^4.1.13", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/public/placeholder.svg b/public/placeholder.svg new file mode 100644 index 0000000..faea08c --- /dev/null +++ b/public/placeholder.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 7a3e99b..359bc22 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -3,11 +3,82 @@ @custom-variant dark (&:is(.dark *)); +:root { + --background: oklch(0.08 0 0); + --foreground: oklch(0.98 0 0); + --card: oklch(0.12 0 0); + --card-foreground: oklch(0.98 0 0); + --popover: oklch(0.12 0 0); + --popover-foreground: oklch(0.98 0 0); + --primary: oklch(0.7 0.15 280); + --primary-foreground: oklch(0.98 0 0); + --secondary: oklch(0.18 0 0); + --secondary-foreground: oklch(0.98 0 0); + --muted: oklch(0.15 0 0); + --muted-foreground: oklch(0.65 0 0); + --accent: oklch(0.7 0.15 280); + --accent-foreground: oklch(0.98 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.98 0 0); + --border: oklch(0.25 0 0); + --input: oklch(0.18 0 0); + --ring: oklch(0.7 0.15 280); + --chart-1: oklch(0.7 0.15 280); + --chart-2: oklch(0.6 0.12 200); + --chart-3: oklch(0.65 0.18 120); + --chart-4: oklch(0.75 0.2 60); + --chart-5: oklch(0.8 0.15 340); + --radius: 0.75rem; + --sidebar: oklch(0.12 0 0); + --sidebar-foreground: oklch(0.98 0 0); + --sidebar-primary: oklch(0.7 0.15 280); + --sidebar-primary-foreground: oklch(0.98 0 0); + --sidebar-accent: oklch(0.18 0 0); + --sidebar-accent-foreground: oklch(0.98 0 0); + --sidebar-border: oklch(0.25 0 0); + --sidebar-ring: oklch(0.7 0.15 280); + --font-inter: 'Inter', sans-serif; + --font-jetbrains-mono: 'JetBrains Mono', monospace; +} + +.dark { + --background: oklch(0.08 0 0); + --foreground: oklch(0.98 0 0); + --card: oklch(0.12 0 0); + --card-foreground: oklch(0.98 0 0); + --popover: oklch(0.12 0 0); + --popover-foreground: oklch(0.98 0 0); + --primary: oklch(0.7 0.15 280); + --primary-foreground: oklch(0.98 0 0); + --secondary: oklch(0.18 0 0); + --secondary-foreground: oklch(0.98 0 0); + --muted: oklch(0.15 0 0); + --muted-foreground: oklch(0.65 0 0); + --accent: oklch(0.7 0.15 280); + --accent-foreground: oklch(0.98 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.98 0 0); + --border: oklch(0.25 0 0); + --input: oklch(0.18 0 0); + --ring: oklch(0.7 0.15 280); + --chart-1: oklch(0.7 0.15 280); + --chart-2: oklch(0.6 0.12 200); + --chart-3: oklch(0.65 0.18 120); + --chart-4: oklch(0.75 0.2 60); + --chart-5: oklch(0.8 0.15 340); + --sidebar: oklch(0.12 0 0); + --sidebar-foreground: oklch(0.98 0 0); + --sidebar-primary: oklch(0.7 0.15 280); + --sidebar-primary-foreground: oklch(0.98 0 0); + --sidebar-accent: oklch(0.18 0 0); + --sidebar-accent-foreground: oklch(0.98 0 0); + --sidebar-border: oklch(0.25 0 0); + --sidebar-ring: oklch(0.7 0.15 280); +} + @theme inline { - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); + --font-sans: var(--font-inter); + --font-mono: var(--font-jetbrains-mono); --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); @@ -23,6 +94,7 @@ --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); @@ -31,6 +103,10 @@ --color-chart-3: var(--chart-3); --color-chart-4: var(--chart-4); --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); @@ -41,80 +117,127 @@ --color-sidebar-ring: var(--sidebar-ring); } -:root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.13 0.028 261.692); - --card: oklch(1 0 0); - --card-foreground: oklch(0.13 0.028 261.692); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.13 0.028 261.692); - --primary: oklch(0.21 0.034 264.665); - --primary-foreground: oklch(0.985 0.002 247.839); - --secondary: oklch(0.967 0.003 264.542); - --secondary-foreground: oklch(0.21 0.034 264.665); - --muted: oklch(0.967 0.003 264.542); - --muted-foreground: oklch(0.551 0.027 264.364); - --accent: oklch(0.967 0.003 264.542); - --accent-foreground: oklch(0.21 0.034 264.665); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.928 0.006 264.531); - --input: oklch(0.928 0.006 264.531); - --ring: oklch(0.707 0.022 261.325); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0.002 247.839); - --sidebar-foreground: oklch(0.13 0.028 261.692); - --sidebar-primary: oklch(0.21 0.034 264.665); - --sidebar-primary-foreground: oklch(0.985 0.002 247.839); - --sidebar-accent: oklch(0.967 0.003 264.542); - --sidebar-accent-foreground: oklch(0.21 0.034 264.665); - --sidebar-border: oklch(0.928 0.006 264.531); - --sidebar-ring: oklch(0.707 0.022 261.325); -} - -.dark { - --background: oklch(0.13 0.028 261.692); - --foreground: oklch(0.985 0.002 247.839); - --card: oklch(0.21 0.034 264.665); - --card-foreground: oklch(0.985 0.002 247.839); - --popover: oklch(0.21 0.034 264.665); - --popover-foreground: oklch(0.985 0.002 247.839); - --primary: oklch(0.928 0.006 264.531); - --primary-foreground: oklch(0.21 0.034 264.665); - --secondary: oklch(0.278 0.033 256.848); - --secondary-foreground: oklch(0.985 0.002 247.839); - --muted: oklch(0.278 0.033 256.848); - --muted-foreground: oklch(0.707 0.022 261.325); - --accent: oklch(0.278 0.033 256.848); - --accent-foreground: oklch(0.985 0.002 247.839); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.551 0.027 264.364); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.21 0.034 264.665); - --sidebar-foreground: oklch(0.985 0.002 247.839); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0.002 247.839); - --sidebar-accent: oklch(0.278 0.033 256.848); - --sidebar-accent-foreground: oklch(0.985 0.002 247.839); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.551 0.027 264.364); -} - @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground; + background: + radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.15) 0%, transparent 50%), + radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.1) 0%, transparent 50%), + linear-gradient(135deg, #0a0a0a 0%, #1a0a2e 25%, #16213e 50%, #0f3460 75%, #0a0a0a 100%); + background-size: + 100% 100%, + 100% 100%, + 100% 100%, + 400% 400%; + animation: gradientShift 15s ease infinite; + position: relative; + } + + @keyframes gradientShift { + 0%, + 100% { + background-position: + 0% 0%, + 0% 0%, + 0% 0%, + 0% 50%; + } + 25% { + background-position: + 0% 0%, + 0% 0%, + 0% 0%, + 100% 50%; + } + 50% { + background-position: + 0% 0%, + 0% 0%, + 0% 0%, + 100% 100%; + } + 75% { + background-position: + 0% 0%, + 0% 0%, + 0% 0%, + 0% 100%; + } + } + + body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: + radial-gradient(2px 2px at 20px 30px, rgba(255, 255, 255, 0.15), transparent), + radial-gradient(2px 2px at 40px 70px, rgba(255, 255, 255, 0.1), transparent), + radial-gradient(1px 1px at 90px 40px, rgba(255, 255, 255, 0.1), transparent), + radial-gradient(1px 1px at 130px 80px, rgba(255, 255, 255, 0.1), transparent), + radial-gradient(2px 2px at 160px 30px, rgba(255, 255, 255, 0.1), transparent); + background-repeat: repeat; + background-size: 200px 100px; + animation: sparkle 20s linear infinite; + pointer-events: none; + z-index: -1; + } + + @keyframes sparkle { + from { + transform: translateY(0px); + } + to { + transform: translateY(-100px); + } + } +} + +.glow-purple { + box-shadow: + 0 0 20px rgba(168, 85, 247, 0.4), + 0 0 40px rgba(168, 85, 247, 0.2), + 0 0 80px rgba(168, 85, 247, 0.1); +} + +.text-glow { + text-shadow: + 0 0 10px rgba(168, 85, 247, 0.5), + 0 0 20px rgba(168, 85, 247, 0.3), + 0 0 40px rgba(168, 85, 247, 0.1); +} + +.futuristic-grid { + background-image: + linear-gradient(rgba(120, 119, 198, 0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(120, 119, 198, 0.1) 1px, transparent 1px); + background-size: 60px 60px; + position: relative; +} + +.futuristic-grid::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient(circle at 50% 50%, rgba(120, 119, 198, 0.05) 0%, transparent 70%); + animation: pulse 4s ease-in-out infinite; +} + +@keyframes pulse { + 0%, + 100% { + opacity: 0.3; + } + 50% { + opacity: 0.8; } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 85aeba5..614bbc8 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,9 +1,21 @@ import type { Metadata } from 'next' +import { Inter, JetBrains_Mono } from 'next/font/google' import './globals.css' +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', +}) + +const jetbrainsMono = JetBrains_Mono({ + subsets: ['latin'], + variable: '--font-jetbrains-mono', +}) + export const metadata: Metadata = { - title: 'Code Showcase Studio', - description: 'Code Showcase Studio', + title: 'Code Showcase Studio - Platform Project Mahasiswa POLNES', + description: + 'Platform untuk memamerkan project-project dari mahasiswa Politeknik Negeri Samarinda', authors: [ { name: 'Klub Pemrograman TI POLNES', @@ -18,8 +30,8 @@ export default function RootLayout({ children: React.ReactNode }>) { return ( - - {children} + + {children} ) } diff --git a/src/app/page.tsx b/src/app/page.tsx index 9811c85..c71bc43 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,13 +1,26 @@ -import styles from './page.module.css' +import CTASection from '@/features/landing/components/cta-section' +import Footer from '@/features/landing/components/footer' +import Header from '@/features/landing/components/header' +import HeroSection from '@/features/landing/components/hero-section' +import LeaderboardSection from '@/features/landing/components/leaderboard-section' +import TimelineSection from '@/features/landing/components/timeline-section' +// import { StatsSection } from '@/components/stats-section' +// import { FeaturesSection } from '@/components/features-section' -export default function Home() { +export default function HomePage() { return ( - <> -
-

Code Showcase Studio

-
+
+
- {/*
*/} - +
+ + {/* */} + {/* */} + + + +
+
) } diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 0000000..be68eb0 --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -0,0 +1,41 @@ +'use client' + +import * as React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' + +import { cn } from '@/lib/utils' + +function Avatar({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..043b577 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,37 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span' + + return +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..a6f6db0 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: + 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2 has-[>svg]:px-3', + sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', + lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', + icon: 'size-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<'button'> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : 'button' + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..0d49a20 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,75 @@ +import * as React from 'react' + +import { cn } from '@/lib/utils' + +function Card({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<'div'>) { + return
+} + +function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent } diff --git a/src/features/landing/components/cta-section.tsx b/src/features/landing/components/cta-section.tsx new file mode 100644 index 0000000..b8b1edc --- /dev/null +++ b/src/features/landing/components/cta-section.tsx @@ -0,0 +1,43 @@ +import { Button } from '@/components/ui/button' +import { ArrowRight, Sparkles } from 'lucide-react' +import Link from 'next/link' + +export default function CTASection() { + return ( +
+
+ +
+
+
+ + Bergabung Sekarang +
+ +

+ Siap untuk showcase project terbaikmu? +

+ +

+ Bergabunglah dengan komunitas developer Klub Pemrograman TI POLNES dan mulai memamerkan + karya-karya terbaikmu. Dapatkan feedback, networking, dan kesempatan karir yang lebih + baik. +

+ +
+ + + +
+ + {/*
+

Gratis untuk semua mahasiswa POLNES • Tidak ada biaya tersembunyi

+
*/} +
+
+
+ ) +} diff --git a/src/features/landing/components/footer.tsx b/src/features/landing/components/footer.tsx new file mode 100644 index 0000000..c2892b4 --- /dev/null +++ b/src/features/landing/components/footer.tsx @@ -0,0 +1,127 @@ +import { Code, Github, Youtube } from 'lucide-react' +import Link from 'next/link' + +export default function Footer() { + return ( + + ) +} diff --git a/src/features/landing/components/header.tsx b/src/features/landing/components/header.tsx new file mode 100644 index 0000000..08de44e --- /dev/null +++ b/src/features/landing/components/header.tsx @@ -0,0 +1,79 @@ +'use client' + +import { Button } from '@/components/ui/button' +import { Code, Menu } from 'lucide-react' +import { useEffect, useState } from 'react' +import Link from 'next/link' + +export default function Header() { + const [bgColor, setBgColor] = useState('bg-transparent') // Initial background color + + const handleScroll = () => { + if (window.scrollY > 200) { + setBgColor('backdrop-blur-sm bg-background/60') + } else { + setBgColor('bg-transparent') + } + } + + useEffect(() => { + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) + + return ( +
+
+
+
+
+ +
+
+

Code Showcase Studio

+

KPTI Project Platform

+
+
+ + + + +
+
+
+ ) +} diff --git a/src/features/landing/components/hero-section.tsx b/src/features/landing/components/hero-section.tsx new file mode 100644 index 0000000..ada95a6 --- /dev/null +++ b/src/features/landing/components/hero-section.tsx @@ -0,0 +1,46 @@ +import { Button } from '@/components/ui/button' +import { ArrowRight, Play, Sparkles } from 'lucide-react' + +export default function HeroSection() { + return ( +
+
+ +
+
+
+ + Platform Showcase Terdepan +
+ +

+ Platform untuk memamerkan project. +

+ +

+ Bergabunglah dengan komunitas Klub Pemrograman TI POLNES. Showcase project terbaikmu, + dapatkan feedback, dan bersaing di leaderboard bulanan. +

+ +
+ + {/* */} +
+ + {/*
+

+ Dipercaya oleh 500+ mahasiswa + POLNES +

+
*/} +
+
+
+ ) +} diff --git a/src/features/landing/components/leaderboard-section.tsx b/src/features/landing/components/leaderboard-section.tsx new file mode 100644 index 0000000..fd8be3f --- /dev/null +++ b/src/features/landing/components/leaderboard-section.tsx @@ -0,0 +1,227 @@ +import { Card } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { Trophy, Medal, Award, TrendingUp, Heart, Eye } from 'lucide-react' + +const topAuthors = [ + { + rank: 1, + name: 'Ahmad Rizki', + nim: '2021001', + projects: 12, + totalLikes: 1250, + totalViews: 15600, + avatar: '/placeholder.svg?height=40&width=40', + }, + { + rank: 2, + name: 'Siti Nurhaliza', + nim: '2021002', + projects: 10, + totalLikes: 980, + totalViews: 12400, + avatar: '/placeholder.svg?height=40&width=40', + }, + { + rank: 3, + name: 'Budi Santoso', + nim: '2021003', + projects: 8, + totalLikes: 850, + totalViews: 11200, + avatar: '/placeholder.svg?height=40&width=40', + }, + { + rank: 4, + name: 'Maya Sari', + nim: '2021004', + projects: 9, + totalLikes: 720, + totalViews: 9800, + avatar: '/placeholder.svg?height=40&width=40', + }, + { + rank: 5, + name: 'Doni Pratama', + nim: '2021005', + projects: 7, + totalLikes: 650, + totalViews: 8900, + avatar: '/placeholder.svg?height=40&width=40', + }, +] + +const topProjects = [ + { + rank: 1, + title: 'Smart Campus IoT System', + author: 'Ahmad Rizki', + likes: 245, + views: 3200, + tech: ['React', 'Node.js', 'IoT'], + }, + { + rank: 2, + title: 'E-Learning Platform', + author: 'Siti Nurhaliza', + likes: 198, + views: 2800, + tech: ['Vue.js', 'Laravel', 'MySQL'], + }, + { + rank: 3, + title: 'Mobile Attendance App', + author: 'Budi Santoso', + likes: 167, + views: 2400, + tech: ['Flutter', 'Firebase'], + }, +] + +const getRankIcon = (rank: number) => { + switch (rank) { + case 1: + return + case 2: + return + case 3: + return + default: + return #{rank} + } +} + +export default function LeaderboardSection() { + return ( +
+
+
+

+ Leaderboard Bulan Ini +

+

+ Kompetisi sehat antar developer. Leaderboard di-refresh setiap bulan. +

+
+ +
+ {/* Top Authors */} + +
+
+ +
+

Top Authors

+ + Per Author + +
+ +
+ {topAuthors.map((author) => ( +
+
+ {getRankIcon(author.rank)} +
+ + + + + {author.name + .split(' ') + .map((n) => n[0]) + .join('')} + + + +
+
{author.name}
+
NIM: {author.nim}
+
+ +
+
+ {author.projects} projects +
+
+ + + {author.totalLikes} + + + + {author.totalViews} + +
+
+
+ ))} +
+
+ + {/* Top Projects */} + +
+
+ +
+

Top Projects

+ + Per Project + +
+ +
+ {topProjects.map((project) => ( +
+
+
+ {getRankIcon(project.rank)} +
+
+

{project.title}

+

by {project.author}

+
+
+ +
+
+ {project.tech.map((tech) => ( + + {tech} + + ))} +
+ +
+ + + {project.likes} + + + + {project.views} + +
+
+
+ ))} +
+
+
+ +
+

+ Leaderboard akan di-refresh pada tanggal 1 setiap bulannya +

+
+
+
+ ) +} diff --git a/src/features/landing/components/timeline-section.tsx b/src/features/landing/components/timeline-section.tsx new file mode 100644 index 0000000..eb56e6a --- /dev/null +++ b/src/features/landing/components/timeline-section.tsx @@ -0,0 +1,180 @@ +import { Card } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Heart, MessageCircle, Eye, Star, ExternalLink } from 'lucide-react' + +const recentProjects = [ + { + id: 1, + title: 'Smart Campus Navigation App', + author: 'Ahmad Rizki', + authorAvatar: '/placeholder.svg?height=40&width=40', + description: + 'Aplikasi navigasi kampus menggunakan AR dan machine learning untuk membantu mahasiswa baru', + technologies: ['React Native', 'TensorFlow', 'Firebase', 'ARCore'], + likes: 42, + comments: 8, + views: 156, + image: '/placeholder.svg?height=200&width=300', + postedAt: '2 jam yang lalu', + featured: true, + }, + { + id: 2, + title: 'E-Learning Platform POLNES', + author: 'Siti Nurhaliza', + authorAvatar: '/placeholder.svg?height=40&width=40', + description: + 'Platform pembelajaran online dengan fitur video conference dan assignment management', + technologies: ['Next.js', 'PostgreSQL', 'WebRTC', 'Prisma'], + likes: 38, + comments: 12, + views: 203, + image: '/placeholder.svg?height=200&width=300', + postedAt: '4 jam yang lalu', + featured: false, + }, + { + id: 3, + title: 'IoT Greenhouse Monitoring', + author: 'Bayu Pratama', + authorAvatar: '/placeholder.svg?height=40&width=40', + description: 'Sistem monitoring greenhouse otomatis dengan sensor IoT dan dashboard real-time', + technologies: ['Arduino', 'Node.js', 'MongoDB', 'Socket.io'], + likes: 29, + comments: 6, + views: 134, + image: '/placeholder.svg?height=200&width=300', + postedAt: '6 jam yang lalu', + featured: false, + }, + { + id: 4, + title: 'Blockchain Voting System', + author: 'Dewi Kartika', + authorAvatar: '/placeholder.svg?height=40&width=40', + description: 'Sistem voting digital menggunakan blockchain untuk transparansi dan keamanan', + technologies: ['Solidity', 'Web3.js', 'React', 'Ethereum'], + likes: 51, + comments: 15, + views: 287, + image: '/placeholder.svg?height=200&width=300', + postedAt: '8 jam yang lalu', + featured: true, + }, +] + +export default function TimelineSection() { + return ( +
+
+
+

+ Proyek Terbaru +

+

+ Jelajahi karya-karya terbaru dari pengguna lainnya yang penuh inovasi +

+
+ +
+
+ {recentProjects.map((project) => ( + + ))} +
+ +
+ +
+
+
+
+ ) +} + +function ProjectCard({ project }: { project: (typeof recentProjects)[number] }) { + return ( + + {project.featured && ( +
+
+
+ + Featured Project +
+
+
+ )} + +
+ {project.title} +
+ +
+ +
+
+ {project.author} +
+

{project.author}

+

{project.postedAt}

+
+
+ +

+ {project.title} +

+

{project.description}

+ +
+ {project.technologies.map((tech) => ( + + {tech} + + ))} +
+ +
+
+
+ + {project.likes} +
+
+ + {project.comments} +
+
+ + {project.views} +
+
+
+
+ + ) +}