Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions STEP_5_PORTFOLIO_POLISH_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Step 5 Portfolio Polish Report

## Scope

Remaining media optimization, asset cleanup, image-link behavior, metadata, Open Graph images, 404 page, redirect review, and final validation.

## Branch Base

`portfolio-step-5-final-polish` from `main` @ `9561f25` (Step 4 merged via PR #20).

## SENTRY Media

| Asset | Before | After | Reduction | Role |
|---|---:|---:|---:|---|
| SENTRY Turret Labeled.png → sentry-turret-labeled.webp | 491 KB | 118 KB | −76% | inline + self full-res link |
| SENTRY Turret Cross Section Labeled.png → …-labeled.webp | 304 KB | 133 KB | −56% | inline + self full-res link |
| SENTRY Cross Section blur.png → sentry-cross-section.webp | 294 KB | 47 KB (1600w) | −84% | inline only |
| SENTRY Board-EDIT.jpg → sentry-board.webp | 244 KB | 104 KB | −57% | inline only |
| SENTRY Turret-EDIT.jpg → sentry-turret.webp | 152 KB | 64 KB | −58% | inline only |
| sentry-pcb-labeled.webp (kept) + new -inline.webp | 273 KB inline | 124 KB inline | −55% inline | inline preview + full-res link |
| SENTRY Schematic.png → sentry-schematic.png (kept PNG) + -inline.webp | 79 KB inline | 46 KB inline | −42% inline | inline preview + full-res PNG link |
| SENTRY PCB.png → sentry-pcb.png | 63 KB | 63 KB (renamed) | — | inline only |
| Functional Block Diagram.png → functional-block-diagram.webp | 23 KB | 7 KB | −70% | inline only |

**SENTRY inline page media: ~2.18 MB → 961 KB** (poster + webm + images; mp4 is a fallback a browser doesn't also download). WebP deliberately *rejected* for the schematic and PCB line art where PNG compressed better. All originals preserved in `source-assets/`.

## Asset Filename Cleanup

Zero publicly served filenames contain spaces (was 14). Also swept: `how-i-build-systems.webp` (homepage, 15 KB from 260 KB-class PNG), `fusion-system-blocks/system-diagram.webp` (89 KB, projects card), and four orphans moved to `source-assets/` (ENT260 Redesign.pdf and three old SPARK section PNGs unreferenced since the Step 3 rewrite). **Compatibility copies intentionally not kept:** the portfolio subdomain is ~3 weeks old and no external references to old asset URLs are known; documented as an accepted break.

## Technical Image Links

Full-resolution links retained (4) for inspect-worthy assets only — labeled turret view, labeled cross-section, labeled PCB, schematic — each as a visible "Open full-resolution … in a new tab →" text link with `target="_blank" rel="noopener noreferrer"`. Raw-image wrapping links removed from the hero diagram, CAD renders, board photo, and unlabeled PCB. No lightbox added.

## Browser Titles

Site title → **"Zac Cohen Portfolio"**; homepage → "Electromechanical Systems Engineer | Zac Cohen Portfolio" (was the doubled "Zachary Cohen—Engineering Portfolio | Zachary Cohen — Engineering Portfolio"); FRC title shortened to "FIRST Robotics". Other route titles were already concise project names.

## Open Graph Images

Six page-specific 1200×630 cards (140–147 KB each) generated by the new committed `scripts/generate-og-pages.ps1` in the established navy card style with per-page accent colors, applied via page `image:` frontmatter:

| Route | Image |
|---|---|
| /projects/sentry-v3/ | og-sentry-v3.png |
| /projects/stlink-v3mods/ | og-spark.png |
| /projects/fusion-system-blocks/ | og-fusion-system-blocks.png |
| /teaching/ | og-teaching.png |
| /documentation/ | og-writing-research.png |
| /frc/ | og-frc.png |

Homepage keeps its existing dedicated card. Cards are text-based, matching the established OG style (no embedded photos — GDI+ cannot decode the WebP sources; noted as possible future enhancement).

## Custom 404

Swizzled `@theme/NotFound/Content`: site-voice copy ("That page wandered off during integration"), links to the project index, portfolio home, and the hub; shared Navbar/Footer preserved. GitHub Pages serves `build/404.html` natively.

## Surfer Fleet Redirect

**Repository result:** the plugin stub (295 bytes) has a meta refresh and canonical but no title or visible fallback link; the plugin's output is not customizable without replacing it — left as-is per spec.
**Recommended Cloudflare rule (manual):** Rules → Redirect Rules → Create: expression `(http.host eq "portfolio.zcohen-nerd.com" and starts_with(http.request.uri.path, "/projects/surfer-fleet"))`, action Dynamic redirect, expression `concat("https://portfolio.zcohen-nerd.com/projects/")`, status **301**, preserve query string: not needed. Note: portfolio DNS is currently gray-cloud (unproxied), so this rule requires proxying the record first — alternatively leave the working meta-refresh stub, which is functionally adequate.

## Structured Identity Links

ORCID not added — no verified user-owned ORCID exists in the résumé, publications, or configuration. IEEE author profile not added; the two DOI links remain the publication evidence.

## FIRST Photography

No FIRST-related images exist anywhere in the workspace (re-verified). Useful later: wide event-operations shot, team workshop photo, robot/field photo, mentoring or judging context photo — all cleared for public use with no identifiable minors' information.

## Validation

New assertions: zero spaced static filenames, seven stale-reference bans, SENTRY media budget (<1200 KB measured; actual 961 KB — media files only, single video source), ≥4 labeled full-res links, deduplicated homepage title, six OG files existing at parsed 1200×630 with per-route references, custom 404 content + links. Full suite passes.

## Build / Browser Result

Strict build clean; browser-verified 404 route, renamed assets loading (zero stale refs), labeled full-res links, clean titles, page-specific OG on teaching vs generic on home. Zero console errors. Lighthouse not run (not installed) — byte measurements reported instead.

## Files Changed

`docusaurus.config.js`, `src/pages/index.md`, `src/pages/frc.md`, `src/pages/projects/sentry-v3.md`, `src/pages/projects/index.md` (+ OG frontmatter on 4 more pages), `src/theme/NotFound/Content/index.js`, `scripts/generate-og-pages.ps1`, `scripts/validate-build.js`, renamed/added assets under `static/`, originals to `source-assets/`, this report.

## Manual Steps Remaining

- Optional Cloudflare 301 for the surfer-fleet route (rule above)
- Plausible: portfolio has no analytics by design so far; adding it needs the dashboard snippet (documented in the hub report)
- Social-card cache refresh (LinkedIn Post Inspector) after deploy

## Explicitly Deferred Work

- Brand npm publication + consumer migration (unchanged blocker)
- Embedded-imagery OG variants
- FIRST photography
- Surfer Fleet rewrite and relaunch
2 changes: 1 addition & 1 deletion docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Zachary Cohen — Engineering Portfolio',
title: 'Zac Cohen Portfolio',
tagline:
'Electromechanical systems engineer — designing integrated hardware systems from architecture to deployment.',
favicon: 'img/zcohen-nerd-icon.png',
Expand Down
76 changes: 76 additions & 0 deletions scripts/generate-og-pages.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Generates page-specific 1200x630 Open Graph cards into static/img/og/.
# Zero dependencies (Windows GDI+), same visual language as the site-wide
# card (scripts/generate-og-image.ps1). Re-run after adding a page below.
# powershell -File scripts/generate-og-pages.ps1

Add-Type -AssemblyName System.Drawing

$pages = @(
@{file='og-sentry-v3.png'; title='SENTRY V3'; sub='Deployed mechatronics platform'; accent='#e11d48'},
@{file='og-spark.png'; title='SPARK'; sub='Hardened STLINK-V3MODS interface board'; accent='#7c3aed'},
@{file='og-fusion-system-blocks.png'; title='Fusion System Blocks'; sub='System block diagrams inside Autodesk Fusion'; accent='#d97706'},
@{file='og-teaching.png'; title='Teaching'; sub='Curriculum projects & engineering education'; accent='#2e8555'},
@{file='og-writing-research.png'; title='Writing & Research'; sub='Publications, presentations, and essays'; accent='#0d9488'},
@{file='og-frc.png'; title='FIRST Robotics'; sub='Two decades of mentoring, judging & event operations'; accent='#2563eb'}
)

$outDir = Join-Path $PSScriptRoot '..\static\img\og'
New-Item -ItemType Directory -Force $outDir | Out-Null

foreach ($p in $pages) {
$w = 1200; $h = 630
$bmp = New-Object System.Drawing.Bitmap($w, $h)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAliasGridFit

$rect = New-Object System.Drawing.Rectangle(0, 0, $w, $h)
$bg = New-Object System.Drawing.Drawing2D.LinearGradientBrush(
$rect,
[System.Drawing.ColorTranslator]::FromHtml('#0a1428'),
[System.Drawing.ColorTranslator]::FromHtml('#1b3358'),
35.0)
$g.FillRectangle($bg, $rect)

$glowPath = New-Object System.Drawing.Drawing2D.GraphicsPath
$glowPath.AddEllipse(760, -320, 760, 760)
$glow = New-Object System.Drawing.Drawing2D.PathGradientBrush($glowPath)
$glow.CenterColor = [System.Drawing.Color]::FromArgb(70, 16, 184, 216)
$glow.SurroundColors = @([System.Drawing.Color]::FromArgb(0, 16, 184, 216))
$g.FillPath($glow, $glowPath)
$glowPath.Dispose()

$white = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
$accent = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml($p.accent))
$sub = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml('#aebed1'))
$faint = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml('#7f95b3'))
$amber = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml('#f8c840'))
$fmt = [System.Drawing.StringFormat]::GenericTypographic
$margin = 90.0

