From da052f78923f48c55b9707df2284e5b8bf7b9ee1 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Tue, 9 Jun 2026 18:51:05 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20single-scroll=20redesign=20=E2=80=94=20?= =?UTF-8?q?kill=20AI-template=20tells,=20tri-panel=20OSS,=20consulting-fir?= =?UTF-8?q?st=20narrative?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements docs/design/redesign-2026-06.md (from the ai-look diagnosis + recruiter positioning review). IA: six identical tab shells → one opinionated scroll (Work, Open Source, Skills, Contact) with a sticky scroll-spy anchor nav. About, Earlier Career, and Education fold into hero, Work, and footer. Visual de-slop: mesh shader + both dot grids + gradient orbs replaced by one static amber glow (drops @paper-design/shaders-react); animated gradient name, pulse pill, glassmorphism stat cards, hash-rotated accent bars, badge chip-walls, and 5-stage staggered entrances all removed. Stats are a borderless typographic band; skills are spec-sheet rows; project accent rules are colored by category. Hero: compact identity block (mono name, modest serif title, one-line Flipside proof) — no display-type headline, no giant name. Content: Jan 2026–present is now jonbogaty.com / Independent Platform & DevOps Consultant (on the resume — closes the gap; OSS is a supporting mention, not a position). Projects focused to the three feature-complete flagships in a tri-panel: paranoid-passwd (copy resynced from the live repo — Rust-native password manager, Slint GUI, encrypted vault), radioactive-ralph, Extended Data Library. Agentic and Strata dropped. Decision: positioning thesis drives layout — proof above the fold, receipts at depth 1, packages at depth 2 Why: recruiter 6-second scan is the primary use case; the six-tab IA hid the proof behind clicks Resolves: 'looks like an AI-generated website' feedback; copy tightening + layout/positioning directive Co-Authored-By: Claude Fable 5 --- .agent-state/decisions.ndjson | 1 + docs/design/redesign-2026-06.md | 65 ++++++++++ package.json | 1 - pnpm-lock.yaml | 24 ---- public/Jon_Bogaty_Resume.docx | Bin 72013 -> 78696 bytes src/App.tsx | 101 +++++---------- src/components/HeroSection.tsx | 151 +++++----------------- src/components/SectionTabs.tsx | 79 ----------- src/components/SiteFooter.tsx | 119 ++++++++--------- src/components/SiteNav.tsx | 74 +++++++++++ src/components/sections/AboutSection.tsx | 39 ------ src/components/sections/EarlierCareer.tsx | 20 --- src/components/sections/EducationList.tsx | 21 --- src/components/sections/JobList.tsx | 116 ++++++++++------- src/components/sections/OpenSource.tsx | 79 +++++++++++ src/components/sections/ProjectGrid.tsx | 113 ---------------- src/components/sections/SkillGrid.tsx | 58 --------- src/components/sections/SkillSheet.tsx | 28 ++++ src/content/resume.ts | 117 +++++------------ src/index.css | 62 +-------- tests/e2e/navigation.spec.ts | 56 +++++--- tests/e2e/resume.spec.ts | 18 +-- tests/unit/resume-data.test.ts | 12 +- 23 files changed, 517 insertions(+), 837 deletions(-) create mode 100644 docs/design/redesign-2026-06.md delete mode 100644 src/components/SectionTabs.tsx create mode 100644 src/components/SiteNav.tsx delete mode 100644 src/components/sections/AboutSection.tsx delete mode 100644 src/components/sections/EarlierCareer.tsx delete mode 100644 src/components/sections/EducationList.tsx create mode 100644 src/components/sections/OpenSource.tsx delete mode 100644 src/components/sections/ProjectGrid.tsx delete mode 100644 src/components/sections/SkillGrid.tsx create mode 100644 src/components/sections/SkillSheet.tsx diff --git a/.agent-state/decisions.ndjson b/.agent-state/decisions.ndjson index d9abb0a0..2ebe6836 100644 --- a/.agent-state/decisions.ndjson +++ b/.agent-state/decisions.ndjson @@ -1 +1,2 @@ {"ts":"2026-06-09T23:13:09.916Z","sha":"2c9a43adaccebc7bda8227d07a70e0df990c4356","subject":"feat: DOCX-first resume pipeline with visual QC and recruiter-driven restructure","decision":"DOCX compiled from typed TS data via turbodocx, not pandoc or raw docx lib","why":"pandoc drops CSS; docx lib broke Apple Pages previously (fdcd220); turbodocx was the validated path and QC now guards it","resolves":["user directive to make DOCX the sole","properly-styled distributable"],"overrides":[]} +{"ts":"2026-06-09T23:31:36.852Z","sha":"c0b9051bfacb93637f99b9d9744051e0552ca15a","subject":"feat: DOCX-first resume pipeline with visual QC and recruiter-driven restructure (#152)","decision":"DOCX compiled from typed TS data via turbodocx, not pandoc or raw docx lib","why":"pandoc drops CSS; docx lib broke Apple Pages previously (fdcd220); turbodocx was the validated path and QC now guards it","resolves":["user directive to make DOCX the sole","properly-styled distributable"],"overrides":[],"source":"gh-pr-merge"} diff --git a/docs/design/redesign-2026-06.md b/docs/design/redesign-2026-06.md new file mode 100644 index 00000000..899968f7 --- /dev/null +++ b/docs/design/redesign-2026-06.md @@ -0,0 +1,65 @@ +# Site Redesign Spec — "The Engineer Who Runs Your Entire Platform Alone" + +**Date:** 2026-06-09 +**Inputs:** `.ui-design/reviews/ai-look-diagnosis_20260609.md` (what reads as AI-generated and why), `docs/resume-review/recruiter-review-2026-06-09.md` (positioning), `src/content/resume.ts` (facts — nothing invented). +**Thesis:** a recruiter lands and within 6 seconds reads *who* (Staff Platform & DevOps engineer), *what proof* (sole operator at Flipside 5yr, ~$100K/mo cut, 10K-line codegen platform), *what action* (resume + contact). OSS users get package detail one scroll down. + +## A. Information architecture — single opinionated scroll + +The six-tab shell dies. Tabs hid the proof-bearing material (Work, Open Source) behind clicks and forced every section into one identical wrapper — the structural "generated IA" tell. Replacement: one vertical narrative with four sticky anchor links (`Work · Open Source · Skills · Contact`), name left, Résumé button right, scroll-spy underline. + +Section roster: + +| Was | Becomes | +|---|---| +| About tab | Folded: summary ¶1 → hero proof line; ¶2 → Open Source lead | +| Work tab | **Section 1.** Keeps master-detail sidebar (the one already-human section) | +| Projects tab | **Section 2: "Open Source."** Featured-vs-rest hierarchy, package tables promoted | +| Skills tab | **Section 3.** Prose lead + spec-sheet rows, zero badges | +| Earlier Career tab | Folded into Work as a quiet `Before 2017` prose block | +| Education tab | One line in the footer/contact band | + +Scroll-depth contract: depth 0 = who + proof + action (above fold); depth 1 = Work receipts (Flipside selected, cost cut + tm_cli leading); depth 2 = Open Source with the featured package table. + +## B. Hero + +- **Mesh shader killed** (v0/Paper-design signature; drops `@paper-design/shaders-react` from the bundle). One static low-opacity amber radial glow, bottom-left, replaces the entire 4-layer ambient stack (`body::before` dot grid, `body::after` orbs, hero dot grid all die). +- **Name static** — Instrument Serif at display size in `--foreground`; `.hero-name` animated gradient deleted. +- **Left-aligned editorial column**, not centered-over-ambient (centered is itself the template look). +- **Eyebrow above the name**: `STAFF PLATFORM & DEVOPS ENGINEER` (mono, amber, tracked) — the role is what recruiters scan first. +- **Hero proof line** (new `about.heroLine`, assembled from existing facts): "The sole infrastructure engineer at Flipside Crypto for five years — modernized AWS to ~99% serverless, built a 10,000-line Python platform generating 146 Terraform modules, and cut cloud spend by ~$100K/month." +- **Stats: borderless typographic band** — serif number, mono label, hairline vertical rules. `15` (drop the `+`), `~70%`, `5`, `5`. No cards, no backdrop-blur. +- **Status: quiet mono line**, no pill, no pulse. +- **Motion: one wrapper fade-up** (0.5s), replacing five staggered child entrances. Bottom gradient-fade strip deleted. + +## C. Sections + +**Work** — keep sidebar/detail; de-card the detail pane (left rule + padding instead of stock Card chrome). Cloud providers (AWS/GCP/Azure) stay as small amber chips; remaining tech renders as one mono comma list. First two highlights of the selected role get lead emphasis. `Before 2017` prose block (from `earlierCareer.summary`) closes the section; `EarlierCareer.tsx` badge cloud deleted. + +**Open Source** — lead with summary ¶2. **Featured project full-width** (paranoid-passwd: the supply-chain stack reads most senior) with its real package list as an aligned two-column table — the single best anti-AI signal gets the most room. Remaining four in a compact 2-col list: name, tagline, one-liner, package names inline as code, tech as a short mono list. Hash-rotated accent bars (`accentFor`) die; left rules are colored **by category** (security=amber, agents=steel, data=green, 3D=purple) so color means something. + +**Skills** — prose lead: "Day to day: AWS and GCP, Terraform and Terragrunt, Kubernetes across EKS/GKE/AKS, Python and Go, and the CI/CD and secrets plumbing that ties them together." Then label/comma-list spec-sheet rows (mono uppercase labels, hairline separators). Zero Badge components. + +**Footer/Contact** — one compact band: `Let's talk.` + email, inline social links, résumé buttons, location + one-line education, `© 2026 Jon Bogaty · v… · updated …` (version stamp kept — good human touch). The 3-column grid, the `DevOps · SRE · …` middot chain, and "All rights reserved" die. + +## D. Copy (chrome strings) + +Nav: `Work / Open Source / Skills / Contact`. Section heading `Projects` → `Open Source`. `Earlier Career` → `Before 2017`. CTAs: `Download Résumé` / `View Résumé`. Footer heading → `Let's talk.` No middot chains (single connective use allowed), no triadic rhythm, no stacked descriptors. + +## E. Motion budget + +One hero fade-up. Hover/focus transitions and the sidebar active state are interaction feedback and stay. Everything else static. `gradientShift`, `pulseDot` keyframes deleted. `prefers-reduced-motion` still honored. + +## F. Build order + +1. `index.css` de-slop (delete ambient layers, hero-name, pulse; add static glow) +2. `HeroSection.tsx` rewrite (shader out, band in, one motion) +3. `resume.ts`: add `heroLine`, `15+` → `15` +4. `App.tsx`: tabs → sections +5. `SectionTabs.tsx` → `SiteNav.tsx` (scroll-spy anchors) +6. `JobList.tsx` refine + absorb Earlier Career +7. `ProjectGrid.tsx` → `OpenSource.tsx` (featured/rest) +8. `SkillGrid.tsx` → `SkillSheet.tsx` (spec sheet) +9. `SiteFooter.tsx` compact +10. Delete `AboutSection.tsx` / `EarlierCareer.tsx` / `EducationList.tsx`; `pnpm rm @paper-design/shaders-react` +11. Screenshot every section at 1440px + 390px, read each, compare against this spec diff --git a/package.json b/package.json index c267cdbb..c98d2e04 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "@fontsource/instrument-serif": "^5.2.8", "@fontsource/inter": "^5.2.8", "@fontsource/jetbrains-mono": "^5.2.8", - "@paper-design/shaders-react": "^0.0.71", "astro": "^6.1.10", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b2d4a7e..e3e33d75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,6 @@ importers: '@fontsource/jetbrains-mono': specifier: ^5.2.8 version: 5.2.8 - '@paper-design/shaders-react': - specifier: ^0.0.71 - version: 0.0.71(@types/react@19.2.14)(react@19.2.4) astro: specifier: ^6.1.10 version: 6.1.10(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.60.4)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) @@ -1178,18 +1175,6 @@ packages: '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} - '@paper-design/shaders-react@0.0.71': - resolution: {integrity: sha512-kTqjIlyZcpkwqJie+3ldEDscTtx1oOi8eRBD5QgWKI21GaNn/SSg26092M5zzqr3e8dVANv0ktS2ICSjbMFKbw==} - peerDependencies: - '@types/react': ^18 || ^19 - react: ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - - '@paper-design/shaders@0.0.71': - resolution: {integrity: sha512-brCt05YxxyjBrhnE3l1wJJHcFXsM8aE4lmpd9TMQp+p0dMU3F+OWkJZL9m/RC1Tt7om5xr0Wg7d0HYm+b9NYZA==} - '@playwright/test@1.58.2': resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} engines: {node: '>=18'} @@ -5807,15 +5792,6 @@ snapshots: '@oslojs/encoding@1.1.0': {} - '@paper-design/shaders-react@0.0.71(@types/react@19.2.14)(react@19.2.4)': - dependencies: - '@paper-design/shaders': 0.0.71 - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 - - '@paper-design/shaders@0.0.71': {} - '@playwright/test@1.58.2': dependencies: playwright: 1.58.2 diff --git a/public/Jon_Bogaty_Resume.docx b/public/Jon_Bogaty_Resume.docx index 7a56c106728b33bae081caba1a04c696e21e71b4..2dca6cee9e9cd644f67ee015712c44559f168861 100644 GIT binary patch delta 1619 zcmb_cZ)hAv6yMqAu4zm!H71wT-Zhif&;;);e|j;olpfXkCx60e1|w;Yra2%0b##;_UdbkS0rii3i~T` zI9xhK3NS@<)7C6i&p=gIuz~qcx3nUcprIivO=u1xU4bWXe#kH()-$S(G35pkZ{M&d zG}X)s2ZUQkO=8m&h87_j-;2S~EX5e6HI(6p4D$#y5Jfssm1xO8rdd#6>#%KFQ5{)o z9&;_L^8;k%g_#5r}My=p;vM6Dl~5HDcs=;E>Q%7Mm7D z77?~0ax07BGgFqxjHiJt1P9d#ifFtDTf1ShnC#=XrY8AqCQq6BLy-G7af)KgjG9F~ z1r61}JRRH?b7p25ot4u87P{fDVEu_(6%X}o*3DUbtfA`Za!HYHIVf?QUDEG?x_+d? zu8yuJ;Loo+FiEk=W98bDY;7VeJ72sJa*V_scD?U$(6hzec62uJ*akuwn27U}TbB?; z$?-xGonQ1GOlibcAmSxaEu6A!iX(6oQ;Gzt5$@gO1v4x)r%roD&dURj_6kFVqJoF0 z?p4nu8jf;wy{d{ZiW7uP!Zam0_^ifJRTb=2hOM0b5R{UeEn!fi*c>_@(0_CXL>kdE z!kUMw3ivqmlUrKkfjXr0|Fy^ibjZ zeo*Ev4z;gyt1pbKb5o-i@{$kEYtu4;A*$f+c zXBn_pXL}=Jc6@&Ga;2c>zxp%a&5*zIdK}a~C&jvu{PNAXopi9up^`J-4AL~K) zzmv}m175^!8FflEkXnIN;H#Wp}OtguPRn1-ReWLsTHOPCL5siC4*32*tcovv delta 981 zcmZ8gT}TvB6rMZny4&UtNS3f`do`BU+}+uqLM<{DiLwM2x0g0i$JudrWSqO)nMoIf zBqQ=6L?#$Yu_)>x$e*S#*eHYu3__$=DTs)C2#Q`x2%5V;Wev=H-=>OD=S##6czJOK}aMagQ_S(MHuGchw*vy}AVHbuR4gc2 z=#6*Aqp&mLgK;4xD+u$AK8X1YMoEXJjO*yL+IE17Dlwy@^fJ4Iud(Uq%CXYY27<#weZj5z?fw6vmA1JW;UZ%({ z^~a0M#>I+klJ6=qN7`Rx!qB+Gi1nE&&I{$Bz|M_|4r5bk0miS1CSWqto4C?gWKXW* zs-^^wk^d}KWd*k5Y(_yT%x2O82V<-(k06|cCwYlC%RyMX?e%EPN-!6eOd%<#PQ=!(#JU9)EJ2zW=FW5gbA)Npqbzn0}lV0RlPY__?roW5b@52flGf81WsaX z1VQw_vDBMFg}~kraM2HP`dA1+5<5ri?w9(r5U7J>l>gR4QPufNQ~I2EtN$#na+>Mr zyR_aK25zE~Aa(null) - return ( - +
+ + -
- - -
- -
-

