mirror of
https://github.com/skidoodle/albert.lol.git
synced 2025-02-15 06:09:15 +01:00
No changes after this point.
This commit is contained in:
parent
e70d71134f
commit
4e2956becb
15 changed files with 1138 additions and 956 deletions
124
next.config.ts
124
next.config.ts
|
@ -1,67 +1,67 @@
|
|||
import type { NextConfig } from 'next'
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const securityHeaders = [
|
||||
{
|
||||
key: 'X-DNS-Prefetch-Control',
|
||||
value: 'on',
|
||||
},
|
||||
{
|
||||
key: 'X-XSS-Protection',
|
||||
value: '1; mode=block',
|
||||
},
|
||||
{
|
||||
key: 'X-Content-Type-Options',
|
||||
value: 'nosniff',
|
||||
},
|
||||
{
|
||||
key: 'Referrer-Policy',
|
||||
value: 'strict-origin',
|
||||
},
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: `frame-ancestors 'self';`,
|
||||
},
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'SAMEORIGIN',
|
||||
},
|
||||
{
|
||||
key: 'Strict-Transport-Security',
|
||||
value: 'max-age=31536000; includeSubDomains; preload',
|
||||
},
|
||||
{
|
||||
key: 'Permissions-Policy',
|
||||
value: 'camera=(), microphone=(), geolocation=()',
|
||||
},
|
||||
{
|
||||
key: 'X-Source',
|
||||
value: 'github.com/skidoodle/albert.lol',
|
||||
},
|
||||
]
|
||||
{
|
||||
key: "X-DNS-Prefetch-Control",
|
||||
value: "on",
|
||||
},
|
||||
{
|
||||
key: "X-XSS-Protection",
|
||||
value: "1; mode=block",
|
||||
},
|
||||
{
|
||||
key: "X-Content-Type-Options",
|
||||
value: "nosniff",
|
||||
},
|
||||
{
|
||||
key: "Referrer-Policy",
|
||||
value: "strict-origin",
|
||||
},
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: `frame-ancestors 'self';`,
|
||||
},
|
||||
{
|
||||
key: "X-Frame-Options",
|
||||
value: "SAMEORIGIN",
|
||||
},
|
||||
{
|
||||
key: "Strict-Transport-Security",
|
||||
value: "max-age=31536000; includeSubDomains; preload",
|
||||
},
|
||||
{
|
||||
key: "Permissions-Policy",
|
||||
value: "camera=(), microphone=(), geolocation=()",
|
||||
},
|
||||
{
|
||||
key: "X-Source",
|
||||
value: "github.com/skidoodle/albert.lol",
|
||||
},
|
||||
];
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/:path*',
|
||||
headers: securityHeaders,
|
||||
},
|
||||
]
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'i.scdn.co',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'placehold.co',
|
||||
},
|
||||
],
|
||||
},
|
||||
reactStrictMode: true,
|
||||
output: 'standalone',
|
||||
}
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: "/:path*",
|
||||
headers: securityHeaders,
|
||||
},
|
||||
];
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "i.scdn.co",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "placehold.co",
|
||||
},
|
||||
],
|
||||
},
|
||||
reactStrictMode: true,
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
export default nextConfig
|
||||
export default nextConfig;
|
||||
|
|
65
package.json
65
package.json
|
@ -1,33 +1,36 @@
|
|||
{
|
||||
"name": "albert.lol",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "biome lint --write src/",
|
||||
"format": "biome format --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"framer-motion": "^11.11.10",
|
||||
"next": "15.0.1",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "19.0.0-rc-69d4b800-20241021",
|
||||
"react-dom": "19.0.0-rc-69d4b800-20241021",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-icons": "^5.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/node": "^22.8.2",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-config-next": "15.0.1",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
"name": "albert.lol",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "biome lint --write src/",
|
||||
"format": "biome format --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"framer-motion": "^11.15.0",
|
||||
"mini-svg-data-uri": "^1.4.4",
|
||||
"next": "15.1.2",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-icons": "^5.4.0",
|
||||
"tailwind-merge": "^2.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-next": "15.1.2",
|
||||
"postcss": "^8.4.49",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
|
|
1612
pnpm-lock.yaml
generated
1612
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,8 @@
|
|||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -6,7 +6,7 @@ import { motion } from "framer-motion";
|
|||
export const AboutMe = () => {
|
||||
return (
|
||||
<motion.div
|
||||
className="p-3 max-w-[325px] lg:max-w-lg md:max-w-md max-h-[300px] h-[265px] rounded-lg shadow-lg backdrop-blur-sm bg-white/20 dark:bg-black/20 object-fit"
|
||||
className="p-3 max-w-xs lg:max-w-sm md:max-w-md max-h-[300px] h-[265px] rounded-lg shadow-lg bg-white dark:bg-black"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export const Background = () => {
|
||||
return (
|
||||
<div className="fixed inset-0 w-screen h-screen overflow-hidden z-[-1] flex justify-center items-center">
|
||||
<motion.div
|
||||
className="absolute w-[800px] h-[800px] bg-gradient-to-r from-pink-500 to-purple-500 rounded-full blur-3xl"
|
||||
style={{ willChange: "transform, opacity" }}
|
||||
animate={{
|
||||
scale: [1.5, 1.2, 1, 1.2, 1.5],
|
||||
x: [0, 200, 0, -200, 0],
|
||||
y: [0, -80, 150, -150, 0],
|
||||
opacity: [0.6, 1, 0.6],
|
||||
}}
|
||||
transition={{
|
||||
duration: 18,
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
className="absolute w-[800px] h-[800px] bg-gradient-to-r from-blue-400 to-red-500 rounded-full blur-3xl"
|
||||
style={{ willChange: "transform, opacity" }}
|
||||
animate={{
|
||||
scale: [1, 1.3, 1.5, 1.3, 1],
|
||||
x: [-150, 150, -150, 150, -150],
|
||||
y: [180, -180, 180, -180, 180],
|
||||
opacity: [0.6, 1, 0.6],
|
||||
}}
|
||||
transition={{
|
||||
duration: 22,
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
53
src/app/components/Highlight.tsx
Normal file
53
src/app/components/Highlight.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@/utils";
|
||||
import { useMotionValue, motion, useMotionTemplate } from "framer-motion";
|
||||
|
||||
export const HeroHighlight = ({
|
||||
children,
|
||||
className,
|
||||
containerClassName,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
}) => {
|
||||
const mouseX = useMotionValue(0);
|
||||
const mouseY = useMotionValue(0);
|
||||
|
||||
function handleMouseMove({
|
||||
currentTarget,
|
||||
clientX,
|
||||
clientY,
|
||||
}: React.MouseEvent<HTMLDivElement>) {
|
||||
if (!currentTarget) return;
|
||||
const { left, top } = currentTarget.getBoundingClientRect();
|
||||
|
||||
mouseX.set(clientX - left);
|
||||
mouseY.set(clientY - top);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative h-screen flex items-center bg-white dark:bg-black justify-center w-full group",
|
||||
containerClassName,
|
||||
)}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<div className="absolute inset-0 bg-dot-thick-neutral-300 dark:bg-dot-thick-neutral-800 pointer-events-none" />
|
||||
<motion.div
|
||||
className="pointer-events-none bg-dot-thick-indigo-500 dark:bg-dot-thick-indigo-500 absolute inset-0 opacity-0 transition duration-300 group-hover:opacity-100"
|
||||
style={{
|
||||
WebkitMaskImage: useMotionTemplate`
|
||||
radial-gradient(200px circle at ${mouseX}px ${mouseY}px, black 0%, transparent 100%)
|
||||
`,
|
||||
maskImage: useMotionTemplate`
|
||||
radial-gradient(200px circle at ${mouseX}px ${mouseY}px, black 0%, transparent 100%)
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<div className={cn("relative z-20", className)}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -111,7 +111,7 @@ export const NowPlayingCard = () => {
|
|||
|
||||
return (
|
||||
<motion.div
|
||||
className="mt-5 w-[325px] rounded-md shadow-lg p-3 dark:bg-black/20 bg-white/20 backfrop-blur-sm"
|
||||
className="mt-5 w-[325px] rounded-md shadow-lg p-3 dark:bg-black bg-white"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
|
|
|
@ -30,7 +30,7 @@ export const ThemeSwitcher = () => {
|
|||
<motion.button
|
||||
aria-label="Switch Theme"
|
||||
type="button"
|
||||
className="fixed top-5 right-5 p-3 rounded-full"
|
||||
className="fixed top-5 right-5 p-3 rounded-full z-50"
|
||||
onClick={toggleTheme}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--dark-background: #000000;
|
||||
--dark-primary: #121212;
|
||||
--dark-background: #282828;
|
||||
--dark-primary: #323232;
|
||||
--dark-secondary: #cecece;
|
||||
--dark-text: #eeeeee;
|
||||
|
||||
|
|
|
@ -6,43 +6,44 @@ import { SocialLayout } from "@/components/SocialLayout";
|
|||
import { AboutMe } from "@/components/AboutMe";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import { motion } from "framer-motion";
|
||||
import { Background } from "@/components/Background";
|
||||
import { HeroHighlight } from "@/components/Highlight";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<Fragment>
|
||||
<Background />
|
||||
<ThemeSwitcher />
|
||||
<Toaster position="top-left" />
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<div className="mx-auto mt-44 flex max-w-3xl flex-col mb-12 z-10 relative">
|
||||
<motion.div
|
||||
className="flex flex-col lg:flex-row items-center justify-center lg:justify-between space-y-10 lg:space-y-5 lg:space-x-10"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<HeroHighlight containerClassName="min-h-screen flex items-start">
|
||||
<ThemeSwitcher />
|
||||
<Toaster position="top-left" />
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<div className="flex max-w-3xl flex-col z-10 relative mt-52">
|
||||
<motion.div
|
||||
className="flex flex-col items-center text-center"
|
||||
initial={{ scale: 0.9 }}
|
||||
animate={{ scale: 1 }}
|
||||
className="flex flex-col lg:flex-row items-center justify-center lg:justify-between space-y-10 lg:space-y-5 lg:space-x-10"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<h1 className="text-[7.5rem] leading-none font-bold dark:text-[--dark-text] text-[--light-text]">
|
||||
albert
|
||||
</h1>
|
||||
<SocialLayout />
|
||||
<NowPlayingCard />
|
||||
<motion.div
|
||||
className="flex flex-col items-center text-center"
|
||||
initial={{ scale: 0.9 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<h1 className="text-[7.5rem] leading-none font-bold dark:text-[--dark-text] text-[--light-text]">
|
||||
albert
|
||||
</h1>
|
||||
<SocialLayout />
|
||||
<NowPlayingCard />
|
||||
</motion.div>
|
||||
<AboutMe />
|
||||
</motion.div>
|
||||
<AboutMe />
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</HeroHighlight>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export default function age() {
|
||||
const BIRTHDATE = process.env.NEXT_PUBLIC_BIRTHDATE;
|
||||
if (!BIRTHDATE) {
|
||||
|
@ -17,3 +20,7 @@ export const truncate = (str: string, n: number): string => {
|
|||
}
|
||||
return str.trim();
|
||||
};
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
|
|
@ -1,20 +1,52 @@
|
|||
import type { Config } from "tailwindcss";
|
||||
import svgToDataUri from "mini-svg-data-uri";
|
||||
import flattenColorPalette from "tailwindcss/lib/util/flattenColorPalette";
|
||||
|
||||
const addVariablesForColors = ({ addBase, theme }: any) => {
|
||||
const allColors = flattenColorPalette(theme("colors"));
|
||||
const newVars = Object.fromEntries(
|
||||
Object.entries(allColors).map(([key, val]) => [`--${key}`, val]),
|
||||
);
|
||||
|
||||
addBase({
|
||||
":root": newVars,
|
||||
});
|
||||
};
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: "var(--background)",
|
||||
foreground: "var(--foreground)",
|
||||
},
|
||||
},
|
||||
},
|
||||
darkMode: "class",
|
||||
plugins: [],
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/**/*.{ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: "var(--background)",
|
||||
foreground: "var(--foreground)",
|
||||
},
|
||||
},
|
||||
},
|
||||
darkMode: "class",
|
||||
plugins: [
|
||||
addVariablesForColors,
|
||||
function ({ matchUtilities, theme }: any) {
|
||||
matchUtilities(
|
||||
{
|
||||
"bg-dot-thick": (value: any) => ({
|
||||
backgroundImage: `url("${svgToDataUri(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" fill="none"><circle fill="${value}" id="pattern-circle" cx="10" cy="10" r="2.5"></circle></svg>`,
|
||||
)}")`,
|
||||
}),
|
||||
},
|
||||
{
|
||||
values: flattenColorPalette(theme("backgroundColor")),
|
||||
type: "color",
|
||||
},
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
6
tailwindcss.d.ts
vendored
Normal file
6
tailwindcss.d.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
declare module "tailwindcss/lib/util/flattenColorPalette" {
|
||||
const flattenColorPalette: (
|
||||
colors: Record<string, any>,
|
||||
) => Record<string, string>;
|
||||
export default flattenColorPalette;
|
||||
}
|
|
@ -1,27 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*", "./src/app/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*", "./src/app/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue