Phase 3: Career Arc Timeline + Portfolio Microsite + RSS Feed Proxy

This commit is contained in:
Tony0410
2026-07-20 21:30:11 +08:00
parent 5bb4b8705c
commit 2c918ae39c
3 changed files with 747 additions and 0 deletions

View File

@@ -12,6 +12,8 @@ import subprocess
import sys import sys
import time import time
import traceback import traceback
import urllib.request
import xml.etree.ElementTree as ET
from datetime import datetime, timezone from datetime import datetime, timezone
from http.server import HTTPServer, SimpleHTTPRequestHandler from http.server import HTTPServer, SimpleHTTPRequestHandler
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
@@ -347,9 +349,65 @@ class DashboardHandler(SimpleHTTPRequestHandler):
}) })
# GET /ops — serve the ops timeline page # GET /ops — serve the ops timeline page
elif path == "/api/feed-proxy":
# Fetch multiple RSS feeds and return as JSON
qs = parse_qs(parsed.query)
# Support both ?urls=url1,url2 and multiple &urls=url1&urls=url2
raw_urls = qs.get("urls", [""])
if isinstance(raw_urls, list) and len(raw_urls) > 1:
# Multiple &urls= params — join them
all_urls = ",".join(raw_urls)
else:
all_urls = raw_urls[0] if isinstance(raw_urls, list) else raw_urls
if not all_urls:
return self._json_response({"error": "Provide ?urls=... (comma-separated URLs)"}, 400)
feed_urls = [u.strip() for u in all_urls.split(",") if u.strip()]
results = []
for fu in feed_urls:
try:
req = urllib.request.Request(fu, headers={
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
"Accept": "application/rss+xml, application/xml, text/xml, */*",
"Referer": "https://anthony.martinwa.org/",
})
with urllib.request.urlopen(req, timeout=15) as resp:
raw = resp.read()
root = ET.fromstring(raw)
# RSS 2.0 or Atom
ns = {"content": "http://purl.org/rss/1.0/modules/content/"}
items = []
# Try RSS 2.0
for item in root.iter("item"):
title = item.findtext("title", "")
link = item.findtext("link", "")
pubdate = item.findtext("pubDate", "")
desc = item.findtext("description", "")
# Strip HTML tags from description
desc_clean = re.sub(r"<[^>]+>", "", desc or "")
items.append({"title": title, "link": link, "date": pubdate[:25] if pubdate else "", "summary": desc_clean[:300]})
# Try Atom
if not items:
for entry in root.iter("{http://www.w3.org/2005/Atom}entry"):
title = entry.findtext("{http://www.w3.org/2005/Atom}title", "")
link_el = entry.find("{http://www.w3.org/2005/Atom}link")
link = link_el.get("href", "") if link_el is not None else ""
pubdate = entry.findtext("{http://www.w3.org/2005/Atom}published", "") or entry.findtext("{http://www.w3.org/2005/Atom}updated", "")
desc = entry.findtext("{http://www.w3.org/2005/Atom}summary", "")
items.append({"title": title, "link": link, "date": pubdate[:25], "summary": (desc or "")[:300]})
results.append({"url": fu, "items": items[:15], "count": len(items)})
except Exception as ex:
results.append({"url": fu, "error": str(ex)[:100], "items": []})
return self._json_response({"feeds": results})
elif path == "/ops": elif path == "/ops":
self.path = "/ops.html" self.path = "/ops.html"
return super().do_GET() return super().do_GET()
elif path == "/timeline":
self.path = "/timeline.html"
return super().do_GET()
elif path == "/portfolio":
self.path = "/portfolio.html"
return super().do_GET()
# Serve draft files (cover letters) — preserve extension for browser rendering # Serve draft files (cover letters) — preserve extension for browser rendering
# Support both /drafts/ and /jobs/drafts/ paths (Tailscale Serve uses /jobs/drafts/ from prompt) # Support both /drafts/ and /jobs/drafts/ paths (Tailscale Serve uses /jobs/drafts/ from prompt)

342
portfolio.html Normal file
View File

