mcpeakml/portfolio #5

Merged
McPeakML merged 24 commits from mcpeakml/portfolio into main 2026-06-12 11:19:57 -04:00
6 changed files with 174 additions and 55 deletions
Showing only changes of commit 55e585186e - Show all commits

8
.dockerignore Normal file
View file

@ -0,0 +1,8 @@
node_modules
dist-ssr
coverage
test-results
playwright-report
.git
.vscode
*.log

View file

@ -0,0 +1,67 @@
name: Build and publish Docker image
on:
push:
tags:
- 'v*'
pull_request:
jobs:
build:
runs-on: docker
container: oven/bun:1
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Audit dependencies
run: bun audit
- name: Run unit tests
run: bun run test:unit run
- name: Build
run: bun run build
- name: Upload dist
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
docker:
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download dist
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Log in to registry
uses: docker/login-action@v3
with:
registry: git.mcpeakdev.com
username: ${{ github.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Extract image tag
id: meta
run: echo "tag=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
git.mcpeakdev.com/mcpeakml/portfolio:${{ steps.meta.outputs.tag }}
git.mcpeakdev.com/mcpeakml/portfolio:latest

7
Dockerfile Normal file
View file

@ -0,0 +1,7 @@
# syntax=docker/dockerfile:1
FROM nginx:1-alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

View file

@ -71,3 +71,28 @@ bun test:e2e --debug
```sh ```sh
bun lint bun lint
``` ```
## Docker
The Dockerfile serves a pre-built `dist/` directory via nginx, so build the
app first:
```sh
bun run build
docker build -t portfolio .
docker run --rm -p 8080:80 portfolio
```
The site will be available at http://localhost:8080.
### CI publishing
A Forgejo Actions workflow (`.forgejo/workflows/docker-publish.yml`) builds the
image and pushes it to the Forgejo container registry whenever a `v*` tag is
pushed (e.g. `v1.2.3`):
- `git.mcpeakdev.com/mcpeakml/portfolio:<version>`
- `git.mcpeakdev.com/mcpeakml/portfolio:latest`
This requires a `REGISTRY_TOKEN` repository secret containing a token with
`write:package` permission for the registry.

10
nginx.conf Normal file
View file

@ -0,0 +1,10 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}

View file

@ -1,83 +1,85 @@
export interface Stat { export interface Stat {
value: string value: string;
label: string label: string;
color: string color: string;
} }
export interface Project { export interface Project {
id: string id: string;
title: string title: string;
color: 'primary' | 'info' | 'success' | 'warn' color: "primary" | "info" | "success" | "warn";
description: string description: string;
tags: string[] tags: string[];
link: string link: string;
} }
export interface PortfolioData { export interface PortfolioData {
name: string name: string;
role: string role: string;
tagline: string tagline: string;
status: string status: string;
about: string about: string;
stats: Stat[] stats: Stat[];
projects: Project[] projects: Project[];
skills: Record<string, string[]> skills: Record<string, string[]>;
social: { social: {
github: string github: string;
linkedin: string linkedin: string;
email: string email: string;
} };
} }
// Edit this block to make the portfolio yours. // Edit this block to make the portfolio yours.
export const portfolioData: PortfolioData = { export const portfolioData: PortfolioData = {
name: 'Alexander Voss', name: "Matthew McPeak",
role: 'Product Designer · Developer', role: "Product Designer · Developer",
tagline: 'I craft digital experiences that feel inevitable — clear, classical, enduring.', tagline: "I craft digital experiences that feel inevitable — clear, classical, enduring.",
status: 'Open for opportunities · 2026', status: "Open for opportunities · 2026",
about: about:
'Based in Athens. I work at the intersection of design and engineering, building systems and products that are precise, durable, and a pleasure to use. Ten years in, I still believe the best interface is the one that gets out of the way.', "Based in Athens. I work at the intersection of design and engineering, building systems and products that are precise, durable, and a pleasure to use. Ten years in, I still believe the best interface is the one that gets out of the way.",
stats: [ stats: [
{ value: '10+', label: 'Years of craft', color: 'var(--primary)' }, { value: "10+", label: "Years of craft", color: "var(--primary)" },
{ value: '40+', label: 'Projects shipped', color: 'var(--info)' }, { value: "40+", label: "Projects shipped", color: "var(--info)" },
{ value: '12', label: 'Satisfied patrons', color: 'var(--success)' }, { value: "12", label: "Satisfied patrons", color: "var(--success)" },
], ],
projects: [ projects: [
{ {
id: 'p1', id: "p1",
title: 'Nychthemeron', title: "Nychthemeron",
color: 'primary', color: "primary",
description: description:
'A dual-theme design system — Hades (dark) and Apollo (light) — for classical, lasting interfaces.', "A dual-theme design system — Hades (dark) and Apollo (light) — for classical, lasting interfaces.",
tags: ['Design System', 'Vue', 'CSS'], tags: ["Design System", "Vue", "CSS"],
link: '#', link: "#",
}, },
{ {
id: 'p2', id: "p2",
title: 'The Oracle', title: "The Oracle",
color: 'info', color: "info",
description: 'A mythology-themed admin interface with consultations, petitioners, and prophecy tracking.', description:
tags: ['UI Kit', 'Dashboard', 'TypeScript'], "A mythology-themed admin interface with consultations, petitioners, and prophecy tracking.",
link: '#', tags: ["UI Kit", "Dashboard", "TypeScript"],
link: "#",
}, },
{ {
id: 'p3', id: "p3",
title: 'Agora', title: "Agora",
color: 'success', color: "success",
description: 'A real-time marketplace for artisans, connecting makers with patrons across the ancient world.', description:
tags: ['Platform', 'Fullstack', 'Postgres'], "A real-time marketplace for artisans, connecting makers with patrons across the ancient world.",
link: '#', tags: ["Platform", "Fullstack", "Postgres"],
link: "#",
}, },
], ],
skills: { skills: {
Design: ['Figma', 'Design Systems', 'Typography', 'Motion'], Design: ["Figma", "Design Systems", "Typography", "Motion"],
Frontend: ['Vue', 'CSS', 'TypeScript', 'Vite'], Frontend: ["Vue", "CSS", "TypeScript", "Vite"],
Backend: ['Node.js', 'Postgres', 'REST', 'GraphQL'], Backend: ["Node.js", "Postgres", "REST", "GraphQL"],
Tools: ['Git', 'Storybook', 'Figma', 'Linear'], Tools: ["Git", "Storybook", "Figma", "Linear"],
}, },
social: { social: {
github: '#', github: "#",
linkedin: '#', linkedin: "#",
email: 'hello@example.com', email: "hello@example.com",
}, },
} };