About

- -
-
- - -
-

Projects

- -
-
- - -
-

Work

- -
-
- - -
-

Skills

- -
-
- - -
-

Earlier Career

- -
-
- - -
-

Education

- -
-
+
+

Work

+ +
+ +
+

Open Source

+ +
+ +
+

Skills

+ +
- +
) } diff --git a/src/components/HeroSection.tsx b/src/components/HeroSection.tsx index 03aec2ee..400ea701 100644 --- a/src/components/HeroSection.tsx +++ b/src/components/HeroSection.tsx @@ -1,6 +1,5 @@ import { Download, FileText, Github, Linkedin, MessageCircle } from 'lucide-react' import { motion } from 'motion/react' -import { useEffect, useState } from 'react' import { Button } from '@/components/ui/button' interface Stat { @@ -8,155 +7,69 @@ interface Stat { label: string } -interface Status { - label: string - pulse?: boolean -} - interface HeroProps { name: string label: string - tagline: string - status?: Status + heroLine: string + status?: { label: string; pulse?: boolean } stats?: Stat[] } -function ShaderBg() { - const [ShaderComponent, setShaderComponent] = useState | null>(null) - - useEffect(() => { - const canvas = document.createElement('canvas') - const gl = canvas.getContext('webgl2') || canvas.getContext('webgl') - if (!gl) return - - import('@paper-design/shaders-react') - .then((mod) => setShaderComponent(() => mod.MeshGradient)) - .catch(() => {}) - }, []) - - if (ShaderComponent) { - return ( - - ) - } - - return ( -
- ) -} - -export function HeroSection({ name, label, tagline, status, stats }: HeroProps) { +export function HeroSection({ name, label, heroLine, status, stats }: HeroProps) { return (
- - -
+
-
+
- {status && ( -
- - {status.pulse && ( - - )} - {status.label} - -
- )} + {/* Compact identity block — no display-type headline. The proof + sentence is the hero; everything above it is just identity. */} +

{name}

-

- {name} -

- - +

{label} - +

- - {tagline} - +

+ {heroLine} +

{stats && stats.length > 0 && ( - - {stats.map((stat) => ( +
+ {stats.map((stat, i) => (
-
+
{stat.value} -
-
+ +
{stat.label} -
+
))} - +
)} - +
@@ -198,11 +111,11 @@ export function HeroSection({ name, label, tagline, status, stats }: HeroProps)
- +
+ + {status &&

{status.label}

}
- -
) } diff --git a/src/components/SectionTabs.tsx b/src/components/SectionTabs.tsx deleted file mode 100644 index df927357..00000000 --- a/src/components/SectionTabs.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Download } from 'lucide-react' -import { useEffect, useRef, useState } from 'react' -import { Button } from '@/components/ui/button' -import { TabsList, TabsTrigger } from '@/components/ui/tabs' -import { cn } from '@/lib/utils' - -interface SectionTabsProps { - tabs: { key: string; label: string }[] - name: string - heroSentinelRef: React.RefObject -} - -export function SectionTabs({ tabs, name, heroSentinelRef }: SectionTabsProps) { - const [collapsed, setCollapsed] = useState(false) - const headerRef = useRef(null) - - useEffect(() => { - const sentinel = heroSentinelRef.current - if (!sentinel) return - const observer = new IntersectionObserver( - (entries) => setCollapsed(!entries[0].isIntersecting), - { threshold: 0 } - ) - observer.observe(sentinel) - return () => observer.disconnect() - }, [heroSentinelRef]) - - return ( -
-
- {collapsed && ( - - {name} - - )} - - - {tabs.map((tab) => ( - - {tab.label} - - ))} - - - {collapsed && ( - - )} -
-
- ) -} diff --git a/src/components/SiteFooter.tsx b/src/components/SiteFooter.tsx index b94f8af6..c5f6c676 100644 --- a/src/components/SiteFooter.tsx +++ b/src/components/SiteFooter.tsx @@ -1,82 +1,65 @@ -import { Download, FileText, Github, Linkedin, Mail, MapPin, MessageCircle } from 'lucide-react' +import { Download, FileText } from 'lucide-react' import { Button } from '@/components/ui/button' -import { Separator } from '@/components/ui/separator' +import resume from '@/content/resume' -const socialLinks = [ - { label: 'GitHub', href: 'https://github.com/jbcom', icon: Github }, - { label: 'LinkedIn', href: 'https://linkedin.com/in/jonbogaty', icon: Linkedin }, - { label: 'Telegram', href: 'https://t.me/jbpersonaldev', icon: MessageCircle }, - { label: 'Email', href: 'mailto:jon@jonbogaty.com', icon: Mail }, -] +const edu = resume.education[0] export function SiteFooter() { const currentYear = new Date().getFullYear() return ( -