Blau — Design System

All design tokens, components, and patterns used in Saúl Ossorio's portfolio. Every component is rendered with the actual CSS from style.css — no mocks. Click motion demos to replay them.

Foundation

Core design tokens defined in :root in style.css. Never hardcode a hex value — always reference a token. Changing a token updates every component that uses it.

Color tokens

Blue--blue#0071C5
Blue hover--blue-hover#005da0
Text--text#1a1a1a
Mid--mid#555555
Light--light#999999
Border--border#e0e0e0
BG subtle--bg-subtle#f8f8f7
Tag BG--tag-bg#f0f0ef
Background--bg#ffffff

#EF5958 — used only for the dot rage easter egg. Not a persistent token.

Font families

Raleway

Aa Bb Cc — weights 400 600 700 800

Display / Headings

Hero name, section titles, contact heading, project titles.

--font-title

Open Sans

Aa Bb Cc — weights 300 400 600 700 + italic

Body / UI text

All body copy, labels, nav, buttons, metadata.

--font

Layout tokens

Sidebar width--sw · 230px
Mobile header--header-h · 56px
Section H-padding6rem desktop · 4rem @1100px · 1.5rem @860px
Section V-padding8rem/7rem · hero+contact: 10rem/10rem
Row label column155px fixed · row-item + skill-row
Project image column380px · 300px @1100px · full-width @860px

Type Scale

Two typefaces, contextual sizing. Each row is rendered at its real size — the visual weight difference between levels is what matters. Sizes shown are desktop target values; fluid headings use clamp(min, viewport, max) in CSS so they scale smoothly across screen sizes.

Heading 1

.hero-name 4.2rem · Raleway 700 · ls −0.03em · page title only ~8× base

Heading 2 — Display

.contact-heading 2.8rem · Raleway 600 · contact section ~3.8× base

Heading 2 — Section

.sec-title 2.2rem · Raleway 600 · lh 1.15 · all main sections ~2.8× base

Heading 3

.proj-title 1.6rem · Raleway 600 · lh 1.2 · project titles ~1.7× base

Subheading

.acc-label 0.95rem · Open Sans 600 1.02× base

The quick brown fox jumps over the lazy dog. Body text is designed for reading comfort at line-height 1.9.

.hero-text p · paragraph 0.93rem · Open Sans 400 · lh 1.9 · base 1× base

List item primary label

.row-title 0.88rem · Open Sans 600 0.95× base

Secondary metadata · supporting detail

.row-sub 0.82rem · Open Sans 400 · --mid 0.88× base

Descriptive tertiary text — small, quiet, supporting.

.row-desc 0.79rem · Open Sans 400 · --light 0.85× base

Section label

.eyebrow 0.7rem · Open Sans 400 · UPPERCASE · ls .18em · --light color · no background 0.75× base
Label tag
.label-tag / .hero-role-tag / .proj-type 0.58-0.68rem · Open Sans 600 · UPPERCASE · ls .08-.12em · --blue color · rgba(blue,.08) background · border-radius 3-4px 0.62-0.73× base

Atomic Design

The portfolio follows atomic design thinking. Design tokens feed atoms, atoms compose into molecules, molecules form organisms, organisms assemble into the page template, and the template filled with real content is the final page.

1 Atoms Smallest indivisible elements. Drive all other layers.
Link Text
<p> paragraph <h1> – <h3> heading <span> label text
2 Molecules Atoms combined into functional UI groups.
CTA pill Default pill
Jan 2025

Row item

3 Organisms Complex, self-contained page sections built from molecules.
Hero section Project block Accordion section Contact section Sidebar Mobile header + nav

Each organism has its own semantic landmark, ARIA roles, scroll/fade behavior, and GTM data attributes. Built inside renderPage() in index.html.

4 Template Page skeleton — layout without real content.

.layout — fixed sidebar (var(--sw) = 230px) + fluid main. Main centers children at max-width 1200px with horizontal padding.

5 Page Template + real content = the final rendered portfolio.
index.html

Content from data.js is injected at runtime by renderPage(). Language switching rerenders all organisms instantly — same template, new content, no page reload.

Brand

The blue dot is the sole brand mark. No ring, circle, or frame — just the dot. It appears in two size contexts, each with different behavior.

Hero dot

54×54px · entrance animation on load · click = pulse · 10 clicks = rage shake + color snap back

.brand-dot--hero
Saúl Ossorio

Nav dot

1em × 1em · inline-left of name, vertically centered · mobile header

.brand-dot--nav

Social

Two variants: icon + label for hero and contact sections, icon-only for sidebar and footer. All three states shown for each.

Default
Live — hover me

Social tag — icon + label

border-radius 100px · --tag-bg fill · 0.78rem · opacity .75 default → 1 on hover · bg darkens + border darkens + text to --text on hover

.social-tag
Default
Hover
Live — hover me

Social icon — icon only

15×15px SVG · --light default → --blue hover · transition color .2s · sidebar only

.sidebar-socials a

Interactive Elements

Pills/CTAs and the accordion. All states are live — click to interact.

Pills / CTAs

border-radius 100px · Default: --mid + border → blue on hover · Blue: circle reveal animation (left to right) with clip-path on hover

.pill.pill-blue
Jan 2025 – Dec 2025

Product Designer & UX Consultant

Fexpocruz · Contract · Bolivia

Led UX and digital product design for Expocruz 2025.

