This is the source for my personal engineering portfolio: aloeveraz.github.io.
I use the site to keep my robotics, CAD, controls, manufacturing, and 3D-printing work in one place. The project cards come from one JSON file, so I can add a build without rewriting the page layout each time.
index.htmlcontains the page structure and project modal.portfolio.csshandles the layout, responsive styles, and visual effects.portfolio.jsrenders the profile, project collections, carousels, and modal content.portfolio-data.jsonholds the profile, skills, project write-ups, media, and links.consent.jskeeps YouTube embeds from loading until a visitor asks for them.assets/contains the project photos, GIFs, graphics, and the self-hosted fonts.project-example.jsonis a project record I can copy when adding something new.
Supporting pages and files: privacy.html, terms.html, 404.html, page.css
and page.js (shared styling and the zoom-aware root font size for those
three), fonts.css, icons.css, robots.txt, sitemap.xml, and
site.webmanifest.
Every vertical gap on the site comes from one of six tokens, declared and
explained at the top of portfolio.css and copied into page.css:
| Token | Value | Used between |
|---|---|---|
--rhythm-label |
12px | a label and the heading it names; one list item and the next |
--rhythm-heading |
16px | a heading and the line that supports it; one line of a passage and the next |
--rhythm-rule |
16px | either side of a rule inside a card |
--rhythm-block |
24px | one block and the next inside a section |
--rhythm-header |
48px | a header and the content it heads |
--rhythm-section |
48–96px | one section and the next |
Which token a gap takes depends on what the two things are to each other, not on where they sit, so the hero's badge stands above the name at exactly the distance a section's label stands above its title. Nothing sets a spacing value of its own; if a new pair does not fit one of the six, the pair is probably named wrong.
These are the distances you see, not the distances between two boxes. A line
box is taller than the letters in it, and by a different amount for every
line-height on the page — which is why the hero's name used to sit visibly
closer to the badge above it than to the tagline below it, despite having the
larger margin of the two. Each text block is therefore leading-trimmed: a pair
of zero-height pseudo-elements pulls its box in to cap height at the top and
the baseline at the bottom, using the font metrics in the same :root block.
The end of portfolio.css carries the arithmetic. To trim a new block, set
--lh to its line-height as a plain number, write line-height: var(--lh),
and add it to the list for the face it is set in.
tools/measure-faces.js re-reads those font metrics, and is the thing to run
if a typeface is ever swapped or resubset.
Fonts, icons, and every project image are served from this domain. Opening the
page makes no request to Google, a CDN, or an analytics service, and the site
sets no cookies. The only outside request is YouTube, and a project video stays
a local placeholder until someone clicks it. privacy.html explains this to
visitors; keep the two in step if that ever changes.
The portfolio is a static site, but it needs a local server so the browser can load the JSON file.
python -m http.server 8000Then open http://localhost:8000.
- Add the project images to
assets/. - Copy the record in
project-example.jsoninto theprojectsarray inportfolio-data.json. - Replace the example values and set
categoryandcollectionOrder. - Write an
imageAltdescribing what is actually in the picture — not the project name, which the heading beside it already says. - Set
featuredtotrueand add afeaturedOrderwhen the project belongs in the first row. - Run the media scripts below, then check the card, project modal, links, and mobile layout locally.
Empty links and media fields are skipped by the page, so unfinished material can stay out of the public portfolio until it is ready.
The scripts in tools/ regenerate the files that are not written by hand. They
need Python with pillow, fonttools, and brotli.
python tools/build-images.py # source photos/GIFs -> compressed WebP
python tools/build-thumbnails.py # 900px card variants next to each image
python tools/link-thumbnails.py # record each card's srcset in the JSON
python tools/build-icons.py # subset Font Awesome to the icons in use
python tools/build-fonts.py # download and self-host Inter + Barlow Condensed
python tools/build-brand.py # favicon set and the social preview cardtools/measure-faces.js is not part of that run: it is pasted into the browser
console and prints the --face-* metrics the leading trim needs.
Run build-icons.py again whenever the markup starts using a new fa- icon,
otherwise that glyph will not be in the subset and will render as a blank box.
After changing portfolio.css, portfolio.js, or portfolio-data.json, bump
the ?v= cache-busting suffix in the HTML files (and the one on the
portfolio-data.json fetch inside portfolio.js) so returning visitors do not
get a stale copy.
GitHub Pages serves this from the repository root. HTTPS needs no action: a
*.github.io host is on the HSTS preload list and GitHub redirects http://
to https:// on its own — verified against the live site. The
upgrade-insecure-requests meta tag in each page covers subresources. If this
ever moves to a custom domain, that stops being automatic and Settings → Pages
→ Enforce HTTPS has to be switched on for the new domain.