SPB Git

spb/spboucher.ai Public

spboucher.ai — personal website of Simon-Pierre Boucher.

TypeScript 93.4% HTML 5.5% CSS 1%

Visual redesign: lighter, cleaner, fully rounded modern UI

- Refreshed oklch palette: airier light mode, softer dark mode, gentler
  borders; soft/lift shadow tokens, selection tint, smooth scrolling
- Buttons become pills (rounded-full) with subtle press scale and shadow
- Cards rounded-3xl with soft shadows and hover lift on app/project cards
- Badges become soft pills (primary/10 tint)
- Header becomes a floating glass pill nav with active-page pill
- Hero: soft gradient blobs, status chip, larger type scale
- Uppercase section kickers on home and all subpages, softer separators

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 6 days ago (Aug 5, 2026) parent efa9e99

Showing 13 changed files with +150 and −114

modified app/apps/page.tsx +2 −1
@@ -22,7 +22,8 @@ export default function AppsPage() {
22 22 return (
23 23 <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6">
24 24 <Reveal>
25 <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
25 + <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">Portfolio</p>
26 + <h1 className="mt-2 text-3xl font-bold tracking-tight sm:text-4xl">
26 27 Zyquo macOS Suite
27 28 </h1>
28 29 <p className="mt-3 max-w-2xl text-muted-foreground">
modified app/cv/page.tsx +2 −1
@@ -64,7 +64,8 @@ export default function CvPage() {
64 64 return (
65 65 <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6">
66 66 <Reveal>
67 <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">CV</h1>
67 + <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">Curriculum Vitae</p>
68 + <h1 className="mt-2 text-3xl font-bold tracking-tight sm:text-4xl">CV</h1>
68 69 <p className="mt-3 max-w-2xl text-muted-foreground">
69 70 Professor in the Department of Administrative Sciences at Université
70 71 du Québec en Outaouais (UQO). Research in financial econometrics,
modified app/globals.css +41 −27
@@ -10,37 +10,37 @@
10 10 @custom-variant dark (&:where(.dark, .dark *));
11 11
12 12 :root {
13 --background: oklch(0.985 0.002 247.839);
14 --foreground: oklch(0.208 0.042 265.755);
13 + --background: oklch(0.993 0.002 247.839);
14 + --foreground: oklch(0.24 0.035 265.755);
15 15 --card: oklch(1 0 0);
16 --card-foreground: oklch(0.208 0.042 265.755);
17 --muted: oklch(0.968 0.007 247.896);
18 --muted-foreground: oklch(0.446 0.043 257.281);
19 --primary: oklch(0.488 0.243 264.376);
20 --primary-foreground: oklch(0.984 0.003 247.858);
21 --secondary: oklch(0.929 0.013 255.508);
22 --secondary-foreground: oklch(0.208 0.042 265.755);
23 --accent: oklch(0.929 0.013 255.508);
24 --accent-foreground: oklch(0.208 0.042 265.755);
25 --border: oklch(0.869 0.022 252.894);
26 --ring: oklch(0.488 0.243 264.376);
16 + --card-foreground: oklch(0.24 0.035 265.755);
17 + --muted: oklch(0.972 0.006 247.896);
18 + --muted-foreground: oklch(0.5 0.035 257.281);
19 + --primary: oklch(0.54 0.19 264.376);
20 + --primary-foreground: oklch(0.99 0.003 247.858);
21 + --secondary: oklch(0.955 0.01 255.508);
22 + --secondary-foreground: oklch(0.3 0.04 265.755);
23 + --accent: oklch(0.955 0.012 262);
24 + --accent-foreground: oklch(0.3 0.04 265.755);
25 + --border: oklch(0.915 0.012 252.894);
26 + --ring: oklch(0.54 0.19 264.376);
27 27 }
28 28
29 29 .dark {
30 --background: oklch(0.179 0.025 265.755);
31 --foreground: oklch(0.929 0.013 255.508);
32 --card: oklch(0.218 0.028 265.755);
33 --card-foreground: oklch(0.929 0.013 255.508);
34 --muted: oklch(0.255 0.03 262.881);
35 --muted-foreground: oklch(0.704 0.04 256.788);
36 --primary: oklch(0.707 0.165 254.624);
37 --primary-foreground: oklch(0.179 0.025 265.755);
38 --secondary: oklch(0.279 0.041 260.031);
39 --secondary-foreground: oklch(0.929 0.013 255.508);
40 --accent: oklch(0.279 0.041 260.031);
41 --accent-foreground: oklch(0.929 0.013 255.508);
42 --border: oklch(0.323 0.038 262.881);
43 --ring: oklch(0.707 0.165 254.624);
30 + --background: oklch(0.165 0.02 265.755);
31 + --foreground: oklch(0.93 0.01 255.508);
32 + --card: oklch(0.205 0.024 265.755);
33 + --card-foreground: oklch(0.93 0.01 255.508);
34 + --muted: oklch(0.245 0.026 262.881);
35 + --muted-foreground: oklch(0.71 0.035 256.788);
36 + --primary: oklch(0.72 0.15 258);
37 + --primary-foreground: oklch(0.165 0.02 265.755);
38 + --secondary: oklch(0.26 0.033 260.031);
39 + --secondary-foreground: oklch(0.93 0.01 255.508);
40 + --accent: oklch(0.27 0.035 260.031);
41 + --accent-foreground: oklch(0.93 0.01 255.508);
42 + --border: oklch(0.29 0.03 262.881);
43 + --ring: oklch(0.72 0.15 258);
44 44 }
45 45
46 46 @theme inline {
@@ -59,12 +59,26 @@
59 59 --color-border: var(--border);
60 60 --color-ring: var(--ring);
61 61 --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
62 + --shadow-soft: 0 1px 2px oklch(0.24 0.035 265.755 / 4%),
63 + 0 8px 24px -8px oklch(0.24 0.035 265.755 / 8%);
64 + --shadow-lift: 0 2px 4px oklch(0.24 0.035 265.755 / 5%),
65 + 0 16px 40px -12px oklch(0.24 0.035 265.755 / 14%);
66 +}
67 +
68 +html {
69 + scroll-behavior: smooth;
62 70 }
63 71
64 72 body {
65 73 background-color: var(--background);
66 74 color: var(--foreground);
67 75 font-family: var(--font-sans);
76 + -webkit-font-smoothing: antialiased;
77 + text-rendering: optimizeLegibility;
78 +}
79 +
80 +::selection {
81 + background: oklch(0.54 0.19 264.376 / 18%);
68 82 }
69 83
70 84 /* Framer Motion reveals bake opacity:0 into SSR inline styles; guarantee
modified app/page.tsx +27 −12
@@ -113,15 +113,26 @@ export default function HomePage() {
113 113 return (
114 114 <div className="mx-auto max-w-5xl px-4 sm:px-6">
115 115 {/* Hero */}
116 <section className="py-16 sm:py-24">
116 + <section className="relative py-20 sm:py-28">
117 + <div
118 + aria-hidden="true"
119 + className="pointer-events-none absolute inset-0 -z-10 overflow-visible"
120 + >
121 + <div className="absolute -top-24 right-0 h-72 w-72 rounded-full bg-primary/10 blur-3xl" />
122 + <div className="absolute top-20 -left-20 h-64 w-64 rounded-full bg-accent blur-3xl dark:bg-primary/5" />
123 + </div>
117 124 <Reveal>
118 <p className="text-sm font-medium text-primary">
125 + <p className="inline-flex items-center gap-2 rounded-full border border-border/70 bg-card/70 px-4 py-1.5 text-sm font-medium text-primary shadow-[var(--shadow-soft)]">
126 + <span
127 + aria-hidden="true"
128 + className="h-1.5 w-1.5 rounded-full bg-primary"
129 + />
119 130 Professor — Department of Administrative Sciences
120 131 </p>
121 <h1 className="mt-3 text-4xl font-bold tracking-tight sm:text-5xl">
132 + <h1 className="mt-6 text-4xl font-bold tracking-tight sm:text-6xl">
122 133 Simon-Pierre Boucher
123 134 </h1>
124 <p className="mt-4 max-w-2xl text-lg text-muted-foreground">
135 + <p className="mt-5 max-w-2xl text-lg text-muted-foreground sm:text-xl">
125 136 Financial econometrics researcher and macOS/AI developer.
126 137 </p>
127 138 <p className="mt-2 text-sm text-muted-foreground">
@@ -169,9 +180,10 @@ export default function HomePage() {
169 180 </section>
170 181
171 182 {/* About */}
172 <section className="border-t border-border py-16">
183 + <section className="border-t border-border/50 py-16 sm:py-20">
173 184 <Reveal>
174 <h2 className="text-2xl font-semibold tracking-tight">About</h2>
185 + <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">Profile</p>
186 + <h2 className="mt-2 text-2xl font-semibold tracking-tight sm:text-3xl">About</h2>
175 187 <p className="mt-4 max-w-3xl leading-relaxed text-muted-foreground">
176 188 Simon-Pierre Boucher is a Professor in the Department of
177 189 Administrative Sciences at Université du Québec en Outaouais (UQO).
@@ -188,9 +200,10 @@ export default function HomePage() {
188 200 </section>
189 201
190 202 {/* Education */}
191 <section className="border-t border-border py-16">
203 + <section className="border-t border-border/50 py-16 sm:py-20">
192 204 <Reveal>
193 <h2 className="text-2xl font-semibold tracking-tight">Education</h2>
205 + <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">Background</p>
206 + <h2 className="mt-2 text-2xl font-semibold tracking-tight sm:text-3xl">Education</h2>
194 207 </Reveal>
195 208 <div className="mt-6 grid gap-4 sm:grid-cols-3">
196 209 {education.map((item, i) => (
@@ -222,9 +235,10 @@ export default function HomePage() {
222 235 </section>
223 236
224 237 {/* Research highlight */}
225 <section className="border-t border-border py-16">
238 + <section className="border-t border-border/50 py-16 sm:py-20">
226 239 <Reveal>
227 <h2 className="text-2xl font-semibold tracking-tight">
240 + <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">Latest</p>
241 + <h2 className="mt-2 text-2xl font-semibold tracking-tight sm:text-3xl">
228 242 Research Highlight
229 243 </h2>
230 244 <Card className="mt-6">
@@ -253,9 +267,10 @@ export default function HomePage() {
253 267 </section>
254 268
255 269 {/* Skills */}
256 <section className="border-t border-border py-16">
270 + <section className="border-t border-border/50 py-16 sm:py-20">
257 271 <Reveal>
258 <h2 className="text-2xl font-semibold tracking-tight">
272 + <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">Toolbox</p>
273 + <h2 className="mt-2 text-2xl font-semibold tracking-tight sm:text-3xl">
259 274 Technical Skills
260 275 </h2>
261 276 </Reveal>
modified app/research/page.tsx +2 −1
@@ -62,7 +62,8 @@ export default function ResearchPage() {
62 62 return (
63 63 <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6">
64 64 <Reveal>
65 <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
65 + <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">Publications & Papers</p>
66 + <h1 className="mt-2 text-3xl font-bold tracking-tight sm:text-4xl">
66 67 Research
67 68 </h1>
68 69 <p className="mt-3 max-w-2xl text-muted-foreground">
modified app/teaching/page.tsx +2 −1
@@ -61,7 +61,8 @@ export default function TeachingPage() {
61 61 return (
62 62 <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6">
63 63 <Reveal>
64 <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
64 + <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">Courses</p>
65 + <h1 className="mt-2 text-3xl font-bold tracking-tight sm:text-4xl">
65 66 Teaching
66 67 </h1>
67 68 <p className="mt-3 max-w-2xl text-muted-foreground">
modified components/app-card.tsx +1 −1
@@ -22,7 +22,7 @@ import { cn } from "@/lib/utils";
22 22
23 23 export function AppCard({ app }: { app: ZyquoApp }) {
24 24 return (
25 <Card className="flex h-full flex-col transition-shadow hover:shadow-md">
25 + <Card className="flex h-full flex-col hover:-translate-y-1 hover:shadow-[var(--shadow-lift)]">
26 26 <CardHeader className="flex-row items-center gap-4 space-y-0">
27 27 <Image
28 28 src={app.icon}
modified components/project-card.tsx +1 −1
@@ -21,7 +21,7 @@ import { cn } from "@/lib/utils";
21 21
22 22 export function ProjectCard({ project }: { project: OpenSourceProject }) {
23 23 return (
24 <Card className="flex h-full flex-col transition-shadow hover:shadow-md">
24 + <Card className="flex h-full flex-col hover:-translate-y-1 hover:shadow-[var(--shadow-lift)]">
25 25 <CardHeader className="flex-row items-center gap-4 space-y-0">
26 26 <div
27 27 aria-hidden="true"
modified components/site-footer.tsx +2 −2
@@ -9,8 +9,8 @@ import Link from "next/link";
9 9
10 10 export function SiteFooter() {
11 11 return (
12 <footer className="border-t border-border">
13 <div className="mx-auto flex max-w-5xl flex-col items-center justify-between gap-4 px-4 py-8 text-sm text-muted-foreground sm:flex-row sm:px-6">
12 + <footer className="border-t border-border/50">
13 + <div className="mx-auto flex max-w-5xl flex-col items-center justify-between gap-4 px-4 py-10 text-sm text-muted-foreground sm:flex-row sm:px-6">
14 14 <p>
15 15 © {new Date().getFullYear()} Simon-Pierre Boucher — Université du
16 16 Québec en Outaouais (UQO)
modified components/site-header.tsx +57 −55
@@ -29,66 +29,68 @@ export function SiteHeader() {
29 29 const [open, setOpen] = React.useState(false);
30 30
31 31 return (
32 <header className="sticky top-0 z-50 w-full border-b border-border bg-background/80 backdrop-blur">
33 <div className="mx-auto flex h-14 max-w-5xl items-center justify-between px-4 sm:px-6">
34 <Link href="/" className="font-semibold tracking-tight">
35 Simon-Pierre Boucher
36 </Link>
32 + <header className="sticky top-0 z-50 w-full px-4 pt-3 sm:px-6">
33 + <div className="mx-auto max-w-5xl rounded-3xl border border-border/60 bg-background/70 shadow-[var(--shadow-soft)] backdrop-blur-xl">
34 + <div className="flex h-14 items-center justify-between pl-5 pr-3">
35 + <Link href="/" className="font-semibold tracking-tight">
36 + Simon-Pierre Boucher
37 + </Link>
37 38
38 <nav className="hidden items-center gap-1 md:flex" aria-label="Main">
39 {navItems.map((item) => (
40 <Link
41 key={item.href}
42 href={item.href}
43 className={cn(
44 "rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground",
45 pathname === item.href
46 ? "text-foreground"
47 : "text-muted-foreground"
48 )}
39 + <nav className="hidden items-center gap-1 md:flex" aria-label="Main">
40 + {navItems.map((item) => (
41 + <Link
42 + key={item.href}
43 + href={item.href}
44 + className={cn(
45 + "rounded-full px-4 py-2 text-sm font-medium transition-colors",
46 + pathname === item.href
47 + ? "bg-secondary text-foreground"
48 + : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
49 + )}
50 + >
51 + {item.label}
52 + </Link>
53 + ))}
54 + <ThemeToggle />
55 + </nav>
56 +
57 + <div className="flex items-center gap-1 md:hidden">
58 + <ThemeToggle />
59 + <Button
60 + variant="ghost"
61 + size="icon"
62 + aria-label={open ? "Close menu" : "Open menu"}
63 + aria-expanded={open}
64 + onClick={() => setOpen(!open)}
49 65 >
50 {item.label}
51 </Link>
52 ))}
53 <ThemeToggle />
54 </nav>
66 + {open ? <X /> : <Menu />}
67 + </Button>
68 + </div>
69 + </div>
55 70
56 <div className="flex items-center gap-1 md:hidden">
57 <ThemeToggle />
58 <Button
59 variant="ghost"
60 size="icon"
61 aria-label={open ? "Close menu" : "Open menu"}
62 aria-expanded={open}
63 onClick={() => setOpen(!open)}
71 + {open && (
72 + <nav
73 + className="border-t border-border/60 px-4 pb-4 pt-2 md:hidden"
74 + aria-label="Mobile"
64 75 >
65 {open ? <X /> : <Menu />}
66 </Button>
67 </div>
76 + {navItems.map((item) => (
77 + <Link
78 + key={item.href}
79 + href={item.href}
80 + onClick={() => setOpen(false)}
81 + className={cn(
82 + "block rounded-full px-4 py-2 text-sm font-medium transition-colors",
83 + pathname === item.href
84 + ? "bg-secondary text-foreground"
85 + : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
86 + )}
87 + >
88 + {item.label}
89 + </Link>
90 + ))}
91 + </nav>
92 + )}
68 93 </div>
69
70 {open && (
71 <nav
72 className="border-t border-border px-4 pb-4 pt-2 md:hidden"
73 aria-label="Mobile"
74 >
75 {navItems.map((item) => (
76 <Link
77 key={item.href}
78 href={item.href}
79 onClick={() => setOpen(false)}
80 className={cn(
81 "block rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground",
82 pathname === item.href
83 ? "text-foreground"
84 : "text-muted-foreground"
85 )}
86 >
87 {item.label}
88 </Link>
89 ))}
90 </nav>
91 )}
92 94 </header>
93 95 );
94 96 }
modified components/ui/badge.tsx +3 −3
@@ -11,13 +11,13 @@ import { cva, type VariantProps } from "class-variance-authority";
11 11 import { cn } from "@/lib/utils";
12 12
13 13 const badgeVariants = cva(
14 "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-medium transition-colors",
14 + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors",
15 15 {
16 16 variants: {
17 17 variant: {
18 default: "border-transparent bg-primary text-primary-foreground",
18 + default: "border-transparent bg-primary/10 text-primary",
19 19 secondary: "border-transparent bg-secondary text-secondary-foreground",
20 outline: "border-border text-foreground",
20 + outline: "border-border text-muted-foreground",
21 21 },
22 22 },
23 23 defaultVariants: {
modified components/ui/button.tsx +9 −8
@@ -11,23 +11,24 @@ import { cva, type VariantProps } from "class-variance-authority";
11 11 import { cn } from "@/lib/utils";
12 12
13 13 const buttonVariants = cva(
14 "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0",
14 + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98] [&_svg]:size-4 [&_svg]:shrink-0",
15 15 {
16 16 variants: {
17 17 variant: {
18 default: "bg-primary text-primary-foreground hover:bg-primary/90",
18 + default:
19 + "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 hover:shadow-md",
19 20 secondary:
20 "bg-secondary text-secondary-foreground hover:bg-secondary/80",
21 + "bg-secondary text-secondary-foreground hover:bg-secondary/70",
21 22 outline:
22 "border border-border bg-transparent hover:bg-accent hover:text-accent-foreground",
23 + "border border-border bg-card/60 hover:border-primary/30 hover:bg-accent hover:text-accent-foreground",
23 24 ghost: "hover:bg-accent hover:text-accent-foreground",
24 25 link: "text-primary underline-offset-4 hover:underline",
25 26 },
26 27 size: {
27 default: "h-9 px-4 py-2",
28 sm: "h-8 rounded-md px-3 text-xs",
29 lg: "h-10 rounded-md px-8",
30 icon: "h-9 w-9",
28 + default: "h-10 px-5 py-2",
29 + sm: "h-8 px-3.5 text-xs",
30 + lg: "h-11 px-8",
31 + icon: "h-10 w-10",
31 32 },
32 33 },
33 34 defaultVariants: {
modified components/ui/card.tsx +1 −1
@@ -14,7 +14,7 @@ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElemen
14 14 <div
15 15 ref={ref}
16 16 className={cn(
17 "rounded-xl border border-border bg-card text-card-foreground shadow-sm",
17 + "rounded-3xl border border-border/70 bg-card text-card-foreground shadow-[var(--shadow-soft)] transition-all duration-300",
18 18 className
19 19 )}
20 20 {...props}
21 21