Laurie Moulard Portfolio Design System
Overview
This design system captures the visual language of Laurie Moulard's freelance makeup artistry portfolio — a space defined by restraint, classical typographic elegance, and an unwavering commitment to letting photography dominate the viewing experience. The atmosphere is gallery-like: walls of warm neutral gray ({colors.canvas}), minimal interface chrome, and compositions that breathe with generous vertical rhythm.
Brand voltage lives almost entirely in the {colors.primary} blue — a deliberate, almost defiant choice of standard web-link blue (#0000ee) deployed sparingly as interactive signifiers. There are no gradients, no decorative color blocks, no saturated accents beyond this single hue. The palette trusts in the emotional resonance of the work itself rather than chromatic intervention.
The typography move that defines this system is its dual-serif architecture: Cormorant Garamond carries all display and heading roles with its elegant high contrast and modulated strokes, while Cardo — a quieter, more humanist serif — handles body copy with dignified readability. Neither face ever exceeds weight 600; bold is foreign here. Letter-spacing remains natural (normal) throughout; the typefaces' intrinsic proportions do the work.
Shape language is deliberately understated. Corners stay sharp or barely softened — {rounded.DEFAULT} at 4px is the maximum radius applied to images, and most containers have none at all. This angularity reads as intentional austerity, not oversight. The only rounded elements are the tiny {component.social-icon-circle} buttons in the navigation bar, which sit at 24px with fully circular treatment.
A single sub-system emerges: the overlay heading pattern, where {typography.heading-overlay} (Cormorant Garamond at 36px, weight 600, rendered in white) floats atop darkened imagery. This creates the system's one moment of dramatic tension — elegant type meeting photographic depth.
Key Characteristics
- Canvas is warm gray
{colors.canvas}(#efefef), not pure white — this grounds the gallery metaphor - Primary brand color is classic link-blue
{colors.primary}(#0000ee) — retro, confident, functional - Display type uses Cormorant Garamond exclusively at weight 600, never bolder
- Body type uses Cardo at weight 400 with 1.5 line-height for long-form comfort
- Maximum content width sits at
{spacing.container-max}(1260px); layout maxes at{spacing.layout-max}(1512px) - Section vertical padding breathes at
{spacing.hero-vertical}(108px) or{spacing.section-large}(180px) - Image corners receive only
{rounded.DEFAULT}(4px) softening — never more - Nav height is fixed at compact
{spacing.nav-height}(45px) - Shadows are nearly absent; only
{extensions.shadows.sm}exists, and barely - Motion duration defaults to 300ms with ease timing — unhurried, graceful
Colors
Brand & Accent
- Primary (
{colors.primary}— #0000ee): Classic web blue used for interactive links and CTAs. This is the sole brand-color voltage in an otherwise achromatic system. Deployed sparingly on hover states, navigation links, and actionable text. - Primary Hover (
{colors.primary-hover}— #0000cc): Darkened variant for hover/focus states on links and buttons. Provides clear interaction feedback without abandoning the blue family. - On Primary (
{colors.on-primary}— #ffffff): White text rendered atop{colors.primary}backgrounds. Ensures WCAG AA compliance against the deep blue.
Surface
- Canvas (
{colors.canvas}— #efefef): The dominant ground color — warm, medium-light gray that serves as the page background. Chosen over pure white to reduce glare and evoke gallery wall neutrality. - Surface (
{colors.surface}— #ffffff): Pure white used for elevated containers, cards, the hero section backing, and modal/drawer panels. Creates subtle lift against the canvas. - Surface Soft (
{colors.surface-soft}— #eeeeee): Slightly darker than canvas, used for secondary zones or subtle differentiation within sections. - Surface Strong (
{colors.surface-strong}— #e6e6e6): Medium gray for image placeholder backgrounds, disabled states, or borders that read as surfaces. - Surface Dark (
{colors.surface-dark}— #111111): Near-black used for dark overlay panels, inverted header contexts, or dramatic band treatments.
Text / Ink
- Ink (
{colors.ink}— #333333): Primary reading color — dark gray rather than pure black for reduced harshness on{colors.canvas}. Used for body text, navigation labels, and most UI copy. - Body (
{colors.body}— #333333): Alias for ink in this system; both resolve to identical value. Maintained for semantic clarity in component references. - Body Deep (
{colors.body-deep}— #111111): Near-black for maximum-weight headings, strong emphasis, or text-on-dark contexts where contrast demands absolute darkness. - Muted (
{colors.muted}— #666666): Secondary text color for captions, metadata, timestamps, and de-emphasized supporting copy. Provides clear hierarchy below{colors.ink}. - On Surface (
{colors.on-surface}— #333333): Semantic alias for text placed directly on{colors.surface}white backgrounds. Resolves to{colors.ink}. - On Dark (
{colors.on-dark}— #ffffff): White text for rendering on{colors.surface-dark}or any dark photographic overlay. Used in{component.overlay-heading}.
Hairlines & Borders
- Hairline (
{colors.hairline}— #dddddd): Subtle dividers, input outlines when unfocused, and thin rule lines. Nearly invisible against{colors.canvas}, which is intentional. - Border Strong (
{colors.border-strong}— #cccccc): Visible borders for form fields in focus state, card edges requiring definition, and structural separators needing slight prominence.
Typography
Font Family
The system employs a dual-serif pairing that signals classical training and editorial intent:
Cormorant Garamond serves as the display face — a modern interpretation of 17th-century French old-style type with high stroke contrast, elegant terminals, and refined proportions. It is loaded via custom @font-face declarations pointing to JournoPortfolio CDN-hosted WOFF2 files, with extensive unicode-range coverage across Latin, Greek, and Cyrillic scripts. Only weight 600 (semibold) is utilized; the design never calls upon lighter or heavier cuts. Cormorant Garamond is reserved for all h1–h4 equivalents, navigation branding, and the signature {component.overlay-heading} treatment that floats atop photography.
Cardo functions as the body face — designed specifically for "comfortable reading of long-form text on screens." It offers lower contrast than Cormorant, more open counters, and a slightly larger x-height that aids legibility at smaller sizes. Also served from the same JournoPortfolio CDN, Cardo appears at weight 400 throughout (though weight 700 exists in the loaded font file, it remains unused). Every paragraph, label, caption, and UI microcopy string renders in Cardo. The fallback stack is Cardo, sans-serif, ensuring graceful degradation if the webfont fails to load.
Hierarchy
| Token | Size | Weight | Line Height | Letter Spacing | Use |
|---|---|---|---|---|---|
| {typography.display-xl} | 48px | 600 | 1.15 | normal | Hero headlines, project titles (largest presence) |
| {typography.display-lg} | 36px | 600 | 1.1 | normal | Section headings, overlay text on images |
| {typography.title-md} | 28px | 600 | 1.2 | normal | Subsection titles, navigation brand name |
| {typography.body-lg} | 20px | 400 | 1.5 | normal | Lead paragraphs, drawer menu items |
| {typography.body-md} | 18px | 400 | 1.5 | normal | Standard body copy (primary reading size) |
| {typography.body-sm} | 16px | 400 | 1.5 | normal | Secondary body text, compact contexts |
| {typography.label-md} | 14px | 400 | 1.4 | normal | Labels, tags, metadata |
| {typography.button} | 16px | 400 | 1.4 | normal | Button text, nav links |
| {typography.caption} | 13px | 400 | 1.38 | normal | Fine print, timestamps, credits |
| {typography.heading-overlay} | 36px | 600 | 1.1 | normal | White text overlaid on dark images |
Principles
The typography system's hierarchy is carried almost entirely by size differential, not weight modulation. Cormorant Garamond at 48px commands attention through sheer presence; Cardo at 18px recedes into comfortable reading distance. The gap between display-xl (48px) and body-md (18px) is nearly 3x — a ratio that ensures unmistakable structural distinction without resorting to aggressive weights.
Weight discipline is paramount: nothing ever exceeds 600. This is a philosophical choice as much as an aesthetic one — the portfolio's voice is confident but quiet, assertive but never shouting. Even the largest headline maintains the same 600 weight as the modest section subheading; size alone does the work.
Line heights follow the "golden rule" of serif typesetting: 1.1–1.15 for display (tighter tracking prevents the eye from getting lost across wide measure), expanding to 1.5 for body copy (generous leading supports sustained reading). No letter-spacing adjustments are applied anywhere — the typefaces' native sidebearings are trusted implicitly.
Color contributes minimally to typographic hierarchy. Body text is {colors.ink} (#333333), headings might shift to {colors.body-deep} (#111111) if they sit on white, and overlay headings flip to {colors.on-dark} (#ffffff). Links adopt {colors.primary} blue, but this is semantic signaling rather than emphasis. The system does not use colored spans, gradient text, or mixed-color words.
Italic and uppercase are absent from the extracted stylesheet. The entire voice resides in the Roman (upright) case, with proper capitalization for titles. This uniformity reinforces the contemplative mood — no exclamation marks, no ALL CAPS urgency, just measured statement.
Note on Font Substitutes
Both Cormorant Garamond and Cardo are available as free Google Fonts, enabling straightforward substitution if the JournoPortfolio CDN sources become unavailable:
- Cormorant Garamond → Google Fonts:
family=Cormorant+Garamond:wght@600 - Cardo → Google Fonts:
family=Cardo:wght@400
CSS variable declarations for implementation:
--font-display: 'Cormorant Garamond', 'PT Serif', Georgia, serif;
--font-body: Cardo, 'Source Serif Pro', Georgia, serif;
If Google Fonts must be avoided entirely, PT Serif approximates Cormorant Garamond's elegance, and Source Serif Pro or Merriweather can substitute Cardo's readable warmth.
Layout
Spacing System
- Base unit: 1px (the system does not enforce an 8px grid; instead, it relies on contextually derived values).
- Tokens:
{spacing.xxs}(2px) for micro-gaps;{spacing.xs}(4px) for icon padding;{spacing.sm}(8px) for internal component gutters;{spacing.md}(12px) for compact element separation;{spacing.base}(18px) — the most frequently occurring value, serving as default paragraph margin and list item spacing;{spacing.lg}(25px) for card internal padding;{spacing.xl}(33px) for section-internal breathing room;{spacing.xxl}(43px) for major block separation;{spacing.section}(54px) for standard section vertical padding;{spacing.hero-vertical}(108px) for hero/feature section padding;{spacing.section-large}(180px) for dramatic full-screen section breaks. - Section padding (vertical):
{spacing.hero-vertical}(108px) used in the primary content wrapper (<section>elements showpadding: 108px 216px). Larger feature bands may employ{spacing.section-large}(180px). - Card internal padding:
{spacing.lg}(25px) for basic{component.card-basic}instances; image containers use zero internal padding. - Gutters: Horizontal padding of 216px observed in the main
<section>container (this is approximately 15% viewport width at desktop, creating substantial left/right margins).
Grid & Container
- Max content width: 1260px (
{spacing.container-max}) — the constrained width for readable text columns and image galleries. - Layout max width: 1512px (
{spacing.layout-max}) — the outermost boundary before the canvas shows through. - Density choice: Editorial/gallery density — this is not a marketplace. Images are presented at substantial size with ample surrounding whitespace. Text columns likely sit at 65–75ch for optimal reading measure.
- Grid behavior: The extracted DOM reveals a single-column layout with full-width image presentation. No multi-column grid was detectable in the above-fold markup, suggesting a linear scroll experience where each image commands full attention sequentially.
- Hero column split: Not applicable — the hero appears to be a single full-width image or centered composition rather than a two-column text/image arrangement.
Whitespace Philosophy
This system practices radical generosity with whitespace. The 108px–180px vertical section paddings alone signal that empty space is not absence but presence. Between the warm-gray canvas, the minimal navigation footprint (45px tall), and the full-bleed image treatment, the layout ensures the viewer's attention has nowhere to rest except the work itself. This is anti-density design — the opposite of e-commerce grids or dashboard dashboards.
Header Architecture
┌─────────────────────────────────────────────────────────────┐
│ [IG] [✉] Laurie Moulard [≡] │ ← 45px fixed height
│ 24px icons center 24px │ canvas bg, ink text
└─────────────────────────────────────────────────────────────┘
↑
nav-brand-name
(title-md, Cormorant 28px)
Navigation follows a center-right layout pattern: social utility icons flush-left (Instagram, Email as 24px circle icons), the artist name centered in the available space, and the hamburger menu icon flush-right. The header background matches {colors.canvas} transparently, creating a seamless blend with the page body. Total height locked at {spacing.nav-height} (45px) via CSS custom property --header-height.
Hero Section
┌─────────────────────────────────────────────────────────────┐
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ │ │
│ │ [FULL-BLEED PORTFOLIO IMAGE] │ │ ← surface bg
│ │ (4px corners, shadow sm) │ │ 108px v-pad
│ │ │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
← 216px horizontal margins on each side →
The hero (and each portfolio section) presents a single large-format image centered within a white ({colors.surface}) container. Vertical padding of 108px provides dramatic top/bottom margins. The image receives {rounded.DEFAULT} (4px) corner softening and potentially {extensions.shadows.sm} elevation. No text overlays the hero image in the default state — the photograph speaks for itself.
Sidebar / Drawer Menu
┌──────────────────────────┐
│ │
│ [Menu Items] │ ← slides in from right
│ - Work │ body-lg (20px Cardo)
│ - About │ 25px internal padding
│ - Contact │ surface bg
│ │
└──────────────────────────┘
When the {component.hamburger-icon} is activated, a {component.hamburger-menu-panel} (or {component.sidebar-drawer}) enters from the right edge with a right 1s transition. The panel uses {colors.surface} white background, {typography.body-lg} for menu items, and generous {spacing.lg} (25px) internal padding.
Elevation & Depth
| Level | Treatment | Use |
|---|---|---|
| 0 (Flat) | No shadow, no border | Base canvas, flat text, unadorned images |
| 1 (Hairline) | {colors.hairline} 1px rule | Subtle dividers between sections |
| 2 (Soft Shadow) | {extensions.shadows.sm} — 0 0 10px rgba(0,0,0,0.05) | Elevated image cards, floating containers |
| 3 (Overlay) | Semi-transparent {colors.surface-dark} dark layer | Image hover states, active overlay headings |
| 4 (Modal) | {colors.surface} panel + potential deeper shadow | Sidebar drawer, expanded menu |
Philosophy: This is a near-flat design system. Shadows exist as an afterthought — a single rgba(0,0,0,0.05) whisper at 10px spread, applied so subtly it may be invisible on many displays. Depth is achieved instead through color-layering: the step from {colors.canvas} (#efefef) to {colors.surface} (#ffffff) creates perceived elevation without any shadow whatsoever. When photography enters the frame, its intrinsic tonal depth (especially in the moody black-and-white and sepia-toned work shown) provides all the visual complexity the page needs. The system rejects material-design-style card stacks, drop-shadow menus, and floating action buttons in favor of honest, planar presentation.
Decorative Depth
No gradients, noise textures, or patterned backdrops were detected. The sole decorative depth source is the photography itself — the featured portrait exhibits rich tonal gradation from deep blacks through warm mid-tones to luminous highlights, with selective gold/gilt accent coloring on jewelry. This organic image depth supplants any synthetic atmospheric effects the designer might have considered.
Shapes
Border Radius Scale
| Token | Value | Use |
|---|---|---|
| {rounded.sm} | 2px | Micro-elements, badge corners |
| {rounded.DEFAULT} | 4px | Primary image corners — the most frequently applied radius |
| {rounded.md} | 6px | Card containers, basic panel rounding |
| {rounded.lg} | 8px | Input fields, button containers |
| {rounded.xl} | 12px | Large feature cards (rarely used) |
| {rounded.pill} | 9999px | Social icon circles, avatar placeholders |
Photography Geometry
Images in this portfolio are treated as full-bleed or near-full-bleed rectangular presentations. The featured portrait occupies the vast majority of the viewport width, constrained only by the 216px horizontal margins of its parent section container. Aspect ratio appears to be portrait-oriented (roughly 4:5 or 3:4), honoring the verticality of the subject matter (fashion/beauty portraiture). Corner rounding is consistently {rounded.DEFAULT} (4px) — a barely-perceptible softening that prevents the harshness of perfect right angles while maintaining photographic integrity. No circular crops, no rounded-rectangle avatars, no organic mask shapes were observed. Images are allowed their rectangular honesty.
Components
No hover states documented. Hover behavior is unreliable to extract from a single screenshot. State variants live as separate entries in the
components:frontmatter (e.g.{component.social-icon-circle-active},{component.link-text-hover}); add hover styling at implementation time per your stack's conventions.
Navigation Elements
{component.top-nav} — The fixed-height header strip spanning the full viewport width. Background {colors.canvas} (transparent-blend), text {colors.ink}, height locked to {spacing.nav-height} (45px). Contains three zones: left-aligned social utilities, centered brand name, right-aligned menu trigger. No visible border-bottom or shadow — it merges imperceptibly with the page body. Typography falls back to {typography.button} (16px Cardo) for any text elements within.
{component.social-icon-circle} — Small interactive icons (Instagram, Email) positioned at the left of the navigation bar. Transparent background with {colors.ink} icon fill/stroke, sized at 24px square. Rendered as SVG symbols (detected in the <defs> block). These are the only circular UI elements outside of {rounded.pill} theoretical applications.
{component.social-icon-circle-active} — Hover/active state for social icons. Inverts to {colors.ink} background with {colors.surface} (white) icon fill — a classic "filled circle" feedback pattern. Size unchanged at 24px.
{component.hamburger-icon} — The menu trigger at the far right of the nav bar. Renders as three-line SVG (standard hamburger convention), 24px size, {colors.ink} color on transparent background. Activates the sidebar drawer.
{component.hamburger-menu-panel} / {component.sidebar-drawer} — Slide-in navigation panel triggered by the hamburger icon. Background {colors.surface} (white), text {colors.ink}, typography {typography.body-lg} (20px Cardo) for menu items. Padding {spacing.lg} (25px) internally. Animates in from the right edge with a 1s transition (right 1s detected in transitions). Width appears to be approximately 50% viewport or a fixed ~500px based on the nav container's 519px computed width.
{component.nav-brand-name} — "Laurie Moulard" text centered in the header. Uses {typography.title-md} (28px Cormorant Garamond weight 600) in {colors.ink}. This is the typographic anchor of the navigation — the only display-weight text in the header zone.
Content Sections
{component.hero-section} — The primary content wrapper for portfolio imagery. Background {colors.surface} (pure white), providing a clean "mat" for photographs against the warmer {colors.canvas} page background. Padding {spacing.hero-vertical} (108px) vertically with 216px horizontally. This component establishes the gallery-frame metaphor.
{component.portfolio-image-container} — The direct parent of each portfolio photograph. Transparent background, {rounded.DEFAULT} (4px) passed through to child, zero internal padding. May carry {extensions.shadows.sm} for subtle lift. Constrained to {spacing.container-max} (1260px) maximum width.
{component.portfolio-image} — The <img> element itself displaying the artwork. Background {colors.surface-strong} (#e6e6e6) shows through during load/fallback. Rounded to {rounded.DEFAULT} (4px). The featured image demonstrates the expected output: a dramatically lit beauty portrait with near-black subject, warm-toned backdrop, and metallic gold detail — all rendered with photographic authenticity, no filters or heavy processing apparent in the CSS.
{component.overlay-heading} — White text overlaid onto darkened image areas (observed in the typeScale data as h4 with white color). Uses {typography.heading-overlay} (36px Cormorant Garamond weight 600) in {colors.on-dark} (#ffffff). Padding {spacing.md} (12px) {spacing.lg} (25px) positions the text away from image edges. This component likely activates on image hover or scroll-trigger, creating the system's one moment of layered depth.
{component.body-text} — Standard paragraph content. Transparent background, {colors.body} text (#333333), {typography.body-md} (18px Cardo weight 400, 1.5 line-height). Used for project descriptions, about text, and any long-form copy. Margins of {spacing.base} (18px) between paragraphs inferred from spacing frequency data.
{component.link-text} — Inline or standalone hyperlinks. Transparent background, {colors.primary} (#0000ee) text, inheriting surrounding typography (typically {typography.body-md}). The choice of standard link blue is distinctive — either a deliberate retro reference or unstyled-default acceptance. Underline behavior should follow browser conventions unless specified otherwise.
{component.link-text-hover} — Link interaction state. Shifts to {colors.primary-hover} (#0000cc), darkening the blue for clear feedback.
Structural Components
{component.footer-section} — Page footer area. Background {colors.canvas} matching the body, text {colors.muted} (#666666) for de-emphasis, {typography.caption} (13px Cardo) for small legal/social text. Padding {spacing.section} (54px) vertically. Likely contains copyright, credits, or secondary contact info.
{component.card-basic} — Generic content card for text blocks, metadata panels, or supplementary information. Background {colors.surface} (white), {colors.body} text, {typography.body-md} type, {rounded.md} (6px) corners, {spacing.base} (18px) internal padding. A versatile container for non-image content needs.
{component.loading-spinner} — Animation element referenced in keyframes (swiper-preloader-spin, button-loading-spinner). Transparent background, {colors.primary} color for the spinning indicator, 32px size. Indicates async content loading for image carousels or form submissions.
Do's and Don'ts
Do
- Do set your page background to
{colors.canvas}(#efefef) rather than pure white — this warm gray is essential to the gallery-wall atmosphere. - Do reserve Cormorant Garamond exclusively for display/heading roles and Cardo for everything else — never cross-pollinate the typeface assignments.
- Do keep all font weights at or below 600 — this system has no concept of "bold" (700+) and introducing it will break the voice.
- Do apply
{rounded.DEFAULT}(4px) to all photograph containers — anything sharper feels digital-harsh; anything rounder feels playful-wrong. - Do use
{spacing.hero-vertical}(108px) minimum for vertical section padding — compressed sections will feel cramped and un-editorial. - Do render links in
{colors.primary}(#0000ee) even though it looks "default" — this is an intentional part of the aesthetic vocabulary. - Do let portfolio images command 80%+ of viewport width — this is a photography-first layout, not a text-with-illustration layout.
- Do maintain the 45px nav height (
{spacing.nav-height}) — taller headers break the minimal-chrome promise. - Do use
{typography.heading-overlay}(white 36px Cormorant) when text must sit atop images — this is the approved overlay pattern. - Do treat whitespace as a first-class design material — the 216px horizontal margins and 108px+ vertical pads are features, not waste.
- Do keep shadows at
{extensions.shadows.sm}intensity or remove them entirely — this system distrusts obvious elevation.
Don't
- Don't introduce gradients, patterns, or textured backgrounds — the canvas is solid
{colors.canvas}and that's final. - Don't use sans-serif fonts for any visible text — the dual-serif pairing (Cormorant + Cardo) is non-negotiable.
- Don't apply letter-spacing adjustments to any text — the typefaces carry their own rhythm; forcing tracking disrupts it.
- Don't exceed
{rounded.lg}(8px) for any corner radius —{rounded.pill}is reserved only for the 24px social icon circles. - Don't place
{colors.primary}blue on anything other than interactive elements (links, buttons, active states) — it's not a decorative accent. - Don't add drop shadows stronger than
0 0 10px rgba(0,0,0,0.05)— if you can see the shadow easily, it's too strong. - Don't create multi-column image grids for the portfolio — each work deserves sequential full-width presentation.
- Don't use uppercase text or italic for emphasis — the system communicates through size differential, not case or slant variation.
- Don't make the navigation sticky with a white background override — the header blends into
{colors.canvas}intentionally. - Don't add border-radius to the navigation bar or footer — these structural bands remain sharp-edged.
Motion & Animation
Transition Tokens
--transition-base: all 0.3s ease;
--transition-background: background 0.5s ease;
--transition-background-alt: background 0.4s ease;
--transition-color: color 0.5s ease;
--transform-slide-right: right 1s ease;
--overlay-reveal: top 0.8s ease, opacity 0.8s ease;
The motion system operates at subtle-to-moderate energy levels. Base interactions (hover, focus) resolve in 300ms — fast enough to feel responsive, slow enough to avoid jarring. Background color transitions extend to 400–500ms for smoother surface changes (drawer opening, theme shifts). The longest transition (1s) is reserved for the sidebar drawer's entrance (right 1s), creating a theatrical, unhurried reveal. Overlay text appearance combines top-position translation with opacity fade over 800ms for elegant emergence.
Keyframe Animations
button-loading-spinner— Rotates a spinner element 360° continuously, used for form submission or async action feedback.v-clipDelay— Likely a clip-path or visibility delay animation for staggered reveal effects (video or image sequence transitions).swiper-preloader-spin— Carousel/loader spinner specific to a Swiper.js instance, rotating during slide preload operations.
Interaction Patterns
- Sidebar drawer reveal: Clicking
{component.hamburger-icon}slides{component.sidebar-drawer}in from the right edge over 1 second with eased timing. Content within may fade in via{overlay-reveal}(top + opacity, 800ms). - Image overlay on hover/scroll:
{component.overlay-heading}text fades upward into view atop portfolio images, using the 800ms top/opacity transition. This creates the system's primary dynamic moment. - Link color shift:
{component.link-text}transitions to{component.link-text-hover}(blue darken) over 500ms on hover/focus. - Background surface transitions: Any panel switching between
{colors.canvas}and{colors.surface}(or dark variants) animates over 400–500ms. - Loading states: Spinner animations (
button-loading-spinner,swiper-preloader-spin) rotate indefinitely until content resolves. - Social icon inversion:
{component.social-icon-circle}fills from transparent to ink-colored circle on hover, with icon color inverting to white — likely a quick 200–300ms background/color transition. - No parallax detected: The system does not appear to use scroll-linked position transforms on images or text layers.
- No marquee/ticker: No horizontal scrolling text or infinite-loop animation patterns were found.
Recommendation: For any new motion added to this system (scroll-triggered reveals, micro-interactions), target 300ms duration with ease-out timing as the default. Faster than 200ms feels nervous; slower than 500ms feels sluggish. The brand voice prefers grace over snappiness.
Imagery Style
- Photography-first: The portfolio's imagery dominates every fold — the above-fold screenshot is 85%+ photograph by area. Text and UI are peripheral.
- Beauty/portraiture specialty: The featured image depicts a Black woman in dramatic side-profile lighting, characteristic of high-end beauty and fashion photography. Makeup artistry is the evident craft being showcased.
- Tonal treatment: Images favor moody, low-key lighting with near-black subject areas, warm neutral-to-sepia backgrounds, and selective metallic/gold accent coloring (visible on jewelry). This suggests a preference for cinematic, emotionally resonant portraiture over bright commercial beauty shots.
- Aspect ratio: Portrait orientation (vertical) appears primary, honoring the figure and face. Landscape images may exist deeper in the portfolio but aren't visible in the sampled view.
- Color grading: Desaturated-to-monochrome base with warm color cast (cream, amber, ochre tones in backgrounds). Selective saturation preserved on metallic elements (gold earring). No heavy filters or artificial vintage effects apparent — the treatment feels like controlled studio lighting and post-production rather than app-style presets.
- Composition: Intimate framing — tight crop on face and shoulders, shallow depth of field implied by tonal softness in background areas. Subject gazes away from camera (profile/three-quarter view), creating introspective, editorial mood.
- Image-as-surface: Photography functions as both content AND textural depth. The tonal variation in the image provides visual interest that replaces any need for background patterns or gradient washes.
- No illustration/3D/abstract: All visual content appears to be photographic. No vector illustrations, 3D renders, or abstract graphic elements were detected in the screenshot or DOM.
- Presentation treatment: Images display at large scale (constrained to 1260px max but filling most of that) with minimal 4px corner rounding and optional faint shadow. They are not masked into shapes, not bordered heavily, not paired with excessive captions. The image is the whole event.
Icon System
- Library: Custom inline SVGs. No external icon library (Lucide, Heroicons, Material, Font Awesome, Bootstrap Icons) was detected in the framework analysis. The
<defs>block contains<symbol>definitions for icons rendered locally. - Specific icons observed:
- Instagram camera outline (social link, left-nav)
- Envelope/mail outline (email contact, left-nav)
- Hamburger menu (three horizontal lines, right-nav)
- Treatment: Stroke-based icons (outline style, not filled). Color set to
currentColor(inherits{colors.ink}by default, switches to{colors.surface}in active/inverted states). Size: 24px for all navigation icons (matching{component.social-icon-circle}and{component.hamburger-icon}dimensions). Stroke width appears to be 1.5–2px for visibility at small size. - Alignment: Icons are center-aligned within their 24px clickable areas. Social icons sit inside circular hit areas (visually suggested, implemented as
{rounded.pill}containers or SVG circle elements). The hamburger icon is free-standing without a circle container. - Additional icons likely present: Based on theJournoPortfolio platform conventions, the sidebar drawer probably includes icons for Home, Gallery, About, Contact, and possibly external-link or download icons — though these weren't visible in the static screenshot.
Recommended Frontend Stack
- Framework: Next.js (App Router) or Astro — portfolio sites benefit from static generation
with optional client-side interactivity for the drawer/carousel
- Styling: CSS Modules or Vanilla CSS with CSS Custom Properties
(Tailwind NOT detected; system predates or avoids utility frameworks)
- Fonts: Self-host WOFF2 files from JournoPortfolio CDN, or Google Fonts fallback:
@import url('https://fonts.googleapis.com/css2?family=Cardo:wght@400&family=Cormorant+Garamond:wght@600&display=swap');
- Animation: Vanilla CSS transitions (extracted timings) + optional IntersectionObserver
for scroll-triggered overlay reveals; GSAP/Framer Motion if complex sequences needed
- Icons: Custom SVG <symbol> sprite (existing pattern) or Lucide icons styled to match
stroke-width and sizing
- Component lib: None (custom components) — or Radix UI primitives for accessible
drawer/dialog patterns if building React
Responsive Behavior
Breakpoints
| Name | Width | Key Changes | |---|---|---| | Mobile | < 640px | Single-column stack; nav collapses to icons-only or hamburger-permanent; 216px horizontal padding reduces to ~16–24px; hero image becomes near-full-bleed edge-to-edge; font sizes may reduce 1 step | | Tablet | 640px – 1024px | Navigation maintains hamburger pattern; image container constrains to ~90% viewport width; section padding reduces to 54–72px; typography scales proportionally | | Desktop | 1024px – 1440px | Full layout realized: 216px horizontal margins, 108px section padding, 1260px content max, 45px nav height | | Wide | > 1440px | Layout maxes at 1512px total width; content centers with increasing canvas exposure on edges; no further scaling of typography or spacing |
Touch Targets
{component.social-icon-circle}: 24×24px — below WCAG AAA 44×44 minimum. Should expand invisible hit-area to 44×44 for mobile touch accessibility (padding or min-width/min-height).{component.hamburger-icon}: 24×24px — below 44×44 minimum. Same hit-area expansion recommended.{component.link-text}: Line-height dependent; 18px body text with 1.5 line-height yields ~27px tap height — adequate for inline links but consider padding expansion for standalone link blocks.- Image containers: Full-width targets — inherently touch-friendly; ensure no pointer-events obstruction from overlapping elements.
Collapsing Strategy
- Navigation: At mobile widths, the header likely retains its three-zone layout (social left, name center, hamburger right) but compresses horizontally. The name may abbreviate or hide on smallest screens. The sidebar drawer becomes the primary (not secondary) navigation method — hamburger is always visible, always functional.
- Hero/images: Portfolio images transition from constrained 1260px container (with 216px margins) to edge-to-edge or near-edge-to-edge presentation on mobile. The 4px corner radius persists. Vertical padding reduces from 108px to ~48–64px to prevent excessive scrolling before content appears.
- Typography scaling: Display text (
{typography.display-xl}at 48px) may reduce to 32–36px on mobile. Body text ({typography.body-md}` at 18px) likely holds steady or drops to 16px. The modular scale compresses rather than rearranges. - Drawer panel: On mobile, the sidebar drawer may expand to 80–100% viewport width (rather than ~50% on desktop), becoming a full-screen overlay. Close mechanism must remain accessible (X button or outside-click dismiss).
- Spacing compression: All padding and margin values scale down proportionally.
{spacing.hero-vertical}(108px) → ~48–64px;{spacing.xl}(33px) → ~16–24px;{spacing.base}(18px) → ~12–16px.
Image Behavior
- Hero/featured image: On desktop, constrained to 1260px max-width with 216px side margins. On mobile, expands to ~95–100% viewport width with ~16px edge margin. Aspect ratio maintained (no cropping). 4px border radius retained.
- Lazy loading: Given the photography-heavy nature and detected
swiper-preloader-spinkeyframe, images likely lazy-load with a spinner placeholder. Mobile connections benefit significantly from intersection-loader patterns. - Art direction: If retina/2x variants exist, they should serve to high-DPI screens; the original image resolution appears suitable for Retina display at desktop sizes.
- Overlay text on mobile:
{component.overlay-heading}(white text on image) must remain readable at small sizes — consider increasing text-shadow or adding a scrim gradient beneath the overlay on mobile where image details may be less discernible.
Iteration Guide
-
Build a single-page portfolio landing page that closely mimics this design system. Use Next.js with App Router (for static portfolio optimization) or Astro (for zero-JS default with island architecture). Style with CSS Modules or plain CSS with custom properties — avoid Tailwind since the original doesn't use it.
-
Pull every color, font, radius, and spacing value from the YAML frontmatter via the
{section.token}paths. Define CSS custom properties (variables) for every token so your implementation stays synchronized with this spec. Never hard-code a value that has a token equivalent. -
Set up font loading: import Cormorant Garamond (weight 600 only) and Cardo (weight 400 only) from Google Fonts as primary sources, falling back to the JournoPortfolio CDN WOFF2 URLs listed in the CSS
@font-facedeclarations if you need exact match. Define--font-displayand--font-bodyvariables. -
Implement the navigation header first: 45px height,
{colors.canvas}background, three-zone layout (social icons left, brand center, hamburger right). Make the hamburger functional — clicking it should slide in a drawer from the right with the 1seasetransition. The drawer uses{colors.surface}white background and{typography.body-lg}(20px Cardo) for menu items. -
Add the hero/portfolio section: white (
{colors.surface}) background band with 108px vertical padding and 216px horizontal margins. Place a large image container inside with 4px border radius and the subtle shadow ({extensions.shadows.sm}). Ensure the image is responsive — max-width 1260px, scaling down gracefully on mobile. -
When adding new components, decide which category they belong to: Navigation (header, drawer, social icons), Content (sections, images, overlays, text blocks), or Structural (cards, footer, loaders). Each category shares surface/background conventions.
-
State variants live as separate top-level entries in the frontmatter (e.g.,
{component.link-text-hover},{component.social-icon-circle-active}), never as nested objects. Map these to CSS:hover,:focus,[aria-expanded="true"]pseudo-classes during implementation. -
Apply motion conservatively: use the extracted transition tokens (
--transition-base: all 0.3s easefor basics, longer durations for drawers/overlays). Avoid adding animations that don't serve the gallery-contemplation mood — no bouncing, no elastic easing, no particle effects. -
Run
npx @google/design.md lint DESIGN.mdafter edits —broken-ref,contrast-ratio, andorphaned-tokenswarnings flag issues automatically. Pay special attention to{colors.primary}(#0000ee) contrast checks against both{colors.on-primary}(white passes) and{colors.canvas}(may fail — ensure links aren't placed directly on canvas without underlining). -
When in doubt about emphasis: bigger size before heavier weight. The system's hierarchy comes from 48px→36px→28px→18px size steps, never from 400→700 weight jumps. Signature moments are the
{component.overlay-heading}white text on dark images and the full-bleed photography — prioritize these over any new component invention.
Font Setup
/* CSS Variables for implementation */
:root {
--font-display: 'Cormorant Garamond', 'PT Serif', Georgia, serif;
--font-body: Cardo, 'Source Serif Pro', Georgia, serif;
--color-canvas: #efefef;
--color-surface: #ffffff;
--color-ink: #333333;
--color-primary: #0000ee;
--color-on-primary: #ffffff;
--color-on-dark: #ffffff;
--color-muted: #666666;
--radius-default: 4px;
--shadow-sm: 0 0 10px rgba(0, 0, 0, 0.05);
--nav-height: 45px;
--section-padding-y: 108px;
}
Google Fonts import (fallback if CDN fonts fail):
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cardo:wght@400&family=Cormorant+Garamond:wght@600&display=swap" rel="stylesheet">
Known Gaps
- Hover state styling per the no-hover policy: While
{component.social-icon-circle-active},{component.link-text-hover}, and{component.button-primary-active}(if applicable) are documented structurally, exact hover visual treatment (background color transitions, scale transforms, underline appearance) is inferred rather than extracted. Implement per platform conventions. - Loading/skeleton states beyond spinner: The system has a loading spinner keyframe but no skeleton placeholder patterns, shimmer effects, or blur-up techniques for image loading. These should be designed to match the minimal aesthetic.
- Form validation states: No input fields, error messages, success toasts, or validation styling were extractable from the screenshot. If a contact form exists elsewhere on the site, its error/success/focus states are undocumented.
- Authenticated/dashboard flows: This spec covers the public portfolio landing experience only. Any admin interface, CMS, or logged-in user views are completely unknown.
- Mobile navigation specifics: How the header behaves at < 640px (whether brand name hides, whether hamburger becomes always-visible, whether social icons persist) is extrapolated from best practices, not observed.
- Carousel/swiper implementation details: The
swiper-preloader-spinkeyframe suggests a Swiper.js-powered image carousel somewhere on the site, but carousel pagination, arrow buttons, swipe-threshold, and autoplay behavior are not captured in the static screenshot. - Footer contents: The footer section is assumed to contain copyright/legal text but its exact content, link structure, and layout are not visible.
- Additional pages: This spec documents the home/portfolio page only. Separate pages (About, Contact, Individual Project detail) may have divergent layouts, particularly if they introduce forms, text-heavy layouts, or video embeds.
- Dark mode: The system is explicitly light-mode only (
extensions.mode: "light"). No dark mode palette, toggle mechanism, or inverted surface colors exist. Adding dark mode would require designing a complementary palette from scratch, respecting the gallery-mood translation to dark walls. - Accessibility annotations: ARIA labels, focus-visible rings, skip-navigation links, screen-reader-only text, and keyboard navigation patterns are not present in the extracted CSS/HTML. These must be engineered into the implementation independently following WCAG 2.1 AA guidelines.