A dark-themed, bilingual (EN/IT) single-page Jekyll site for a three-person performance engineering team in Venice. The "Proven Results" section profiles four real research codes — Fulgor, COSMICA, MiThyCA and Line-Laser INR — each with an animation built from inline SVG and metrics traceable to the published papers.
The site deliberately carries no brand name: page titles and descriptions
come from _data/i18n.yml, and the header has no wordmark.
| Tool | Version | Needed for |
|---|---|---|
| Ruby | 3.x | Jekyll |
| Bundler | 2.x | gem install |
| Node.js | 18+ | Tailwind CLI |
Node is only required if you intend to change the styles. The compiled
stylesheet is committed, so a plain bundle install && bundle exec jekyll serve
is enough to run the site.
bundle installbundle exec jekyll serve --livereloadThe site is then at http://localhost:4000. Edits to .html, .md and
_data/*.yml trigger a rebuild automatically.
Tailwind compiles src/input.css into assets/css/main.css. Install once:
npm installThen run the watcher in a second terminal, alongside jekyll serve:
npm run css:watchFor a one-off minified build:
npm run cssCommit assets/css/main.css whenever you change src/input.css — the
deployment does not run Tailwind, so an uncommitted rebuild means the live site
keeps the old styles.
The output is a fully static _site/ directory — any static host will serve it.
Set url and baseurl in _config.yml first. For a project page served from
https://<user>.github.io/<repo>/, baseurl must be "/<repo>"; for a user or
custom-domain site leave it as "". Every internal link already runs through
relative_url, so setting it correctly is all that is required.
Then add .github/workflows/pages.yml:
name: Deploy
on:
push: { branches: [master] }
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with: { ruby-version: '3.3', bundler-cache: true }
- run: bundle exec jekyll build
env: { JEKYLL_ENV: production }
- uses: actions/upload-pages-artifact@v3
with: { path: _site }
deploy:
needs: build
runs-on: ubuntu-latest
environment: github-pages
steps:
- uses: actions/deploy-pages@v4Then set Settings → Pages → Source to GitHub Actions.
Build command bundle exec jekyll build, publish directory _site.
JEKYLL_ENV=production bundle exec jekyll buildand upload the contents of _site/.
.
├── _config.yml Site metadata. `exclude` keeps the toolchain out of _site.
├── index.html English page: front matter only (lang: en), includes home.html.
├── it/index.html Italian page: same, with lang: it.
│
├── _layouts/
│ └── default.html <head>, header, {{ content }}, footer, script tags.
│
├── _data/ All copy lives here — edit these, not the markup.
│ ├── i18n.yml UI strings per locale, plus the nav links.
│ ├── capabilities.yml The four capability cards.
│ └── case_studies.yml The four projects: copy, metrics, papers, animation.
│
├── _includes/
│ ├── home.html The whole page body, shared by both locales.
│ ├── header.html Sticky nav, language switcher, mobile disclosure.
│ ├── footer.html
│ ├── icons.html Inline SVG sprite, selected by name.
│ ├── case-visual.html Maps a `visual:` key to one of the four below.
│ ├── visual-dna.html Fulgor — scrolling DNA double helix.
│ ├── visual-heliosphere.html COSMICA — cosmic rays lensed around Earth.
│ ├── visual-histology.html MiThyCA — slide scan with nuclei detection.
│ ├── visual-laser.html Line-Laser INR — laser sweep, level-set closing.
│ └── visual-hero.html Hero circuit board.
│
├── src/input.css Tailwind source: @theme tokens + component layer.
├── assets/
│ ├── css/main.css BUILD OUTPUT — generated, but committed.
│ └── js/
│ ├── carousel.js Slides, arrows, autoplay. Progressive enhancement.
│ └── motion.js Pauses SVG animation under prefers-reduced-motion.
│
├── package.json Tailwind CLI + the css / css:watch scripts.
└── Gemfile jekyll + webrick.
Almost nothing requires touching HTML.
- Change a capability card — edit
_data/capabilities.yml. Adding an entry adds a card; the grid reflows on its own. Theicon:key must match a name in_includes/icons.html. - Change a project — edit
_data/case_studies.yml. Each entry carries its copy, its two metrics, its source papers, and avisual:key naming the animation. Adding an entry adds a carousel slide and a pagination dot. - Add a new animation — drop
_includes/visual-<name>.htmlin, register it in_includes/case-visual.html, then point a project'svisual:at it. Use a square viewBox: the panel is 1:1 and the SVG is drawn withpreserveAspectRatio="xMidYMid slice", so a non-square viewBox gets cropped.
Locales are separate pages, not a runtime switch, so the site stays static and the switcher works without JavaScript.
- Add a top-level block to
_data/i18n.ymlkeyed by the language code. It must define every key theenblock does — a missing key renders empty rather than falling back. - Add
<code>entries to thetitle,category,descriptionand metriclabelmaps in_data/capabilities.ymland_data/case_studies.yml. - Create
<code>/index.htmlwith front matterlang: <code>,permalink: /<code>/, and a single{% include home.html %}.
The header switcher builds itself from the locales present in i18n.yml, so
no markup change is needed. _layouts/default.html hardcodes the hreflang
alternates — add the new one there.
assets/js/carousel.js drives the slides, the side arrows, the autoplay cycle
and its countdown ring. Three attributes on the carousel element in
_includes/home.html tune it; remove data-autoplay to disable autoplay
entirely.
| Attribute | Default | Meaning |
|---|---|---|
data-autoplay |
8000 |
Dwell per slide, in ms |
data-autoplay-manual |
40000 |
Longer dwell after the visitor navigates by hand |
data-progress-window |
8000 |
How long before the hand-off the ring appears |
The countdown ring wraps the next arrow. It only shows during the final
data-progress-window of whichever dwell is running — so on the default cycle
it is visible throughout, while after a manual navigation the 40s cycle stays
quiet for its first 32s and the countdown never becomes a distraction.
Autoplay holds to these rules, worth preserving if you touch it:
- it never starts when the visitor prefers reduced motion;
- it pauses on hover, on keyboard focus inside the carousel, and while the browser tab is in the background;
- manual navigation buys the reader the long dwell rather than stopping the cycle, so a slide is never pulled away mid-read but the carousel does eventually carry on.
Each project shows two metrics, and both are documented by a comment directly
beneath the value in _data/case_studies.yml — source table, raw numbers, and
arithmetic.
First metric: an estimated annual saving. These are ours, not the papers'.
Each is marked estimate: true, which renders an asterisk linking to a note
under the carousel. The full derivation — instance types, list prices, assumed
workload — lives only in the YAML comment and is deliberately not published:
the figure is a headline, the assumptions behind it are for whoever maintains
the numbers. Every one rests on a load-bearing assumption about workload scale;
the comment always says which, because that is what a reader would challenge.
Second metric: whatever best characterises the project — query speedup for Fulgor, concurrent simulations for COSMICA, and so on.
Two labels mark weaker evidence in the comments:
OUR ESTIMATE/ESTIMATE— extrapolated beyond what the paper states.READ FROM PLOT— measured off a figure rather than a printed table, so the value is approximate (Line-Laser INR).
Keep that discipline when adding projects: a number on the page should always be reproducible from the comment under it, and anything softer than a table should say so.
- Asset URLs carry
?v={{ site.time }}so a rebuild busts stale caches. - Setting
excludein_config.ymloverrides Jekyll's defaults entirely, sonode_modules,srcandpackage.jsonare listed explicitly. Removing them from that list will publish them. - The carousel degrades gracefully: the first slide ships visible and the rest
carry
hidden, so the page is readable with JavaScript disabled.