# Accent bar in the page's signature color
$g.FillRectangle($accent, [single]$margin, 118, 76, 10)

# Page title — shrink to fit if long
$size = 88
do {
$titleFont = New-Object System.Drawing.Font('Segoe UI', $size, [System.Drawing.FontStyle]::Bold, [System.Drawing.GraphicsUnit]::Pixel)
$tw = $g.MeasureString($p.title, $titleFont, [System.Drawing.PointF]::new(0, 0), $fmt).Width
if ($tw -le ($w - 2 * $margin)) { break }
$titleFont.Dispose(); $size -= 4
} while ($size -gt 40)
$g.DrawString($p.title, $titleFont, $white, [single]$margin, 180, $fmt)

$subFont = New-Object System.Drawing.Font('Segoe UI', 40, [System.Drawing.FontStyle]::Regular, [System.Drawing.GraphicsUnit]::Pixel)
$g.DrawString($p.sub, $subFont, $sub, [single]$margin, 330, $fmt)

$g.FillEllipse($amber, [single]$margin, 520, 14, 14)
$attrFont = New-Object System.Drawing.Font('Segoe UI', 27, [System.Drawing.FontStyle]::Regular, [System.Drawing.GraphicsUnit]::Pixel)
$dot = ' ' + [char]0x00B7 + ' '
$g.DrawString('Zac Cohen Portfolio' + $dot + 'portfolio.zcohen-nerd.com', $attrFont, $faint, [single]($margin + 28), 511, $fmt)

