Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Portfolio · Lourdes Pedaci

Bilingual static site (EN / ES) for Learning Experience Design, UX and Data. Every project has its own URL in both languages.

https://lpedaci.github.io/portfolio/                     home EN
https://lpedaci.github.io/portfolio/es/                  home ES
https://lpedaci.github.io/portfolio/work/intersafe/      case EN
https://lpedaci.github.io/portfolio/es/work/intersafe/   case ES

Hand-written HTML, CSS and JavaScript, generated by a small Node script. No framework, no runtime dependencies, no build tooling beyond Node itself. Content lives in one source and renders to 24 pages, so a string is written once.


Build

Node 18 or newer, for the build only. The published site does not run Node.

npm run build     # writes the HTML, sitemap.xml and robots.txt
npm run serve     # builds, then serves the folder
npm run og        # redraws the social share cards (needs Chrome installed)

index.html, es/, work/, es/work/, 404.html, sitemap.xml and robots.txt are generated output and are overwritten on every build.

Social share cards

Every page has its own 1200 x 630 card for LinkedIn, X, WhatsApp and Slack: og-card.jpg for the home page and og-<slug>.jpg for each case study, plus an -es twin of each. Twenty-two files, all in assets/img/.

They are not hand-exported. src/og/card.mjs is a small page written in the same tokens as assets/css/site.css, and npm run og paints it with headless Chrome and encodes it to JPEG. Two kinds share one skeleton, so a link to the home page and a link to a project read as the same set:

left column right
identity monogram, name and role, the years stat portrait on its sand mat
project category and year, title and subtitle, signature cover in a 5:3 frame

Project titles run from 7 to 63 characters, so the display size steps down as the title grows. Covers run from 2.17:1 to square, so the card reuses the 5:3 crop the work grid already applies to them.

Change the palette, the portrait in site.heroPortrait, or any project title, run npm run og, and the cards follow the site.

npm run og is a separate step from npm run build on purpose: it needs a browser on the machine, and the cards only change when the brand or the copy does. All the cards of one language are painted as a single tall strip and sliced apart, so the whole set takes two browser launches per language and a few seconds.

LinkedIn caches a link's preview for roughly a week. After deploying, paste the URL into the Post Inspector to force a re-scrape.


Structure

src/
  data/site.mjs        global copy: hero, practice, process, about, contact, links
  data/projects.mjs    the 11 case studies, in EN and ES
  lib/layout.mjs       head, header, footer, relative path resolution
  lib/pages.mjs        home, project page, 404
  lib/blocks.mjs       case-study block renderers
  lib/icons.mjs        icon set and the LP monogram
  og/card.mjs          the 1200 x 630 share card, as a page
  og/render.mjs        paints the card with headless Chrome  (npm run og)
  build.mjs            writes everything to disk

assets/
  css/site.css         the design system (tokens in the first 55 lines)
  js/site.js           filters, mobile menu, deferred embeds, lightbox, reveal
  img/                 project images and portraits

Content model

Every visible string is a { en: '…', es: '…' } pair. A missing language renders empty.

Projects are objects in src/data/projects.mjs. Array order sets the home grid order, and the card number (01, 02, …) is derived from it.

{
  slug: 'my-project',             // sets the URL: /work/my-project/
  year: '2026',
  cats: ['lxd', 'ux'],            // category ids from site.mjs; the first is the card
                                  // label, all of them feed the filters
  cover: 'my-cover.jpg',          // file inside assets/img/
  coverAlt: { en: '…', es: '…' },
  title:    { en: '…', es: '…' },
  subtitle: { en: '…', es: '…' },
  summary:  { en: '…', es: '…' },  // card copy and meta description
  tags: ['Figma', 'SCORM'],
  facts: {
    role:     { en: '…', es: '…' },
    timeline: { en: '…', es: '…' },
    context:  { en: '…', es: '…' },
    stack:   ['Figma', 'iSpring'],
    methods: ['ADDIE', 'Kirkpatrick']
  },
  links: [{ label: { en: 'Live site', es: 'Sitio en vivo' }, url: 'https://…' }],
  blocks: [ /* see below */ ],
  outcome: { h: { en: 'Outcomes', es: 'Resultados' }, body: { en: '…', es: '…' } }
}

