// B05 v4 - Three-tab structure: 'Professional' (resume, default) + 'Personal' (project cards) + 'Academic' (Rice coursework).
// Header has name + contact (email + LinkedIn). Tabs swap via JS state, no reload.
// Card animations preserved from v3.

const B05v3_CardGrid = () => {
  const TABS = [
    { id: 'professional', label: 'Professional', color: 'var(--tab-professional)', fg: 'var(--on-tab-light)' },
    { id: 'personal', label: 'Personal', color: 'var(--tab-personal)', fg: 'var(--on-tab-light)' },
    { id: 'academic', label: 'Academic', color: 'var(--tab-academic)', fg: 'var(--on-tab-light)' },
  ];
  const tabFromHash = () => {
    const h = ((typeof window !== 'undefined' && window.location && window.location.hash) || '').replace('#', '');
    return TABS.some((t) => t.id === h) ? h : 'personal';
  };
  const [tab, setTab] = React.useState(tabFromHash); // deep-linkable: /#personal (default) · /#professional · /#academic
  const [modalImg, setModalImg] = React.useState(null);
  React.useEffect(() => {
    if (!modalImg) return;
    document.body.style.overflow = 'hidden';
    const onEsc = (e) => { if (e.key === 'Escape') setModalImg(null); };
    document.addEventListener('keydown', onEsc);
    return () => { document.body.style.overflow = ''; document.removeEventListener('keydown', onEsc); };
  }, [modalImg]);
  React.useEffect(() => {
    const expectedHash = '#' + tabFromHash();
    if (window.location.hash !== expectedHash) history.replaceState(null, '', expectedHash);
    const sync = () => setTab(tabFromHash());
    window.addEventListener('hashchange', sync);
    return () => window.removeEventListener('hashchange', sync);
  }, []);
  const goTab = (id) => { if (window.location.hash !== '#' + id) history.replaceState(null, '', '#' + id); setTab(id); };
  const activeIndex = TABS.findIndex((t) => t.id === tab);
  const emailTo = () => { window.location.href = 'mailto:' + ['hello', 'andrewmeaux.com'].join('@'); }; // assembled at click; no literal address in source/DOM

  const css = `
:root{
 /* RULE: solid brand hue = active tab only | tinted brand = category tags | tinted green/amber = status pills. White text only on the blue + red active tabs. */
 --tab-professional:#2b5aa0; --tab-personal:#b8392c; --tab-academic:#d9a823;
 --on-tab-light:#ffffff;
 --tag-engineering-bg:#eaf0f9; --tag-engineering-border:#c8d8ec; --tag-engineering-text:#1f4475;
 --tag-creative-bg:#fbece9; --tag-creative-border:#efcfc8; --tag-creative-text:#8a291e;
 --tag-research-bg:#fbf2d8; --tag-research-border:#ecd99a; --tag-research-text:#6e530c;
 --text-primary:#1c1b16; --text-body:#3a3833; --text-secondary:#5a5a52; --text-muted:#7a7a72; --text-faint:#9a9a92; --text-lightest:#bdbdb5;
 --bg-page:#f4f3ee; --surface-card:#ffffff; --surface-tint:#ebe9df; --line:#e0ddd0; --line-strong:#ddd9ca; --image-backdrop:#0e1116;
 --link:#1d4e8e;
 --status-live-bg:#eaf7ee; --status-live-border:#cae6d4; --status-live-text:#0a7a41;
 --status-dev-bg:#fbf0e2; --status-dev-border:#ecd9bd; --status-dev-text:#9a5310;
 --shadow-card-hover:rgba(0,0,0,0.22);
}
 .b05v3{background:var(--bg-page);color:var(--text-primary);font-family:'Inter',system-ui,sans-serif;min-height:100%;padding:64px 56px 72px;font-size:15px;line-height:1.55}
 .b05v3 a{color:var(--text-primary);text-decoration:underline;text-decoration-color:var(--text-lightest);text-underline-offset:3px}
 .b05v3 a:hover{text-decoration-color:var(--text-primary)}
 .b05v3 button,.b05v3 a,.b05v3 summary{-webkit-tap-highlight-color:transparent;touch-action:manipulation}
 .b05v3 .top{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;margin:0 auto 56px;gap:24px;max-width:1040px}
 .b05v3 .top .left{display:flex;flex-direction:column;gap:7px;min-width:0}
 .b05v3 .top .name{font-family:'Inter Tight',sans-serif;font-weight:600;font-size:28px;letter-spacing:-0.015em;color:var(--text-primary);white-space:nowrap;margin:0}
 .b05v3 .top nav{display:flex;gap:0;justify-self:center;background:var(--surface-tint);border:1px solid var(--line-strong);border-radius:99px;padding:3px;position:relative}
 .b05v3 .top nav .slider{position:absolute;top:3px;left:3px;bottom:3px;width:calc((100% - 6px)/3);border-radius:99px;z-index:0;transition:transform .32s cubic-bezier(.4,0,.2,1),background-color .32s ease}
 .b05v3 .top nav button{font-family:'Inter',sans-serif;font-size:13px;letter-spacing:-0.005em;color:var(--text-secondary);background:transparent;border:0;padding:6px 16px;min-width:8em;border-radius:99px;cursor:pointer;font-weight:500;flex:1;position:relative;z-index:1;white-space:nowrap;transition:color .25s}
 .b05v3 .top nav button:hover{color:var(--text-primary)}
 .b05v3 .top nav button.on{font-weight:600}
 .b05v3 .top .contact{display:flex;justify-content:flex-start;gap:18px;font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:.01em;flex-wrap:wrap}
 .b05v3 .top .contact a,.b05v3 .top .contact button{text-decoration:none;color:var(--text-body);display:inline-flex;align-items:center;gap:5px;border:0;border-bottom:1px solid transparent;padding:0 0 1px 0;background:transparent;font:inherit;cursor:pointer;transition:border-color .15s}
 .b05v3 .top .contact a:hover,.b05v3 .top .contact button:hover{border-bottom-color:var(--text-primary)}
 .b05v3 .top .contact a .ic{width:11px;height:11px;display:inline-block;flex-shrink:0;opacity:.7}
 .b05v3 .skip-link{position:absolute;left:-9999px;top:0;background:var(--surface-card);padding:8px 16px;text-decoration:none;color:var(--text-primary);border:1px solid var(--line-strong);border-radius:4px;z-index:100;font-family:'Inter',system-ui,sans-serif;font-size:13px}
 .b05v3 .skip-link:focus{left:16px;top:16px}
 .b05v3 .top nav button:focus-visible,.b05v3 .card:focus-visible,.b05v3 .top .contact a:focus-visible,.b05v3 .top .contact button:focus-visible{outline:2px solid var(--link);outline-offset:2px}
 .b05v3 h2{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--text-muted);margin:0 0 20px;display:flex;justify-content:space-between;align-items:center}
 .b05v3 h2 .ct{color:var(--text-lightest)}

 .b05v3 .grid{display:grid;grid-template-columns:repeat(2,1fr);gap:18px;margin-bottom:56px}
 .b05v3 .card{background:var(--surface-card);border:1px solid var(--line);border-radius:6px;overflow:hidden;display:flex;flex-direction:column;transition:transform .25s,box-shadow .25s;text-decoration:none;color:inherit;cursor:pointer;font:inherit;text-align:left;padding:0;width:100%}
 .b05v3 .card:hover{transform:translateY(-3px);box-shadow:0 16px 34px -18px var(--shadow-card-hover)}
 .b05v3 .card.dead{cursor:zoom-in}
 .b05v3 .modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.88);display:flex;align-items:center;justify-content:center;z-index:1000;padding:40px;cursor:zoom-out;animation:b05v3-fadein .18s ease-out}
 .b05v3 .modal-img{max-width:100%;max-height:100%;object-fit:contain;box-shadow:0 12px 48px rgba(0,0,0,0.6);border-radius:4px}
 .b05v3 .modal-close{position:absolute;top:18px;right:22px;background:transparent;border:0;color:#fff;font-size:32px;cursor:pointer;padding:8px 12px;line-height:1;opacity:.85;transition:opacity .15s}
 .b05v3 .modal-close:hover{opacity:1}
 @keyframes b05v3-fadein{from{opacity:0}to{opacity:1}}
 .b05v3 .card .vis{aspect-ratio:5/3;position:relative;overflow:hidden;background:var(--image-backdrop)}
 /* pause all card animations by default; resume on hover so only the hovered card runs */
 .b05v3 .card .vis *,.b05v3 .card .vis *::before,.b05v3 .card .vis *::after{animation-play-state:paused !important}
 .b05v3 .card:hover .vis *,.b05v3 .card:hover .vis *::before,.b05v3 .card:hover .vis *::after{animation-play-state:running !important}
 .b05v3 .card .body{padding:18px 20px 22px;display:flex;flex-direction:column;flex:1}
 .b05v3 .card h3{font-family:'Inter Tight',sans-serif;font-weight:600;font-size:18px;letter-spacing:-0.01em;margin:0 0 4px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
 .b05v3 .card .tag{font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-muted);background:var(--surface-tint);border:1px solid var(--line);padding:2px 7px;border-radius:99px;font-weight:500;white-space:nowrap}
 .b05v3 .card .tag.research{color:var(--tag-research-text);background:var(--tag-research-bg);border-color:var(--tag-research-border)}
 .b05v3 .card .tag.creative{color:var(--tag-creative-text);background:var(--tag-creative-bg);border-color:var(--tag-creative-border)}
 .b05v3 .card .tag.engineering{color:var(--tag-engineering-text);background:var(--tag-engineering-bg);border-color:var(--tag-engineering-border)}
 .b05v3 .card .tag.live{color:var(--status-live-text);background:var(--status-live-bg);border-color:var(--status-live-border)}
 .b05v3 .card .tag.dev{color:var(--status-dev-text);background:var(--status-dev-bg);border-color:var(--status-dev-border)}
 .b05v3 .card .tag.live::before,.b05v3 .card .tag.dev::before{content:"";display:inline-block;width:5px;height:5px;border-radius:50%;background:currentColor;margin-right:5px;vertical-align:middle}
 .b05v3 .card .linkrow{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
 .b05v3 .card .meta{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.04em;color:var(--text-muted);margin-bottom:10px}
 .b05v3 .card p{font-size:14px;line-height:1.55;color:var(--text-body);margin:0 0 14px;flex:1}
 .b05v3 .card .arrow{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--link);letter-spacing:.02em}
 .b05v3 .card.dead .arrow{color:var(--text-muted)}
 .b05v3 .card .built{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-faint);letter-spacing:.02em;margin-top:6px}

 /* ============== RESUME TAB ============== */
 .b05v3 .resume{max-width:1040px;margin:0 auto;padding-top:8px}
 .b05v3 .resume .rh{font-family:'Inter Tight',sans-serif;font-weight:500;font-size:34px;line-height:1.15;letter-spacing:-0.025em;color:var(--text-primary);margin:0 0 8px}
 .b05v3 .resume .rsub{font-size:15px;color:var(--text-body);margin:0 0 36px;line-height:1.55;min-height:3.1em}
 .b05v3 .resume section{margin-bottom:38px}
 .b05v3 .resume section > h3{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--text-muted);margin:0 0 18px;padding-bottom:8px;border-bottom:1px solid var(--line);font-weight:500}
 .b05v3 .resume .job{display:grid;grid-template-columns:120px 1fr;gap:22px;padding:14px 0;border-bottom:1px dotted var(--line)}
 .b05v3 .resume .job:last-child{border-bottom:0}
 .b05v3 .resume .job .when{font-family:'JetBrains Mono',monospace;font-size:11.5px;letter-spacing:.04em;color:var(--text-muted);line-height:1.5;padding-top:3px}
 .b05v3 .resume .job .what .title{font-family:'Inter Tight',sans-serif;font-weight:600;font-size:16px;letter-spacing:-0.01em;color:var(--text-primary);margin:0 0 2px}
 .b05v3 .resume .job .what .org{font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:.04em;color:var(--text-muted);margin:0 0 8px}
 .b05v3 .resume .job .what p{font-size:14px;line-height:1.6;color:var(--text-body);margin:0 0 6px}
 .b05v3 .resume .job .what ul{margin:6px 0 0;padding:0;list-style:none}
 .b05v3 .resume .job .what ul li{font-size:14px;line-height:1.55;color:var(--text-body);padding:3px 0 3px 16px;position:relative}
 .b05v3 .resume .job .what ul li::before{content:"";position:absolute;left:2px;top:11px;width:6px;height:1px;background:var(--text-lightest)}
 .b05v3 .resume .edu{display:grid;grid-template-columns:120px 1fr;gap:22px;padding:14px 0;border-bottom:1px dotted var(--line)}
 .b05v3 .resume .edu:last-child{border-bottom:0}
 .b05v3 .resume .edu .when{font-family:'JetBrains Mono',monospace;font-size:11.5px;letter-spacing:.04em;color:var(--text-muted);padding-top:3px}
 .b05v3 .resume .edu .what .title{font-family:'Inter Tight',sans-serif;font-weight:600;font-size:16px;letter-spacing:-0.01em;color:var(--text-primary);margin:0 0 2px}
 .b05v3 .resume .edu .what .org{font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:.04em;color:var(--text-muted);margin:0 0 8px}
 .b05v3 .resume .edu .what .note{font-size:14px;line-height:1.6;color:var(--text-body);margin:0}
 .b05v3 .resume .what .team{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-faint);letter-spacing:.04em;margin:0 0 8px}
 .b05v3 .resume .what .paper{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--link);letter-spacing:.02em;text-decoration:none;display:inline-block;margin-top:2px}
 .b05v3 .resume .what .paper:hover{text-decoration:underline}
 .b05v3 .resume .what .chips{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 12px}
 .b05v3 .resume .what .chip{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.02em;color:var(--text-secondary);background:var(--surface-tint);border:1px solid var(--line);border-radius:99px;padding:2px 8px;white-space:nowrap}
 .b05v3 .resume .what details.case{margin:0 0 12px}
 .b05v3 .resume .what details.case > summary{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--link);cursor:pointer;list-style:none;display:inline-block;padding:2px 0}
 .b05v3 .resume .what details.case > summary::-webkit-details-marker{display:none}
 .b05v3 .resume .what details.case > summary::before{content:"▸ ";color:var(--text-muted)}
 .b05v3 .resume .what details.case[open] > summary::before{content:"▾ "}
 .b05v3 .resume .what .cs{font-size:13px;line-height:1.55;color:var(--text-body);margin:7px 0 0;max-width:760px}
 .b05v3 .resume .what .cs .csk{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.05em;text-transform:uppercase;color:var(--text-muted);display:block;margin-bottom:2px}
 .b05v3 .resume .what .pipe{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.01em;color:var(--text-muted);margin:0 0 12px;line-height:1.5}

 /* projects tab small intro */
 .b05v3 .pintro{font-size:15px;color:var(--text-body);margin:0 0 28px;line-height:1.55;max-width:680px}

 /* ============== RESPONSIVE ============== */
 /* Small laptop / tablet landscape — drop card grid to 2 columns, ease padding */
 @media (max-width: 980px) {
   .b05v3{padding:48px 32px 56px}
   .b05v3 .grid{grid-template-columns:repeat(2,1fr);gap:16px}
 }

 /* Tablet portrait / large mobile — stack header, 1-col cards, stack resume entries */
 @media (max-width: 720px) {
   .b05v3{padding:28px 18px 40px;font-size:14px}
   .b05v3 .top{grid-template-columns:1fr;gap:14px;margin-bottom:32px}
   .b05v3 .top nav{justify-self:stretch;width:100%}
   .b05v3 .top nav button{flex:1;padding:8px 10px}
   .b05v3 .top .contact{justify-content:flex-start;gap:14px;flex-wrap:wrap}
   .b05v3 .grid{grid-template-columns:1fr;gap:14px;margin-bottom:36px}
   .b05v3 .card h3{font-size:17px}
   .b05v3 h2{margin-bottom:14px}
   .b05v3 .pintro{margin:-2px 0 18px}
   .b05v3 .resume{padding-top:0}
   .b05v3 .resume .rh{font-size:26px}
   .b05v3 .resume .rsub{font-size:14px;margin-bottom:24px}
   .b05v3 .resume section{margin-bottom:28px}
   .b05v3 .resume section > h3{margin-bottom:12px}
   .b05v3 .resume .job,
   .b05v3 .resume .edu{grid-template-columns:1fr;gap:4px;padding:14px 0}
   .b05v3 .resume .job .when,
   .b05v3 .resume .edu .when{padding-top:0;font-size:11px}
   .b05v3 .resume .job .what .title,
   .b05v3 .resume .edu .what .title{font-size:15px}
   .b05v3 .resume .job .what p,
   .b05v3 .resume .job .what ul li{font-size:13.5px}
 }

 /* Small mobile — tighter padding + scaled type */
 @media (max-width: 420px) {
   .b05v3{padding:22px 14px 32px}
   .b05v3 .top .name{font-size:22px}
   .b05v3 .top .contact{font-size:11.5px;gap:12px}
   .b05v3 .top nav button{font-size:12px;padding:7px 8px}
   .b05v3 .resume .rh{font-size:23px}
 }
 `;

  return (
    <div className="b05v3"><style>{css}</style>
 <a href="#main-content" className="skip-link">Skip to content</a>
 <div className="top">
 <div className="left">
 <h1 className="name">Andrew Meaux</h1>
 <div className="contact">
 <button type="button" onClick={emailTo}>Email ↗</button>
 <a href="https://www.linkedin.com/in/andrew-meaux/" target="_blank" rel="noopener">LinkedIn ↗</a>
 <a href="https://github.com/meaux247" target="_blank" rel="noopener">GitHub ↗</a>
 </div>
 </div>
 <nav role="tablist" aria-label="Sections" aria-orientation="horizontal">
 <span className="slider" aria-hidden="true" style={{transform:`translateX(${activeIndex * 100}%)`,background:TABS[activeIndex].color}} />
 {TABS.map((t, idx) => (
 <button key={t.id} id={`tab-${t.id}`} role="tab" aria-selected={tab === t.id} aria-controls={`panel-${t.id}`} tabIndex={tab === t.id ? 0 : -1} className={tab === t.id ? 'on' : ''} style={tab === t.id ? {color:t.fg} : undefined} onClick={() => goTab(t.id)} onKeyDown={(e) => {
   const k = e.key;
   if (k !== 'ArrowRight' && k !== 'ArrowLeft' && k !== 'Home' && k !== 'End') return;
   e.preventDefault();
   const next = k === 'Home' ? 0 : k === 'End' ? TABS.length - 1 : (idx + (k === 'ArrowRight' ? 1 : -1) + TABS.length) % TABS.length;
   goTab(TABS[next].id);
   requestAnimationFrame(() => document.getElementById(`tab-${TABS[next].id}`)?.focus());
 }}>{t.label}</button>
 ))}
 </nav>
 </div>

 <main id="main-content">
 {tab === 'professional' &&
      <div className="resume" role="tabpanel" id="panel-professional" aria-labelledby="tab-professional">
 <p className="rsub">Enterprise AI engineer designing industrial agentic systems, with 11 years across offshore operations &amp; engineering, digital portfolio ownership, and enterprise AI delivery, including competitive selection for Chevron’s Digital Scholar Program and completion of a full-time Professional Master of Data Science (MDS) with specialization in machine learning at Rice University.</p>

 <section>
 <h3>Experience</h3>

 <div className="job">
 <div className="when">Nov 2025 - Present</div>
 <div className="what">
 <div className="title">AI Engineer - Enterprise AI</div>
 <div className="org">Chevron · Houston, TX</div>
 <p>Translating endorsed agentic AI strategies and emerging technologies into production-oriented architectures, agent patterns, and working implementations. Lead engineer on a multi-agent deep research system that automates long-horizon research across unified external and internal data streams. Engaging directly with technical and business teams to translate applied AI between technical depth and business value.</p>
 </div>
 </div>

 <div className="job">
 <div className="when">Nov 2024 - Nov 2025</div>
 <div className="what">
 <div className="title">Systems Engineer - Enterprise AI</div>
 <div className="org">Chevron · Houston, TX</div>
 <p>Acted as solutions architect for the enterprise's exploration business, translating business needs and value drivers into target AI capabilities and a delivery roadmap. Co-authored an internal "Perspective on Agentic AI" whitepaper establishing a practical AI agent taxonomy and governance pattern that shaped cross-functional partnerships and foundational agentic AI and data strategies.</p>
 </div>
 </div>

 <div className="job">
 <div className="when">Sep 2023 - Oct 2024</div>
 <div className="what">
 <div className="title">Digital Integration Lead - Offshore Wells</div>
 <div className="org">Chevron · Houston, TX</div>
 <p>Sole business and technology liaison to the enterprise digital platform and cross-functional partners, with full ownership of the BU's digital strategy, portfolio, and demands.</p>
 </div>
 </div>

 <div className="job">
 <div className="when">Aug 2022 - Aug 2023</div>
 <div className="what">
 <div className="title">Digital Scholar Program</div>
 <div className="org">Chevron · Rice University · Houston, TX</div>
 <p>Selected for competitive, company-sponsored educational program. Completed full-time, one-year Professional Master of Data Science (MDS) with specialization in Machine Learning at Rice University.</p>
 </div>
 </div>

 <div className="job">
 <div className="when">Mar 2019 - Jul 2022</div>
 <div className="what">
 <div className="title">Lead Field Engineer - Offshore Wells</div>
 <div className="org">Chevron · Deepwater Gulf of Mexico</div>
 <p>Onsite lead engineer (team of 3) on deepwater drilling &amp; completions platform managing high-stakes, complex operations at ~$1M/day spread-rate in support of safe, incident-free operations, engineering execution, and offshore logistics.</p>
 </div>
 </div>

 <div className="job">
 <div className="when">Sep 2017 - Mar 2019</div>
 <div className="what">
 <div className="title">Field Engineer - Offshore Wells</div>
 <div className="org">Chevron · Deepwater Gulf of Mexico</div>
 </div>
 </div>

 <div className="job">
 <div className="when">May 2016 - Sep 2017</div>
 <div className="what">
 <div className="title">Accelerated Drilling Program - DWEP</div>
 <div className="org">Chevron · Deepwater Gulf of Mexico</div>
 </div>
 </div>

 <div className="job">
 <div className="when">Jun 2015 - May 2016</div>
 <div className="what">
 <div className="title">Drill / Well Site Manager - Offshore East</div>
 <div className="org">Chevron · Gulf of Mexico Shelf</div>
 </div>
 </div>

 </section>

 <section>
 <h3>Education</h3>

 <div className="edu">
 <div className="when">2022 - 2023</div>
 <div className="what">
 <div className="title">Professional Master of Data Science (MDS)</div>
 <div className="org">Rice University · Houston, TX</div>
 </div>
 </div>

 <div className="edu">
 <div className="when">2011 - 2015</div>
 <div className="what">
 <div className="title">B.S., Petroleum Engineering</div>
 <div className="org">Louisiana State University · Baton Rouge, LA</div>
 </div>
 </div>
 </section>


 <section>
 <h3>Publications</h3>
 <div className="edu">
 <div className="when">2024</div>
 <div className="what">
 <div className="title">Editor &amp; Self-Publisher - Posthumous Poetry Collection</div>
 <div className="org">Family Archive Project</div>
 <div className="note">Edited and published a curated posthumous collection of my late grandfather's poetry, preserving South Louisiana cultural history. Managed manuscript prep, book design, ISBN/metadata, and print-on-demand.</div>
 </div>
 </div>
 </section>
 </div>
      }

 {tab === 'personal' &&
      <div className="resume" role="tabpanel" id="panel-personal" aria-labelledby="tab-personal">
 <p className="rsub">Selected independent builds exploring agentic software, retro interfaces, AI-native games, and model evaluation. These projects are built outside work through agentic AI engineering workflows and shown through screenshots, live links, and short writeups. They’re how I test evolving agent capabilities, workflows, and features upon release.</p>
 <section>
 <h3>Projects</h3>
 <div className="grid">

 {/* ============ PEGPOINT - only live link ============ */}
 <a className="card" href="https://pegpoint.io" target="_blank" rel="noopener">
 <div className="vis">
 <img src="assets/pegpoint-shot.webp" alt="Pegpoint screenshot" decoding="async" style={{width:'100%',height:'100%',objectFit:'cover',objectPosition:'center center',display:'block'}} />
 </div>
 <div className="body">
 <h3>Pegpoint <span className="tag kind creative">CREATIVE</span></h3>
 <div className="meta">retro gaming · pixel art · procedural audio</div>
 <p>A retro pixel-art puzzle game with visuals, audio, physics, and levels programmatically generated by Claude Design and Claude Code. Built for Vibe Jam 2026, a video-game hackathon.</p>
 <div className="linkrow"><span className="arrow">pegpoint.io ↗</span><span className="tag live">live</span></div>
 <div className="built">built with Claude Design x Claude Code</div>
 </div>
 </a>

 {/* ============ RUFF & HONOURS - in development ============ */}
 <button type="button" className="card dead" onClick={() => setModalImg({src:'assets/ruff-honours-shot.webp', alt:'Ruff & Honours screenshot'})}>
 <div className="vis">
 <img src="assets/ruff-honours-shot.webp" alt="Ruff & Honours screenshot" loading="lazy" decoding="async" style={{width:'100%',height:'100%',objectFit:'cover',objectPosition:'left center',display:'block'}} />
 </div>
 <div className="body">
 <h3>Ruff &amp; Honours <span className="tag kind research">RESEARCH</span></h3>
 <div className="meta">interpretability · linear probes · SAE features</div>
 <p>An interpretability experiment exploring frozen-LLM deception in a trick-taking card game. Agents bid and signal partners with innocent-sounding table-talk. It attempts to read honest-vs-bluff signals from internal activations via linear probes and SAE features, against a words-only baseline near a coin-flip.</p>
 <div className="linkrow"><span className="arrow">ruffandhonours.com</span><span className="tag dev">in development</span></div>
 <div className="built">built with Claude Design x Claude Code</div>
 </div>
 </button>

 {/* ============ AGENT HUB '98 - private, dead link ============ */}
 <button type="button" className="card dead" onClick={() => setModalImg({src:'assets/agent-hub-shot.webp', alt:"Agent Hub '98 screenshot"})}>
 <div className="vis">
 <img src="assets/agent-hub-shot.webp" alt="Agent Hub '98 screenshot" loading="lazy" decoding="async" style={{width:'100%',height:'100%',objectFit:'cover',objectPosition:'left top',transform:'scale(1.3)',transformOrigin:'left top',display:'block'}} />
 </div>
 <div className="body">
 <h3>Agent Hub '98 <span className="tag kind engineering">ENGINEERING</span></h3>
 <div className="meta">node-pty · sqlite · MCP · electron</div>
 <p>Local-first multiplexer for CLI agents on Windows. Spawn unlimited Claude Code / Codex / OpenCode terminals, route work via templates with role-scoped permissions, and compound knowledge in a wiki the agents author themselves.</p>
 <div className="linkrow"><span className="arrow">agenthub98.com</span><span className="tag dev">in development</span></div>
 <div className="built">built with Claude Code x Codex</div>
 </div>
 </button>

 {/* ============ ESUNA - in development (hidden from prod) ============ */}
 {false && (
 <div className="card dead">
 <div className="vis">
 <img src="assets/esuna-splash-shot.webp" alt="Esuna screenshot" loading="lazy" decoding="async" style={{width:'100%',height:'100%',objectFit:'cover',objectPosition:'center center',display:'block'}} />
 </div>
 <div className="body">
 <h3>Esuna <span className="tag kind research">RESEARCH</span></h3>
 <div className="meta">typed language · bounded narration · replayable</div>
 <p>A tiny experimental typed language for bounded LLM tabletop RPGs. Code owns the rules, dice, and state, while the LLM owns bounded narration. Every prompt has a return type, every action must compile, making the seam between deterministic mechanics and model improvisation explicit, typed, replayable, and measurable.</p>
 <div className="linkrow"><span className="arrow">esuna.org</span><span className="tag dev">in development</span></div>
 <div className="built">built with Claude Code</div>
 </div>
 </div>
 )}

 {/* ============ CYBULATION - in development ============ */}
 <button type="button" className="card dead" onClick={() => setModalImg({src:'assets/cybulation-shot.webp', alt:'Cýbulation screenshot'})}>
 <div className="vis">
 <img src="assets/cybulation-shot.webp" alt="Cybulation screenshot" loading="lazy" decoding="async" style={{width:'100%',height:'100%',objectFit:'cover',objectPosition:'center 25%',display:'block'}} />
 </div>
 <div className="body">
 <h3>Cýbulation <span className="tag kind engineering">ENGINEERING</span></h3>
 <div className="meta">preservation · browser emulator · on-device LM</div>
 <p>A browser emulator preserving the Cybiko (CyOS) handheld and its software, plus original apps and games, including CyLM, a tiny language model trained to run on the device.</p>
 <div className="linkrow"><span className="arrow">cybulation.org</span><span className="tag dev">in development</span></div>
 <div className="built">built with Claude Code x Codex</div>
 </div>
 </button>

 {/* ============ ROUGAROUX - in development (hidden from prod) ============ */}
 {false && (
 <div className="card dead">
 <div className="vis">
 <img src="assets/rougaroux-shot.webp" alt="Rougaroux screenshot" loading="lazy" decoding="async" style={{width:'100%',height:'100%',objectFit:'cover',objectPosition:'center center',display:'block'}} />
 </div>
 <div className="body">
 <h3>Rougaroux <span className="tag kind research">RESEARCH</span></h3>
 <div className="meta">safety · interpretability · social-deduction</div>
 <p>A pre-registered interpretability study: can game-defined deception be read from an open-weight model's activations as LLM agents play a Werewolf-like social-deduction game? Ships with an observable replay: agent view, omniscient view, and a live probe-vs-ground-truth overlay. Game-defined deception, not a lie detector.</p>
 <div className="linkrow"><span className="arrow">rougaroux.org</span><span className="tag dev">in development</span></div>
 <div className="built">built with Claude Code</div>
 </div>
 </div>
 )}

 {/* ============ AGENTS ONLINE - in development, dead link (hidden from prod) ============ */}
 {false && (
 <button type="button" className="card dead" onClick={() => setModalImg({src:'assets/agents-online-shot.webp', alt:'Agents Online screenshot'})}>
 <div className="vis">
 <img src="assets/agents-online-shot.webp" alt="Agents Online screenshot" loading="lazy" decoding="async" style={{width:'100%',height:'100%',objectFit:'cover',objectPosition:'left top',transform:'scale(1.15)',transformOrigin:'left top',display:'block'}} />
 </div>
 <div className="body">
 <h3>Agents Online <span className="tag kind creative">CREATIVE</span></h3>
 <div className="meta">experimental · node · websockets · sqlite</div>
 <p>AIM 4.x parody where every buddy is an LLM. 25 personalities, ambient bot-to-bot DMs, warning levels, sound effects. The future of agent communications through the lens of 2000s instant messaging.</p>
 <div className="linkrow"><span className="arrow">agentsonline.chat</span><span className="tag dev">in development</span></div>
 <div className="built">built with Claude Code x Codex</div>
 </div>
 </button>
 )}

 </div>
 </section>
 </div>
      }

 {tab === 'academic' &&
      <div className="resume" role="tabpanel" id="panel-academic" aria-labelledby="tab-academic">
 <p className="rsub">Selected group projects from my full-time Professional Master of Data Science (MDS) with specialization in machine learning at Rice University (2022–2023), completed as part of Chevron’s Digital Scholar Program. Each summary reflects the final team report, with links to the source paper and technical details drawn from it.</p>

 <section>
 <h3>Projects</h3>

 <div className="job">
 <div className="when">Summer 2023</div>
 <div className="what">
 <div className="title">EnGenIR — RAG for engineering Q&amp;A</div>
 <div className="org">retrieval-augmented LLM · vector embeddings · prompt engineering</div>
 <div className="team">D2K Capstone · team of 6</div>
 <p>Retrieval-augmented question-answering over facilities-engineering pipeline standards: it chunks and embeds technical specifications, retrieves the relevant passages, and answers design questions through a prompt-engineered LLM with cited source context.</p>
 <div className="chips"><span className="chip">84.0% recall@10</span><span className="chip">250K+ words indexed</span><span className="chip">23-question blind eval</span><span className="chip">3 standards + synthetic spec</span></div>
 <div className="pipe">Standards → chunks → embeddings → retrieval → Power Prompt → cited answer</div>
 <details className="case">
 <summary>Details</summary>
 <div className="cs"><span className="csk">Problem</span>Engineering standards are long, technical, and high-consequence; answering a design question often means synthesizing requirements across several documents.</div>
 <div className="cs"><span className="csk">Approach</span>Parse and chunk the standards, embed the passages, retrieve the relevant context, and combine it with the query in an engineered "Power Prompt."</div>
 <div className="cs"><span className="csk">Result</span>E5-Large-v2 reached 84.0% recall@10; in a 23-question project evaluation, EnGenIR outscored zero-shot Claude 2 and GPT-4 on relevance and accuracy.</div>
 <div className="cs"><span className="csk">Limitation</span>Focused on textual requirements; tables, equations, and semantic chunking are future work.</div>
 <div className="cs"><span className="csk">Takeaway</span>In high-consequence domains, retrieval quality and prompt discipline are what make LLM answers usable and grounded.</div>
 </details>
 <a className="paper" href="papers/COMP_549_Final_Report_EnGenIR.pdf" target="_blank" rel="noopener">Read the paper ↗</a>
 </div>
 </div>

 <div className="job">
 <div className="when">Summer 2023</div>
 <div className="what">
 <div className="title">HARP — human vs. AI music detector</div>
 <div className="org">mel-spectrogram classifier · HPSS · ViT-L/16 · PyTorch</div>
 <div className="team">group project · team of 3</div>
 <p>Binary classifier that flags AI-generated music from raw audio. Audio is converted to mel-spectrograms and split into harmonic and percussive components (HPSS), then classified with a Vision Transformer.</p>
 <div className="chips"><span className="chip">81.84% test accuracy</span><span className="chip">0.901 AUROC</span><span className="chip">HPSS + ViT-L/16</span><span className="chip">MusicLM unseen in test</span></div>
 <div className="pipe">Raw audio → mel-spectrogram → HPSS → harmonic / percussive → ViT → human / AI</div>
 <details className="case">
 <summary>Details</summary>
 <div className="cs"><span className="csk">Problem</span>As AI-generated music gets easier to produce, platforms and artists need to identify model-generated recordings.</div>
 <div className="cs"><span className="csk">Approach</span>Normalize audio, convert to mel-spectrograms, separate harmonic and percussive components (HPSS), and classify with a Vision Transformer.</div>
 <div className="cs"><span className="csk">Result</span>81.84% test accuracy at 0.901 AUROC, with an entire generator (MusicLM) held out for the test set.</div>
 <div className="cs"><span className="csk">Limitation</span>Generalization depends on dataset diversity; newer music models would need broader retraining data.</div>
 <div className="cs"><span className="csk">Takeaway</span>Treating audio as spectrogram images let an image-classification architecture do AI-music detection.</div>
 </details>
 <a className="paper" href="papers/COMP_653_Final.pdf" target="_blank" rel="noopener">Read the paper ↗</a>
 </div>
 </div>

 <div className="job">
 <div className="when">Spring 2023</div>
 <div className="what">
 <div className="title">VIBE — album artwork to music</div>
 <div className="org">CLIP · GPT · Jukebox · multimodal</div>
 <div className="team">group project · team of 4</div>
 <p>Multimodal pipeline that turns album artwork into genre-conditioned music: CLIP predicts genre from the cover, an LLM writes lyrics, and Jukebox is conditioned on artist, genre, and lyrics to generate audio.</p>
 <div className="chips"><span className="chip">56.3% top-1</span><span className="chip">78.6% top-3</span><span className="chip">136.5K mapped albums</span><span className="chip">best vs. 4 human testers</span></div>
 <div className="pipe">Album art → CLIP genre → GPT lyrics → Jukebox → music</div>
 <details className="case">
 <summary>Details</summary>
 <div className="cs"><span className="csk">Problem</span>Image-to-music chains vision, language, and audio models across noisy intermediate representations.</div>
 <div className="cs"><span className="csk">Approach</span>Build a Spotify album-art dataset (143,109 images, 136,507 mapped to 15 parent genres), context-optimize CLIP, generate lyrics, and condition Jukebox on genre, artist, and lyrics.</div>
 <div className="cs"><span className="csk">Result</span>Context optimization lifted CLIP genre top-1 from 30.9% to 56.3% (78.6% top-3); on a 75-image blind set, optimized VIBE-CLIP ranked first, beating all four human testers.</div>
 <div className="cs"><span className="csk">Limitation</span>Jukebox was computationally expensive, so generation was limited and audio quality leaned heavily on artist conditioning.</div>
 <div className="cs"><span className="csk">Takeaway</span>Multimodal chains are compelling, but every intermediate prediction is a new failure point.</div>
 </details>
 <a className="paper" href="papers/COMP646_VIBE_Project_Report.pdf" target="_blank" rel="noopener">Read the paper ↗</a>
 </div>
 </div>

 <div className="job">
 <div className="when">Spring 2023</div>
 <div className="what">
 <div className="title">GraMR — graph-based music recommendations</div>
 <div className="org">GraphSAGE · GNN · PyTorch Geometric</div>
 <div className="team">group project · team of 2</div>
 <p>Graph neural network for playlist generation from a seed song: songs and playlists form a bipartite graph, GraphSAGE learns embeddings, and link prediction over them generates coherent playlists.</p>
 <div className="chips"><span className="chip">0.93 link-prediction AUC</span><span className="chip">100K playlists</span><span className="chip">681,660 song nodes</span><span className="chip">6.59M edges</span></div>
 <div className="pipe">Spotify playlists → bipartite graph → GraphSAGE → link prediction → playlist</div>
 <details className="case">
 <summary>Details</summary>
 <div className="cs"><span className="csk">Problem</span>Playlist recommendation blends collaborative filtering, content features, and subjective musical cohesion.</div>
 <div className="cs"><span className="csk">Approach</span>Convert Spotify playlist data into a song-and-playlist bipartite graph and train a GraphSAGE link-prediction model.</div>
 <div className="cs"><span className="csk">Result</span>Link-prediction AUC scaled with data: 0.68 at 1K playlists, 0.83 at 10K, and 0.93 at 100K.</div>
 <div className="cs"><span className="csk">Limitation</span>Used one-tenth of the full Million Playlist Dataset; playlist quality was judged subjectively.</div>
 <div className="cs"><span className="csk">Takeaway</span>Scale mattered: average song-node degree rose from 1.9 to 9.7 as playlists grew, improving link-prediction performance.</div>
 </details>
 <a className="paper" href="papers/COMP559_GraMR_Playlist_Generation_Report.pdf" target="_blank" rel="noopener">Read the paper ↗</a>
 </div>
 </div>
 </section>
 </div>
      }
 </main>
 {modalImg && (
   <div className="modal-overlay" role="dialog" aria-modal="true" aria-label="Screenshot" onClick={() => setModalImg(null)}>
     <img src={modalImg.src} alt={modalImg.alt} className="modal-img" onClick={(e) => e.stopPropagation()} />
     <button type="button" className="modal-close" aria-label="Close screenshot" onClick={(e) => { e.stopPropagation(); setModalImg(null); }}>×</button>
   </div>
 )}
 </div>);

};