Skip to content

Single-source CV content, bilingual ES/EN site, and visual redesign - #6

Merged
britoruben merged 11 commits into
mainfrom
sandbox
Sep 1, 2026
Merged

britoruben merged 11 commits into
mainfrom
sandbox

Conversation

@britoruben

@britoruben britoruben commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Content now lives in a single content/cv.json, generating both the website and the LaTeX CV (tools/build.mjs + render-html.mjs/render-tex.mjs) instead of maintaining them by hand in two places.
  • Bilingual site: English at the root, Spanish at /es/, with proper hreflang alternates and a per-locale canonical.
  • Visual redesign: new palette, inline SVG icons instead of the Font Awesome CDN (corporate/bank networks often block cdnjs), PDF rebalanced to two pages, SEO files (robots.txt, sitemap.xml, og-image.png).
  • New CI (verify-build.yml) fails the build if the generated files drift from content/cv.json or an internal link/anchor breaks.
  • A follow-up pass corrected several content-accuracy issues from the rewrite (role framing, job title, hero copy, dead code) and a Windows CRLF bug in the build check — see the last two commits.

Test plan

  • npm run check passes (generated files in sync, including on Windows with core.autocrlf=true after the .gitattributes fix)
  • npm run check:links passes
  • Verified locally in the browser: light/dark theme, EN/ES switch, mobile (375px) and desktop, no console errors
  • Content reviewed line by line in both languages against what Ruben actually does day to day; no confidential or off-limits content
  • Ruben to review the live PR preview PDF artifact before merging

🤖 Generated with Claude Code

claude and others added 11 commits September 1, 2026 02:28
docs/index.html and main.tex both carried the same CV content, maintained by
hand in two places. Every edit had to be made twice, and the two had already
drifted apart in a few spots.

Content now lives in content/cv.json and both outputs are generated from it:

  content/cv.json  ->  docs/index.html   (build/render-html.mjs)
                   ->  main.tex          (build/render-tex.mjs + preamble.tex)

The generated files stay committed, so GitHub Pages keeps serving /docs and the
LaTeX workflow keeps compiling main.tex with no build step in between. A new
workflow runs `npm run check` on every push and pull request, failing if either
generated file was edited by hand or if cv.json changed without regenerating.

Text fields are locale-keyed ({"en": ...}) and fall back to the default locale
when a translation is missing, so Spanish can be added incrementally later.

Rendering is unchanged. main.tex comes out byte-identical to the previous
version. The page renders at the same height (3492px) with the same sections
and the same 19 links; the only visible difference is three characters in the
HTML where hyphens became proper en/em dashes, matching what the LaTeX version
already produced.

No dependencies - the build is plain Node.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ubYyHTXGyiRAqPMqeQgJE
The previous commit put the generator under build/, and .gitignore has a
`build/` rule for compilation artefacts. Git silently skipped the whole
directory, so that commit describes a build system whose files are not in the
repository and `npm run check` would fail in CI with a missing module.

Moved to tools/, which is source rather than output and reads correctly next
to the `build/` ignore rule. References updated in package.json, the README and
the generated-file banners; regenerated both outputs.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ubYyHTXGyiRAqPMqeQgJE
Content rework agreed with the owner, plus the ES side of the bilingual
content model.

Positioning
- Drops every claim about leading a team; the seniority signal is now the
  official title (Software Engineering Specialist) and the work itself.
- Adds the dimension the CV was missing entirely: platform and runtime work.
  Nothing previously mentioned delivery pipelines, load balancing, autoscaling,
  resource tuning or monitoring, which is a large part of the job.
- Adds security: dependency and library vulnerability management, and the CI
  pipelines behind it.
- Reframes the AI work from a list of five tools to what is built with them.
- Hero gains a tagline and a context line; drops the metric tiles, which read
  as marketing rather than as a CV.

Facts corrected
- Education dates were both a year out: Domenico Scarlatti 2012-2014 (was
  2011-2013), Enrique Tierno Galvan 2014-2015 (was 2013-2014).
- Gfi now starts May 2015 with the internship, closing the gap between the end
  of study and the first listed job.
- Eleven years, not "10+".
- Santander UK carries the UK/remote note.

Structure
- Gfi Spain becomes a parent entry with Telefonica, Securitas Direct and
  Santander Global Tech nested inside it as client engagements. The five
  overlapping top-level roles are now three entries with a readable timeline,
  and Securitas Direct fills what was an unexplained gap in 2017.
- New Languages section.

Verified: both locales render with no untranslated strings, no JS errors and
no horizontal overflow. The LaTeX compiles clean with zero overfull boxes and
the new engagement macros work. The PDF now runs to two pages, but the split is
poor - page one is crammed and page two is mostly empty. Rebalancing it is
phase 4.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ubYyHTXGyiRAqPMqeQgJE
Two changes that together make up the design-system layer.

