diff --git a/.gitignore b/.gitignore index 1380c2e..b82fa5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -.next \ No newline at end of file +.next +.openvisio/ diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..7ac0ab7 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "openvisio": { + "command": "openvisio", + "args": [ + "mcp", + ".", + "--watch" + ] + } + } +} diff --git a/app/components/bookSection.tsx b/app/components/bookSection.tsx deleted file mode 100644 index b248de3..0000000 --- a/app/components/bookSection.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { BookOpen } from 'lucide-react'; - -export default function BookSection() { - return ( -
-
-
-
- -
- Now Reading -
-
- -
- Book cover -
-

- Psychology of Intelligence Analysis -

-

- Richards J. Heuer Jr. -

-
-
-
- 68% -
-
-
-
- ); -} diff --git a/app/components/button.tsx b/app/components/button.tsx deleted file mode 100644 index 7d5466a..0000000 --- a/app/components/button.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { ButtonProps } from "../types/global"; -import classNames from "classnames"; -import React from "react"; - -const Button = ({ - text, - icon, - forwardIcon, - bg, - border, - color, - className, - iconSize -} : ButtonProps) => { - return( - <> - - - ) -} - -export default Button; \ No newline at end of file diff --git a/app/components/caseFiles.tsx b/app/components/caseFiles.tsx deleted file mode 100644 index 02e3072..0000000 --- a/app/components/caseFiles.tsx +++ /dev/null @@ -1,182 +0,0 @@ -'use client'; - -import React from 'react'; -import { Fade } from 'react-awesome-reveal'; -import { ArrowUpRight } from 'lucide-react'; - -interface CaseProject { - name: string; - pullquote: string; - desc: string; - img: string; - link: string; - year: string; - role: string; - client: string; - stacks: string[]; -} - -const projectList: CaseProject[] = [ - { - name: 'Ministry of Finance Incorporated — Housing', - pullquote: 'A national housing platform engineered to feel personal.', - desc: - 'Built the public-facing platform serving over 1 million Nigerians with property search, multi-method payment flows, and admin controls.', - img: '/works/mofi2.png', - link: 'https://mreif.com.ng', - year: '2024', - role: 'Lead Frontend', - client: 'Qshelter NG', - stacks: ['Next.js', 'TypeScript', 'Tailwind', 'Redux', 'GraphQL'], - }, - { - name: 'Renewed Hope Housing Platform', - pullquote: 'Helping a country come home — one unit at a time.', - desc: - 'Made owning a home achievable for Nigerians: users select units and complete purchases with multiple payment methods. 50K+ registered users, ₦1B+ in transactions.', - img: '/works/rh.png', - link: 'https://renewedhopehomes.fmhud.gov.ng/properties/search', - year: '2024', - role: 'Senior Frontend', - client: 'Qshelter / FMHUD', - stacks: ['Next.js', 'TypeScript', 'GraphQL', 'Redux', 'Tailwind'], - }, - { - name: 'Mage', - pullquote: 'Banking, billing, and breathing room for creatives.', - desc: - 'A platform for creative businesses to manage payments, expenses, and access funding for their projects. Owned the Creator Store and the Creator Dashboard.', - img: '/works/mage.png', - link: 'https://trymage.com', - year: '2022 – 2023', - role: 'Frontend Lead', - client: 'Mage Inc', - stacks: ['React', 'Next.js', 'TypeScript', 'Firebase', 'WebSocket'], - }, - { - name: 'Contribuild', - pullquote: 'Save now, own later.', - desc: - 'Find the property of your dreams and start saving towards owning it — a savings-first real estate platform built for the diaspora and onshore alike.', - img: '/works/contribuild.png', - link: 'https://contribuild.ng', - year: '2024', - role: 'Senior Frontend', - client: 'Qshelter NG', - stacks: ['React', 'TypeScript', 'Redux', 'Tailwind'], - }, - { - name: 'Metropolitan Club Management', - pullquote: 'A members-only system, built like the members are watching.', - desc: - 'Back-office management system for The Metropolitan Club covering members, billing, events, and analytics in one cohesive admin.', - img: '/works/tmc.png', - link: '', - year: '2023', - role: 'Frontend Developer', - client: 'Metropolitan Club', - stacks: ['React', 'TypeScript', 'Tailwind', 'React Query'], - }, - { - name: 'Video Streaming App', - pullquote: 'Composition. Motion. Snappy controls.', - desc: - 'Frontend implementation for a Netflix-style streaming experience with clean composition, snappy interactions, and a refined hero.', - img: '/works/video.png', - link: 'https://vid-react.netlify.app', - year: '2021', - role: 'Frontend', - client: 'Concept Project', - stacks: ['React', 'Tailwind', 'JavaScript'], - }, -]; - -const Case: React.FC<{ p: CaseProject; index: number }> = ({ p, index }) => { - const flip = index % 2 === 1; - const openLink = () => p.link && window.open(p.link, '_blank', 'noopener,noreferrer'); - - return ( -
- {/* Image */} -
-
- - {String(index + 1).padStart(2, '0')} - - {p.name} -
-
- - {/* Meta */} -
-
Case File · No. {String(index + 1).padStart(2, '0')}
-

{p.name}

-

- "{p.pullquote}" -

-

{p.desc}

- -
-
- Client - {p.client} -
-
- Role - {p.role} -
-
- Year - {p.year} -
-
- Stack -
- {p.stacks.map((s, i) => ( - {s} - ))} -
-
-
- - {p.link && ( - - )} -
-
- ); -}; - -export default function CaseFiles() { - return ( -
- -
-
- — Page 04 -

- Selected Case Files -

-
-

- Six recent reports from the desk — clients, stack, outcome. -

-
-
- -
- {projectList.map((p, i) => ( - - - - ))} -
-
- ); -} diff --git a/app/components/clusterIcon.tsx b/app/components/clusterIcon.tsx new file mode 100644 index 0000000..11c8a54 --- /dev/null +++ b/app/components/clusterIcon.tsx @@ -0,0 +1,14 @@ +'use client'; + +export default function ClusterIcon() { + return ( + + + + + + + + + ); +} diff --git a/app/components/directLine.tsx b/app/components/directLine.tsx deleted file mode 100644 index 35c1796..0000000 --- a/app/components/directLine.tsx +++ /dev/null @@ -1,114 +0,0 @@ -'use client'; - -import React from 'react'; -import { Fade } from 'react-awesome-reveal'; -import { Mail, Github, Linkedin, Twitter, FileText, Phone, ArrowUpRight } from 'lucide-react'; - -const myEmail = 'adewaled03@gmail.com'; -const resumeLink = - 'https://docs.google.com/document/d/1sCQ_9keryAYoOxwaVqu05fWl3DgkAjroKQPTLqtVFQ4/edit?tab=t.0'; - -const channels = [ - { Icon: Mail, cmd: '→ email', label: 'address', val: myEmail, link: `mailto:${myEmail}` }, - { Icon: Phone, cmd: '→ call', label: 'tel', val: '+234 8188 869 197', link: 'tel:+2348188869197' }, - { Icon: FileText, cmd: '→ resume', label: 'doc', val: 'open in tab', link: resumeLink }, - { Icon: Linkedin, cmd: '→ linkedin', label: 'profile', val: 'linkedin/in/syntaxpriest', link: 'https://www.linkedin.com/in/syntaxpriest/' }, - { Icon: Github, cmd: '→ github', label: 'profile', val: 'github/syntaxPriest', link: 'https://github.com/syntaxPriest' }, - { Icon: Twitter, cmd: '→ twitter', label: 'handle', val: '@syntaxPriest', link: 'https://twitter.com/syntaxPriest' }, -]; - -export default function DirectLine() { - return ( -
- -
-
- — Closing Note -

- The Direct Line -

-
-

- Six ways to find me. The fastest is email. -

-
-
- - -
- {/* Terminal-style header */} -
-
-
- -
- daniel ~ /contact -
- - - Listening - -
- - {/* Greeting block */} -
-

- ${' '} - echo "Hello, world." -

-

- Have an idea worth building? -

-

- I'm open to senior frontend roles, focused contract work, and collaborations - on products that deserve to feel inevitable. Pick a channel — I respond fast. -

-
- - {/* Primary CTA */} - - - {/* Channels list */} -
- {channels.map((c, i) => ( - - - {c.cmd} - {c.label}: {c.val} - - - ))} -
- - {/* Footer */} -
-

- © {new Date().getFullYear()} · Daniel Adewale · Lagos, Nigeria -

-

- Built by hand · v6.0 -

-
-
-
-
- ); -} diff --git a/app/components/faqItem.tsx b/app/components/faqItem.tsx new file mode 100644 index 0000000..ed3d5c2 --- /dev/null +++ b/app/components/faqItem.tsx @@ -0,0 +1,21 @@ +'use client'; + +import React, { useState } from 'react'; +import { ChevronDown } from 'lucide-react'; + +export default function FAQItem({ q, a }: { q: string; a: string }) { + const [open, setOpen] = useState(false); + return ( +
+ +
+
{a}
+
+
+ ); +} diff --git a/app/components/heroBackgroundDesign.tsx b/app/components/heroBackgroundDesign.tsx deleted file mode 100644 index 64b9231..0000000 --- a/app/components/heroBackgroundDesign.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -export default function HeroBackgroundDesign() { - return ( -
- {/* Grid */} -
- - {/* Top spotlight */} -
- - {/* Orbiting rings */} -
-
-
-
-
-
- - {/* Orbiting dots */} -
-
-
-
-
-
-
-
- - {/* Floating accent dots */} -
-
-
-
-
- ); -} diff --git a/app/components/heroCover.tsx b/app/components/heroCover.tsx deleted file mode 100644 index b675ed7..0000000 --- a/app/components/heroCover.tsx +++ /dev/null @@ -1,184 +0,0 @@ -'use client'; - -import React, { useEffect, useState } from 'react'; -import { Fade } from 'react-awesome-reveal'; -import { ArrowUpRight, Mail, FileText, MapPin, Clock, Compass } from 'lucide-react'; - -const myEmail = 'adewaled03@gmail.com'; -const resumeLink = - 'https://docs.google.com/document/d/1sCQ_9keryAYoOxwaVqu05fWl3DgkAjroKQPTLqtVFQ4/edit?tab=t.0'; - -const tickerItems = [ - 'Frontend', 'Architecture', 'React', 'Next.js', 'TypeScript', - 'Design Systems', 'Performance', 'GraphQL', 'Tooling', 'Mentorship', 'DX', -]; - -function useClock() { - const [time, setTime] = useState('--:--:--'); - useEffect(() => { - const update = () => { - const t = new Date().toLocaleTimeString('en-GB', { - timeZone: 'Africa/Lagos', - hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, - }); - setTime(t); - }; - update(); - const id = setInterval(update, 1000); - return () => clearInterval(id); - }, []); - return time; -} - -export default function HeroCover() { - const time = useClock(); - const today = new Date().toLocaleDateString('en-US', { - weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', - }); - - return ( -
- {/* Soft grid backdrop */} -
- -
- {/* Masthead bar */} - -
- The Daniel Press · Vol. VI - {today} - Lagos · GMT+1 · {time} -
-
- -
- - {/* Main spread */} -
- {/* Left rail — bio / lead */} -
- -
- - Available · Q3 2026 -
-

- I'm Daniel Adewale, a senior frontend - engineer building interfaces for fast-moving teams. Six years deep in - production React, TypeScript, and design systems. -

-
-
- - 06° 27′ N · 03° 24′ E -
-
- - Lagos, Nigeria -
-
- - WAT {time} -
-
-
-
- - {/* Center — giant display headline */} -
- -
— Issue No. 06 · Cover Story
-
- -

- Daniel -
- Adewale -

-
- -

- Software engineer, frontend specialist, builder of quietly excellent interfaces. -

-
-
- - {/* Right rail — portrait + cover stories */} -
- -
- Daniel Adewale -
-

- The Engineer -

-
-
- -
-

Also Inside

- {[ - { num: 'P. 02', t: 'Field notes on the operator' }, - { num: 'P. 03', t: 'Trajectory & mission logs' }, - { num: 'P. 04', t: 'Case files: built things' }, - { num: 'P. 05', t: 'Reading room' }, - ].map((s, i) => ( -
- {s.num} - {s.t} -
- ))} -
-
-
-
- - {/* CTAs */} - - - -
- - {/* Bottom ticker strip */} -
-
- {[...tickerItems, ...tickerItems, ...tickerItems].map((item, i) => ( - - {item} - - - ))} -
-
- - -
- ); -} diff --git a/app/components/mapDisplay.tsx b/app/components/mapDisplay.tsx deleted file mode 100644 index d623fed..0000000 --- a/app/components/mapDisplay.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { MapPin } from 'lucide-react'; - -export default function LocationDisplay() { - return ( -
- Map showing Lagos, Nigeria -
- - {/* Pin */} -
-
-
-
- Avatar -
-
-
- - {/* Location badge */} -
-
- - Lagos, Nigeria -
- GMT+1 -
-
- ); -} diff --git a/app/components/missionDossiers.tsx b/app/components/missionDossiers.tsx deleted file mode 100644 index 835dbdd..0000000 --- a/app/components/missionDossiers.tsx +++ /dev/null @@ -1,195 +0,0 @@ -'use client'; - -import React from 'react'; -import { Fade } from 'react-awesome-reveal'; -import { Briefcase, FileBadge2, Calendar, MapPin } from 'lucide-react'; - -interface Mission { - codename: string; - role: string; - company: string; - location: string; - timeline: string; - classification: 'ACTIVE' | 'COMPLETE'; - brief: string; - outcomes: { num: string; lbl: string }[]; - arsenal: string[]; -} - -const missions: Mission[] = [ - { - codename: 'OPERATION · HOMEFRONT', - role: 'Senior Frontend Engineer', - company: 'Qshelter NG', - location: 'Lagos, Nigeria', - timeline: 'Sept 2023 — Present', - classification: 'ACTIVE', - brief: - 'Lead engineer on national-scale housing platforms. Architected and shipped the Renewed Hope Housing portal, the MREIF Platform, and Contribuild — collaborating across product, design, and backend to deliver gov-grade systems.', - outcomes: [ - { num: '50K+', lbl: 'Users registered' }, - { num: '₦1B+', lbl: 'Sales facilitated' }, - { num: '3', lbl: 'Platforms shipped' }, - ], - arsenal: ['Next.js', 'TypeScript', 'GraphQL', 'Tailwind', 'Redux', 'Styled-Components'], - }, - { - codename: 'OPERATION · CREATOR', - role: 'Frontend Developer', - company: 'Mage Inc', - location: 'Lagos, Nigeria', - timeline: 'Dec 2021 — Oct 2023', - classification: 'COMPLETE', - brief: - 'Built creator-economy tooling end-to-end — the Creator Store, payment infrastructure including virtual wallets and secure checkouts, and the data-driven Creator Dashboard.', - outcomes: [ - { num: '6K+', lbl: 'Users onboarded' }, - { num: '80%', lbl: 'Retention rate' }, - { num: '4', lbl: 'Products shipped' }, - ], - arsenal: ['React', 'Next.js', 'TypeScript', 'Firebase', 'WebSocket', 'Styled-Components'], - }, - { - codename: 'OPERATION · ORTHO', - role: 'Platform Engineer · Contract', - company: 'Tryortho', - location: 'FCT Abuja, Nigeria', - timeline: 'Apr 2022 — Aug 2022', - classification: 'COMPLETE', - brief: - 'Developed payment SDKs to streamline integration for client platforms, plus designed and built a conversion-focused marketing site.', - outcomes: [ - { num: '1', lbl: 'Payment SDK' }, - { num: '5+', lbl: 'Integrations' }, - { num: '1', lbl: 'Landing page' }, - ], - arsenal: ['React', 'TypeScript', 'REST API'], - }, - { - codename: 'OPERATION · GROUNDFLOOR', - role: 'Frontend Developer', - company: 'Kpie Technologies', - location: 'Lagos, Nigeria', - timeline: 'Nov 2019 — Oct 2021', - classification: 'COMPLETE', - brief: - 'Built user-focused platforms including SellMega (UX-driven e-commerce), shipped the PPMI website ahead of schedule, improved MultiGrid Limited UX, and collaborated on the Hua Ou Brick platform.', - outcomes: [ - { num: '5+', lbl: 'Platforms' }, - { num: '4', lbl: 'Domains served' }, - { num: '2yr', lbl: 'Tenure' }, - ], - arsenal: ['React', 'JavaScript', 'CSS', 'REST API'], - }, -]; - -export default function MissionDossiers() { - return ( -
-
-
- -
- -
-
- — Page 03 -

- Mission Dossiers -

-
-

- Field reports from companies served and missions completed. -

-
-
- -
- {missions.map((m, i) => ( - -
-
- - {/* Header row */} -
-
-
- - File · {String(i + 1).padStart(3, '0')} - / - {m.codename} -
-

- {m.role} ·{' '} - {m.company} -

-
- - {m.classification === 'ACTIVE' && ( - - )} - {m.classification} - -
- - {/* Meta strip */} -
- - - {m.timeline} - - - - {m.location} - - - - Full-time - -
- -
- - {/* Two-column: brief + outcomes */} -
-
-

Mission Brief

-

- {m.brief} -

- -

Arsenal Deployed

-
- {m.arsenal.map((a, j) => ( - - {a} - - ))} -
-
- -
-

Outcomes Logged

-
- {m.outcomes.map((o, j) => ( -
-
{o.num}
-
{o.lbl}
-
- ))} -
-
-
-
-
- ))} -
-
-
- ); -} diff --git a/app/components/operatorDashboard.tsx b/app/components/operatorDashboard.tsx deleted file mode 100644 index f33a1bd..0000000 --- a/app/components/operatorDashboard.tsx +++ /dev/null @@ -1,281 +0,0 @@ -'use client'; - -import React, { useEffect, useState } from 'react'; -import { Fade } from 'react-awesome-reveal'; -import { - Github, Linkedin, Twitter, Mail, BookOpen, MapPin, Activity, - Cpu, Globe, Headphones, -} from 'lucide-react'; - -const myEmail = 'adewaled03@gmail.com'; - -const proficiency = [ - { label: 'React', pct: 96 }, - { label: 'TypeScript', pct: 94 }, - { label: 'Next.js', pct: 92 }, - { label: 'Tailwind', pct: 90 }, - { label: 'GraphQL', pct: 84 }, - { label: 'Node.js', pct: 76 }, - { label: 'Figma', pct: 78 }, -]; - -const persona = ['Honest', 'Introvert', 'Music', 'Hard-Working', 'Collaborative']; - -const social = [ - { Icon: Github, label: 'github', val: 'syntaxPriest', link: 'https://github.com/syntaxPriest' }, - { Icon: Linkedin, label: 'linkedin', val: 'syntaxpriest', link: 'https://www.linkedin.com/in/syntaxpriest/' }, - { Icon: Twitter, label: 'twitter', val: '@syntaxPriest', link: 'https://twitter.com/syntaxPriest' }, - { Icon: Mail, label: 'email', val: myEmail, link: `mailto:${myEmail}` }, -]; - -const terminalLog = [ - { type: 'cmd', text: 'whoami' }, - { type: 'out', text: 'daniel.adewale@portfolio:~' }, - { type: 'cmd', text: 'cat status.txt' }, - { type: 'out', text: 'Open to senior FE roles · Q3 2026' }, - { type: 'cmd', text: 'tail -n 3 ./recent.log' }, - { type: 'out', text: 'Shipped: MREIF Housing v2.0' }, - { type: 'out', text: 'Mentored: 2 mid-level engineers' }, - { type: 'out', text: 'Reading: Psychology of Intelligence' }, -]; - -function useLagosTime() { - const [time, setTime] = useState('--:--'); - const [period, setPeriod] = useState('--'); - useEffect(() => { - const update = () => { - const t = new Date().toLocaleTimeString('en-GB', { - timeZone: 'Africa/Lagos', - hour: '2-digit', minute: '2-digit', hour12: false, - }); - const h = parseInt(t.slice(0, 2), 10); - setTime(t); - setPeriod(h < 12 ? 'Morning' : h < 17 ? 'Afternoon' : h < 21 ? 'Evening' : 'Night'); - }; - update(); - const id = setInterval(update, 1000 * 30); - return () => clearInterval(id); - }, []); - return { time, period }; -} - -export default function OperatorDashboard() { - const { time, period } = useLagosTime(); - - return ( -
- -
-
- — Page 02 -

- The Operator's Dashboard -

-
-

- A live read-out of the human, the habits, and the stack behind the work. -

-
-
- -
- {/* TERMINAL — wide */} -
-
-
-
-
- -
- daniel ~ status -
- live -
-
- {terminalLog.map((line, i) => ( -
- {line.type === 'cmd' ? ( - <> - ${' '} - {line.text} - - ) : ( - {line.text} - )} -
- ))} -
- ${' '} - -
-
-
-
- - {/* WORLD CLOCK + LOCATION */} -
-
-
-
- - Local Time · Lagos -
- GMT+1 -
- -
-
-
{time}
-

- Good {period.toLowerCase()} from Lagos. -

-
-
- -
-
- - 06°27′N · 03°24′E -
-
- - Focused -
-
-
-
- - {/* PROFICIENCY CHART */} -
-
-
-
- - Proficiency Levels -
- YoE-weighted -
-
- {proficiency.map((p, i) => ( - -
- {p.label} -
-
-
- {p.pct} -
- - ))} -
-
-
- - {/* NOW READING */} -
-
-
- - Now Reading -
- -
- Book -
-

- Psychology of Intelligence Analysis -

-

- Richards J. Heuer Jr. -

-
-
-
- - 68% · ch. 7 - -
-
-
-
-
- - {/* NOW PLAYING / PERSONA */} -
-
-
- - Personality Mix -
-

- The traits that show up at standup. -

-
- {persona.map((t, i) => ( - {t} - ))} -
-
-
- - {/* LOCATION MAP */} -
-
- Lagos -
-
-
-
-
- -
-
-
-
-
- - Lagos, Nigeria -
- Open to remote -
-
-
- - {/* SOCIALS / DIRECT ROW */} -
-
-
- Contact Channels - tap to copy / open -
-
- {social.map((s, i) => ( - - -
-

{s.label}

-

{s.val}

-
-
- ))} -
-
-
-
-
- ); -} diff --git a/app/components/project-card.tsx b/app/components/project-card.tsx deleted file mode 100644 index 0fa6f0e..0000000 --- a/app/components/project-card.tsx +++ /dev/null @@ -1,80 +0,0 @@ -'use client'; - -import React from 'react'; -import { Fade } from 'react-awesome-reveal'; -import { ArrowUpRight } from 'lucide-react'; - -interface CardProps { - index: number; - img?: string; - title: string; - description: string; - link: string; - stacks?: string[]; - featured?: boolean; -} - -const Card: React.FC = ({ index, link, img, title, description, stacks = [], featured }) => { - const openLink = () => { - if (!link) return; - window.open(link, '_blank', 'noopener,noreferrer'); - }; - - return ( - -
-
- {title} -
- -
- -
- -
-
- {String(index + 1).padStart(2, '0')} - · - Live -
-

- {title} -

-

- {description} -

- - {stacks.length > 0 && ( -
- {stacks.slice(0, featured ? 6 : 4).map((stack, i) => ( - - {stack} - - ))} - {stacks.length > (featured ? 6 : 4) && ( - - +{stacks.length - (featured ? 6 : 4)} - - )} -
- )} -
-
-
- ); -}; - -export default Card; diff --git a/app/components/readingRoom.tsx b/app/components/readingRoom.tsx deleted file mode 100644 index e6da494..0000000 --- a/app/components/readingRoom.tsx +++ /dev/null @@ -1,100 +0,0 @@ -'use client'; - -import React from 'react'; -import { Fade } from 'react-awesome-reveal'; -import { ArrowUpRight } from 'lucide-react'; - -interface Piece { - name: string; - excerpt: string; - link: string; - category: string; - readTime: string; - date: string; -} - -const publications: Piece[] = [ - { - name: 'Understanding basic styled components with React, Next and Vue.', - excerpt: - 'A walkthrough of how styled-components composes across the three biggest frameworks, with patterns I keep reusing in production.', - link: 'https://dev.to/syntaxpriest/understanding-styled-components-a-basic-guide-on-styled-components-usage-with-reactnextvue-45a4', - category: 'Engineering', - readTime: '6 min', - date: 'Aug 2023', - }, - { - name: 'Image Upload And File Preview With ReactJs.', - excerpt: - 'A reusable React pattern for previewing uploaded images before they hit the server — small UX, big difference.', - link: 'https://dev.to/syntaxpriest/image-file-previewing-with-reactjs-883', - category: 'Tutorial', - readTime: '5 min', - date: 'Jun 2023', - }, -]; - -export default function ReadingRoom() { - return ( -
- -
-
- — Page 05 -

- The Reading Room -

-
-

- Lessons, trends, and tutorials I've committed to the page. -

-
-
- - {/* Table-of-contents header */} -
- Table of Contents - Issue · Spring 2026 -
-
- ); -} diff --git a/app/components/socialSection.tsx b/app/components/socialSection.tsx deleted file mode 100644 index 40acbd0..0000000 --- a/app/components/socialSection.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { Github, Linkedin, Twitter, Mail, ArrowUpRight } from 'lucide-react'; - -const myEmail = "adewaled03@gmail.com"; - -export default function SocialSection() { - return ( -
-
-
- -
- Elsewhere -
-

- Drop in. Say hi. Pick a channel. -

- -
- {social.map((item, index) => ( - -
- - {item.name} -
- -
- ))} -
-
- ); -} - -export const social = [ - { - id: "1", - name: "Github", - Icon: Github, - link: "https://github.com/syntaxPriest", - }, - { - id: "2", - name: "LinkedIn", - Icon: Linkedin, - link: "https://www.linkedin.com/in/syntaxpriest/", - }, - { - id: "3", - name: "Twitter", - Icon: Twitter, - link: "https://twitter.com/syntaxPriest", - }, - { - id: "4", - name: "Email", - Icon: Mail, - link: `mailto:${myEmail}`, - }, -]; diff --git a/app/components/techStackSection.tsx b/app/components/techStackSection.tsx deleted file mode 100644 index a1b0705..0000000 --- a/app/components/techStackSection.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import Marquee from 'react-fast-marquee'; -import { Layers } from 'lucide-react'; - -export default function TechStackSection() { - return ( -
-
-
- -
- Tech Stack -
-

- Languages, frameworks and tools I reach for daily. -

- -
- - {TechList.map((item, index) => ( -
- {item.name} -
- ))} -
-
-
- ); -} - -export const TechList = [ - { id: "1", name: "HTML", icon: "/stack/html.png" }, - { id: "2", name: "CSS", icon: "/stack/css.png" }, - { id: "3", name: "Javascript", icon: "/stack/js.png" }, - { id: "12", name: "Typescript", icon: "/stack/ts.png" }, - { id: "13", name: "NextJs", icon: "/stack/next.png" }, - { id: "4", name: "React", icon: "/stack/react.png" }, - { id: "5", name: "Redux", icon: "/stack/redux.png" }, - { id: "6", name: "Tailwind", icon: "/stack/tailwind.png" }, - { id: "7", name: "Figma", icon: "/stack/figma.png" }, - { id: "8", name: "React Native", icon: "/stack/Reactnative.png" }, - { id: "9", name: "NodeJs", icon: "/stack/nodejs.png" }, - { id: "15", name: "GraphQL", icon: "/stack/GraphQL.png" }, - { id: "16", name: "Firebase", icon: "/stack/firebase.png" }, - { id: "20", name: "Jest", icon: "/stack/jest.png" }, - { id: "21", name: "Chakra UI", icon: "/stack/chakra.png" }, - { id: "10", name: "SASS", icon: "/stack/sass.png" }, - { id: "11", name: "Git & CI/CD", icon: "/stack/git.png" }, - { id: "14", name: "React Query", icon: "/stack/Reactquery.png" }, - { id: "151", name: "DSA", icon: "/stack/dsa.png" }, -]; diff --git a/app/data/portfolio.ts b/app/data/portfolio.ts new file mode 100644 index 0000000..be068bc --- /dev/null +++ b/app/data/portfolio.ts @@ -0,0 +1,374 @@ +/* =================================================================== + * Portfolio Data — single source of truth for all content. + * Import these constants in page.tsx, works/page.tsx, etc. + * =================================================================== */ + +// ---- Types ---- + +export type NavSection = 'home' | 'about' | 'works' | 'projects' | 'articles' | 'experience'; + +export type Category = 'All' | 'Platform' | 'Web app' | 'Marketing site'; + +export interface Project { + name: string; + img: string; + link?: string; + /** Display-friendly duration e.g. "2025", "Personal build", "6 weeks" */ + duration: string; + /** Sortable year for the works page */ + year: string; + /** Category for filter on works page */ + category: Category; + /** Short description */ + desc: string; +} + +export interface Experience { + role: string; + company: string; + location: string; + period: string; + summary: string; + points: string[]; +} + +export interface JournalEntry { + title: string; + link: string; + date: string; + img: string; +} + +export interface FAQ { + q: string; + a: string; +} + +export interface Stat { + num: string; + lbl: string; +} + +export interface Outcome { + kicker: string; + title: string; + desc: string; +} + +export interface Service { + title: string; + desc: string; + tags: string[]; + img: string; +} + +export interface HeroStackItem { + name: string; + file: string; +} + +// ---- Data ---- + +export const myEmail = 'adewaled03@gmail.com'; + +export const heroStack: HeroStackItem[] = [ + { name: 'React', file: 'react.png' }, + { name: 'Next.js', file: 'next-dark.svg' }, + { name: 'TypeScript', file: 'ts.png' }, + { name: 'JavaScript', file: 'js.png' }, + { name: 'Tailwind CSS', file: 'tailwind.png' }, + { name: 'Redux', file: 'redux.png' }, + { name: 'GraphQL', file: 'GraphQL.png' }, + { name: 'Node.js', file: 'nodejs.png' }, + { name: 'React Query', file: 'Reactquery.png' }, + { name: 'React Native', file: 'Reactnative.png' }, + { name: 'Figma', file: 'figma.png' }, +]; + +export const stats: Stat[] = [ + { num: '20+', lbl: 'Projects shipped end-to-end with care' }, + { num: '1M+', lbl: 'Users served across platforms built' }, + { num: '6+', lbl: 'Years building production interfaces' }, + { num: '4', lbl: 'Companies and teams partnered with' }, +]; + +export const outcomes: Outcome[] = [ + { + kicker: 'Engineering at scale', + title: 'Interfaces engineered for scale, not screenshots', + desc: 'Production systems built to onboard tens of thousands without buckling.', + }, + { + kicker: 'Design systems', + title: 'Components that compound across teams', + desc: 'Reusable systems that cut cycles and keep velocity without quality drift.', + }, +]; + +export const services: Service[] = [ + { + title: 'Web engineering', + desc: 'Production React, Next.js, and TypeScript — built to scale and built to last.', + tags: ['Next.js', 'React', 'TypeScript', 'GraphQL'], + img: '/works/services/web.svg', + }, + { + title: 'Design systems', + desc: 'Reusable component libraries with strong primitives and tight DX.', + tags: ['Tokens', 'Tailwind', 'Storybook', 'Accessibility'], + img: '/works/services/design.svg', + }, + { + title: 'Performance', + desc: 'Core Web Vitals, bundle diet, hydration strategy, perceived speed.', + tags: ['Profiling', 'CWV', 'Caching', 'RSC'], + img: '/works/services/performance.svg', + }, + { + title: 'Product partnership', + desc: 'Embedded with PM and design, end-to-end ownership, weekly demos.', + tags: ['Discovery', 'Specs', 'QA', 'Reviews'], + img: '/works/services/partnership.svg', + }, +]; + +export const experience: Experience[] = [ + { + role: 'Senior Front End Engineer', + company: 'Fatbeehive', + location: 'London, UK', + period: 'Jan 2025 — Present', + summary: + 'At Fatbeehive I build for purpose-driven teams — charities and ethical organizations — translating intricate briefs into accessible, high-performing websites. My days move between pixel-perfect Figma builds, reusable design systems, and keeping clients unblocked by resolving support tickets quickly.', + points: [ + 'Lead development of accessible, user-centric platforms for charities and ethical organizations, turning complex briefs into performant, responsive frontends.', + 'Build custom UI components and reusable design systems with pixel-perfect fidelity to Figma, championing semantic HTML, SCSS and WCAG 2.1 AA compliance.', + ], + }, + { + role: 'Senior Front End Developer', + company: 'Qshelter Limited', + location: 'Lagos, Nigeria', + period: 'Sep 2023 — Present', + summary: + 'Qshelter is where I earned a senior role within two weeks. I led frontend work across their housing products — building Contribuild, shipping the MoFI Housing MVP in 15 days, and partnering with the Federal Government of Nigeria on the Renewed Hope platform, which drove 50,000+ registrations and over ₦1B in sales.', + points: [ + 'Built Contribuild in React and TypeScript, earning a senior role within two weeks; delivered Qshelter features driving 100,000 visits and 90% retention in a month.', + 'Shipped the MoFI Housing MVP in 15 days and built the Renewed Hope platform with the Federal Government of Nigeria — 50,000+ registrations and ₦1B+ in sales.', + ], + }, + { + role: 'Front End Developer', + company: 'Mage Inc', + location: 'Lagos, Nigeria', + period: 'Dec 2021 — Oct 2023', + summary: + 'At Mage I helped a creator-economy product find its footing — building the Creator Store from scratch to 6,000+ users, a data-rich Creator Dashboard, and the payment rails (wallets, checkout, virtual cards) that let creators monetize securely.', + points: [ + 'Built the Creator Store from scratch, attracting 6,000+ users at an 80% retention rate through an intuitive purchase experience.', + 'Developed the Creator Dashboard and payment infrastructure — wallets, checkouts, and a Virtual Card system for secure, seamless monetization.', + ], + }, + { + role: 'Front End Developer', + company: 'Kpie Technologies', + location: 'Lagos, Nigeria', + period: 'Nov 2019 — Oct 2021', + summary: + 'Kpie was a fast-paced agency chapter: I delivered client sites ahead of deadline, applied UX best practices to lift engagement, and built products like SellMega end-to-end while collaborating closely with backend teams.', + points: [ + 'Delivered the PPMI website ahead of schedule and improved MultiGrid Limited with UX best practices for a more engaging experience.', + 'Built SellMega, a user-friendly e-commerce platform, and collaborated on the Hua Ou Brick site and SafeScout.', + ], + }, + { + role: 'Freelance Frontend Engineer', + company: 'Independent', + location: 'Remote', + period: 'Jun 2018 — Present', + summary: + 'Across freelance engagements since 2018 I\'ve shipped responsive apps, dashboards, e-commerce platforms and secure payment systems in React and TypeScript — owning the frontend end-to-end and collaborating with cross-functional teams to deliver on time and in scope.', + points: [ + 'Build responsive websites and apps in React, TypeScript and modern tooling — e-commerce platforms, dashboards, and secure payment systems.', + 'Partner with cross-functional teams to ship high-quality, performant, user-centric products on time and in scope.', + ], + }, +]; + +export const faqs: FAQ[] = [ + { + q: 'Do you take on small projects?', + a: 'Yes — small, well-scoped projects are some of my favorite. If the brief is sharp and the timeline is honest, I am interested.', + }, + { + q: 'Can you work with an existing codebase?', + a: 'Absolutely. I will audit before I refactor, and I prefer evolution to rewrites.', + }, + { + q: 'What is your typical project timeline?', + a: 'Contract engagements typically run 4–12 weeks. Full-time engagements run quarter-by-quarter with milestones we agree on upfront.', + }, + { + q: 'What happens after I reach out?', + a: 'You get a reply within 24 hours, we book a 20-minute intro call, and within 48 hours I send a scoped proposal.', + }, + { + q: 'Do you offer free intro calls?', + a: 'Yes — 20 minutes, no slides, no pitch. Just a conversation about your product and whether I am the right fit.', + }, + { + q: 'Can I reach out if I am not sure what I need yet?', + a: 'Please do. Half the value of the intro call is figuring that out together.', + }, +]; + +export const journalEntries: JournalEntry[] = [ + { + title: 'Understanding styled components with React, Next and Vue.', + link: 'https://dev.to/syntaxpriest/understanding-styled-components-a-basic-guide-on-styled-components-usage-with-reactnextvue-45a4', + date: 'Aug 3, 2025', + img: '/works/journal/styled.svg', + }, + { + title: 'Image upload and file preview with ReactJS.', + link: 'https://dev.to/syntaxpriest/image-file-previewing-with-reactjs-883', + date: 'Jun 6, 2025', + img: '/works/journal/upload.svg', + }, + { + title: 'Rethinking the frontend engineering process.', + link: 'https://dev.to/syntaxpriest', + date: 'Jul 3, 2025', + img: '/works/journal/process.svg', + }, +]; + +/** Merged project list — single source for both the home page HuePanels and the works page filter/grid. + * Rich shape includes `duration` (display) and `year` (sortable) + `category` for filtering. */ +export const projectList: Project[] = [ + { + name: 'Openvisio', + img: '/works/openvisio.png', + link: 'https://openvisio.io', + duration: '2025', + year: '2025', + category: 'Web app', + desc: 'A code-intelligence tool that turns a repository into a navigable knowledge graph — trace call chains, map dependencies, and surface structure so AI agents and engineers can understand large codebases at a glance.', + }, + { + name: '2004 Estate', + img: '/works/2004estate.png', + link: 'https://2004estate.ng', + duration: '2025', + year: '2025', + category: 'Platform', + desc: 'A property platform for a coastal residential estate — browse residences, explore floor plans, and choose flexible installment or mortgage plans in a fast, trust-building interface.', + }, + { + name: 'Song Explainer', + img: '/works/lyrist.png', + link: 'https://lyrist.netlify.app', + duration: 'Personal build', + year: '2025', + category: 'Web app', + desc: 'Paste a Spotify link, an ISRC, or just a song and artist, and Lyrist unpacks what the track is really about — themes, line-by-line meaning, and the references hiding in the lyrics — in a calm, focused reading view.', + }, + { + name: 'BrimAI', + img: '/works/brim.png', + link: 'https://trybrim.app', + duration: 'Personal build', + year: '2025', + category: 'Web app', + desc: 'A prompt-engineering app that turns rough ideas into expert-level prompts. Brim refines and structures your input so any AI model returns sharper, more accurate results — with a browser extension to use it anywhere.', + }, + { + name: 'MoFI Housing', + img: '/works/mofi2.png', + link: 'https://mreif.com.ng', + duration: '6 weeks', + year: '2024', + category: 'Platform', + desc: 'A housing-finance platform that turns a dense mortgage and property catalogue into a calm, guided search — fast listings, clear flows, and a frontend tuned to feel trustworthy from the first click.', + }, + { + name: 'Renewed Hope Homes', + img: '/works/rh.png', + link: 'https://renewedhopehomes.fmhud.gov.ng/properties/search', + duration: '8 weeks', + year: '2024', + category: 'Platform', + desc: 'The public property-search experience for a national housing initiative. Built to stay quick and accessible under heavy traffic and large inventories, even on modest devices and patchy connections.', + }, + { + name: 'Mage', + img: '/works/mage.png', + link: 'https://trymage.com', + duration: 'Ongoing', + year: '2023', + category: 'Platform', + desc: 'An ongoing product partnership shaping the core interface from the ground up — a component system and considered flows that let the team ship new surfaces fast without the UI drifting.', + }, + { + name: 'Contribuild', + img: '/works/contribuild.png', + link: 'https://contribuild.ng', + duration: '5 weeks', + year: '2023', + category: 'Platform', + desc: 'A platform that brings structure to construction projects and contributions, pairing heavy data with an interface that stays readable, scannable, and easy to act on.', + }, + { + name: 'Metropolitan Club', + img: '/works/tmc.png', + link: '', + duration: '4 weeks', + year: '2023', + category: 'Marketing site', + desc: 'A members\' club presence with a refined, editorial feel — typography-led and image-forward, tuned so every page reads as deliberate and quietly premium.', + }, + { + name: 'Stream UI', + img: '/works/video.png', + link: 'https://vid-react.netlify.app', + duration: '3 weeks', + year: '2023', + category: 'Web app', + desc: 'A streaming interface exploration in React — fluid playback controls, a responsive content grid, and motion that makes browsing the catalogue feel effortless.', + }, + { + name: 'Multigrid Limited', + img: '/works/multigrid.png', + link: '', + duration: '2021', + year: '2021', + category: 'Marketing site', + desc: 'A marketing site for a firm specializing in architecture, construction, and engineering.', + }, + { + name: 'Otayemi Law Firm', + img: '/works/law.jpg', + link: '', + duration: '2021', + year: '2021', + category: 'Marketing site', + desc: 'A polished presence for a team of lawyers with expertise across diverse fields of practice.', + }, + { + name: 'Hua Ou Clay Bricks', + img: '/works/huaoy.png', + link: '', + duration: '2020', + year: '2020', + category: 'Marketing site', + desc: 'A site for a construction company that evaluates and sells clay bricks to clients.', + }, +]; + +/** Stats for the works page hero */ +export const heroStats: Stat[] = [ + { num: '12', lbl: 'Projects shipped' }, + { num: '6+', lbl: 'Years building' }, + { num: '1M+', lbl: 'Users reached' }, + { num: '4', lbl: 'Teams partnered' }, +]; diff --git a/app/globals.css b/app/globals.css index d4e339f..63b3b9a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2,1548 +2,8 @@ @tailwind components; @tailwind utilities; -:root { - --bg: #FFFFFF; - --bg-soft: #F6F5F2; - --bg-card: #F1EFEA; - --ink: #0E0E0E; - --ink-soft: #5A5A5A; - --ink-mute: #9A988F; - --line: #EDEBE5; -} +/* Core design tokens, typography, scrollbar */ +@import "./styles/base.css"; -@font-face { - font-family: "Cal Sans"; - font-style: normal; - font-display: swap; - font-weight: 600; - src: url("./fonts/webfonts/CalSans-SemiBold.woff2") format("woff2"), - url("./fonts/webfonts/CalSans-SemiBold.woff") format("woff"), - url("./fonts/webfonts/CalSans-SemiBold.ttf") format("truetype"); -} - -html, body { - background: var(--bg); - color: var(--ink); - font-family: 'Cal Sans', 'Inter', Helvetica, sans-serif; - scroll-behavior: smooth; - /* clip (not hidden) prevents the full-bleed 100vw panels from causing a - horizontal scrollbar WITHOUT creating a scroll container that would - break `position: sticky` (the service stack, FAQ rail). */ - overflow-x: clip; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -::selection { - background: var(--ink); - color: var(--bg); -} - -h1, h2, h3, h4, h5, h6 { - font-family: 'Cal Sans', 'Inter', sans-serif; - letter-spacing: -0.02em; - color: var(--ink); - font-weight: 600; -} - -/* ============ Small eyebrow pill ============ */ -.eyebrow { - display: inline-flex; - align-items: center; - gap: 7px; - font-size: 12px; - font-weight: 500; - color: var(--ink); - padding: 6px 14px; - border-radius: 100px; - background: var(--bg-card); - letter-spacing: 0; - line-height: 1; -} -.eyebrow .dot { - width: 5px; - height: 5px; - border-radius: 100px; - background: var(--ink); -} - -/* ============ Buttons ============ */ -.btn-link { - display: inline-flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - font-size: 14px; - font-weight: 500; - color: var(--ink); -} -.btn-link .label { - display: inline-flex; - align-items: center; - gap: 6px; -} -.btn-link .underline { - display: block; - width: 28px; - height: 1px; - background: var(--ink); - transition: width 0.3s ease; -} -.btn-link:hover .underline { width: 100%; } - -.btn-pill-dark { - display: inline-flex; - align-items: center; - gap: 10px; - padding: 12px 16px 12px 22px; - border-radius: 100px; - background: var(--ink); - color: var(--bg); - font-size: 14px; - font-weight: 500; - transition: all 0.25s ease; -} -.btn-pill-dark:hover { - background: #2A2A2A; - transform: translateY(-1px); -} -.btn-pill-dark .arrow-circle { - width: 26px; - height: 26px; - border-radius: 100px; - background: var(--bg); - color: var(--ink); - display: inline-flex; - align-items: center; - justify-content: center; -} - -.btn-pill-light { - display: inline-flex; - align-items: center; - gap: 10px; - padding: 12px 16px 12px 22px; - border-radius: 100px; - background: var(--bg-card); - color: var(--ink); - font-size: 14px; - font-weight: 500; - transition: all 0.25s ease; -} -.btn-pill-light:hover { - background: #E6E4DF; - transform: translateY(-1px); -} -.btn-pill-light .arrow-circle { - width: 26px; - height: 26px; - border-radius: 100px; - background: var(--bg); - color: var(--ink); - display: inline-flex; - align-items: center; - justify-content: center; -} - -/* ============ Display heading ============ */ -.display { - font-family: 'Cal Sans', 'Inter', sans-serif; - font-weight: 600; - letter-spacing: -0.035em; - line-height: 1.02; - color: var(--ink); -} - -/* ============ Cards ============ */ -.soft-card { - background: var(--bg-card); - border: 1px solid var(--line); - border-radius: 24px; - position: relative; - overflow: hidden; -} - -/* Stat cells */ -.stat-cell { - padding: 22px 0 0; - border-top: 1px solid var(--line); - transition: border-color 0.3s ease; -} -.stat-cell:hover { border-color: var(--ink); } - -/* ============ Full-bleed hue panels (Projects + Services) ============ */ -.full-bleed { - width: 100vw; - margin-left: calc(50% - 50vw); - margin-right: calc(50% - 50vw); -} - -.pv-panel { - position: relative; - display: flex; - align-items: center; - width: 100%; - min-height: 100vh; - color: var(--ink); - background-color: #eef1f6; - background-image: linear-gradient(160deg, hsl(222 24% 95%), hsl(222 26% 91%)); - overflow: hidden; -} -@media (max-width: 900px) { - .pv-panel { min-height: auto; padding: 84px 0; } -} -/* subtle film grain over the mesh, behind the content */ -.pv-panel::after { - content: ''; - position: absolute; - inset: 0; - z-index: 0; - pointer-events: none; - opacity: 0.05; - mix-blend-mode: multiply; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); -} - -.pv-inner { - position: relative; - z-index: 1; - width: 100%; - max-width: 1380px; - margin: 0 auto; - padding: 80px 40px; - display: grid; - grid-template-columns: 0.62fr 1.38fr; - gap: 60px; - align-items: center; -} -@media (max-width: 900px) { - .pv-inner { - grid-template-columns: 1fr; - gap: 40px; - padding: 0 28px; - } -} -@media (max-width: 640px) { - .pv-inner { padding: 0 20px; } -} - -/* Image side flips per panel for rhythm (desktop only). Reverse the column - widths too so the media always gets the WIDE column, text the narrow one. */ -@media (min-width: 901px) { - .pv-panel.is-flipped .pv-inner { grid-template-columns: 1.38fr 0.62fr; } - .pv-panel.is-flipped .pv-media { order: 1; } - .pv-panel.is-flipped .pv-text { order: 2; } -} - -.pv-text { max-width: 460px; } -.pv-title { - color: var(--ink); - margin-top: 22px; - letter-spacing: -0.035em; -} -.pv-desc { - font-size: 16px; - line-height: 1.68; - color: var(--ink-soft); - margin-top: 22px; - max-width: 420px; -} -.pv-foot { - display: flex; - align-items: center; - gap: 18px; - margin-top: 36px; -} -.pv-meta { - font-size: 13px; - color: var(--ink-mute); - white-space: nowrap; -} -.pv-cta { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 12px 22px; - border-radius: 100px; - font-size: 14px; - font-weight: 500; - color: var(--bg); - background: var(--ink); - transition: transform 0.25s ease, box-shadow 0.25s ease; -} -.pv-panel:hover .pv-cta { - transform: translateY(-2px); - box-shadow: 0 14px 30px -12px rgba(0, 0, 0, 0.3); -} - -/* Browser-frame showcase for the screenshot */ -.pv-media { perspective: 1800px; } -.browser { - border-radius: 16px; - overflow: hidden; - background: #fff; - border: 1px solid rgba(0, 0, 0, 0.06); - box-shadow: 0 50px 90px -45px rgba(0, 0, 0, 0.35), - 0 16px 36px -26px rgba(0, 0, 0, 0.25); - transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); -} -.pv-panel:hover .browser { transform: translateY(-8px); } -.browser-bar { - display: flex; - align-items: center; - gap: 14px; - height: 44px; - padding: 0 16px; - background: #f3f3f1; - border-bottom: 1px solid #e7e7e3; -} -.browser-dots { display: flex; gap: 7px; } -.browser-dots i { - width: 11px; - height: 11px; - border-radius: 100px; - background: #d4d4d2; - display: block; -} -.browser-url { - flex: 1; - max-width: 300px; - font-size: 12px; - color: #8a8a86; - background: #fff; - border: 1px solid #e7e7e3; - border-radius: 100px; - padding: 5px 16px; - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.browser-shot { - position: relative; - aspect-ratio: 16 / 10; - overflow: hidden; - background: #fff; -} -.browser-shot .parallax-layer img { - object-position: top center; - transition: transform 0.9s cubic-bezier(0.4, 0, 0.2, 1); -} -.pv-panel:hover .browser-shot .parallax-layer img { transform: scale(1.04); } - -/* Light eyebrow + tags for use on the colored panels */ -.eyebrow-light { - display: inline-flex; - align-items: center; - gap: 7px; - font-size: 12px; - font-weight: 500; - color: #fff; - padding: 6px 14px; - border-radius: 100px; - background: rgba(255, 255, 255, 0.16); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); - line-height: 1; -} -.eyebrow-light .dot { - width: 5px; - height: 5px; - border-radius: 100px; - background: #fff; -} -.panel-tags { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 28px; -} -.panel-tag { - font-size: 12px; - padding: 6px 14px; - border-radius: 100px; - color: #fff; - background: rgba(255, 255, 255, 0.14); - border: 1px solid rgba(255, 255, 255, 0.22); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); -} - -/* ============ Services — sticky stack ============ */ -/* Plain block flow (not flex) — flex + position:sticky is fragile; block - children with sticky is the reliable stacking pattern. */ -.service-stack { - /* small tail so the last card can settle into its pinned position */ - padding-bottom: 5vh; -} -.service-sticky { - display: block; - position: sticky; /* top + z-index set inline so cards pile on scroll */ - margin-bottom: 24px; -} -.service-row { - background: var(--bg-soft); - border: 1px solid var(--line); - border-radius: 28px; - padding: 20px; - display: grid; - grid-template-columns: 0.92fr 1.08fr; - gap: 36px; - align-items: center; - box-shadow: 0 28px 60px -42px rgba(0, 0, 0, 0.32); -} -@media (max-width: 768px) { - .service-stack { padding-bottom: 0; } - /* no pinning on small screens — cards just stack normally */ - .service-sticky { position: static !important; top: auto !important; margin-bottom: 18px; } - .service-row { grid-template-columns: 1fr; padding: 14px; } -} -.service-img { - border-radius: 20px; - overflow: hidden; - background: var(--bg); - aspect-ratio: 5 / 4; -} -.service-img img { - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1); -} -.service-row:hover .service-img img { transform: scale(1.04); } -.service-content { - padding: 28px 36px 28px 12px; -} -@media (max-width: 768px) { - .service-content { padding: 18px 16px 22px; } -} -.service-num { - font-size: 13px; - font-weight: 600; - color: var(--ink-mute); - letter-spacing: 0.04em; -} -.service-title { - margin-top: 14px; - line-height: 1.04; -} -.service-desc { - font-size: 15px; - line-height: 1.62; - color: var(--ink-soft); - margin-top: 16px; - max-width: 400px; -} -.service-tags { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 30px; -} -.service-tag { - font-size: 12px; - padding: 7px 15px; - border-radius: 100px; - background: var(--bg); - border: 1px solid var(--line); - color: var(--ink-soft); -} - -/* Outcome cards */ -.outcome-card { - background: var(--bg-card); - border: 1px solid var(--line); - border-radius: 24px; - padding: 56px 40px; - text-align: center; - min-height: 420px; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-between; - transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 0.4s ease, border-color 0.4s ease; -} -.outcome-card:hover { - transform: translateY(-6px); - box-shadow: 0 24px 50px -28px rgba(0, 0, 0, 0.18); - border-color: var(--ink-mute); -} -@media (max-width: 640px) { - .outcome-card { padding: 32px 24px; min-height: 0; } -} - -/* ============ FAQ ============ */ -.faq-item { - background: var(--bg-card); - border: 1px solid var(--line); - border-radius: 18px; - overflow: hidden; - transition: background 0.25s ease, border-color 0.25s ease; -} -.faq-item:hover { background: #E9E7E1; } -.faq-item.is-open { - background: var(--bg-card); - border-color: var(--ink-mute); -} - -.faq-trigger { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - gap: 18px; - padding: 22px 22px 22px 26px; - text-align: left; - font-size: 16px; - font-weight: 500; - color: var(--ink); - cursor: pointer; - background: transparent; -} -.faq-q-text { flex: 1; line-height: 1.35; } - -.faq-chev { - width: 36px; - height: 36px; - border-radius: 100px; - background: var(--bg); - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--ink); - flex-shrink: 0; - transition: transform 0.3s ease; -} -.faq-item.is-open .faq-chev { transform: rotate(180deg); } - -.faq-panel { - display: grid; - grid-template-rows: 0fr; - transition: grid-template-rows 0.35s cubic-bezier(0.4, 0, 0.2, 1); -} -.faq-item.is-open .faq-panel { grid-template-rows: 1fr; } -.faq-panel-inner { - overflow: hidden; - font-size: 14.5px; - color: var(--ink-soft); - line-height: 1.65; - padding: 0 26px; -} -.faq-item.is-open .faq-panel-inner { - padding: 0 26px 24px; -} - -/* ============ Journal ============ */ -.journal-card { - display: block; - border-radius: 22px; - overflow: hidden; - border: 1px solid var(--line); - background: var(--bg-card); - padding: 12px 12px 22px; - transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 0.4s ease, border-color 0.4s ease; -} -.journal-card:hover { - transform: translateY(-6px); - box-shadow: 0 24px 50px -28px rgba(0, 0, 0, 0.2); - border-color: var(--ink-mute); -} -.journal-img { - aspect-ratio: 16 / 11; - overflow: hidden; - background: var(--bg); - border-radius: 14px; - position: relative; -} -.journal-img img { - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); -} -.journal-card:hover .journal-img img { transform: scale(1.05); } -.journal-arrow { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: rgba(14, 14, 14, 0.08); - opacity: 0; - transition: opacity 0.3s ease; -} -.journal-card:hover .journal-arrow { opacity: 1; } -.journal-arrow span { - width: 48px; - height: 48px; - border-radius: 100px; - background: var(--bg); - display: flex; - align-items: center; - justify-content: center; - color: var(--ink); - transform: scale(0.85); - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} -.journal-card:hover .journal-arrow span { transform: scale(1); } -.journal-card p, -.journal-card h3 { padding-left: 8px; padding-right: 8px; } - -/* ============ Hero project marquee ============ */ -.marquee { - position: relative; - overflow: hidden; - width: 100%; - -webkit-mask-image: linear-gradient( - to right, - transparent 0, - #000 8%, - #000 92%, - transparent 100% - ); - mask-image: linear-gradient( - to right, - transparent 0, - #000 8%, - #000 92%, - transparent 100% - ); -} -.marquee-track { - display: flex; - width: max-content; - gap: 14px; - animation: marquee-scroll 38s linear infinite; -} -.marquee:hover .marquee-track { - animation-play-state: paused; -} -@keyframes marquee-scroll { - from { transform: translateX(0); } - to { transform: translateX(calc(-50% - 7px)); } -} -.marquee-pill { - display: inline-flex; - align-items: center; - gap: 14px; - flex-shrink: 0; - padding: 10px 20px 10px 10px; - border-radius: 100px; - background: var(--bg-card); - border: 1px solid var(--line); - transition: background 0.25s ease, border-color 0.25s ease, transform 0.25s ease; -} -.marquee-pill:hover { - background: var(--bg); - border-color: var(--ink-mute); - transform: translateY(-2px); -} -.marquee-thumb { - width: 44px; - height: 44px; - border-radius: 100px; - overflow: hidden; - background: var(--bg); - flex-shrink: 0; -} -.marquee-thumb img { - width: 100%; - height: 100%; - object-fit: cover; -} -.marquee-name { - font-size: 15px; - font-weight: 600; - color: var(--ink); - white-space: nowrap; -} -.marquee-dur { - font-size: 12px; - color: var(--ink-mute); - padding-left: 12px; - border-left: 1px solid var(--line); - white-space: nowrap; -} -@media (prefers-reduced-motion: reduce) { - .marquee-track { animation: none; } -} - -/* ============ Hero — bold statement ============ */ -.hero-top { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; -} -.hero-avail { - display: inline-flex; - align-items: center; - gap: 9px; - font-size: 13px; - font-weight: 500; - color: var(--ink); - padding: 8px 16px; - border-radius: 100px; - background: var(--bg-card); -} -.hero-cv { - display: inline-flex; - align-items: center; - gap: 5px; - font-size: 13px; - font-weight: 500; - color: var(--ink-soft); - transition: color 0.2s ease; -} -.hero-cv:hover { color: var(--ink); } -.live-dot { - width: 8px; - height: 8px; - border-radius: 100px; - background: #46d17f; - box-shadow: 0 0 0 0 rgba(70, 209, 127, 0.6); - animation: live-pulse 2s infinite; -} -@keyframes live-pulse { - 0% { box-shadow: 0 0 0 0 rgba(70, 209, 127, 0.55); } - 70% { box-shadow: 0 0 0 9px rgba(70, 209, 127, 0); } - 100% { box-shadow: 0 0 0 0 rgba(70, 209, 127, 0); } -} - -.hero-statement { - margin-top: 52px; - font-size: clamp(44px, 8.4vw, 116px); - line-height: 0.98; - letter-spacing: -0.045em; - max-width: 1000px; -} -@media (max-width: 640px) { .hero-statement { margin-top: 36px; } } -.hero-em { - position: relative; - white-space: nowrap; - background: linear-gradient(180deg, transparent 64%, #e6dfcd 64%); -} - -.hero-meta-row { - display: flex; - align-items: flex-end; - justify-content: space-between; - gap: 28px; - flex-wrap: wrap; - margin-top: 48px; -} -.hero-meta { - display: flex; - flex-direction: column; - gap: 4px; - font-size: 15px; - color: var(--ink-soft); -} -.hero-meta span:first-child { color: var(--ink); font-weight: 500; } -.hero-cta { border: none; cursor: pointer; } - -/* tools strip in the hero */ -.hero-tools { margin-top: 96px; } -@media (max-width: 640px) { .hero-tools { margin-top: 64px; } } - -.tool-logo { - position: relative; - display: inline-flex; - align-items: center; - flex-shrink: 0; - cursor: pointer; -} -.tool-logo img { - height: 64px; - width: auto; - object-fit: contain; - filter: grayscale(1); - opacity: 0.42; - transition: filter 0.4s ease, opacity 0.4s ease, transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} -/* in the limelight (auto, as it passes center) or hovered */ -.tool-logo:hover img, -.tool-logo.is-spot img { - filter: grayscale(0); - opacity: 1; - transform: scale(1.14); -} -.tool-tip { - position: absolute; - bottom: calc(100% + 12px); - left: 50%; - transform: translateX(-50%) translateY(5px); - padding: 6px 11px; - border-radius: 9px; - background: var(--ink); - color: var(--bg); - font-size: 12px; - font-weight: 500; - white-space: nowrap; - opacity: 0; - pointer-events: none; - z-index: 5; - transition: opacity 0.2s ease, transform 0.2s ease; -} -.tool-tip::after { - content: ''; - position: absolute; - top: 100%; - left: 50%; - transform: translateX(-50%); - border: 5px solid transparent; - border-top-color: var(--ink); -} -.tool-logo:hover .tool-tip, -.tool-logo.is-spot .tool-tip { - opacity: 1; - transform: translateX(-50%) translateY(0); -} - -/* ============ Reusable logo/tool marquee ============ */ -.x-marquee { - position: relative; - /* clip the scrolling track horizontally, but let tooltips overflow upward */ - overflow-x: clip; - overflow-y: visible; - width: 100%; -} -/* edge fades (replaces mask-image, which clipped the overflowing tooltip) */ -.x-marquee::before, -.x-marquee::after { - content: ''; - position: absolute; - top: 0; - bottom: 0; - width: 72px; - z-index: 3; - pointer-events: none; -} -.x-marquee::before { - left: 0; - background: linear-gradient(90deg, var(--bg), rgba(255, 255, 255, 0)); -} -.x-marquee::after { - right: 0; - background: linear-gradient(270deg, var(--bg), rgba(255, 255, 255, 0)); -} -.x-track { - display: flex; - width: max-content; - align-items: center; - gap: 44px; - animation: x-scroll 30s linear infinite; -} -.x-marquee:hover .x-track { animation-play-state: paused; } -@keyframes x-scroll { - from { transform: translateX(0); } - to { transform: translateX(calc(-50% - 22px)); } -} -@media (prefers-reduced-motion: reduce) { - .x-track { animation: none; } -} - -.tools-marquee .x-track { animation-duration: 24s; } - -.companies-marquee .x-track { animation-duration: 34s; } -.logo-item { display: inline-flex; align-items: center; } -.companies-marquee .logo-item img { - height: 26px; - width: auto; - opacity: 0.55; - transition: opacity 0.25s ease; -} -.companies-marquee .logo-item:hover img { opacity: 0.95; } - -/* ============ Parallax image layer ============ */ -.parallax-layer { - position: absolute; - left: 0; - right: 0; - /* small overlap so screenshots stay anchored near the top while drifting */ - top: -8%; - height: 116%; - will-change: transform; -} -.parallax-layer img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -/* ============ Scroll reveal ============ */ -.reveal { - opacity: 0; - transform: translateY(var(--reveal-y, 28px)); - transition: opacity 0.75s cubic-bezier(0.16, 1, 0.3, 1), - transform 0.75s cubic-bezier(0.16, 1, 0.3, 1); - will-change: opacity, transform; -} -.reveal.is-visible { - opacity: 1; - transform: none; -} -/* Respect reduced motion: drop the movement + parallax, keep a gentle fade */ -@media (prefers-reduced-motion: reduce) { - .reveal { - opacity: 0; - transform: none; - transition: opacity 0.5s ease; - } - .reveal.is-visible { - opacity: 1; - } - .parallax-layer { - position: absolute; - inset: 0; - height: 100%; - top: 0; - transform: none !important; - } -} - -/* ============ Nav ============ */ -.nav-wrap { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 100; - padding: 28px 36px; - display: flex; - align-items: center; - justify-content: space-between; - background: rgba(255,255,255,0.85); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - transition: background 0.45s ease, color 0.45s ease; -} -@media (max-width: 640px) { - .nav-wrap { padding: 18px 20px; } -} -.nav-link { - font-size: 14px; - color: var(--ink); - font-weight: 500; - transition: opacity 0.2s ease, color 0.45s ease; -} -.nav-link:hover { opacity: 0.55; } - -/* Over a parallax panel the bar takes a soft light tint of the panel hue - (set inline); text stays dark, so no color overrides are needed here. */ - -/* ============ Experience ============ */ -.exp-list { - border-bottom: 1px solid var(--line); -} -.exp-row { - position: relative; - width: 100%; - text-align: left; - background: transparent; - cursor: pointer; - display: grid; - grid-template-columns: 220px 1fr; - gap: 40px; - padding: 38px 4px; - border-top: 1px solid var(--line); -} -/* no whole-row hover fill — just a subtle affordance on the open control */ -@media (max-width: 768px) { - .exp-row { - grid-template-columns: 1fr; - gap: 14px; - padding: 28px 56px 28px 0; - } -} -.exp-open { - position: absolute; - top: 36px; - right: 4px; - width: 36px; - height: 36px; - border-radius: 100px; - border: 1px solid var(--line); - display: flex; - align-items: center; - justify-content: center; - color: var(--ink-mute); - flex-shrink: 0; - transition: background 0.25s ease, color 0.25s ease, - border-color 0.25s ease, transform 0.25s ease; -} -.exp-row:hover .exp-open { - background: var(--ink); - color: var(--bg); - border-color: var(--ink); - transform: rotate(90deg); -} -.exp-period { - font-size: 14px; - font-weight: 500; - color: var(--ink-mute); - white-space: nowrap; -} -.exp-role { - font-size: 24px; - font-weight: 600; - letter-spacing: -0.01em; -} -@media (max-width: 640px) { .exp-role { font-size: 20px; } } -.exp-company { - font-size: 14px; - color: var(--ink-soft); - margin-top: 5px; -} -.exp-points { - margin-top: 18px; - display: flex; - flex-direction: column; - gap: 12px; - max-width: 640px; -} -.exp-points li { - position: relative; - padding-left: 20px; - font-size: 14.5px; - line-height: 1.62; - color: var(--ink-soft); -} -.exp-points li::before { - content: ''; - position: absolute; - left: 0; - top: 9px; - width: 6px; - height: 6px; - border-radius: 100px; - background: var(--ink); -} - -/* ============ Experience modal ============ */ -.exp-modal-overlay { - position: fixed; - inset: 0; - z-index: 200; - display: flex; - align-items: center; - justify-content: center; - padding: 24px; - background: rgba(14, 14, 14, 0.55); - backdrop-filter: blur(5px); - -webkit-backdrop-filter: blur(5px); - animation: exp-fade 0.22s ease; -} -.exp-modal { - position: relative; - width: 100%; - max-width: 560px; - max-height: 86vh; - overflow-y: auto; - background: var(--bg); - border: 1px solid var(--line); - border-radius: 24px; - padding: 46px 42px; - box-shadow: 0 50px 100px -40px rgba(0, 0, 0, 0.55); - animation: exp-modal-in 0.32s cubic-bezier(0.16, 1, 0.3, 1); -} -@media (max-width: 640px) { - .exp-modal { padding: 34px 24px; } -} -.exp-modal-close { - position: absolute; - top: 20px; - right: 20px; - width: 38px; - height: 38px; - border-radius: 100px; - background: var(--bg-card); - color: var(--ink); - display: flex; - align-items: center; - justify-content: center; - transition: background 0.2s ease; -} -.exp-modal-close:hover { background: #e6e4df; } -.exp-modal-summary { - font-size: 15.5px; - line-height: 1.7; - color: var(--ink-soft); - margin-top: 20px; -} -.exp-modal-points { margin-top: 22px; } -@keyframes exp-fade { - from { opacity: 0; } - to { opacity: 1; } -} -@keyframes exp-modal-in { - from { opacity: 0; transform: translateY(16px) scale(0.98); } - to { opacity: 1; transform: none; } -} - -/* ============ Booking modal ============ */ -.book-overlay { - position: fixed; - inset: 0; - z-index: 200; - display: flex; - align-items: center; - justify-content: center; - padding: 24px; - background: rgba(14, 14, 14, 0.55); - backdrop-filter: blur(5px); - -webkit-backdrop-filter: blur(5px); - animation: exp-fade 0.22s ease; -} -.book-modal { - position: relative; - width: 100%; - max-width: 480px; - max-height: 90vh; - overflow-y: auto; - background: var(--bg); - border: 1px solid var(--line); - border-radius: 24px; - padding: 40px; - box-shadow: 0 50px 100px -40px rgba(0, 0, 0, 0.55); - animation: exp-modal-in 0.32s cubic-bezier(0.16, 1, 0.3, 1); -} -.book-modal--wide { max-width: 760px; } -@media (max-width: 640px) { - .book-modal { padding: 30px 22px; } -} -.book-head { margin-bottom: 24px; } -.book-sub { - display: inline-flex; - align-items: center; - gap: 8px; - font-size: 14px; - color: var(--ink-soft); - margin-top: 14px; -} -.book-embed { - border-radius: 16px; - overflow: hidden; - border: 1px solid var(--line); -} -.book-embed iframe { - width: 100%; - height: 600px; - border: 0; - display: block; -} - -.book-form { display: flex; flex-direction: column; gap: 16px; } -.book-field { display: flex; flex-direction: column; gap: 7px; } -.book-field label { - font-size: 13px; - font-weight: 500; - color: var(--ink-soft); -} -.book-field input, -.book-field textarea { - width: 100%; - padding: 12px 14px; - border-radius: 12px; - border: 1px solid var(--line); - background: var(--bg-soft); - font-size: 14px; - color: var(--ink); - font-family: inherit; - transition: border-color 0.2s ease, background 0.2s ease; -} -.book-field input:focus, -.book-field textarea:focus { - outline: none; - border-color: var(--ink); - background: var(--bg); -} -.book-field textarea { resize: vertical; } -.book-submit { - justify-content: center; - margin-top: 4px; - border: none; - cursor: pointer; -} -.book-hint { - font-size: 12.5px; - color: var(--ink-mute); - text-align: center; -} - -/* ============ CV page ============ */ -.cv-page { - max-width: 920px; - margin: 0 auto; - padding: 40px 24px 80px; -} -.cv-topbar { - position: sticky; - top: 0; - z-index: 20; - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 16px 0; - margin-bottom: 24px; - background: rgba(255, 255, 255, 0.88); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); -} -.cv-back { - display: inline-flex; - align-items: center; - gap: 8px; - font-size: 14px; - font-weight: 500; - color: var(--ink-soft); - transition: color 0.2s ease; -} -.cv-back:hover { color: var(--ink); } - -.cv-sheet { - background: var(--bg); - border: 1px solid var(--line); - border-radius: 24px; - padding: 64px 64px 56px; - box-shadow: 0 40px 90px -60px rgba(0, 0, 0, 0.3); -} -@media (max-width: 640px) { - .cv-sheet { padding: 36px 24px; border-radius: 18px; } -} - -.cv-head { padding-bottom: 36px; border-bottom: 1px solid var(--line); } -.cv-name { font-size: 52px; line-height: 1; } -@media (max-width: 640px) { .cv-name { font-size: 36px; } } -.cv-title { - font-size: 16px; - color: var(--ink-soft); - margin-top: 14px; - font-weight: 500; -} -.cv-contacts { - display: flex; - flex-wrap: wrap; - gap: 10px 22px; - margin-top: 22px; -} -.cv-contacts span, -.cv-contacts a { - display: inline-flex; - align-items: center; - gap: 7px; - font-size: 13.5px; - color: var(--ink-soft); -} -.cv-contacts a:hover { color: var(--ink); } -.cv-links { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 20px; } -.cv-chip { - display: inline-flex; - align-items: center; - gap: 7px; - font-size: 13px; - font-weight: 500; - padding: 8px 16px; - border-radius: 100px; - background: var(--bg-card); - color: var(--ink); - transition: background 0.2s ease; -} -.cv-chip:hover { background: #e6e4df; } - -.cv-section { margin-top: 44px; } -.cv-h2 { - font-size: 13px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--ink-mute); - margin-bottom: 22px; -} -.cv-summary { - font-size: 16px; - line-height: 1.7; - color: var(--ink-soft); - max-width: 680px; -} - -.cv-exp-list { display: flex; flex-direction: column; gap: 34px; } -.cv-exp-top { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 20px; - flex-wrap: wrap; -} -.cv-exp-role { font-size: 19px; font-weight: 600; } -.cv-exp-org { font-size: 14px; color: var(--ink-soft); margin-top: 3px; } -.cv-exp-period { - font-size: 13px; - font-weight: 500; - color: var(--ink-mute); - white-space: nowrap; -} -.cv-bullets { - margin-top: 16px; - display: flex; - flex-direction: column; - gap: 10px; -} -.cv-bullets li { - position: relative; - padding-left: 18px; - font-size: 14.5px; - line-height: 1.6; - color: var(--ink-soft); -} -.cv-bullets li::before { - content: ''; - position: absolute; - left: 0; - top: 9px; - width: 5px; - height: 5px; - border-radius: 100px; - background: var(--ink-mute); -} - -.cv-skill-groups { display: flex; flex-direction: column; gap: 22px; } -.cv-skill-label { - font-size: 14px; - font-weight: 600; - margin-bottom: 12px; -} -.cv-skill-tags { display: flex; flex-wrap: wrap; gap: 8px; } -.cv-tag { - font-size: 13px; - padding: 7px 14px; - border-radius: 100px; - background: var(--bg-card); - color: var(--ink-soft); -} - -.cv-proj-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 14px; -} -@media (max-width: 640px) { .cv-proj-grid { grid-template-columns: 1fr; } } -.cv-proj { - display: block; - padding: 22px; - border-radius: 16px; - border: 1px solid var(--line); - background: var(--bg-card); - transition: transform 0.3s ease, border-color 0.3s ease; -} -.cv-proj:hover { transform: translateY(-3px); border-color: var(--ink-mute); } -.cv-proj-name { font-size: 16px; font-weight: 600; } -.cv-proj-desc { - font-size: 13.5px; - line-height: 1.55; - color: var(--ink-soft); - margin-top: 8px; -} - -.cv-grid-2 { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 40px; -} -@media (max-width: 640px) { .cv-grid-2 { grid-template-columns: 1fr; gap: 36px; } } -.cv-pub-list { display: flex; flex-direction: column; gap: 12px; } -.cv-pub-list a { - font-size: 14.5px; - line-height: 1.5; - color: var(--ink-soft); - text-decoration: underline; - text-underline-offset: 3px; - text-decoration-color: var(--line); - transition: color 0.2s ease; -} -.cv-pub-list a:hover { color: var(--ink); } - -.cv-footer { display: flex; justify-content: center; margin-top: 40px; } - -/* ============ Works page ============ */ -.wp-hero { - padding-top: 184px; - padding-bottom: 72px; -} -@media (max-width: 640px) { - .wp-hero { padding-top: 120px; padding-bottom: 48px; } -} -.wp-stats { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 24px; - margin-top: 64px; - padding-top: 36px; - border-top: 1px solid var(--line); -} -@media (max-width: 640px) { - .wp-stats { grid-template-columns: repeat(2, 1fr); gap: 28px 20px; } -} -.wp-stat { display: flex; flex-direction: column; gap: 8px; } -.wp-stat-num { font-size: 44px; letter-spacing: -0.04em; line-height: 1; } -.wp-stat-lbl { font-size: 13px; color: var(--ink-mute); } - -.wp-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - flex-wrap: wrap; - margin-bottom: 36px; - scroll-margin-top: 110px; -} -.wp-filters { display: flex; flex-wrap: wrap; gap: 8px; } -.wp-filter { - font-size: 13px; - font-weight: 500; - padding: 9px 18px; - border-radius: 100px; - border: 1px solid var(--line); - background: transparent; - color: var(--ink-soft); - cursor: pointer; - transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; -} -.wp-filter:hover { border-color: var(--ink-mute); color: var(--ink); } -.wp-filter.is-active { - background: var(--ink); - border-color: var(--ink); - color: var(--bg); -} -.wp-count { font-size: 13px; color: var(--ink-mute); } - -.wp-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 22px; -} -@media (max-width: 980px) { .wp-grid { grid-template-columns: repeat(2, 1fr); } } -@media (max-width: 600px) { .wp-grid { grid-template-columns: 1fr; } } - -.wp-card { - display: flex; - flex-direction: column; - border-radius: 20px; - overflow: hidden; - background: var(--bg-card); - border: 1px solid var(--line); - transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 0.4s ease, border-color 0.4s ease; -} -.wp-card:hover { - transform: translateY(-6px); - box-shadow: 0 26px 54px -30px rgba(0, 0, 0, 0.24); - border-color: var(--ink-mute); -} -.wp-card.is-static { cursor: default; } -.wp-card.is-static:hover { transform: none; box-shadow: none; border-color: var(--line); } -.wp-card-img { - position: relative; - aspect-ratio: 16 / 11; - overflow: hidden; - background: var(--bg); -} -.wp-card-img img { - width: 100%; - height: 100%; - object-fit: cover; - object-position: top center; - transition: transform 0.7s cubic-bezier(0.4, 0, 0.2, 1); -} -.wp-card:not(.is-static):hover .wp-card-img img { transform: scale(1.05); } -.wp-card-badge { - position: absolute; - top: 12px; - right: 12px; - width: 38px; - height: 38px; - border-radius: 100px; - background: var(--bg); - color: var(--ink); - display: flex; - align-items: center; - justify-content: center; - opacity: 0; - transform: translateY(-6px) scale(0.85); - transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} -.wp-card:not(.is-static):hover .wp-card-badge { opacity: 1; transform: none; } -.wp-card-body { padding: 22px 22px 26px; } -.wp-card-top { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 12px; -} -.wp-card-name { font-size: 18px; font-weight: 600; letter-spacing: -0.01em; } -.wp-card-year { font-size: 12px; color: var(--ink-mute); white-space: nowrap; } -.wp-card-cat { - display: inline-block; - font-size: 12px; - color: var(--ink-soft); - margin-top: 8px; -} -.wp-card-desc { - font-size: 13.5px; - line-height: 1.6; - color: var(--ink-soft); - margin-top: 12px; -} - -.wp-pagination { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - margin-top: 56px; -} -.wp-page-num, -.wp-page-arrow { - width: 42px; - height: 42px; - border-radius: 100px; - border: 1px solid var(--line); - background: transparent; - color: var(--ink); - font-size: 14px; - font-weight: 500; - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; -} -.wp-page-num:hover, -.wp-page-arrow:hover:not(:disabled) { border-color: var(--ink-mute); } -.wp-page-num.is-active { - background: var(--ink); - border-color: var(--ink); - color: var(--bg); -} -.wp-page-arrow:disabled { opacity: 0.35; cursor: default; } - -.wp-cta { - text-align: center; - padding: 72px 40px; - border: 1px solid var(--line); - border-radius: 28px; - background: var(--bg-card); -} -@media (max-width: 640px) { .wp-cta { padding: 48px 24px; } } - -/* ============ Scrollbar ============ */ -::-webkit-scrollbar { width: 8px; height: 8px; } -::-webkit-scrollbar-track { background: var(--bg); } -::-webkit-scrollbar-thumb { background: var(--line); border-radius: 100px; } -::-webkit-scrollbar-thumb:hover { background: var(--ink-mute); } +/* All component-specific styles */ +@import "./styles/components.css"; diff --git a/app/helpers/carouselResponsives.ts b/app/helpers/carouselResponsives.ts deleted file mode 100644 index 9e6f438..0000000 --- a/app/helpers/carouselResponsives.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const projectResponsive = { - superLargeDesktop: { - // the naming can be any, depends on you. - breakpoint: { max: 4000, min: 3000 }, - items: 1, - }, - desktop: { - breakpoint: { max: 3000, min: 1024 }, - items: 1, - }, - tablet: { - breakpoint: { max: 1024, min: 464 }, - items: 1, - }, - mobile: { - breakpoint: { max: 464, min: 0 }, - items: 1, - }, -}; \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index bd41101..fc1757d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -14,9 +14,48 @@ const geistMono = localFont({ weight: "100 900", }); +const SITE_URL = "https://syntaxpriest.vercel.app"; + export const metadata: Metadata = { - title: "Daniel Adewale — Software Engineer", - description: "Software Engineer & Senior Frontend Developer with 6+ years building quality products.", + metadataBase: new URL(SITE_URL), + title: { + default: "Daniel Adewale — Software Engineer", + template: "%s · Daniel Adewale", + }, + description: + "Senior Frontend Engineer with 6+ years building production interfaces in React, Next.js and TypeScript. I turn sharp product strategy into interfaces that feel obvious.", + openGraph: { + type: "website", + locale: "en_US", + siteName: "Daniel Adewale", + title: "Daniel Adewale — Software Engineer", + description: + "Senior Frontend Engineer with 6+ years building production interfaces in React, Next.js and TypeScript.", + url: SITE_URL, + images: [ + { + url: "/user/avatar.png", + width: 512, + height: 512, + alt: "Daniel Adewale", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "Daniel Adewale — Software Engineer", + description: + "Senior Frontend Engineer with 6+ years building production interfaces.", + images: ["/user/avatar.png"], + creator: "@syntaxPriest", + }, + robots: { + index: true, + follow: true, + }, + alternates: { + canonical: SITE_URL, + }, }; export default function RootLayout({ @@ -26,6 +65,48 @@ export default function RootLayout({ }>) { return ( + + +