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 7a56c106..2dca6cee 100644 Binary files a/public/Jon_Bogaty_Resume.docx and b/public/Jon_Bogaty_Resume.docx differ diff --git a/src/App.tsx b/src/App.tsx index bacf0f16..80b05a39 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,90 +1,55 @@ -import { useRef } from 'react' import { HeroSection } from '@/components/HeroSection' -import { SectionTabs } from '@/components/SectionTabs' import { SiteFooter } from '@/components/SiteFooter' -import { AboutSection } from '@/components/sections/AboutSection' -import { EarlierCareer } from '@/components/sections/EarlierCareer' -import { EducationList } from '@/components/sections/EducationList' +import { SiteNav } from '@/components/SiteNav' import { JobList } from '@/components/sections/JobList' -import { ProjectGrid } from '@/components/sections/ProjectGrid' -import { SkillGrid } from '@/components/sections/SkillGrid' -import { Tabs, TabsContent } from '@/components/ui/tabs' +import { OpenSource } from '@/components/sections/OpenSource' +import { SkillSheet } from '@/components/sections/SkillSheet' import resume from '@/content/resume' -// Explicit, reader-value ordering (not alphabetical, not JSON order) -const TABS = [ - { key: 'about', label: 'About' }, - { key: 'projects', label: 'Projects' }, - { key: 'work', label: 'Work' }, - { key: 'skills', label: 'Skills' }, - { key: 'earlierCareer', label: 'Earlier Career' }, - { key: 'education', label: 'Education' }, -] as const - +/** + * One opinionated scroll, not tabs: who + proof + action above the fold, + * the Work receipts at depth 1, the open-source package detail at depth 2. + * Each section gets its own layout — no shared shell. + */ export default function App() { - const heroSentinelRef = useRef(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 ( -