Palette
- Replaces the Santander red (#EC0000) with "Basalto & Oceano": a deep ocean
  brand colour and a warm sand accent over basalt neutrals. The site no longer
  wears an employer's brand colours.
- Tokens are now semantic (--brand, --brand-hover, --accent, --on-brand)
  instead of literal (--red, --red-dark, --red-text), so a future palette
  change is a token edit rather than a search and replace.
- The LaTeX preamble carries the same palette, so the PDF matches the site.
- Audited on computed colours in the rendered page: 24 selectors across both
  themes, zero AA failures. This also fixes a real defect in the old palette,
  where the active nav link in dark mode sat at 3.86:1, below AA.

Icons
- Font Awesome came from cdnjs as a render-blocking stylesheet plus font files,
  for fifteen glyphs. Any network that blocks the CDN stripped every icon from
  the page, including the theme toggle, which then rendered as an empty circle
  with no visible label - reproducible, and visible in the review screenshots.
- Replaced with inline SVG in tools/icons.mjs: outline icons on a 24x24 grid
  inheriting currentColor, brand marks as filled paths. 21 icons inline in the
  page, no icon requests at all.

The only remaining third-party request is Google Fonts.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ubYyHTXGyiRAqPMqeQgJE
The previous commit staged only content/, tools/ and docs/, so the regenerated
main.tex was left behind. That published tools/preamble.tex carrying the new
palette while main.tex still carried the Santander red - an inconsistent tree,
and `npm run check` returns 1 against it, so CI would have failed on that
commit.

The Fase 0 guard caught the class of error it was built for; it was run before
staging rather than after pushing, which is where it needed to be.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ubYyHTXGyiRAqPMqeQgJE
Hero
- Rebuilt around the agreed V2 treatment: mark, name, role label, tagline,
  context line and real calls to action. The metric tiles are gone; they read
  as marketing on a CV. The one figure worth stating lives in its bullet, with
  context.
- The mark aligns to the top of the text block instead of floating centred
  against it.

Experience
- Client engagements inside Gfi Spain are now a timeline: a rail with a node
  per client, each one a <details open>. Content is visible and printable with
  no JavaScript, keyboard-operable, and collapsible for a reader who wants to
  skim. Five overlapping top-level roles now read as three placements.

Bilingual
- The build emits docs/index.html (en) and docs/es/index.html (es) with
  hreflang alternates, x-default and a per-locale canonical. Relative asset
  paths are prefixed for the subdirectory. Language switch sits in the nav and
  marks the current locale with aria-current.

CV & contact
- The two near-empty sections are one panel. It finally uses cv-preview.png,
  which CI has been generating on every build and nothing referenced.
- Regenerated the PDF and its thumbnail, which were still the old red
  one-page version - the thumbnail sat next to the new palette showing the old
  one. Used the workflow's own pdftoppm invocation so CI does not churn it.

Also
- Nav reordered: Projects moves ahead of Skills and Education, CV joins it.
  Contact drops out, folded into the CV panel.
- scroll-margin-top now tracks the nav's measured height through a CSS
  variable. On mobile the nav is 118px and the margin was hardcoded to 104px,
  so tapping a nav link left the section title under the nav. Verified fixed:
  title at 199px, nav ends at 118px.
- Reveal on scroll, opt-in from JS so no-JS and reduced-motion readers get
  everything, and disabled in print.
- <noscript> fallback for the projects grid, which previously sat on "Loading
  from GitHub..." forever without JavaScript.
- Projects filter wired: skips forks, archived repos, the exclude list, and
  repos with no description.

Verified in both locales, both themes, desktop and mobile: no JS errors, no
horizontal overflow, all 16 reveal targets fire, both PDFs compile clean.

Known: cv-preview.png is 472KB for something displayed 220px wide. Flagged for
the performance pass.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ubYyHTXGyiRAqPMqeQgJE
The one-page layout was held together by negative \vspace throughout - after
the name, after every section heading, after every list - which is what made it
feel cramped. With the phase 1 content it spilled onto a second page anyway,
badly: page one packed, page two at a quarter.

Replaced the compression with real spacing:
- Margins go from 1.2/1.1/1.0/1.1cm to 1.6/1.3/1.6/1.5cm.
- Section headings use \titlespacing instead of negative offsets around the
  rule.
- List item separation, entry dividers and the gaps around nested engagements
  all get room to breathe.
- Footer carries the name and "page / of", via lastpage.

Tuned in two passes: the first relaxation ran to three pages with a single
orphaned skills line, so the added spacing was scaled back until it settled at
two. The break now falls on the Education heading - a section boundary, not
mid-entry - leaving experience whole on page one.

Compiles clean, zero overfull boxes. Regenerated the committed PDF and its
thumbnail with the workflow's own pdftoppm invocation.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ubYyHTXGyiRAqPMqeQgJE
Social
- og:image was the 240x240 personal mark with twitter:card=summary, so a link
  shared on LinkedIn - the actual channel for this page - rendered as a small
  square. Added a real 1200x630 card (name, role, tagline, mark, URL) in the
  site palette, with summary_large_image and og:image:alt.

SEO
- robots.txt and sitemap.xml, both generated from content/cv.json so their URLs
  cannot drift from the site. The sitemap carries xhtml:link alternates for
  both locales.

Weight
- cv-preview.png was 407KB at 1241px wide for something displayed at 220px.
  Regenerated at 60dpi: 497px, 117KB - a 71% cut with no visible difference at
  display size. The workflow's own pdftoppm call was changed to match, so CI
  does not silently restore the large version on the next build.
- The <img> now declares the thumbnail's real dimensions.
- hero-photo declared width/height 132 while CSS painted it at 96.

Mobile
- The sticky nav wrapped to two rows at 118px, 14% of a 390px-tall viewport,
  held for the entire scroll. Tightened padding, type and control sizes: 87px,
  10%. Desktop is unchanged at 62px.

Verified across both locales, both themes, desktop and mobile: no JS errors, no
horizontal overflow, sitemap parses.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ubYyHTXGyiRAqPMqeQgJE
PR preview
- The workflow called createComment on every push, so a PR with five pushes
  collected five identical comments. It now finds its own previous comment via
  a hidden marker and updates it, falling back to create when there is none.
  Also gained a concurrency group, which it lacked, and the comment names the
  head SHA it was built from.

Link checking
- Added tools/check-links.mjs and wired it into verify-build. Relative paths
  and #fragments are authoritative: an anchor with no matching id, or a path
  that does not resolve, fails the run. External URLs are only probed under
  --external and only ever warn, because CI runners get bot-blocked (LinkedIn
  answers 999) and a check that cries wolf gets ignored.
- Checks both locales: 62 links across the two pages. Verified against
  deliberately broken input - a dead anchor and a missing file both fail with
  exit 1.
- Implemented as a repo tool rather than a script embedded in YAML, so it runs
  locally with `npm run check:links` and is reviewable like the rest of tools/.

compile-latex
- Documented, in the workflow and the README, why the PAT exists (the ruleset
  requiring PRs on main cannot be bypassed by GITHUB_TOKEN) and that it expires
  silently, failing with an unhelpful push permission error. A GitHub App
  installation token would remove the expiry; that needs an App created and
  installed, so it is written up rather than applied.
- Documented why filtering the trigger on main.tex alone is sound: the file is
  generated and committed, and a content change that skipped regeneration is
  caught by verify-build.

README now covers the bilingual layout, the generated files and the branch flow.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ubYyHTXGyiRAqPMqeQgJE
npm run check spuriously failed on Windows (core.autocrlf=true, no
.gitattributes): tools/build.mjs writes raw LF, but checkout converted
the committed files to CRLF, so the raw-file comparison in --check
never matched even when content was identical. Every local npm run
build also showed the generated files as modified in git status for
the same reason.

`* text=auto eol=lf` normalises line endings on checkout regardless of
local git config, fixing both symptoms. Binary files are unaffected --
git's own text/binary detection still applies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes to the content/cv.json rewrite from the previous round, based on
Ruben's own review against what he actually does day to day.

Accuracy corrections:
- Santander UK bullet 1 overstated ownership of the credit risk models
  themselves; he integrates and implements them into existing systems,
  he does not own the models. Reworded in both locales.
- "core API" -> "internal APIs" (both locales).
- Removed the "advise technical architects" bullet -- not how he'd
  describe his own role.
- Reverted the Santander UK title from "Software Engineering
  Specialist" back to "Software Engineer".

Content trims:
- Removed the hero tagline and context line from the web page, and the
  PDF subtitle (profile.pdfHeadline) -- added no value. Removing the
  hero paragraphs required editing render-html.mjs, since the template
  rendered them unconditionally; removing the PDF subtitle required
  the same in render-tex.mjs.
- Removed the trailing "custom MCP agents..." clause from the About
  summary in both locales.

Bilingual and language fixes:
- Fixed three experience/engagement start dates (UST Global Spain,
  Telefonica, Securitas Direct) that were plain strings instead of
  locale objects, so English month abbreviations leaked into the
  Spanish page ("Jan" instead of "Ene").
- Fixed Spanish grammar: "alertado" -> "alertas" (not a real noun),
  "Mejoro" -> "Mejore" (wrong grammatical person), and two bullets
  that had drifted from first-person verb form into noun-phrase form,
  breaking parallelism with their English counterparts.
- Standardised on British spelling per Ruben's preference: "program"
  -> "programme" in the Gfi Spain education note.

Dead code:
- Removed docs/styles.css rules and content/cv.json fields
  (copy.viewCv, profile.headline) not referenced by either template.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

CV PDF Preview

CV PDF preview built successfully from commit 53a3c3a. Download it from the workflow run artifacts (expires per repo artifact retention settings):
https://github.com/britoruben/RubenBrito-CV/actions/runs/33566131000

Last updated by run #5.

@britoruben
britoruben merged commit 0727937 into main Sep 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants