start of landing page, needs 2 intro paragraphs & links, also images
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
12
.idea/kdot.iml
generated
Normal file
12
.idea/kdot.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/kdot.iml" filepath="$PROJECT_DIR$/.idea/kdot.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
119
package-lock.json
generated
119
package-lock.json
generated
@@ -8,6 +8,8 @@
|
||||
"name": "kdot",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"gray-matter": "^4.0.3",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "15.3.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
@@ -3073,6 +3075,19 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"license": "BSD-2-Clause",
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
"esvalidate": "bin/esvalidate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/esquery": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
||||
@@ -3119,6 +3134,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extendable": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -3441,6 +3468,43 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gray-matter": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
|
||||
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-yaml": "^3.13.1",
|
||||
"kind-of": "^6.0.2",
|
||||
"section-matter": "^1.0.0",
|
||||
"strip-bom-string": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
@@ -3739,6 +3803,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extendable": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -4104,6 +4177,15 @@
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/language-subtag-registry": {
|
||||
"version": "0.3.23",
|
||||
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
|
||||
@@ -4413,6 +4495,15 @@
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.511.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz",
|
||||
"integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.17",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||
@@ -5200,6 +5291,19 @@
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/section-matter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"kind-of": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
@@ -5422,6 +5526,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stable-hash": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
|
||||
@@ -5560,6 +5670,15 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-bom-string": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
|
||||
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
|
||||
14
package.json
14
package.json
@@ -9,19 +9,21 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"gray-matter": "^4.0.3",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "15.3.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"next": "15.3.2"
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"tailwindcss": "^4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.2",
|
||||
"@eslint/eslintrc": "^3"
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
17
posts/dna.md
Normal file
17
posts/dna.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: "DNA"
|
||||
date: "2024-01-15"
|
||||
excerpt: "Analysis of Kendrick's exploration of identity and heritage"
|
||||
---
|
||||
|
||||
# DNA - The Foundation of Identity
|
||||
|
||||
"DNA" serves as a powerful opening statement about Kendrick Lamar's genetic and cultural inheritance. The track explores themes of identity, heritage, and the complex relationship between nature and nurture.
|
||||
|
||||
## Key Themes
|
||||
|
||||
- **Identity Formation**: How our DNA shapes who we are
|
||||
- **Cultural Heritage**: The weight of ancestral history
|
||||
- **Resilience**: Strength passed down through generations
|
||||
|
||||
The aggressive production mirrors the intensity of Kendrick's delivery, creating a sonic representation of the fire that runs through his veins.
|
||||
3
src/app/api/post/[slug].ts
Normal file
3
src/app/api/post/[slug].ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export async function GET(slug: string) {
|
||||
|
||||
}
|
||||
46
src/app/api/posts/route.ts
Normal file
46
src/app/api/posts/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import matter from "gray-matter"
|
||||
|
||||
export interface Post {
|
||||
slug: string
|
||||
title: string
|
||||
date: string
|
||||
excerpt?: string
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const postsDirectory = path.join(process.cwd(), "posts")
|
||||
console.log(postsDirectory)
|
||||
|
||||
// Check if posts directory exists
|
||||
if (!fs.existsSync(postsDirectory)) {
|
||||
return NextResponse.json([])
|
||||
}
|
||||
|
||||
const fileNames = fs.readdirSync(postsDirectory)
|
||||
const posts = fileNames
|
||||
.filter((name) => name.endsWith(".md") || name.endsWith(".mdx"))
|
||||
.map((fileName) => {
|
||||
const slug = fileName.replace(/\.(md|mdx)$/, "")
|
||||
const fullPath = path.join(postsDirectory, fileName)
|
||||
const fileContents = fs.readFileSync(fullPath, "utf8")
|
||||
const { data } = matter(fileContents)
|
||||
|
||||
return {
|
||||
slug,
|
||||
title: data.title || slug,
|
||||
date: data.date || "",
|
||||
excerpt: data.excerpt || "",
|
||||
}
|
||||
})
|
||||
.sort((a, b) => (a.date > b.date ? -1 : 1))
|
||||
|
||||
return NextResponse.json(posts)
|
||||
} catch (error) {
|
||||
console.error("Error reading posts:", error)
|
||||
return NextResponse.json([])
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,5 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
@@ -24,9 +7,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<body>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
246
src/app/page.tsx
246
src/app/page.tsx
@@ -1,103 +1,155 @@
|
||||
import Image from "next/image";
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import Navbar from "@/components/navbar";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
const [scrollY, setScrollY] = useState(0)
|
||||
const [titleOpacity, setTitleOpacity] = useState(0)
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const currentScrollY = window.scrollY
|
||||
setScrollY(currentScrollY)
|
||||
|
||||
// Calculate title opacity based on scroll position
|
||||
const opacity = Math.min(currentScrollY / 400, 1)
|
||||
setTitleOpacity(opacity)
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", handleScroll, { passive: true })
|
||||
return () => window.removeEventListener("scroll", handleScroll)
|
||||
}, [])
|
||||
|
||||
const scrollToContent = () => {
|
||||
window.scrollTo({
|
||||
top: window.innerHeight,
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative font-mono">
|
||||
<Navbar />
|
||||
|
||||
{/* Parallax Hero Section */}
|
||||
<section className="relative h-screen overflow-hidden">
|
||||
{/* Parallax Background */}
|
||||
<div
|
||||
className="absolute inset-0 w-full h-[120%] bg-cover bg-center bg-no-repeat"
|
||||
style={{
|
||||
backgroundImage: `url('/vercel.svg?height=1080&width=1920')`,
|
||||
transform: `translateY(${scrollY * 0.5}px)`,
|
||||
filter: "brightness(0.7) contrast(1.1)",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Dark Overlay */}
|
||||
<div className="absolute inset-0 bg-black/40" />
|
||||
|
||||
{/* Content Overlay */}
|
||||
<div className="relative z-10 flex flex-col items-center justify-center h-full text-white px-4">
|
||||
<div className="text-center space-y-6">
|
||||
<h1 className="text-6xl md:text-8xl font-bold tracking-wider opacity-90">DAMN.</h1>
|
||||
<p className="text-xl md:text-2xl font-light tracking-widest opacity-80">by Kendrick Lamar</p>
|
||||
</div>
|
||||
|
||||
{/* Scroll Indicator */}
|
||||
<button
|
||||
onClick={scrollToContent}
|
||||
className="absolute bottom-8 left-1/2 transform -translate-x-1/2 animate-bounce"
|
||||
aria-label="Scroll to content"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
<ChevronDown className="w-8 h-8 text-white/70 hover:text-white transition-colors" />
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
{/* Title Reveal Section */}
|
||||
<section className="relative min-h-screen bg-black text-white">
|
||||
<div className="container mx-auto px-4 py-20">
|
||||
<div className="flex flex-col items-center justify-center min-h-screen space-y-12">
|
||||
{/* Main Title with Fade-in Effect */}
|
||||
<div className="text-center space-y-8 transition-opacity duration-1000" style={{ opacity: titleOpacity }}>
|
||||
<h2 className="text-8xl md:text-9xl font-black tracking-tighter text-red-600">DAMN.</h2>
|
||||
<div className="space-y-4">
|
||||
<p className="text-2xl md:text-3xl font-light tracking-wide">KENDRICK LAMAR</p>
|
||||
<p className="text-lg md:text-xl text-gray-400 max-w-2xl mx-auto leading-relaxed">
|
||||
An exploration of duality, spirituality, and the human condition through one of hip-hop's most
|
||||
critically acclaimed albums.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Album Details */}
|
||||
<div
|
||||
className="grid grid-cols-1 md:grid-cols-3 gap-8 w-full max-w-4xl transition-opacity duration-1000 delay-500"
|
||||
style={{ opacity: titleOpacity }}
|
||||
>
|
||||
<div className="text-center space-y-2">
|
||||
<h3 className="text-xl font-semibold text-red-500">RELEASED</h3>
|
||||
<p className="text-gray-300">April 14, 2017</p>
|
||||
</div>
|
||||
<div className="text-center space-y-2">
|
||||
<h3 className="text-xl font-semibold text-red-500">LABEL</h3>
|
||||
<p className="text-gray-300">Top Dawg Entertainment</p>
|
||||
</div>
|
||||
<div className="text-center space-y-2">
|
||||
<h3 className="text-xl font-semibold text-red-500">GENRE</h3>
|
||||
<p className="text-gray-300">Hip Hop, Conscious Rap</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quote Section */}
|
||||
<div
|
||||
className="text-center max-w-3xl mx-auto transition-opacity duration-1000 delay-1000"
|
||||
style={{ opacity: titleOpacity }}
|
||||
>
|
||||
<blockquote className="text-xl md:text-2xl italic text-gray-300 leading-relaxed">
|
||||
"Is it wickedness? Is it weakness? You decide. Are we gonna live or die?"
|
||||
</blockquote>
|
||||
<cite className="block mt-4 text-red-500 font-semibold">— Kendrick Lamar, FEAR.</cite>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Additional Content Section */}
|
||||
<section className="bg-gray-900 text-white py-20">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-4xl mx-auto space-y-12">
|
||||
<h3 className="text-4xl md:text-5xl font-bold text-center mb-12">ABOUT THE ALBUM</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div className="space-y-6">
|
||||
<p className="text-lg leading-relaxed text-gray-300">
|
||||
DAMN. is the fourth studio album by American rapper Kendrick Lamar. The album explores themes of
|
||||
loyalty, faith, and the duality of human nature through introspective lyrics and innovative
|
||||
production.
|
||||
</p>
|
||||
<p className="text-lg leading-relaxed text-gray-300">
|
||||
Featuring collaborations with Rihanna, Zacari, and U2, the album received widespread critical acclaim
|
||||
and won the Pulitzer Prize for Music, making Lamar the first non-classical or jazz artist to receive
|
||||
the honor.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="bg-red-600/20 p-6 rounded-lg border border-red-600/30">
|
||||
<h4 className="text-xl font-semibold mb-3 text-red-400">ACCOLADES</h4>
|
||||
<ul className="space-y-2 text-gray-300">
|
||||
<li>• Pulitzer Prize for Music (2018)</li>
|
||||
<li>• Grammy Award for Best Rap Album</li>
|
||||
<li>• #1 on Billboard 200</li>
|
||||
<li>• Triple Platinum Certification</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
49
src/app/posts/[slug]/page.tsx
Normal file
49
src/app/posts/[slug]/page.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { getPost } from "@/lib/posts"
|
||||
import { notFound } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
import { ArrowLeft } from "lucide-react"
|
||||
|
||||
interface PostPageProps {
|
||||
params: Promise<{ slug: string }>
|
||||
}
|
||||
|
||||
export default async function PostPage({ params }: PostPageProps) {
|
||||
const { slug } = await params
|
||||
const post = await getPost(slug)
|
||||
|
||||
if (!post) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black text-white font-mono">
|
||||
<div className="container mx-auto px-4 py-20">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Back button */}
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center space-x-2 text-red-400 hover:text-red-300 transition-colors mb-8"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
|
||||
{/* Post header */}
|
||||
<header className="mb-12">
|
||||
<h1 className="text-4xl md:text-6xl font-bold text-red-600 mb-4">{post.title}</h1>
|
||||
{post.date && <time className="text-gray-400 text-lg">{post.date}</time>}
|
||||
{post.excerpt && <p className="text-xl text-gray-300 mt-4 leading-relaxed">{post.excerpt}</p>}
|
||||
</header>
|
||||
|
||||
{/* Post content */}
|
||||
<article className="prose prose-invert prose-red max-w-none">
|
||||
<div
|
||||
className="text-gray-300 leading-relaxed space-y-6"
|
||||
dangerouslySetInnerHTML={{ __html: post.content.replace(/\n/g, "<br />") }}
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
60
src/components/navbar.tsx
Normal file
60
src/components/navbar.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
interface Post {
|
||||
slug: string
|
||||
title: string
|
||||
date: string
|
||||
excerpt?: string
|
||||
}
|
||||
|
||||
export default function Navbar() {
|
||||
const [posts, setPosts] = useState<Post[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/posts")
|
||||
.then((res) => res.json())
|
||||
.then((data) => setPosts(data))
|
||||
.catch((err) => console.error("Error fetching posts:", err))
|
||||
}, [])
|
||||
|
||||
console.log(posts)
|
||||
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 bg-black/90 backdrop-blur-sm border-b border-red-600/20 font-mono">
|
||||
<div className="container max-w-screen px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* Left side - Logo and title */}
|
||||
<Link href="/" className="flex items-center space-x-3 hover:opacity-80 transition-opacity">
|
||||
<div className="w-8 h-8 bg-red-600 rounded-sm flex items-center justify-center">
|
||||
<span className="text-white font-bold text-sm">D</span>
|
||||
</div>
|
||||
<span className="text-white font-bold text-xl tracking-wider">DAMN.</span>
|
||||
</Link>
|
||||
|
||||
{/* Right side - Posts navigation */}
|
||||
<div className="hidden md:flex items-center space-x-8">
|
||||
{posts.map((post) => (
|
||||
<Link
|
||||
key={post.slug}
|
||||
href={`/posts/${post.slug}`}
|
||||
className="text-gray-300 hover:text-red-400 transition-colors text-sm uppercase tracking-wide"
|
||||
>
|
||||
{post.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<button className="md:hidden text-white">
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
45
src/lib/posts.ts
Normal file
45
src/lib/posts.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import matter from "gray-matter"
|
||||
|
||||
export interface Post {
|
||||
slug: string
|
||||
title: string
|
||||
date: string
|
||||
excerpt?: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const postsDirectory = path.join(process.cwd(), "posts")
|
||||
|
||||
export async function getPost(slug: string): Promise<Post | null> {
|
||||
try {
|
||||
const fullPath = path.join(postsDirectory, `${slug}.md`)
|
||||
|
||||
// Try .md first, then .mdx
|
||||
let fileContents: string
|
||||
if (fs.existsSync(fullPath)) {
|
||||
fileContents = fs.readFileSync(fullPath, "utf8")
|
||||
} else {
|
||||
const mdxPath = path.join(postsDirectory, `${slug}.mdx`)
|
||||
if (fs.existsSync(mdxPath)) {
|
||||
fileContents = fs.readFileSync(mdxPath, "utf8")
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const { data, content } = matter(fileContents)
|
||||
|
||||
return {
|
||||
slug,
|
||||
title: data.title || slug,
|
||||
date: data.date || "",
|
||||
excerpt: data.excerpt || "",
|
||||
content,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error reading post:", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user