/* Shared portfolio components — Titlebar, Statusbar, Footer, ProjectThumb */ const { useState, useEffect, useRef } = React; // Tweakable defaults window.PORTFOLIO_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "theme": "dark", "accent": "#ec5e94", "density": "comfortable" }/*EDITMODE-END*/; function applyTweaks(t) { document.documentElement.setAttribute('data-theme', t.theme); document.documentElement.setAttribute('data-density', t.density); document.documentElement.style.setProperty('--accent', t.accent); } // Apply on load — read persisted theme override if present (function initTweaks(){ const saved = (typeof localStorage !== 'undefined') ? localStorage.getItem('gg-theme') : null; const t = { ...window.PORTFOLIO_TWEAK_DEFAULTS }; if (saved === 'light' || saved === 'dark' || saved === 'dim') t.theme = saved; applyTweaks(t); window.PORTFOLIO_TWEAK_DEFAULTS = t; })(); function ThemeToggle() { const [theme, setTheme] = useState(() => document.documentElement.getAttribute('data-theme') || 'dark'); function toggle() { const next = theme === 'dark' ? 'light' : 'dark'; setTheme(next); document.documentElement.setAttribute('data-theme', next); try { localStorage.setItem('gg-theme', next); } catch (e) {} // notify any listening Tweaks panels try { window.parent?.postMessage({ type: '__edit_mode_set_keys', edits: { theme: next } }, '*'); } catch (e) {} } return ( ); } function Titlebar({ active, path }) { const isInProjects = path?.startsWith('projects/'); const indexHref = isInProjects ? '../index.html' : 'index.html'; const aboutHref = isInProjects ? '../about.html' : 'about.html'; const contactHref = isInProjects ? '../contact.html' : 'contact.html'; const projectsHref = isInProjects ? '../projects.html' : 'projects.html'; const projectsActive = active === 'projects' || active === 'work' || active === 'school'; return (
ggonzales.dev
Contact
); } function Statusbar({ extra }) { const year = new Date().getFullYear(); return ( ); } // Realistic miniature UI mockups for each project — small product previews. // Each one mimics the actual app's layout at thumbnail scale. function ProjectThumb({ vibe, accent, accentSoft, title }) { const common = { width: '100%', height: '100%', display: 'block' }; const BG = "#0d0e10"; const PANEL = "#16181c"; const PANEL_2 = "#1c1e23"; const LINE = "#26282e"; const TEXT_DIM = "#3a3d45"; const TEXT_MUTE = "#5a5d66"; // EVA — chat-style agent surface with sidebar of agents and a trace rail if (vibe === 'agents') { return ( {/* app chrome */} eva · session #482 {/* left rail — agents */} AGENTS {[0,1,2,3].map(i => ( ))} {/* center — chat */} {/* user message */} {/* agent message */} {/* tool call */} ▸ search.run("translation logs") {/* streaming */} {/* input */} {/* right — trace */} TRACE {[0,1,2,3,4,5].map(i => ( ))} ); } // TMT — Windows desktop app: ribbon, file browser, queue table if (vibe === 'desktop') { return ( {/* window chrome (Windows-style) */} Translation Management Tool — ☐ ✕ {/* menu bar */} {['File','Edit','View','Tools','Help'].map((m,i)=>( {m} ))} {/* toolbar */} {[0,1,2,3,4].map(i=>( ))} 🔍 Search files… {/* sidebar tree */} PROJECTS {['▾ Sprint 29',' EN→JA',' EN→ZH','▸ Sprint 28','▸ Archive'].map((t,i)=>( {t} ))} {/* main — queue table */} {/* table header */} {['File','Status','Words','Owner'].map((t,i)=>( {t} ))} {/* rows */} {[ {n:'ch01_intro.xml', s:'Validating', w:'1,420', sc:accent}, {n:'ch02_setup.xml', s:'In Review', w:'2,210', sc:'#62c554'}, {n:'ch03_combat.xml',s:'Translating',w:'3,890', sc:'#f5bf4f'}, {n:'ch04_world.xml', s:'Queued', w:'1,765', sc:TEXT_DIM}, {n:'ch05_end.xml', s:'Queued', w:' 920', sc:TEXT_DIM}, {n:'glossary.xml', s:'Done', w:' 340', sc:'#62c554'}, ].map((r,i)=>( {r.n} {r.s} {r.w} ))} {/* status bar */} ● Connected · 6 files · 10,545 words ); } // Scrum Memories — recall-style memory feed if (vibe === 'memory') { return ( {/* top bar */} SM Scrum Memories recall: "decisions about caching" {/* sidebar */} SPRINTS {[ {n:'Sprint 29', a:true}, {n:'Sprint 28', a:false}, {n:'Sprint 27', a:false}, {n:'Sprint 26', a:false}, ].map((s,i)=>( {s.n} ))} {/* main feed */} Sprint 29 · memories 14 entries {[ {tag:'DECISION', tagC:accent, t:'Use Redis for session cache', meta:'Mar 14 · 3 mentions'}, {tag:'BLOCKER', tagC:'#ed6a5e', t:'Auth flow breaks on Safari 16', meta:'Mar 13 · resolved'}, {tag:'STANDUP', tagC:'#f5bf4f', t:'API contract aligned with backend', meta:'Mar 12'}, ].map((m,i)=>( {m.tag} {m.t} {m.meta} ))} ); } // Translation Verifier — CLI / diff output style if (vibe === 'verify') { return ( {/* terminal chrome */} verify · sprint-29 {/* command */} $ verify --spec game-strict ./out/ja {/* output lines */} › Loading 142 files... › Running 8 validators {/* check rows */} {[ {ok:true, t:'tag-balance.spec', n:'142 / 142'}, {ok:true, t:'placeholder-parity', n:'142 / 142'}, {ok:true, t:'length-budget', n:'140 / 142', warn:true}, {ok:false, t:'glossary.consistency', n:'138 / 142'}, {ok:true, t:'no-orphan-tokens', n:'142 / 142'}, ].map((r,i)=>( {r.ok ? '✓' : '✗'} {r.t} {r.n} ))} {/* diff block */} › ch04_world.xml:128 glossary mismatch - 戦士 (warrior) + 兵士 (soldier — per glossary) fix? (y/n) ▎ ); } // Translation Model Dashboard — stats panel with metrics, sparkline, model rows if (vibe === 'dashboard') { return ( {/* top bar */} Model Dashboard EN→JA · last 30d Compare {/* metric cards */} {[ {l:'BLEU', v:'42.7', d:'+1.2'}, {l:'chrF++', v:'68.4', d:'+0.8'}, {l:'COMET', v:'0.84', d:'+0.02'}, {l:'Latency', v:'0.31s',d:'-12%'}, ].map((m,i)=>( {m.l} {m.v} ▲ {m.d} ))} {/* big chart */} BLEU OVER TIME {/* gridlines */} {[0,1,2,3].map(i=>( ))} {/* line */} {[26,74,122,170,218].map((x,i)=>( ))} {/* rankings */} MODEL LEADERBOARD {[ {n:'mt-v4.2', s:'42.7'}, {n:'mt-v4.1', s:'41.5'}, {n:'mt-v3.8', s:'40.2'}, {n:'baseline',s:'37.1'}, ].map((r,i)=>( 0{i+1} {r.n} {r.s} ))} {/* footer band */} REGRESSIONS · 4 segments {[0,1,2,3,4,5,6,7,8,9,10,11].map(i=>( ))} ); } // Notes Collector — pipeline / job runner if (vibe === 'scrape') { return ( {/* top bar */} notes-collector running · next in 04:12 ▶ Run now {/* pipeline diagram */} PIPELINE {[ {x:14, t:'Cron', s:'*/15'}, {x:90, t:'Fetch', s:'14 src'}, {x:166,t:'Parse', s:'PW'}, {x:242,t:'Normalize', s:'schema'}, {x:328,t:'Sink', s:'DB'}, ].map((n,i)=>( {n.t} {n.s} {i<4 && } {i<4 && } ))} {/* recent runs table */} RECENT RUNS {['Time','Source','Records','Duration','Status'].map((h,i)=>( {h} ))} {[ {t:'14:30', s:'notion + 3', r:'1,247', d:'42s', st:'OK', sc:'#62c554'}, {t:'14:15', s:'notion + 3', r:'1,238', d:'39s', st:'OK', sc:'#62c554'}, {t:'14:00', s:'notion + 3', r:'1,220', d:'1m04', st:'WARN', sc:'#f5bf4f'}, {t:'13:45', s:'notion + 3', r:'1,201', d:'38s', st:'OK', sc:'#62c554'}, {t:'13:30', s:'notion + 3', r:' 892', d:'53s', st:'RETRY', sc:accent}, {t:'13:15', s:'notion + 3', r:'1,184', d:'40s', st:'OK', sc:'#62c554'}, ].map((r,i)=>( {r.t} {r.s} {r.r} {r.d} {r.st} ))} ); } // Gengo Timer — focus app, large timer + task list if (vibe === 'timer') { return ( {/* top bar */} Gengo Timer 2 of 4 sessions · 1h 23m today {/* big timer card */} FOCUS · SESSION 2 {/* circular progress */} 17:42 remaining {/* controls */} {/* task pane */} TODAY · 4 TASKS {[ {t:'Translate ch04', d:true, cur:false}, {t:'Review ch03', d:false, cur:true}, {t:'QA pass · ch02', d:false, cur:false}, {t:'Glossary review',d:false, cur:false}, ].map((task,i)=>( {task.d && } {task.t} ))} {/* stats */} THIS WEEK {/* mini bar chart */} {['M','T','W','T','F','S','S'].map((d,i)=>{ const h = [22,32,18,40,28,12,0][i]; return ( {d} ); })} 8h 42m ▲ 18% ); } if (vibe === 'stitch') { // StitchKit — clothing pack IDE: sidebar with component slots, // grid of drawables with texture variants, validation panel. const slots = [ { id: '03', code: 'uppr', n: 12, on: false }, { id: '04', code: 'lowr', n: 8, on: false }, { id: '06', code: 'feet', n: 6, on: false }, { id: '11', code: 'jbib', n: 18, on: true }, { id: '08', code: 'accs', n: 4, on: false }, ]; return ( {/* top bar */} StitchKit midnight_pack · 48 drawables {/* left sidebar — slot list */} SLOTS {slots.map((s, i) => ( {s.id} · {s.code} {s.n} drawables ))} {/* main pane — drawable grid for jbib (active slot) */} jbib · 11 / TORSO 2 (TOP) 18 / 128 {/* drawable tiles 4x3 */} {Array.from({length: 12}).map((_, i) => { const c = i % 4, r = Math.floor(i / 4); const x = 110 + c * 70, y = 60 + r * 48; const isActive = i === 5; const variantBars = [3, 5, 2, 4, 6, 5, 3, 4, 2, 5, 3, 4][i]; return ( {String(i).padStart(3,'0')}_u {/* mock garment shape */} {/* texture variant dots */} {Array.from({length: variantBars}).map((_, vi) => ( ))} ); })} {/* validation panel */} VALIDATION · 2 ISSUES jbib_005 — texture _d missing variant lowr — index gap at 003 · re-index suggested re-index → export FiveM → ); } if (vibe === 'city') { // Mantra RP — FiveM RP server status board: live player count, // factions roster, recent activity feed, server rules nav. const factions = [ { code: 'PD', name: 'Police Dept', count: 14 }, { code: 'EMS', name: 'Emergency Med', count: 6 }, { code: 'DOJ', name: 'Justice Dept', count: 4 }, { code: 'CIV', name: 'Civilian', count: 38 }, { code: 'BIZ', name: 'Player Biz', count: 11 }, ]; const events = [ { t: 'just now', c: '#10b981', txt: 'Officer Reyes signed in to PD shift' }, { t: '2m ago', c: '#10b981', txt: 'New civilian application — approved' }, { t: '5m ago', c: '#f59e0b', txt: 'Bank robbery — Vinewood · resolved' }, { t: '12m ago', c: '#10b981', txt: 'Mantra Coffee — opened for the day' }, ]; return ( {/* top bar */} Mantra RP LIVE 73 / 128 online {/* hero bar — server status */} SERVER · BETA · v0.42 Welcome to the city CONNECT → {/* factions panel */} FACTIONS · 5 {factions.map((f, i) => ( {f.code} {f.name} {f.count} ))} {/* live activity feed */} ACTIVITY · LIVE {events.map((e, i) => ( {e.txt} {e.t} ))} {/* nav cards */} RULES City conduct · OOC · combat APPLY Whitelist civilian · faction · biz ); } return null; } /* ===================================================================== ProjectStage — Rescale-style "stage" wrapper around ProjectThumb. - Saturated pink gradient background - Vertical project wordmark down the left edge - Glassy 3D blob shapes (Rescale-style) - Window chrome (macOS or Windows) around the actual UI mockup - Optional 3D tilt for featured cards - Featured cards include title overlay inside the stage ===================================================================== */ function GGMark() { return ( ); } function GlassBlob({ variant = 1 }) { // Rescale-style 3D candy shape: gradient orb with soft highlight + shadow. const id = `gb${variant}`; return ( ); } function ProjectStage({ p, featured = false, chrome = "mac" }) { const tiltClass = featured ? "stage-tilt" : ""; const word = p.slug.split('-')[0]; return (
{/* glassy 3D blobs */}
{featured && } {featured && }
{/* vertical wordmark down the left */} {/* GG monogram top-left */} {/* small index pill top-right */} {/* the actual UI mockup, framed in window chrome */}
{/* Featured: full-bleed title overlay sits below the screen */} {featured && (
★ Featured · {p.role} · {p.year}
{p.title.split(p.italWord)[0]} {p.italWord} {p.title.split(p.italWord)[1]}
{p.summary}
{p.tags.slice(0,3).join(' · ')} Read case study →
)} {/* Smaller cards: caption strip at the bottom */} {!featured && (
{p.role} · {p.year} {p.tags.slice(0,2).join(' · ')}
)}
); } function WindowChrome({ kind = "mac", title = "", children }) { if (kind === "win") { return (
{title}
{children}
); } return (
{title}
{children}
); } window.Titlebar = Titlebar; window.Statusbar = Statusbar; window.ThemeToggle = ThemeToggle; window.ProjectThumb = ProjectThumb; window.ProjectStage = ProjectStage; window.WindowChrome = WindowChrome; window.GGMark = GGMark; window.applyTweaks = applyTweaks;