diff --git a/app/globals.css b/app/globals.css index a2dc41e..9c8d7bd 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,2 @@ @import "tailwindcss"; - -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} +@import "../src/shared/styles/theme.css"; diff --git a/app/layout.tsx b/app/layout.tsx index 976eb90..6b624cd 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,33 +1,38 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; +import type { Metadata } from 'next' +import { Fraunces, Public_Sans } from 'next/font/google' +import './globals.css' -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); +/** + * Heading font — variable axes for brutalist variation settings + */ +const fraunces = Fraunces({ + subsets: ['latin'], + variable: '--font-fraunces', + axes: ['opsz', 'SOFT', 'WONK'], +}) -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +/** + * Body font + */ +const publicSans = Public_Sans({ + subsets: ['latin'], + variable: '--font-public-sans', +}) export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); + title: 'Portfolio', + description: 'Portfolio', +} + +/** + * Root layout — injects font CSS variables used by theme.css + */ +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ) } diff --git a/src/shared/styles/theme.css b/src/shared/styles/theme.css new file mode 100644 index 0000000..2d63cb3 --- /dev/null +++ b/src/shared/styles/theme.css @@ -0,0 +1,184 @@ +:root { + /* === TYPOGRAPHY SCALE (Augmented Fourth 1.414) === */ + --font-size: 16px; + --text-xs: 0.707rem; + --text-sm: 0.840rem; + --text-base: 1rem; + --text-lg: 1.414rem; + --text-xl: 2rem; + --text-2xl: 2.828rem; + --text-3xl: 4rem; + --text-4xl: 5.657rem; + --text-5xl: 8rem; + + /* === FONT FAMILIES (set by next/font CSS vars in layout.tsx) === */ + --font-heading: var(--font-fraunces), serif; + --font-body: var(--font-public-sans), sans-serif; + + /* === FONT WEIGHTS === */ + --font-weight-heading: 900; + --font-weight-body: 600; + --font-weight-normal: 400; + + /* === LINE HEIGHT === */ + --line-height-tight: 1.2; + --line-height-normal: 1.5; + + /* === FRAUNCES VARIABLE AXES === */ + --fraunces-wonk: 1; + --fraunces-soft: 0; + + /* === COLOR PALETTE === */ + --ochre-clay: #D9B48F; + --slate-indigo: #3B4A59; + --burnt-oxide: #A64B35; + --carbon-black: #121212; + + /* === SEMANTIC COLORS === */ + --background: var(--ochre-clay); + --foreground: var(--carbon-black); + --card: var(--ochre-clay); + --card-foreground: var(--carbon-black); + --primary: var(--burnt-oxide); + --primary-foreground: var(--ochre-clay); + --secondary: var(--slate-indigo); + --secondary-foreground: var(--ochre-clay); + --muted: var(--slate-indigo); + --muted-foreground: var(--ochre-clay); + --accent: var(--burnt-oxide); + --accent-foreground: var(--ochre-clay); + --destructive: #d4183d; + --border: var(--carbon-black); + --ring: var(--carbon-black); + + /* === SPACING (8pt Linear Scale) === */ + --space-0: 0; + --space-1: 0.5rem; + --space-2: 1rem; + --space-3: 1.5rem; + --space-4: 2rem; + --space-5: 2.5rem; + --space-6: 3rem; + --space-7: 3.5rem; + --space-8: 4rem; + --space-10: 5rem; + --space-12: 6rem; + --space-16: 8rem; + --space-20: 10rem; + + /* === BORDERS === */ + --border-width: 3px; + --radius: 0px; + + /* === BRUTALIST SHADOWS === */ + --shadow-brutal: 8px 8px 0 var(--carbon-black); + --shadow-brutal-sm: 4px 4px 0 var(--carbon-black); + --shadow-brutal-lg: 12px 12px 0 var(--carbon-black); + + /* === GRID === */ + --grid-gap: var(--space-3); +} + +@theme inline { + --color-ochre-clay: var(--ochre-clay); + --color-slate-indigo: var(--slate-indigo); + --color-burnt-oxide: var(--burnt-oxide); + --color-carbon-black: var(--carbon-black); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-border: var(--border); + --radius-sm: var(--radius); + --radius-md: var(--radius); + --radius-lg: var(--radius); +} + +@layer base { + * { + @apply border-border; + } + + html { + font-size: var(--font-size); + } + + body { + @apply bg-background text-foreground; + font-family: var(--font-body); + font-weight: var(--font-weight-body); + line-height: var(--line-height-tight); + overflow-x: hidden; + } + + /* Paper grain texture */ + body::before { + content: ''; + position: fixed; + inset: 0; + background-image: + repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.02) 2px, rgba(0,0,0,0.02) 4px), + repeating-linear-gradient(90deg, transparent, transparent 2px, rgba(0,0,0,0.02) 2px, rgba(0,0,0,0.02) 4px); + opacity: 0.4; + pointer-events: none; + z-index: 1; + } + + h1, h2, h3, h4, h5 { + font-family: var(--font-heading); + font-weight: var(--font-weight-heading); + line-height: var(--line-height-tight); + font-variation-settings: 'WONK' var(--fraunces-wonk), 'SOFT' var(--fraunces-soft); + color: var(--carbon-black); + } + + h1 { font-size: var(--text-4xl); } + h2 { font-size: var(--text-3xl); } + h3 { font-size: var(--text-2xl); } + h4 { font-size: var(--text-xl); } + h5 { font-size: var(--text-lg); } + + p { + font-family: var(--font-body); + font-size: var(--text-base); + font-weight: var(--font-weight-body); + color: var(--carbon-black); + } + + a { + color: var(--burnt-oxide); + text-decoration: none; + border-bottom: 2px solid var(--carbon-black); + transition: all 0.2s; + } + + a:hover { + border-bottom-width: 4px; + } + + blockquote { + font-family: var(--font-heading); + font-size: var(--text-xl); + border-left: var(--border-width) solid var(--carbon-black); + padding-left: var(--space-4); + margin: var(--space-6) 0; + } +} + +/* Brutalist utility classes */ +.brutal-shadow { box-shadow: var(--shadow-brutal); } +.brutal-shadow-sm { box-shadow: var(--shadow-brutal-sm); } +.brutal-shadow-lg { box-shadow: var(--shadow-brutal-lg); } +.brutal-border { border: var(--border-width) solid var(--carbon-black); } +.brutal-border-top { border-top: var(--border-width) solid var(--carbon-black); } +.brutal-border-bottom { border-bottom: var(--border-width) solid var(--carbon-black); } +.brutal-border-left { border-left: var(--border-width) solid var(--carbon-black); } +.brutal-border-right { border-right: var(--border-width) solid var(--carbon-black); } + +/* Animations */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} +.animate-fadeIn { animation: fadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1); }