$out = Join-Path $outDir $p.file
$bmp.Save($out, [System.Drawing.Imaging.ImageFormat]::Png)
$g.Dispose(); $bmp.Dispose()
Write-Output ("wrote {0}" -f $p.file)
Comment on lines +72 to +75
}
70 changes: 70 additions & 0 deletions scripts/validate-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,76 @@ check('Fusion System Blocks page shows Public Beta', fsbPage.includes('Public Be
const ids = [...indexHtml.matchAll(/ id="([^"]+)"/g)].map((m) => m[1]);
check('no duplicate ids', ids.length === new Set(ids).size);

// ── Step 5 polish guards ─────────────────────────────────────────────────
// No publicly served filenames may contain spaces.
const spaced = [];
(function walkStatic(dir) {
for (const f of fs.readdirSync(dir)) {
const p2 = path.join(dir, f);
if (fs.statSync(p2).isDirectory()) walkStatic(p2);
else if (f.includes(' ')) spaced.push(path.relative(root, p2));
}
})(path.join(root, 'static'));
check('no spaced filenames in static/', spaced.length === 0, spaced[0]);

// No stale references to renamed assets in built HTML.
const staleNeedles = ['How%20I%20Build', 'SENTRY%20Turret', 'SENTRY%20Schematic', 'SENTRY%20PCB.png', 'Cross%20Section%20blur', 'System%20Diagram.png', 'SENTRY Reveal'];
for (const needle of staleNeedles) {
const hit = htmlFiles.find((f) => fs.readFileSync(f, 'utf8').includes(needle));
check(`no stale asset ref: ${needle}`, !hit, hit ? path.relative(build, hit) : '');
}

// SENTRY inline media budget: every media file referenced by the page,
// summed from the build output, stays under 1200 KB.
const sentryPage = fs.readFileSync(path.join(build, 'projects', 'sentry-v3', 'index.html'), 'utf8');
// Media only (not JS bundles); a browser downloads one video source, so
// the mp4 fallback is excluded when the webm is present.
const mediaRefs = [...new Set([...sentryPage.matchAll(/(?:src|poster)="(\/(?:assets|media)\/[^"]+\.(?:webp|png|jpg|jpeg|gif|webm|mp4))"/g)].map((m) => m[1]))]
.filter((r) => !(r.endsWith('.mp4') && sentryPage.includes(r.replace('.mp4', '.webm'))));
let mediaTotal = 0;
for (const ref of mediaRefs) {
const fp = path.join(build, decodeURIComponent(ref).replace(/^\//, ''));
if (fs.existsSync(fp)) mediaTotal += fs.statSync(fp).size;
}
check('SENTRY inline media under 1200 KB', mediaTotal / 1024 < 1200, `${Math.round(mediaTotal / 1024)} KB across ${mediaRefs.length} files`);

// Retained full-resolution links are clearly labeled.
check('full-resolution links carry visible labels', (sentryPage.match(/Open full-resolution/g) || []).length >= 4);

// Titles: homepage must not repeat itself; site title is concise.
const homeTitle = (indexHtml.match(/<title[^>]*>([^<]+)<\/title>/) || [])[1] || '';
check('homepage title is deduplicated', homeTitle.includes('Electromechanical Systems Engineer') && homeTitle.includes('Zac Cohen Portfolio') && !/Zachary Cohen—Engineering Portfolio \| Zachary/.test(homeTitle), homeTitle);

// Page-specific OG images: files exist at 1200x630 and pages reference them.
function pngDims(fp) {
const b = fs.readFileSync(fp);
return {w: b.readUInt32BE(16), h: b.readUInt32BE(20)};
}
const ogPages = [
['projects/sentry-v3', 'og-sentry-v3.png'],
['projects/stlink-v3mods', 'og-spark.png'],
['projects/fusion-system-blocks', 'og-fusion-system-blocks.png'],
['teaching', 'og-teaching.png'],
['documentation', 'og-writing-research.png'],
['frc', 'og-frc.png'],
];
for (const [route, img] of ogPages) {
const fp = path.join(build, 'img', 'og', img);
const exists = fs.existsSync(fp);
check(`OG image exists: ${img}`, exists);
if (exists) {
const d = pngDims(fp);
check(`OG image ${img} is 1200x630`, d.w === 1200 && d.h === 630, `${d.w}x${d.h}`);
}
const pageHtml = fs.readFileSync(path.join(build, route, 'index.html'), 'utf8');
check(`route /${route}/ uses its own OG image`, pageHtml.includes(`/img/og/${img}`));
}

// Custom 404.
const notFound = fs.readFileSync(path.join(build, '404.html'), 'utf8');
check('custom 404 content present', notFound.includes('wandered off during integration'));
check('404 links to projects and hub', notFound.includes('href="/projects/"') && notFound.includes('https://zcohen-nerd.com/'));

if (failures.length) {
console.error(`\n${failures.length} validation failure(s).`);
process.exit(1);
Expand Down
1 change: 1 addition & 0 deletions src/pages/documentation/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
image: /img/og/og-writing-research.png
title: Writing & Research
description: "Peer-reviewed publications, selected presentations, and occasional essays on engineering judgment and systems thinking."
---
Expand Down
3 changes: 2 additions & 1 deletion src/pages/frc.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: FIRST Robotics Competition - Engineering Leadership & Event Operations
image: /img/og/og-frc.png
title: FIRST Robotics
description: "Two decades in FIRST Robotics — from founding student to lead mentor, judge, and district event manager."
---

Expand Down
6 changes: 2 additions & 4 deletions src/pages/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Zachary Cohen—Engineering Portfolio
title: Electromechanical Systems Engineer
description: "Electromechanical engineer specializing in systems integration across hardware, firmware, and documentation."
---

Expand All @@ -21,9 +21,7 @@ I also apply the same systems mindset through volunteer leadership in [*FIRST* R

My work centers on defining system architecture, developing mechanical and electronic subsystems, integrating embedded firmware, and building the infrastructure needed to test and validate complex systems.

<a href="https://portfolio.zcohen-nerd.com/assets/images/How I Build Systems.png">
<img src="/assets/images/How I Build Systems.png" alt="How I build systems diagram" style="display: block; width: auto; max-width: 100%; max-height: 420px; height: auto; border-radius: 8px; margin: 1rem auto 2rem;" loading="lazy">
</a>
<img src="/assets/images/how-i-build-systems.webp" alt="How I build systems diagram" width="683" height="795" style="display: block; width: auto; max-width: 100%; max-height: 420px; height: auto; border-radius: 8px; margin: 1rem auto 2rem;" loading="lazy">

## Featured Systems {#featured-systems}

Expand Down
1 change: 1 addition & 0 deletions src/pages/projects/fusion-system-blocks.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
image: /img/og/og-fusion-system-blocks.png
title: Fusion System Blocks
description: "An Autodesk Fusion add-in that provides a system block-diagram editor inside the CAD environment — typed connections, CAD links, rule checks, snapshots, and 11 export formats."
status: Public Beta
Expand Down
2 changes: 1 addition & 1 deletion src/pages/projects/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Project pages emphasize the problem, architecture, interfaces, implementation de
<p><a href="/projects/stlink-v3mods/">View project →</a></p>
</div>
<div class="project-card">
<img src="/assets/images/projects/Fusion System Blocks/System Diagram.png" alt="Fusion System Blocks" class="project-image" loading="lazy">
<img src="/assets/images/projects/fusion-system-blocks/system-diagram.webp" alt="Fusion System Blocks" class="project-image" width="3032" height="1040" loading="lazy">
<h3><a href="/projects/fusion-system-blocks/">Fusion System Blocks</a></h3>
<p>Framework for visual systems documentation using Autodesk Fusion. Enables modular schematic visualization and hierarchical design planning.</p>
<p class="project-status"><strong>Public Beta</strong> | 2025–Present</p>
Expand Down
Loading
Loading