Mar 2024 – Mar 2026

Master's Degree, Design

Universidade Federal do Paraná (UFPR)

Dissertation: Usability Heuristics for Immersive Virtual Learning Environments.

Mar 2021 – Nov 2022

Bachelor's Degree, Science and Technology

Universidade Federal de Santa Catarina (UFSC)

Accordion

border-top on section · arrow rotates 90° when open · max-height 0→9999px · 0.45s ease · aria-expanded via toggleAcc()

.accordion-section.accordion-trigger.open.accordion-body.open

Lists

Two patterns sharing the same two-column grid: 155px label column + 1fr content.

Jul 2023 – Dec 2023

Web Analytics Consultant

Fexpocruz · Contract · Remote

Drove 70% increase in traffic and 350% increase in online ticket sales.

Spanish

Native or bilingual proficiency

Celpe-Bras

Row item

grid-template-columns: 155px 1fr · gap: 2rem · border-bottom: 1px · experience, education, courses, languages

.row-list.row-item

Design & UX

Product Design · UX Research · UI Design · Wireframing · Prototyping · Figma · Webflow · Accessibility

Data & Analytics

Google Analytics 4 · GTM · BigQuery · SQL · Python · Data Visualization · Microsoft Clarity

Skill row

Same grid (155px 1fr) · category in --light · items in --mid · line-height 1.75

.skills-rows.skill-row

Layout

Structural elements shared across all sections.

— section above —

— section below —

Section divider

1px · --border · max-width 1200px · separates sections

.divider
Scroll

Scroll hint

position:absolute in hero · scrollDot keyframe 1.6s infinite

.scroll-hint

Triggered by IntersectionObserver on scroll into viewport

Contact line

--blue→transparent gradient · width 0→100% over 6s · .in class added by IntersectionObserver

.contact-line.in

Skoob

UX/UI DesignWeb Platform
Challenge

Redesign Brazil's largest reading platform.

1:1 image

Project block

grid-template-columns: 1fr 380px · gap: 4rem · image aspect-ratio 1:1 · mobile: image moves to top as 16:9

.project-block.proj-img

Motion

Click each card to replay the animation. All animations respect prefers-reduced-motion: reduce — durations collapse to 0.01ms for users who opt out.

Section content
click to replay

fadeUp / .fade.in

opacity 0→1 · translateY 24px→0 · 0.65s ease · IntersectionObserver threshold 0.08

click to replay

dotPop

scale 0→1.08→1 · 0.9s cubic-bezier(.22,.61,.36,1) · hero dot entrance on load

Scroll

scrollDot

translateY 0→14px + opacity 1→0 · 1.6s ease infinite · running live above

click to replay

growLine

width 0→100% + opacity · 2.5s cubic-bezier(.05,0,.15,1) · contact section divider

click to replay

Dot bounce (on click)

scale 1→0.82→1.06→1 · 380ms · Web Animations API (GPU composited)

Content revealed with max-height transition.

Accordion open/close

max-height 0→9999px · 0.45s ease · arrow rotates 90° · click trigger above

Saúl Ossorio click to replay

Mobile name reveal

opacity 0→1 + translateY 8px→0 · 0.4s ease · triggers when hero H1 leaves viewport

Burger menu open/close

lines 1+3 rotate ±45° · line 2 width→0 · 0.35s cubic-bezier · click to toggle

Accessibility

WCAG AA compliance patterns implemented throughout the portfolio. Contrast ratios are tested against white (#ffffff).

Skip link

Skip to main content

First focusable element in the DOM. Visually hidden until focused — then appears at the top of the page. Targets #main-content with tabindex="-1".

Focus ring

Keyboard focus indicator

:focus-visible — 2px solid --blue, 3px offset, border-radius 4px. Hidden for mouse via :focus:not(:focus-visible) so it only shows for keyboard users.

ARIA

ARIA attributes

aria-expanded on accordion triggers and burger. aria-modal + role="dialog" on image lightbox. aria-hidden="true" on decorative dots, dividers, and all SVGs.

Keyboard

Keyboard navigation

Image modal: navigate carousel, Esc closes. Nav links use tabindex="0" + onkeydown Enter handler since they're role="button" anchors.

Reduced motion

prefers-reduced-motion

Global rule sets all durations to 0.01ms and disables smooth scroll. Exception: scrollDot re-enabled on mobile via !important — a simple translate/opacity dot is not a vestibular risk.

Contrast

Color contrast ratios

--text #1a1a1a → 16.1:1 AAA · --mid #555 → 7.46:1 AAA · --blue #0071C5 → 4.56:1 AA · --light #999 → 2.85:1 — decorative/secondary labels only, never body copy.

Images

Alt text

Profile: "Portrait of Saúl Ossorio". Project thumbnails use the project title. Carousel slides use the alt defined per-image in data.js → images[].alt.

SVG icons

Inline SVG accessibility

All SVGs carry aria-hidden="true" and focusable="false". The accessible name is provided by the parent <a> via aria-label="Visit LinkedIn (opens in new tab)".

Lang attr

Language declaration

lang on <html> updates on every language switch via document.documentElement.lang = lang. Screen readers use this to select the correct pronunciation engine.

Semantic HTML

Landmark roles

Uses <aside>, <main>, <header>, <footer>, <nav>, <article>, <section> with aria-labelledby pointing to each section heading.