feat: Implement Sprint 8 - Portfolio website expansion (MVP)

New pages:
- Home: Redesigned with hero, impact stats, featured project
- About: 6-section professional narrative
- Projects: Hub with 4 project cards and status badges
- Resume: Inline display with download placeholders
- Contact: Form UI (disabled) with contact info
- Blog: Markdown-based system with frontmatter support

Infrastructure:
- Blog system with markdown loader (python-frontmatter, markdown, pygments)
- Sidebar callback for active state highlighting on navigation
- Separated navigation into main pages and projects/dashboards groups

Closes #36, #37, #38, #39, #40, #41, #42, #43

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-15 15:40:01 -05:00
parent cd7b5ce154
commit 138e6fe497
15 changed files with 1937 additions and 134 deletions

View File

@@ -1,81 +1,118 @@
"""Bio landing page."""
"""Home landing page - Portfolio entry point."""
import dash
import dash_mantine_components as dmc
from dash import dcc
from dash_iconify import DashIconify
dash.register_page(__name__, path="/", name="Home")
# Content from bio_content_v2.md
HEADLINE = "Leo | Data Engineer & Analytics Developer"
TAGLINE = "I build data infrastructure that actually gets used."
# Hero content from blueprint
HEADLINE = "I turn messy data into systems that actually work."
SUBHEAD = (
"Data Engineer & Analytics Specialist. 8 years building pipelines, dashboards, "
"and the infrastructure nobody sees but everyone depends on. Based in Toronto."
)
SUMMARY = """Over the past 5 years, I've designed and evolved an enterprise analytics platform
from scratch—now processing 1B+ rows across 21 tables with Python-based ETL pipelines and
dbt-style SQL transformations. The result: 40% efficiency gains, 30% reduction in call
abandon rates, and dashboards that executives actually open.
My approach: dimensional modeling (star schema), layered transformations
(staging → intermediate → marts), and automation that eliminates manual work.
I've built everything from self-service analytics portals to OCR-powered receipt processing systems.
Currently at Summitt Energy supporting multi-market operations across Canada and 8 US states.
Previously cut my teeth on IT infrastructure projects at Petrobras (Fortune 500) and the
Project Management Institute."""
TECH_STACK = [
"Python",
"Pandas",
"SQLAlchemy",
"FastAPI",
"SQL",
"PostgreSQL",
"MSSQL",
"Power BI",
"Plotly/Dash",
"dbt patterns",
"Genesys Cloud",
# Impact metrics
IMPACT_STATS = [
{"value": "1B+", "label": "Rows processed daily across enterprise platform"},
{"value": "40%", "label": "Efficiency gain through automation"},
{"value": "5 Years", "label": "Building DataFlow from zero"},
]
PROJECTS = [
{
"title": "Toronto Housing Dashboard",
"description": "Choropleth visualization of GTA real estate trends with TRREB and CMHC data.",
"status": "In Development",
"link": "/toronto",
},
{
"title": "Energy Pricing Analysis",
"description": "Time series analysis and ML prediction for utility market pricing.",
"status": "Planned",
"link": "/energy",
},
]
# Featured project
FEATURED_PROJECT = {
"title": "Toronto Housing Market Dashboard",
"description": (
"Real-time analytics on Toronto's housing trends. "
"dbt-powered ETL, Python scraping, Plotly visualization."
),
"status": "Live",
"dashboard_link": "/toronto",
"repo_link": "https://github.com/leomiranda/personal-portfolio",
}
AVAILABILITY = "Open to Senior Data Analyst, Analytics Engineer, and BI Developer opportunities in Toronto or remote."
# Brief intro
INTRO_TEXT = (
"I'm a data engineer who's spent the last 8 years in the trenches—building the "
"infrastructure that feeds dashboards, automates the boring stuff, and makes data "
"actually usable. Most of my work has been in contact center operations and energy, "
"where I've had to be scrappy: one-person data teams, legacy systems, stakeholders "
"who need answers yesterday."
)
INTRO_CLOSING = "I like solving real problems, not theoretical ones."
def create_hero_section() -> dmc.Stack:
"""Create the hero section with name and tagline."""
"""Create the hero section with headline, subhead, and CTAs."""
return dmc.Stack(
[
dmc.Title(HEADLINE, order=1, ta="center"),
dmc.Text(TAGLINE, size="xl", c="dimmed", ta="center"),
dmc.Title(
HEADLINE,
order=1,
ta="center",
size="2.5rem",
),
dmc.Text(
SUBHEAD,
size="lg",
c="dimmed",
ta="center",
maw=700,
mx="auto",
),
dmc.Group(
[
dcc.Link(
dmc.Button(
"View Projects",
size="lg",
variant="filled",
leftSection=DashIconify(icon="tabler:folder", width=20),
),
href="/projects",
),
dcc.Link(
dmc.Button(
"Get In Touch",
size="lg",
variant="outline",
leftSection=DashIconify(icon="tabler:mail", width=20),
),
href="/contact",
),
],
justify="center",
gap="md",
mt="md",
),
],
gap="xs",
gap="md",
py="xl",
)
def create_summary_section() -> dmc.Paper:
"""Create the professional summary section."""
paragraphs = SUMMARY.strip().split("\n\n")
def create_impact_stat(stat: dict[str, str]) -> dmc.Stack:
"""Create a single impact stat."""
return dmc.Stack(
[
dmc.Text(stat["value"], fw=700, size="2rem", ta="center"),
dmc.Text(stat["label"], size="sm", c="dimmed", ta="center"),
],
gap="xs",
align="center",
)
def create_impact_strip() -> dmc.Paper:
"""Create the impact statistics strip."""
return dmc.Paper(
dmc.Stack(
[
dmc.Title("About", order=2, size="h3"),
*[dmc.Text(p.replace("\n", " "), size="md") for p in paragraphs],
],
gap="md",
dmc.SimpleGrid(
[create_impact_stat(stat) for stat in IMPACT_STATS],
cols={"base": 1, "sm": 3},
spacing="xl",
),
p="xl",
radius="md",
@@ -83,16 +120,56 @@ def create_summary_section() -> dmc.Paper:
)
def create_tech_stack_section() -> dmc.Paper:
"""Create the tech stack section with badges."""
def create_featured_project() -> dmc.Paper:
"""Create the featured project card."""
return dmc.Paper(
dmc.Stack(
[
dmc.Title("Tech Stack", order=2, size="h3"),
dmc.Group(
[
dmc.Badge(tech, size="lg", variant="light", radius="sm")
for tech in TECH_STACK
dmc.Title("Featured Project", order=2, size="h3"),
dmc.Badge(
FEATURED_PROJECT["status"],
color="green",
variant="light",
size="lg",
),
],
justify="space-between",
),
dmc.Title(
FEATURED_PROJECT["title"],
order=3,
size="h4",
),
dmc.Text(
FEATURED_PROJECT["description"],
size="md",
c="dimmed",
),
dmc.Group(
[
dcc.Link(
dmc.Button(
"View Dashboard",
variant="light",
leftSection=DashIconify(
icon="tabler:chart-bar", width=18
),
),
href=FEATURED_PROJECT["dashboard_link"],
),
dmc.Anchor(
dmc.Button(
"View Repository",
variant="subtle",
leftSection=DashIconify(
icon="tabler:brand-github", width=18
),
),
href=FEATURED_PROJECT["repo_link"],
target="_blank",
),
],
gap="sm",
),
@@ -105,38 +182,13 @@ def create_tech_stack_section() -> dmc.Paper:
)
def create_project_card(project: dict[str, str]) -> dmc.Card:
"""Create a project card."""
status_color = "blue" if project["status"] == "In Development" else "gray"
return dmc.Card(
[
dmc.Group(
[
dmc.Text(project["title"], fw=500, size="lg"),
dmc.Badge(project["status"], color=status_color, variant="light"),
],
justify="space-between",
align="center",
),
dmc.Text(project["description"], size="sm", c="dimmed", mt="sm"),
],
withBorder=True,
radius="md",
p="lg",
)
def create_projects_section() -> dmc.Paper:
"""Create the portfolio projects section."""
def create_intro_section() -> dmc.Paper:
"""Create the brief intro section."""
return dmc.Paper(
dmc.Stack(
[
dmc.Title("Portfolio Projects", order=2, size="h3"),
dmc.SimpleGrid(
[create_project_card(p) for p in PROJECTS],
cols={"base": 1, "sm": 2},
spacing="lg",
),
dmc.Text(INTRO_TEXT, size="md"),
dmc.Text(INTRO_CLOSING, size="md", fw=500, fs="italic"),
],
gap="md",
),
@@ -146,20 +198,13 @@ def create_projects_section() -> dmc.Paper:
)
def create_availability_section() -> dmc.Text:
"""Create the availability statement."""
return dmc.Text(AVAILABILITY, size="sm", c="dimmed", ta="center", fs="italic")
layout = dmc.Container(
dmc.Stack(
[
create_hero_section(),
create_summary_section(),
create_tech_stack_section(),
create_projects_section(),
dmc.Divider(my="lg"),
create_availability_section(),
create_impact_strip(),
create_featured_project(),
create_intro_section(),
dmc.Space(h=40),
],
gap="xl",