@@ -0,0 +1,342 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Anthony Martin · Portfolio</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--green:#1a472a;--green-l:#2d7a47;--green-d:#0f2b1a;--gold:#d4a843;--cream:#f5f0e8;--dark:#0a0f0d;--text:#c8d6c0;--text-m:#7a9a7a;--card:#111f16;--card-border:#1a3a24}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--dark);color:var(--text);-webkit-font-smoothing:antialiased;overflow-x:hidden}
a{color:var(--gold);text-decoration:none}
a:hover{text-decoration:underline}
/* Header */
.header{position:fixed;top:0;left:0;right:0;z-index:100;background:rgba(10,15,13,0.92);backdrop-filter:blur(8px);border-bottom:1px solid var(--card-border);padding:0.8rem 1.5rem;display:flex;align-items:center;justify-content:space-between}
.header .logo{font-size:1rem;font-weight:700;color:var(--cream);letter-spacing:-0.02em}
.header .logo span{color:var(--gold)}
.header nav{display:flex;gap:1.5rem;font-size:0.85rem}
.header nav a{color:var(--text-m);transition:color 0.3s}
.header nav a:hover{color:var(--gold);text-decoration:none}
/* Hero */
.hero{min-height:100vh;display:flex;flex-direction:column;justify-content:center;padding:6rem 2rem 4rem;max-width:1100px;margin:0 auto}
.hero .greeting{font-size:0.85rem;color:var(--gold);font-weight:600;text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.8rem}
.hero h1{font-size:clamp(2.5rem,5vw,4rem);font-weight:800;color:var(--cream);line-height:1.08;margin-bottom:1rem;letter-spacing:-0.02em}
.hero h1 span{color:var(--green-l)}
.hero .blurb{font-size:clamp(1rem,1.8vw,1.2rem);color:var(--text-m);max-width:600px;line-height:1.6;margin-bottom:2rem}
.hero .cta-row{display:flex;gap:1rem;flex-wrap:wrap}
.hero .cta{display:inline-flex;align-items:center;gap:0.5rem;padding:0.75rem 1.5rem;border-radius:8px;font-size:0.9rem;font-weight:600;transition:all 0.3s;cursor:pointer}
.hero .cta-primary{background:var(--green);color:var(--cream);border:none}
.hero .cta-primary:hover{background:var(--green-l);transform:translateY(-1px)}
.hero .cta-secondary{background:transparent;color:var(--text);border:1px solid var(--card-border)}
.hero .cta-secondary:hover{border-color:var(--green-l);color:var(--cream);transform:translateY(-1px)}
/* Sections */
.section{padding:4rem 2rem;max-width:1100px;margin:0 auto}
.section-label{font-size:0.75rem;color:var(--gold);font-weight:700;text-transform:uppercase;letter-spacing:0.12em;margin-bottom:0.5rem}
.section h2{font-size:clamp(1.5rem,3vw,2rem);font-weight:700;color:var(--cream);margin-bottom:2rem}
/* About */
.about-grid{display:grid;grid-template-columns:1fr 1fr;gap:2rem}
.about-grid p{font-size:0.95rem;line-height:1.7;color:var(--text)}
.about-stats{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem}
.about-stat{background:var(--card);border:1px solid var(--card-border);border-radius:8px;padding:1.2rem;text-align:center}
.about-stat .num{font-size:1.8rem;font-weight:800;color:var(--gold)}
.about-stat .lbl{font-size:0.75rem;color:var(--text-m);margin-top:0.2rem}
/* Projects */
.projects-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}
.project-card{background:var(--card);border:1px solid var(--card-border);border-radius:8px;padding:1.5rem;transition:border-color 0.3s,transform 0.3s}
.project-card:hover{border-color:var(--green-l);transform:translateY(-2px)}
.project-card .proj-icon{font-size:1.5rem;margin-bottom:0.5rem}
.project-card h3{font-size:1rem;color:var(--cream);margin-bottom:0.3rem}
.project-card .proj-tag{display:inline-block;font-size:0.65rem;color:var(--green-l);background:rgba(26,71,42,0.2);padding:0.15rem 0.5rem;border-radius:4px;margin-bottom:0.6rem}
.project-card p{font-size:0.82rem;color:var(--text-m);line-height:1.5;margin-bottom:0.5rem}
.project-card .proj-link{font-size:0.8rem;color:var(--gold);font-weight:600}
/* Capabilities */
.caps-grid{display:flex;flex-wrap:wrap;gap:0.6rem}
.cap-tag{background:var(--card);border:1px solid var(--card-border);border-radius:100px;padding:0.5rem 1.2rem;font-size:0.85rem;color:var(--text);transition:all 0.3s}
.cap-tag:hover{background:var(--green);border-color:var(--green-l);color:var(--cream);transform:translateY(-1px)}
/* News Feed */
.news-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}
.news-card{background:var(--card);border:1px solid var(--card-border);border-radius:8px;padding:1.2rem;transition:border-color 0.3s}
.news-card:hover{border-color:var(--gold)}
.news-card .news-source{font-size:0.65rem;color:var(--green-l);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:0.3rem}
.news-card h4{font-size:0.9rem;color:var(--cream);margin-bottom:0.3rem;line-height:1.4}
.news-card .news-date{font-size:0.7rem;color:var(--text-m);margin-bottom:0.3rem}
.news-card p{font-size:0.78rem;color:var(--text-m);line-height:1.5}
.news-card a{color:var(--cream)}
.news-card a:hover{color:var(--gold);text-decoration:underline}
.news-loading{text-align:center;color:var(--text-m);padding:2rem;font-size:0.85rem}
.news-error{text-align:center;color:#ef4444;padding:1rem;font-size:0.8rem}
/* Contact */
.contact-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:1rem}
.contact-card{background:var(--card);border:1px solid var(--card-border);border-radius:8px;padding:1.2rem;text-align:center;transition:border-color 0.3s}
.contact-card:hover{border-color:var(--green-l)}
.contact-card .ci{font-size:1.5rem;margin-bottom:0.4rem}
.contact-card h4{font-size:0.85rem;color:var(--cream);margin-bottom:0.1rem}
.contact-card a{font-size:0.75rem;color:var(--text-m)}
.contact-card a:hover{color:var(--gold);text-decoration:underline}
/* Footer */
.footer{text-align:center;padding:2rem;color:var(--text-m);font-size:0.7rem;border-top:1px solid var(--card-border);max-width:1100px;margin:3rem auto 0}
/* Animations */
.fade-in{opacity:0;transform:translateY(20px);transition:opacity 0.6s ease,transform 0.6s ease}
.fade-in.visible{opacity:1;transform:translateY(0)}
/* Mobile */
@media(max-width:700px){
.hero h1{font-size:2rem}
.about-grid{grid-template-columns:1fr}
.header nav{display:none}
.hero .cta-row{flex-direction:column}
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="logo">Anthony<span>.</span>Martin</div>
<nav>
<a href="#about">About</a>
<a href="#projects">Projects</a>
<a href="#capabilities">Capabilities</a>
<a href="#news">Industry</a>
<a href="#contact">Contact</a>
<a href="/api/jobs" style="color:var(--green-l)">Job Track</a>
<a href="/timeline">Timeline</a>
</nav>
</header>
<!-- Hero -->
<section class="hero">
<div class="greeting">Marketing &amp; Communications · Perth, WA</div>
<h1>Strategy, <span>Storytelling</span>,<br>Energy Transition</h1>
<p class="blurb">Corporate marketing and communications professional with 15+ years across three of WA's leading energy organisations. Specialising in integrated campaigns, digital strategy, AI enablement and stakeholder engagement.</p>
<div class="cta-row">
<a href="https://anthony.martinwa.org" target="_blank" class="cta cta-primary">📄 View Résumé</a>
<a href="#contact" class="cta cta-secondary">✉️ Get in Touch</a>
</div>
</section>
<!-- About -->
<section class="section" id="about">
<div class="section-label">About</div>
<h2>Bringing energy brands to life</h2>
<div class="about-grid">
<div>
<p>I'm a marketing and communications professional with over 15 years of experience across the energy sector — from WA's largest electricity retailer to the state's regional energy provider, and most recently at a leading energy infrastructure company.</p>
<p style="margin-top:1rem">I've led integrated campaigns, managed multi-million dollar budgets, launched renewable energy products, built AI marketing strategies, and won national awards for digital content. I bring a commercial mindset, a creative instinct, and a genuine interest in the energy transition.</p>
</div>
<div class="about-stats">
<div class="about-stat"><div class="num">15+</div><div class="lbl">Years in Energy</div></div>
<div class="about-stat"><div class="num">3</div><div class="lbl">WA Energy Companies</div></div>
<div class="about-stat"><div class="num">6</div><div class="lbl">Marketing Roles</div></div>
<div class="about-stat"><div class="num">$2M+</div><div class="lbl">Annual Budget Managed</div></div>
</div>
</div>
</section>
<!-- Projects -->
<section class="section" id="projects">
<div class="section-label">Featured Work</div>
<h2>Projects &amp; Programs</h2>
<div class="projects-grid">
<div class="project-card fade-in">
<div class="proj-icon">🏆</div>
<h3>Award-Winning Corporate Website</h3>
<div class="proj-tag">Pacific Energy · 2026</div>
<p>Led marketing collaboration on Pacific Energy's corporate website, named Science and Sustainability Website of the Year at the 2026 Australian Web Awards.</p>
</div>
<div class="project-card fade-in">
<div class="proj-icon">🤖</div>
<h3>Marketing AI Strategy</h3>
<div class="proj-tag">Pacific Energy · 20252026</div>
<p>Defined and implemented a marketing AI strategy covering content generation, digital workflows and marketing operations. Hands-on with LLMs, automation and open-source AI tools.</p>
</div>
<div class="project-card fade-in">
<div class="proj-icon">🚗</div>
<h3>WA EV Network Launch</h3>
<div class="proj-tag">Horizon Power · 20222024</div>
<p>Led marketing and communications for the state's electric vehicle charging network rollout across regional and remote WA, including community engagement and stakeholder communications.</p>
</div>
<div class="project-card fade-in">
<div class="proj-icon">☀️</div>
<h3>SolarReturn Product Launch</h3>
<div class="proj-tag">Synergy · 20152016</div>
<p>Led development from research through launch, contributing to a 40% sales increase in year one. Mapped the solar customer journey, reducing inquiry-to-sale timeframes by 20%.</p>
</div>
<div class="project-card fade-in">
<div class="proj-icon">🎓</div>
<h3>Bright Horizons STEM Program</h3>
<div class="proj-tag">Horizon Power · 2020</div>
<p>Created a program providing solar-powered education kits to remote schools, combining community engagement with renewable energy education.</p>
</div>
<div class="project-card fade-in">
<div class="proj-icon">🌍</div>
<h3>Clean Energy Summit</h3>
<div class="proj-tag">Pacific Energy · 20252026</div>
<p>Managed end-to-end marketing delivery for the Australian Clean Energy Summit and Pilbara Summit — sponsorship activation, creative, content and stakeholder coordination.</p>
</div>
</div>
</section>
<!-- Capabilities -->
<section class="section" id="capabilities">
<div class="section-label">Expertise</div>
<h2>Core Capabilities</h2>
<div class="caps-grid">
<span class="cap-tag">Marketing Strategy</span>
<span class="cap-tag">Integrated Campaigns</span>
<span class="cap-tag">Corporate Comms</span>
<span class="cap-tag">Digital Marketing</span>
<span class="cap-tag">AI-Enabled Marketing</span>
<span class="cap-tag">Brand Management</span>
<span class="cap-tag">Social Media Strategy</span>
<span class="cap-tag">Content Strategy</span>
<span class="cap-tag">Event Management</span>
<span class="cap-tag">Budget Management</span>
<span class="cap-tag">Stakeholder Engagement</span>
<span class="cap-tag">Customer Marketing</span>
<span class="cap-tag">Product Marketing</span>
<span class="cap-tag">Regulatory Comms</span>
<span class="cap-tag">Agency Management</span>
<span class="cap-tag">Energy Transition</span>
</div>
</section>
<!-- Industry News -->
<section class="section" id="news">
<div class="section-label">Industry Feed</div>
<h2>Energy &amp; Marketing News</h2>
<div class="news-grid" id="news-grid">
<div class="news-loading">Loading industry news...</div>
</div>
<div style="text-align:center;margin-top:1.5rem">
<a href="https://www.marketingmag.com.au" target="_blank" style="font-size:0.8rem;color:var(--text-m)">via Marketing Mag · B&amp;T · ABC News</a>
</div>
</section>
<!-- Contact -->
<section class="section" id="contact">
<div class="section-label">Connect</div>
<h2>Let's talk</h2>
<div class="contact-grid">
<div class="contact-card">
<div class="ci">📄</div>
<h4>Résumé</h4>
<a href="https://anthony.martinwa.org" target="_blank">anthony.martinwa.org</a>
</div>
<div class="contact-card">
<div class="ci">📧</div>
<h4>Email</h4>
<a href="#" id="email-link">(loaded from vault)</a>
</div>
<div class="contact-card">
<div class="ci">🐙</div>
<h4>GitHub</h4>
<a href="https://github.com/trevleigh" target="_blank">@trevleigh</a>
</div>
<div class="contact-card">
<div class="ci">🌐</div>
<h4>Job Tracker</h4>
<a href="/api/jobs">Dashboard</a>
</div>
</div>
</section>
<div class="footer">
<p>Anthony Martin · Built by the Rhino in the Green Jacket · Jul 2026</p>
</div>
<script>
// Scroll animations
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.fade-in, .project-card, .about-stat').forEach(el => observer.observe(el));
// Header nav highlight
document.querySelectorAll('nav a[href^="#"]').forEach(a => {
a.addEventListener('click', e => {
e.preventDefault();
const target = document.querySelector(a.getAttribute('href'));
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
});
// RSS feed loader
async function loadFeeds() {
const grid = document.getElementById('news-grid');
const feeds = [
{ url: 'https://www.marketingmag.com.au/feed/', name: 'Marketing Mag' },
{ url: 'https://www.bandt.com.au/feed/', name: 'B&amp;T' },
{ url: 'https://www.abc.net.au/news/feed/51120/rss.xml', name: 'ABC News' },
];
const urls = feeds.map(f => encodeURIComponent(f.url)).join(',');
try {
const resp = await fetch('/api/feed-proxy?urls=' + urls);
const data = await resp.json();
if (!data.feeds || data.feeds.every(f => f.items.length === 0)) {
grid.innerHTML = '<div class="news-error">No feed items loaded. Try again later.</div>';
return;
}
const allItems = [];
data.feeds.forEach(feed => {
const name = feeds.find(f => f.url === feed.url)?.name || 'Industry';
feed.items.forEach(item => {
allItems.push({ ...item, source: name });
});
});
// Sort by date (best effort)
allItems.sort((a, b) => {
const da = new Date(a.date);
const db = new Date(b.date);
return isNaN(da) || isNaN(db) ? 0 : db - da;
});
const top = allItems.slice(0, 12);
if (top.length === 0) {
grid.innerHTML = '<div class="news-loading">No recent articles found.</div>';
return;
}
grid.innerHTML = top.map(item => `
<div class="news-card fade-in">
<div class="news-source">${item.source}</div>
<h4><a href="${item.link}" target="_blank">${item.title}</a></h4>
<div class="news-date">${item.date ? new Date(item.date).toLocaleDateString('en-AU', {day:'numeric',month:'short',year:'numeric'}) : ''}</div>
<p>${item.summary || ''}</p>
</div>
`).join('');
// Animate new cards
setTimeout(() => {
document.querySelectorAll('#news-grid .news-card').forEach(el => observer.observe(el));
document.querySelectorAll('#news-grid .news-card.fade-in').forEach(el => {
if (el.getBoundingClientRect().top < window.innerHeight) el.classList.add('visible');
});
}, 100);
} catch(e) {
grid.innerHTML = '<div class="news-error">Could not load industry news feeds.</div>';
}
}
loadFeeds();
// Load email from Notion via API
async function loadEmail() {
try {
const resp = await fetch('/api/stats');
// Stats doesn't have email, but let's check if we can grab it from somewhere
// Fallback: just show a placeholder for now
} catch(e) {}
}
</script>
</body>
</html>

347
timeline.html Normal file
View File

@@ -0,0 +1,347 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Career Arc · Anthony Martin</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--green:#1a472a;--green-l:#2d7a47;--green-d:#0f2b1a;--gold:#d4a843;--cream:#f5f0e8;--dark:#0a0f0d;--text:#c8d6c0;--text-m:#7a9a7a;--card:#111f16;--card-border:#1a3a24;--spacing:clamp(40px,8vw,120px)}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--dark);color:var(--text);overflow-x:hidden;-webkit-font-smoothing:antialiased}
/* Hero */
.hero{min-height:100vh;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:2rem;position:relative}
.hero::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse at 50% 100%,rgba(26,71,42,0.15) 0%,transparent 70%);pointer-events:none}
.hero h1{font-size:clamp(2.5rem,6vw,5rem);font-weight:800;color:var(--cream);letter-spacing:-0.03em;line-height:1.05;margin-bottom:0.5rem;animation:fadeUp 1s ease-out}
.hero h1 span{color:var(--gold)}
.hero .sub{font-size:clamp(1rem,2vw,1.3rem);color:var(--text-m);margin-bottom:1.5rem;animation:fadeUp 1s ease-out 0.2s both}
.hero .tag{display:inline-block;background:var(--green);color:var(--cream);padding:0.4rem 1rem;border-radius:100px;font-size:0.8rem;font-weight:600;letter-spacing:0.05em;animation:fadeUp 1s ease-out 0.4s both}
.hero .scroll-hint{position:absolute;bottom:2rem;color:var(--text-m);font-size:0.75rem;display:flex;flex-direction:column;align-items:center;gap:0.5rem;animation:bounce 2s infinite}
.hero .scroll-hint svg{width:20px;height:20px}
/* Now Banner */
.now-banner{background:linear-gradient(135deg,var(--green-d),var(--green));border:1px solid var(--green-l);border-radius:12px;padding:1.5rem 2rem;margin:0 auto var(--spacing);max-width:700px;text-align:center;position:relative}
.now-banner .label{font-size:0.7rem;text-transform:uppercase;letter-spacing:0.15em;color:var(--gold);font-weight:700}
.now-banner h2{font-size:1.4rem;color:var(--cream);margin:0.3rem 0}
.now-banner p{font-size:0.9rem;color:var(--text-m);line-height:1.5}
.now-banner .status-dot{display:inline-block;width:10px;height:10px;background:var(--gold);border-radius:50%;margin-right:0.4rem;animation:pulse 2s infinite}
/* Timeline */
.timeline-wrapper{max-width:900px;margin:0 auto;padding:0 1.5rem var(--spacing)}
.timeline{position:relative}
.timeline::before{content:'';position:absolute;left:50%;top:0;bottom:0;width:2px;background:linear-gradient(to bottom,var(--green-l),var(--gold),var(--green-l));transform:translateX(-50%)}
.timeline-item{position:relative;margin-bottom:3rem;opacity:0;transform:translateY(40px);transition:opacity 0.8s ease,transform 0.8s ease}
.timeline-item.visible{opacity:1;transform:translateY(0)}
.timeline-item:nth-child(odd) .timeline-card{margin-left:auto}
.timeline-dot{position:absolute;left:50%;top:2rem;width:16px;height:16px;background:var(--gold);border:3px solid var(--dark);border-radius:50%;transform:translateX(-50%);z-index:2;transition:transform 0.3s,box-shadow 0.3s}
.timeline-item.visible .timeline-dot{box-shadow:0 0 20px rgba(212,168,67,0.4)}
.timeline-card{width:calc(50% - 30px);background:var(--card);border:1px solid var(--card-border);border-radius:10px;padding:1.5rem;cursor:pointer;transition:border-color 0.3s,box-shadow 0.3s;position:relative}
.timeline-card:hover{border-color:var(--green-l);box-shadow:0 4px 20px rgba(26,71,42,0.2)}
.timeline-card .date{font-size:0.7rem;color:var(--gold);font-weight:700;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:0.3rem}
.timeline-card .role{font-size:1.1rem;font-weight:700;color:var(--cream);margin-bottom:0.1rem}
.timeline-card .company{font-size:0.9rem;color:var(--green-l);font-weight:600;margin-bottom:0.2rem}
.timeline-card .location{font-size:0.75rem;color:var(--text-m);margin-bottom:0.6rem}
.timeline-card .summary{font-size:0.85rem;color:var(--text);line-height:1.6;margin-bottom:0.5rem}
.timeline-card .expand-btn{font-size:0.75rem;color:var(--green-l);cursor:pointer;font-weight:600;display:inline-flex;align-items:center;gap:0.3rem}
.timeline-card .expand-btn:hover{color:var(--gold)}
.timeline-card .details{max-height:0;overflow:hidden;transition:max-height 0.5s ease,opacity 0.4s ease;opacity:0}
.timeline-card .details.open{max-height:2000px;opacity:1}
.timeline-card .details ul{list-style:none;padding:0.8rem 0 0;border-top:1px solid var(--card-border)}
.timeline-card .details ul li{padding:0.4rem 0;font-size:0.8rem;color:var(--text);line-height:1.5;padding-left:1rem;position:relative}
.timeline-card .details ul li::before{content:'→';position:absolute;left:0;color:var(--gold)}
.timeline-card .details .tags{display:flex;flex-wrap:wrap;gap:0.4rem;padding-top:0.8rem;border-top:1px solid var(--card-border)}
.timeline-card .details .tags span{background:rgba(26,71,42,0.3);color:var(--text-m);font-size:0.7rem;padding:0.2rem 0.6rem;border-radius:4px;border:1px solid var(--card-border)}
/* Arrow connectors */
.timeline-item:nth-child(odd) .timeline-card::after{content:'';position:absolute;top:1.8rem;left:-8px;border:8px solid transparent;border-right-color:var(--card-border);z-index:1}
.timeline-item:nth-child(even) .timeline-card::after{content:'';position:absolute;top:1.8rem;right:-8px;border:8px solid transparent;border-left-color:var(--card-border);z-index:1}
/* Optional gap filler for short side */
.timeline-item:last-child{margin-bottom:0}
/* Education section */
.edu-section{max-width:700px;margin:0 auto var(--spacing);padding:0 1.5rem}
.edu-section h2{font-size:1.2rem;color:var(--gold);text-align:center;margin-bottom:1.5rem;text-transform:uppercase;letter-spacing:0.1em}
.edu-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
.edu-card{background:var(--card);border:1px solid var(--card-border);border-radius:8px;padding:1.2rem;opacity:0;transform:translateY(20px);transition:opacity 0.6s ease,transform 0.6s ease}
.edu-card.visible{opacity:1;transform:translateY(0)}
.edu-card h3{font-size:0.9rem;color:var(--cream);margin-bottom:0.2rem}
.edu-card .degree{font-size:0.8rem;color:var(--text-m);margin-bottom:0.1rem}
.edu-card .year{font-size:0.7rem;color:var(--green-l)}
/* Skills */
.skills-section{max-width:700px;margin:0 auto var(--spacing);padding:0 1.5rem}
.skills-section h2{font-size:1.2rem;color:var(--gold);text-align:center;margin-bottom:1.5rem;text-transform:uppercase;letter-spacing:0.1em}
.skills-grid{display:flex;flex-wrap:wrap;gap:0.6rem;justify-content:center}
.skill-tag{background:var(--card);border:1px solid var(--card-border);border-radius:100px;padding:0.5rem 1rem;font-size:0.8rem;color:var(--text);opacity:0;transform:scale(0.9);transition:opacity 0.4s ease,transform 0.4s ease,border-color 0.3s}
.skill-tag.visible{opacity:1;transform:scale(1)}
.skill-tag:hover{border-color:var(--green-l);color:var(--cream)}
/* Footer */
.footer{text-align:center;padding:2rem;color:var(--text-m);font-size:0.75rem;border-top:1px solid var(--card-border);max-width:700px;margin:0 auto}
.footer a{color:var(--green-l);text-decoration:none}
.footer a:hover{color:var(--gold)}
/* Animations */
@keyframes fadeUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
@keyframes bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
/* Mobile: full-width cards */
@media(max-width:700px){
.timeline::before{left:20px}
.timeline-dot{left:20px}
.timeline-card{width:calc(100% - 50px);margin-left:50px!important}
.timeline-item:nth-child(odd) .timeline-card::after,.timeline-item:nth-child(even) .timeline-card::after{left:-8px;border:8px solid transparent;border-right-color:var(--card-border);right:auto}
.edu-grid{grid-template-columns:1fr}
}
</style>
</head>
<body>
<!-- Hero -->
<section class="hero">
<h1>Career <span>Arc</span></h1>
<p class="sub">Marketing &amp; communications professional · Energy transition · Perth, WA</p>
<div class="tag">15+ years in energy · 3 companies · 6 roles</div>
<div class="scroll-hint">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 13l5 5 5-5M7 6l5 5 5-5"/></svg>
<span>Scroll to explore</span>
</div>
</section>
<div class="timeline-wrapper">
<!-- Now Banner -->
<div class="now-banner" style="opacity:0;animation:fadeUp 1s ease-out 0.6s both">
<div class="label"><span class="status-dot"></span>Current Status</div>
<h2>Open to Opportunities</h2>
<p>Recently finished a Marketing Advisor role at Pacific Energy. Actively exploring corporate marketing, communications and digital strategy roles in Perth and across WA.</p>
</div>
<div class="timeline" id="timeline">
<!-- 1. Pacific Energy -->
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-card" onclick="this.querySelector('.details').classList.toggle('open')">
<div class="date">Jun 2025 Jul 2026</div>
<div class="role">Marketing Advisor</div>
<div class="company">Pacific Energy</div>
<div class="location">Kewdale, Perth WA</div>
<div class="summary">Corporate marketing, digital content, technical communications, major events and marketing AI strategy for a leading energy infrastructure company.</div>
<div class="expand-btn">▼ Show achievements</div>
<div class="details">
<ul>
<li>Developed and rolled out a social media strategy and capability content series across LinkedIn and corporate channels</li>
<li>Defined Pacific Energy's marketing AI strategy and identified practical applications across content, digital workflows and operations</li>
<li>Led marketing collaboration on the corporate website, named Science and Sustainability Website of the Year at the 2026 Australian Web Awards</li>
<li>End-to-end marketing delivery for Australian Clean Energy Summit and Pilbara Summit — sponsorship activation, creative, content, logistics and stakeholder coordination</li>
<li>Translated complex project information into executive comms covering battery storage, remote power, hybrid systems and renewable integration</li>
<li>Maintained brand voice and visual standards across digital, print, signage, social and event channels</li>
</ul>
<div class="tags">
<span>B2B Marketing</span><span>AI Strategy</span><span>Web Awards</span><span>Event Management</span><span>Corporate Comms</span><span>Social Media</span>
</div>
</div>
</div>
</div>
<!-- 2. Horizon Power -->
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-card" onclick="this.querySelector('.details').classList.toggle('open')">
<div class="date">2019 Sep 2024</div>
<div class="role">Marketing Specialist</div>
<div class="company">Horizon Power</div>
<div class="location">Perth, WA</div>
<div class="summary">Integrated marketing, regulated communications, customer engagement and energy transition programs across WA's regional energy provider.</div>
<div class="expand-btn">▼ Show achievements</div>
<div class="details">
<ul>
<li>Led integrated marketing and customer communication campaigns across digital, social, email, direct mail, television, print and community channels</li>
<li>Managed annual marketing budgets exceeding $2 million, using research and audience segmentation to prioritise investment</li>
<li>Led marketing for the WA EV Network — the state's electric vehicle charging infrastructure rollout</li>
<li>Supported launch of Smart Connect Solar and other distributed energy initiatives</li>
<li>Directed compliance-critical communications for government price changes, energy concessions and cost-of-living initiatives</li>
<li>Created the Bright Horizons STEM program, providing solar-powered education kits to remote schools</li>
</ul>
<div class="tags">
<span>Integrated Campaigns</span><span>$2M+ Budget</span><span>WA EV Network</span><span>Energy Transition</span><span>Regulatory Comms</span><span>STEM Program</span>
</div>
</div>
</div>
</div>
<!-- 3. Synergy: Energy Solutions Marketing Specialist -->
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-card" onclick="this.querySelector('.details').classList.toggle('open')">
<div class="date">2015 2018</div>
<div class="role">Energy Solutions Marketing Specialist</div>
<div class="company">Synergy</div>
<div class="location">Perth, WA</div>
<div class="summary">Commercial growth and customer experience across renewable energy products, digital channels and agency management.</div>
<div class="expand-btn">▼ Show achievements</div>
<div class="details">
<ul>
<li>Led development of the SolarReturn proposition from research through launch, contributing to a 40% sales increase in year one</li>
<li>Managed three-plus creative, digital and media agencies, improving conversion rates by 35% through iterative optimisation</li>
<li>Mapped the end-to-end solar customer journey, reducing inquiry-to-sale timeframes by 20%</li>
<li>Converted renewable energy policy, rebates and feed-in tariffs into accessible customer communications across TV, radio, outdoor, social, email and SMS</li>
</ul>
<div class="tags">
<span>Product Launch</span><span>SolarReturn</span><span>Agency Management</span><span>Conversion Optimisation</span><span>Customer Journey</span>
</div>
</div>
</div>
</div>
<!-- 4. Synergy: Business Segment Coordinator -->
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-card" onclick="this.querySelector('.details').classList.toggle('open')">
<div class="date">2014 2015</div>
<div class="role">Business Segment Coordinator</div>
<div class="company">Synergy</div>
<div class="location">Perth, WA</div>
<div class="summary">Financial and portfolio management, enterprise communications and cross-functional coordination.</div>
<div class="expand-btn">▼ Show achievements</div>
<div class="details">
<ul>
<li>Managed Synergy's streetlighting portfolio and resolved $1.3 million in billing discrepancies through detailed contract and process reviews</li>
<li>Introduced stronger reconciliation controls and led cross-functional input into the Annual Review of Products and Pricing</li>
<li>Developed a company-wide communications framework across email, SMS and print for 1M+ customers</li>
</ul>
<div class="tags">
<span>Financial Management</span><span>Billing Resolution</span><span>Enterprise Comms</span><span>Cross-functional Leadership</span>
</div>
</div>
</div>
</div>
<!-- 5. Synergy: Assistant Product Manager -->
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-card" onclick="this.querySelector('.details').classList.toggle('open')">
<div class="date">2012 2014</div>
<div class="role">Assistant Product Manager, Industrial &amp; Commercial</div>
<div class="company">Synergy</div>
<div class="location">Perth, WA</div>
<div class="summary">Product and sales development for commercial renewable energy products, digital sales platforms and training.</div>
<div class="expand-btn">▼ Show achievements</div>
<div class="details">
<ul>
<li>Developed and launched the Synergy Sellback Program, enabling commercial customers to receive value from exported renewable energy</li>
<li>Created an online sales platform with pricing calculators and training material</li>
<li>Supported sales performance 150% above target through platform and training initiatives</li>
</ul>
<div class="tags">
<span>Product Development</span><span>Sellback Program</span><span>Digital Platform</span><span>Sales Enablement</span><span>B2B</span>
</div>
</div>
</div>
</div>
<!-- 6. Synergy: Billing Officer -->
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-card" onclick="this.querySelector('.details').classList.toggle('open')">
<div class="date">2010 2012</div>
<div class="role">Billing Officer</div>
<div class="company">Synergy</div>
<div class="location">Perth, WA</div>
<div class="summary">Customer billing, enquiry management, dispute resolution and data accuracy in a regulated energy retail environment.</div>
<div class="expand-btn">▼ Show achievements</div>
<div class="details">
<ul>
<li>Managed billing enquiries, account corrections and dispute resolution for residential and small business customers</li>
<li>Ensured data accuracy and compliance in a regulated energy retail environment</li>
</ul>
<div class="tags">
<span>Customer Service</span><span>Billing Operations</span><span>Dispute Resolution</span><span>Regulatory Compliance</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Education -->
<section class="edu-section">
<h2>Education</h2>
<div class="edu-grid">
<div class="edu-card">
<h3>University of Western Australia</h3>
<div class="degree">Bachelor of Commerce — Marketing &amp; Management</div>
<div class="year">2005 2010</div>
</div>
<div class="edu-card">
<h3>University of Western Australia</h3>
<div class="degree">Bachelor of Arts — History &amp; Politics</div>
<div class="year">2005 2010</div>
</div>
</div>
</section>
<!-- Professional Development -->
<section class="skills-section">
<h2>Professional Development</h2>
<div class="skills-grid">
<span class="skill-tag">IAP2 Essentials of Engagement (2023)</span>
<span class="skill-tag">AIM Time Management (2024)</span>
<span class="skill-tag">AI in Marketing — RMIT (2019)</span>
<span class="skill-tag">ADMA WFH Marketing Masterclass (2020)</span>
</div>
</section>
<!-- Core Capabilities -->
<section class="skills-section">
<h2>Core Capabilities</h2>
<div class="skills-grid">
<span class="skill-tag">Marketing Strategy</span>
<span class="skill-tag">Integrated Campaigns</span>
<span class="skill-tag">Corporate Communications</span>
<span class="skill-tag">Digital &amp; AI Marketing</span>
<span class="skill-tag">Brand Management</span>
<span class="skill-tag">Stakeholder Engagement</span>
<span class="skill-tag">Event Management</span>
<span class="skill-tag">Budget Management</span>
<span class="skill-tag">Social Media Strategy</span>
<span class="skill-tag">Energy Transition</span>
</div>
</section>
<div class="footer">
<p>Anthony Martin · <a href="https://anthony.martinwa.org" target="_blank">anthony.martinwa.org</a></p>
<p style="margin-top:0.3rem;font-size:0.7rem">Built by the Rhino in the Green Jacket · Jul 2026</p>
</div>
<script>
// Scroll-triggered animations
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.15, rootMargin: '0px 0px -50px 0px' });
document.querySelectorAll('.timeline-item, .edu-card, .skill-tag').forEach(el => observer.observe(el));
// Stagger skill tags
document.querySelectorAll('.skill-tag').forEach((el, i) => {
el.style.transitionDelay = (i * 0.05) + 's';
});
// Timeline auto-scroll highlight
const items = document.querySelectorAll('.timeline-item');
const dotObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.querySelector('.timeline-dot').style.transform = 'translateX(-50%) scale(1.3)';
} else {
entry.target.querySelector('.timeline-dot').style.transform = 'translateX(-50%) scale(1)';
}
});
}, { threshold: 0.4 });
items.forEach(item => dotObserver.observe(item));
// Smooth scroll for hero arrow
document.querySelector('.scroll-hint')?.addEventListener('click', () => {
document.querySelector('.now-banner')?.scrollIntoView({ behavior: 'smooth' });
});
</script>
</body>
</html>