Case-study blocks

t renders
prose heading plus paragraphs
list items with a bold term and an explanation
roadmap numbered phases (E01, F01, M01…), two columns on desktop
callout a principle or standout idea, on a sage ground
stats three or more large figures
cases case cards with their own links
embed video, audio, document or interactive piece
live a real site embedded inside a browser frame
gallery images with a lightbox (wide: true for a single column)
{ t: 'live',
  h: { en: 'Unit 2, live', es: 'Unidad 2, en vivo' },
  src: 'https://lpedaci.github.io/so_unidad-02/',
  url: 'https://lpedaci.github.io/so_unidad-02/',
  label: { en: 'Unit 2 site', es: 'Sitio de la Unidad 2' } },

{ t: 'embed', kind: 'video',          // video | audio | doc | interactive
  h: { en: 'Watch', es: 'Ver' },
  src: 'https://player.vimeo.com/video/1025598739',   // the embed URL
  host: 'Vimeo',
  label: { en: 'Play', es: 'Reproducir' },
  open: 'https://vimeo.com/1025598739' }              // the normal URL, for a new tab

No embed loads until it is clicked. Until then the page holds a light frame, so a case study with ten videos still paints fast.


Design system

Tokens live in the first 55 lines of assets/css/site.css and drive the whole site.

Paper #F7F4EC ground · #FDFBF6 cards · #ECE5D7 bands
Ink #17150F
Clay #965E44, the editorial voice: eyebrows, the stressed word, selection
Sage #596959, data and behaviour: figures, hovers, selected states
Sand #E2BB99
Headlines Archivo, lowercase, tight tracking
Long-form text Newsreader (serif)
Labels and figures JetBrains Mono

Implementation notes:

  • Grid cards are aspect-ratio: 5 / 3 with object-fit: cover, so covers are cropped.
  • The hero panel has two modes, switched by heroPortrait in src/data/site.mjs: a full-bleed portrait on a clay mat (--mat sets the mat width, object-position the crop), or a cropped circular portrait with the orbit ring and watermark.
  • Circles biting into panel edges use a ring in the background colour (box-shadow: 0 0 0 9px var(--bone)), not real cutouts.
  • The paper grain is an SVG texture on the body background, not an overlay layer.
  • Line measures (max-width in ch) sit on the text element, not its container, since ch resolves against the element's own font-size.
  • Z-index scale: skip link 10000, masthead 200, mobile sheet 150. The sheet stays under the masthead so the button that closes it remains reachable.

Accessibility and performance

  • All content is visible without JavaScript. Entrance animations turn on only once the browser confirms JS runs (a js class on <html>). Anything already inside the viewport at first paint is resolved synchronously and without a fade, so the reveal never delays the h1. A CSS keyframe releases anything left after 4 s, so a stylesheet that arrives without its script cannot leave the page blank.
  • prefers-reduced-motion is honoured.
  • Keyboard navigation, visible focus, skip to content, aria-current on the language switch.
  • The mobile sheet is a real modal: role="dialog", aria-modal, a Tab cycle that includes the burger, and focus restored on every close path.
  • Filtering the work grid announces its new result count through a role="status" message that is visible as well as spoken.
  • Controls are outlined with --line-ctrl, measured at 3:1 against both grounds; the lighter --line is reserved for decorative rules, where 1.4.11 does not apply.
  • Every interactive target is at least 44 px on its constrained axis.
  • loading="lazy" on images, deferred embeds, no external libraries.
  • Print stylesheet: case studies print legibly.

Deployment

GitHub Pages serves the repository from the root, so a push publishes. .nojekyll stops Jekyll from processing the output, and 404.html uses absolute URLs so it renders correctly from any broken path.

Canonicals, hreflang, Open Graph and the sitemap are all derived from site.domain in src/data/site.mjs. Internal links are relative, so the site works under /portfolio/ and at the root of a domain alike.