Bhavya Portfolio Design System
Overview
Bhavya's portfolio presents a strikingly minimalist creative showcase built around a signature sky-blue canvas ({colors.surface-soft} — #90c5e8) that serves as the dominant visual field. The design rejects traditional dark-mode portfolio conventions in favor of an airy, almost ethereal atmosphere where near-black ink ({colors.ink} — #00090f) type creates maximum legibility against the cool-toned background. The overall mood is confidently understated — conversational rather than corporate, with the hero text "Alright let's do this one last..." establishing an intimate, process-oriented voice that suggests the designer is inviting viewers into their creative workflow.
Brand voltage lives primarily in the canvas color itself — that distinctive sky blue acts as the signature surface across {component.hero-canvas} and {component.project-card} components. Rather than applying brand color as accents or CTAs, the system inverts expectations by making the background the primary brand expression. This is reinforced by the typography choice: Zalando Sans Expanded carries all weight with its wide-set, confident letterforms at regular weight (never bold), creating hierarchy through scale alone. The custom HitMePunk typeface ({typography.display-punk}) appears reserved for signature moments — likely section openers or identity lockups — adding a raw, counter-cultural texture to an otherwise refined system.
The shape language leans heavily toward the extreme: {rounded.pill} (9999px) dominates interactive elements like {component.button-primary} and {component.button-outline}, while cards use {rounded.lg} for softer containment. The detected border radius of ~33.5 million pixels on certain elements confirms this pill-first philosophy. Spacing follows a tight rhythm anchored at {spacing.md} (14px), creating compact, efficient layouts that let the expansive canvas breathe around content clusters.
Sub-systems appear limited on this single-view capture, but the presence of marquee animations, grain overlays, and cursor glow effects suggests a rich micro-interaction layer that transforms static surfaces into living ones. The portfolio likely unfolds as a single-scroll experience with distinct band sections — hero, work grid, about, contact — each sharing the sky-blue DNA while varying in density.
Key Characteristics:
- Signature sky-blue canvas (
{colors.surface-soft}) as primary brand carrier, not an accent - Near-black ink (
{colors.ink}) for all text, ensuring 18+:1 contrast ratios - Single typeface family (Zalando Sans Expanded) handling 90% of content at weight 400 only
- Pill-radius (
{rounded.pill}) on all interactive elements;{rounded.lg}on containers - 14px base spacing unit (
{spacing.md}) creating tight, intentional rhythm - Motion-forward with marquee scroll, grain-shift texture, and cursor-follow glow
- HitMePunk as display-accent font for identity moments (
{typography.display-punk}) - Flat elevation model — no box-shadows detected; depth comes from color layering and motion
Colors
Brand & Accent
- Primary (
{colors.primary}— #000a0f): Near-black brand anchor used for primary button fills, heavy UI elements, and maximum-contrast text applications. - Primary Active (
{colors.primary-active}— #001420): Slightly lighter black-blue for pressed/active states on{component.button-primary-active}. - Accent (
{colors.accent}— #90c5e8): The signature sky-blue that doubles as the hero canvas color and primary link/interactive highlight.
Surface
- Canvas (
{colors.canvas}— #00090f): Deep near-black base (detected in DOM though visually overridden in hero); likely used for dark-section alternates or footer inversions. - Surface Soft (
{colors.surface-soft}— #90c5e8): The dominant hero and card background — the visual heart of the portfolio. - Surface Elevated (
{colors.surface-elevated}— #7bb8d9): Darker sky-blue for{component.project-card}surfaces, creating subtle lift from the base canvas. - Surface Strong (
{colors.surface-strong}— #5fa4ca): Deepest sky-blue variant for hover states on cards ({component.project-card-hover}) and active borders.
Text
- Ink (
{colors.ink}— #00090f): Primary text color — near-black for maximum readability against light surfaces. - Body (
{colors.body}— #000a0f): Functionally identical to ink; used interchangeably for paragraph text per extracted CSS frequencies. - Muted (
{colors.muted}— #1a3a4d): Secondary text for captions, labels, and supporting copy — dark enough to read, quiet enough to recede. - Muted Soft (
{colors.muted-soft}— #2d5066): Tertiary text for timestamps, metadata, and truly secondary information. - On Primary (
{colors.on-primary}— #ffffff): White text for contrast against{colors.primary}buttons and dark surfaces. - On Surface (
{colors.on-surface}— #00090f): Ink-colored text guaranteed readable on any light surface variant.
Hairlines & Borders
- Hairline (
{colors.hairline}— #5fa4ca): Subtle divider lines matching{colors.surface-strong}— used for input underlines, card separators, and section dividers. - Border Strong (
{colors.border-strong}— #3d7a9a): More visible borders for focus rings, active states, and emphasized container edges.
Semantic
- Link (
{colors.link}— #90c5e8): Interactive link color matching the accent/canvas — creates cohesion between navigation and background. - Link Active (
{colors.link-active}— #b5d9ef): Lighter sky-blue for visited/hovered links. - Error (
{colors.error}— #c13515): Standard error red for form validation failures. - Success (
{colors.success}— #006400): Standard success green for confirmation states.
Signature / Brand-specific
The entire palette orbits around the single signature color {colors.surface-soft} (sky-blue #90c5e8). This is not an accent in this system — it is the foundation. All other colors exist in service of making this canvas readable, interactive, and hierarchically clear.
Typography
Font Family
The system is built almost entirely on Zalando Sans Expanded, a wide-set geometric sans-serif that conveys contemporary confidence without aggression. The fallback stack (system-ui, sans-serif) ensures graceful degradation. Every text element — from {typography.display-xl} headlines down to {typography.caption} metadata — uses this face at weight 400, with the sole exception of {typography.button} which bumps to weight 500 for slight tactile emphasis.
The proprietary HitMePunk typeface (served locally as /hitmepunk.ttf) enters exclusively through {typography.display-punk}, reserved for high-impact identity moments — likely the designer's name in the hero, section numerals, or a signature lockup. Its raw, irregular character forms provide deliberate friction against Zalando's polish, signaling creative risk-taking.
Hierarchy
| Token | Size | Weight | Line Height | Letter Spacing | Use |
|---|---|---|---|---|---|
| {typography.display-xl} | 56px | 400 | 1.1 | -0.02em | Hero headline, major section openers |
| {typography.display-punk} | 48px | 400 | 1.1 | 0 | Signature identity text (HitMePunk) |
| {typography.display-lg} | 40px | 400 | 1.15 | -0.01em | Sub-hero headlines, project titles |
| {typography.title-md} | 24px | 400 | 1.3 | 0 | Card titles, section headings |
| {typography.body-md} | 16px | 400 | 1.5 | 0 | Paragraph text, descriptions |
| {typography.body-sm} | 14px | 400 | 1.43 | 0 | Supporting text, nav items |
| {typography.caption} | 13px | 400 | 1.38 | 0 | Metadata, timestamps, footers |
| {typography.button} | 14px | 500 | 1.25 | 0.02em | Button labels, CTAs |
| {typography.label-md} | 12px | 500 | 1.33 | 0.08em | Section labels, category tags |
Principles
The typography system's defining move is its refusal of bold. At no point does weight exceed 500, and that occurs only on 14px button text. Hierarchy is carried entirely through size (56px → 16px across nine steps), line-height compression (headlines tighten to 1.1, body opens to 1.5), and subtle negative letter-spacing on the largest displays (-0.02em at 56px). This creates a "whisper-loud" dynamic where the biggest statements feel expansive rather than aggressive.
The expanded width of Zalando Sans Expanded is critical — each character occupies more horizontal space than a standard sans, giving the text an editorial, almost architectural quality. Combined with the sky-blue canvas, this produces a feeling of spaciousness that matches the portfolio's airy aesthetic. The {typography.label-md} role introduces the only uppercase treatment (implied by 0.08em letter-spacing convention), likely for section tags like "SELECTED WORK" or "ABOUT".
HitMePunk's role as {typography.display-punk} is the system's typographic wildcard. Used sparingly — perhaps once per screen — it disrupts the geometric calm with irregular, punk-rock energy. This duality (polished expanded sans + raw display face) mirrors the designer's positioning as both professional and creatively unconventional.
Note on Font Substitutes
Zalando Sans Expanded is a proprietary typeface not available on Google Fonts. For implementation without the licensed font, substitute Inter (variable font) or Plus Jakarta Sans with font-stretch: expanded applied. Define the CSS variable --font-sans: 'Inter', system-ui, sans-serif and update all {typography.*} entries accordingly. HitMePunk has no direct Google Fonts equivalent; consider Rubik Dirt or Gochi Hand for a similarly expressive, irregular character if the local .ttf file is unavailable.
Layout
Spacing System
- Base unit: 14px (
{spacing.md}). This non-standard base (vs. the typical 4px/8px) creates the system's distinctively tight rhythm. - Tokens:
{spacing.xxs}(2px — micro-adjustments),{spacing.xs}(4px — inline gaps),{spacing.sm}(8px — element spacing),{spacing.md}(14px — base padding/gap),{spacing.lg}(24px — card internal),{spacing.xl}(48px — section breaks),{spacing.section}(80px — vertical band padding). - Section padding (vertical):
{spacing.section}(80px) — used on{component.hero-canvas}and likely all major band sections. - Card internal padding:
{spacing.lg}(24px) — standard for{component.project-card}. - Gutters: Inferred at
{spacing.lg}(24px) between grid columns in portfolio sections.
Grid & Container
The single-screen capture limits definitive grid analysis, but the centered hero text suggests a max-content container of approximately 900–1100px for text-heavy sections, potentially widening to 1400px for project grid areas. The layout philosophy appears editorial-single-column for narrative content (hero, about) transitioning to multi-column for scannable content (work grid). No sticky rails or complex dashboard layouts are evident in the extracted DOM structure.
Hero content is center-aligned both horizontally and vertically within the viewport, suggesting a flexbox or grid place-items: center approach on the {component.hero-canvas} container.
Whitespace Philosophy
Whitespace is generous in the vertical dimension (80px section padding) but horizontally constrained around text blocks, creating a "column of breath" effect where the eye can rest left and right of the content column while the canvas color extends infinitely. This is not marketplace-dense nor purely editorial-airy — it's portfolio-confident: enough space to signal curation, tight enough to maintain momentum through a single-scroll experience.
Header Architecture
[Logo: BHAVYA] [Nav Links...] [Contact CTA]
← max-width: 1200px, height: 64px, padding: 0 {spacing.lg} →
─────────────────────────────────────────────────────────────────────────
Note: Navigation structure inferred from standard portfolio patterns; the captured viewport shows hero-only content.
Hero Section
┌─────────────────────────────────────────────────────────────┐
│ │
│ │
│ Alright let's do this one last... ← marquee scroll │
│ │
│ [View Work] [Contact] │
│ │
└─────────────────────────────────────────────────────────────┘
↑ {component.hero-canvas}, bg: {colors.surface-}
The hero features center-aligned {typography.display-xl} text with a detected marquee animation (text scrolls horizontally). The blurred tail visible in the screenshot ("...") suggests either a CSS text-shadow blur or a mask-gradient fading the text edge as it exits the marquee viewport. Two CTA buttons — likely {component.button-primary} and {component.button-outline} — sit below the headline.
Project Grid Section (Inferred)
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Project Card │ │ Project Card │ │ Project Card │
│ {surface-elevated}│ │ {surface-elevated}│ │ {surface-elevated}│
│ rounded: {lg} │ │ rounded: {lg} │ │ rounded: {lg} │
└──────────────────┘ └──────────────────┘ └──────────────────┘
↓ gap: {spacing.md} (14px)
Cards use {component.project-card} with {colors.surface-elevated} background, lifting subtly from the {colors.surface-soft} section background.
Elevation & Depth
| Level | Treatment | Use |
|---|---|---|
| 0 (Flat) | No shadow, {colors.surface-soft} background | Base canvas, hero band |
| 1 (Raised) | Color shift to {colors.surface-elevated} | {component.project-card} default state |
| 2 (Hover) | Color shift to {colors.surface-strong} | {component.project-card-hover}, button hovers |
| 3 (Overlay) | Fixed-position, pointer-events-none | {component.cursor-glow}, {component.grain-overlay} |
| 4 (Focus) | {colors.border-strong} outline | {component.text-input-focus} ring |
The elevation philosophy is color-block, not shadow-based. No box-shadow values were detected in the extracted CSS. Depth is communicated through three mechanisms: (1) luminance shifts in the blue spectrum (softer blues recede, darker blues advance), (2) fixed-position overlay layers for grain texture and cursor effects, and (3) motion (marquee scrolling, pulse animations) creating perceived depth through temporal layering.
This flat-color approach aligns with contemporary brutalist-lite and Swiss-design-revival trends where material honesty overrides skeuomorphic illusion. The {component.grain-overlay} (likely using the detected grain-shift keyframe) adds organic noise that prevents the flat colors from feeling digitally sterile.
Decorative Depth
Beyond the color-block elevation, two significant decorative depth layers exist:
-
Grain Texture Overlay: A fixed-position full-viewport overlay running the
grain-shiftkeyframe animation, adding subtle film-grain noise. This prevents banding on the sky-blue gradient (if one exists) and adds tactile quality to the otherwise flat surfaces. -
Cursor Glow: Two fixed-position
rounded-fullelements (detected in DOM asz-[9999]withpointer-events-none) follow cursor position via JavaScript, creating a soft spotlight or color-shift halo that reveals interaction-responsive depth. These map to{component.cursor-glow}.
Shapes
Border Radius Scale
| Token | Value | Use |
|---|---|---|
| {rounded.sm} | 4px | Subtle inner corners, badges |
| {rounded.DEFAULT} | 8px | Input fields, small containers |
| {rounded.md} | 12px | Medium cards, modals |
| {rounded.lg} | 16px | {component.project-card}, large content containers |
| {rounded.xl} | 24px | Hero image masks, featured panels |
| {rounded.pill} | 9999px | {component.button-primary}, {component.button-outline}, all CTAs |
| {rounded.full} | 9999px | Avatars, {component.cursor-glow} circles |
The detected radius value of 3.35544e+07px (approximately 33.5 million pixels) confirms the system's preference for geometric perfection on interactive elements — this is {rounded.pill} in practice, rendering perfect circles or stadium shapes depending on aspect ratio.
Photography Geometry
No photography was visible in the captured viewport. However, based on the portfolio context and {rounded.xl} (24px) token existence, project thumbnails likely use 16px corner rounding with 4:3 or 16:9 aspect ratios. Full-bleed hero imagery (if present in other sections) would likely use zero radius to extend edge-to-edge against the sky-blue canvas.
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.button-primary-active},{component.text-input-focus},{component.project-card-hover}); add hover styling at implementation time per your stack's conventions.
Buttons
{component.button-primary} — The primary call-to-action button featuring {colors.primary} (near-black) background with {colors.on-primary} (white) text. Uses {typography.button} at 14px/500 weight with 0.02em letter-spacing for slight expansion. Fully pill-shaped via {rounded.pill}. Padding is 12px 28px providing generous horizontal breathing room. Transitions use the detected all property, suggesting background, color, and transform all animate on interaction. Active state delegates to {component.button-primary-active} with deepened {colors.primary-active} background.
{component.button-primary-active} — Pressed/active state for the primary button. Background shifts to {colors.primary-active} (#001420) — a perceptible but subtle darkening that provides tactile feedback without jarring contrast changes. Maintains all other properties from {component.button-primary}.
{component.button-outline} — Secondary CTA with transparent background and {colors.ink} text, bordered (inferred) with {colors.hairline} or {colors.border-strong}. Same pill radius and dimensions as {component.button-primary} for visual parity. Creates a "ghost" button effect that feels lighter in hierarchy. Active state inverts to {component.button-outline-active} with solid {colors.ink} background and {colors.surface-soft} text — a satisfying fill-in animation.
{component.button-outline-active} — Pressed state for outline button. Inverts the color relationship: solid ink background with sky-blue text. This "fill on press" pattern is common in premium portfolios and provides clear interaction feedback.
Cards & Containers
{component.project-card} — The primary content container for portfolio work items. Background uses {colors.surface-elevated} (#7bb8d9), creating approximately 30% darkness lift from the base {colors.surface-soft} canvas. Text renders in {colors.ink} at {typography.body-md} (16px/400). Rounded at {rounded.lg} (16px) for soft, modern corners. Internal padding of {spacing.lg} (24px) gives content room to breathe. Hover state transitions to {component.project-card-hover}.
{component.project-card-hover} — Hover state for project cards. Background deepens to {colors.surface-strong} (#5fa4ca), advancing the card visually toward the viewer. This color-only transition (no shadow, no scale detected) maintains the system's flat-elevation philosophy while providing clear interactive feedback.
{component.hero-canvas} — The dominant full-viewport hero container. Background is {colors.surface-soft} (signature sky-blue), establishing the portfolio's primary visual identity. Text uses {typography.display-xl} (56px/400) in {colors.ink} for maximum impact. Vertical padding of {spacing.section} (80px) centers content generously. Contains the {component.marquee-text} child component and CTA button pair.
{component.footer} — Page footer spanning full width with {colors.surface-soft} background (maintaining canvas continuity). Text in {colors.muted} at {typography.caption} (13px) for quiet, supportive presence. Generous {spacing.xl} (48px) vertical padding creates clear separation from preceding content. Likely contains copyright, social links (mapped to {component.footer-link}), and optional contact email.
Inputs & Forms
{component.text-input} — Form input field with transparent background (letting the parent surface show through). Text in {colors.ink} at {typography.body-md}. Rounded at {rounded.DEFAULT} (8px) with 10px 14px padding. Border is inferred as 1px {colors.hairline} based on the elevation table. Focus state transitions to {component.text-input-focus}.
{component.text-input-focus} — Focused input state. Maintains transparent background and text properties. Focus indication comes from {colors.border-strong} outline (2-3px offset) rather than background change, preserving the flat-aesthetic while meeting accessibility requirements for focus visibility.
Navigation
{component.nav-link} — Navigation menu item with transparent background and {colors.ink} text at {typography.body-sm} (14px). No rounded corners (inline text element). Likely spaced at {spacing.lg} (24px) intervals in the header. Active/current page state maps to {component.nav-link-active}.
{component.nav-link-active} — Current-page nav indicator. Text color shifts to {colors.muted}, providing subtle de-emphasis that signals "you are here" without aggressive highlighting. May combine with an underline or bottom-border in {colors.accent}.
Signature Components
{component.marquee-text} — The animated headline container in the hero. Transparent background with {colors.ink} text at {typography.display-xl} scale. Uses the detected marquee keyframe animation for continuous horizontal scroll. The blurred text tail visible in the screenshot suggests a mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent) or similar fade effect at the edges. This is the portfolio's signature motion moment — conversational text in perpetual movement suggests ongoing creative process.
{component.hero-text-line} — Individual line within the marquee or static hero text stack. Inherits {typography.display-xl} properties. Transparent background allows layering effects (grain overlay, cursor glow) to pass through.
{component.grain-overlay} — Fixed-position full-viewport overlay (detected in DOM as high z-index, pointer-events-none). Runs the grain-shift keyframe animation, likely implemented as an SVG noise filter or repeated background-image with CSS animation transforming background-position. Adds organic texture preventing digital flatness. Both backgroundColor and textColor are transparent as this is a pure-decorative layer.
{component.cursor-glow} — One of two fixed-position circular elements (both rounded-full, z-[9999], pointer-events-none) that follow cursor movement via JavaScript. The dual-element structure suggests a "glow + core" effect where one circle is larger/more transparent (outer halo) and one is smaller/more opaque (inner highlight). Both reference {rounded.full}. Colors likely interpolate from {colors.accent} to transparent. This creates the portfolio's most magical interaction moment — the canvas responds to user presence.
{component.section-label} — Small categorical label for section headers (e.g., "SELECTED WORK", "ABOUT"). Uses {colors.muted} text at {typography.label-md} (12px/500) with generous 0.08em letter-spacing implying uppercase transformation. Transparent background, inline display. Precedes {typography.title-md} or {typography.display-lg} section headings.
{component.punk-heading} — Identity/heading component using the custom HitMePunk typeface ({typography.display-punk}). 48px size matches the system's second-largest scale. Irregular, expressive character forms provide stark contrast to Zalando Sans Expanded's geometric precision. Reserved for the designer's name, site title, or manifesto-style statements.
Do's and Don'ts
Do
- Do use
{colors.surface-soft}as the primary canvas background for at least the hero and footer sections — this sky-blue is the portfolio's visual signature. - Do keep all Zalando Sans Expanded text at
font-weight: 400except{typography.button}which may use 500 — the system achieves hierarchy through size, not weight. - Do apply
{rounded.pill}(9999px) to every button and CTA — the pill shape is non-negotiable for interactive elements in this system. - Do implement the
marqueekeyframe animation for the hero headline — this motion is central to the portfolio's "process-in-progress" narrative voice. - Do include the
{component.grain-overlay}as a fixed-position,pointer-events-nonelayer runninggrain-shift— it prevents the flat colors from feeling sterile. - Do use color-only hover states on
{component.project-card}(shifting to{colors.surface-strong}) rather than adding shadows or transforms — maintain the flat-elevation philosophy. - Do reserve HitMePunk (
{typography.display-punk}) for 1-2 identity moments per screen maximum — its impact dilutes with overuse. - Do ensure the
{component.cursor-glow}dual-circle effect follows cursor position with slight easing (not 1:1 tracking) for organic feel. - Do set section vertical padding to
{spacing.section}(80px) minimum — the system breathes through vertical generosity. - Do use
{spacing.md}(14px) as your base grid/gap unit when in doubt — it's the system's rhythmic heartbeat.
Don't
- Don't apply
box-shadowto any component — the extracted CSS shows zero shadows; depth comes from color layering and motion only. - Don't bold headlines or body text beyond weight 500 —
{typography.display-xl}at weight 400 is intentional, not an oversight. - Don't replace
{colors.surface-soft}with white or off-white — the sky-blue is mandatory brand expression, not a decorative option. - Don't use border-radius values outside the defined scale — especially avoid intermediate values like 6px, 10px, or 20px that aren't in
{rounded.*}. - Don't let
{component.marquee-text}text clip at container edges — implement fade masks (gradient or SVG) for smooth entry/exit. - Don't place
{component.cursor-glow}elements belowz-index: 9999— they must float above all content including modals. - Don't use
{colors.primary}(near-black) as a background for large areas — it exists for buttons and text, not canvases. - Don't add gradient overlays to the hero unless they're extremely subtle (opacity < 0.1) — the canvas should read as flat sky-blue.
- Don't implement hover states without corresponding
-activefrontmatter entries — every interactive component needs both Default and Active/Pressed variants documented separately. - Don't substitute Inter or Roboto for Zalando Sans Expanded without applying
font-stretch: expanded— the width is as important as the glyph shapes.
Motion & Animation
Transition Tokens
--transition-base: all 300ms ease;
--transition-color: color 300ms ease, background-color 300ms ease, border-color 300ms ease;
--transform-bounce: transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
The extracted CSS shows a single transition value: all, indicating a unified transition approach where every animatable property (color, background, transform, opacity, box-shadow) transitions over the same duration. The extensions.motion.duration of 300ms and ease timing function define the system's tempo — moderate pace, gentle deceleration, never jarring.
Keyframe Animations
marquee— Continuous horizontal text scroll for the hero headline, creating the "process in motion" effect. Likely implementstranslateX(0)totranslateX(-100%)or similar, with duplicated content for seamless looping.grain-shift— SVG noise filter or background-position animation creating subtle film-grain texture movement. Prevents static-band perception on flat color surfaces. Probably operates at 0.3-0.5s duration with infinite loop.spin— Detected in keyframes list; likely reserved for loading indicators or decorative rotating elements (possibly the cursor glow's outer ring).pulse— Detected in keyframes list; probably used for attention-drawing micro-animations on CTAs or status indicators.
Interaction Patterns
- Cursor Glow Tracking: Two fixed-position circular elements follow mouse position with slight lag/easing (likely 50-100ms behind actual cursor position), creating a responsive "flashlight" or "halo" effect over the sky-blue canvas. Outer circle (~200-400px diameter, low opacity) provides ambient glow; inner circle (~40-80px, higher opacity) creates focal highlight.
- Marquee Scroll: Hero text moves continuously left-to-right (or right-to-left) at a slow, readable pace (~60-120px per second). Text fades at container edges via gradient mask, preventing hard clipping.
- Grain Overlay: Full-viewport noise texture shifts subtly (1-2px translation) on a continuous loop, adding organic imperfection to the digital surface. Opacity likely 3-7% — felt, not seen.
- Card Color Shift: On hover,
{component.project-card}backgrounds transition from{colors.surface-elevated}to{colors.surface-strong}over 300ms, creating "press toward viewer" effect without Z-axis movement. - Button Fill-In:
{component.button-outline}inverts from transparent-background/ink-text to ink-background/surface-soft-text on active/pressed state — a satisfying "fill" animation. - Focus Ring Expansion:
{component.text-input-focus}shows border-color transition to{colors.border-strong}with possible 2px outline-offset expansion for accessibility-compliant focus indication.
If implementing additional motion beyond these patterns, maintain the moderate level defined in extensions.motion.level: nothing should feel "still" (the marquee and grain prevent this), but nothing should feel "expressive" or playful in a way that undermines the portfolio's professional creative positioning.
Imagery Style
- Primary style: The captured viewport is typographic-only, but the portfolio context implies project thumbnail imagery (UI mockups, brand applications, product shots).
- Treatment: Likely displayed within
{component.project-card}containers with{rounded.lg}(16px) corner rounding, sitting on{colors.surface-elevated}background. - Color relationship: Images probably maintain their native color palettes rather than being tinted toward the sky-blue system — the canvas color frames the work without contaminating it.
- Aspect ratios: Project thumbnails likely use 16:9 (landscape) or 4:3 (presentation) ratios consistent with design-portfolio conventions.
- Full-bleed potential: Hero sections beyond the captured view may feature full-viewport imagery with
{colors.surface-soft}overlay at 40-60% opacity for text readability. - Illustration style: If custom illustrations exist (given the "creative designer" positioning), they likely employ flat vector or textured styles complementary to the sky-blue palette — possibly incorporating
{colors.accent}as a unifying accent within the artwork. - Photography: Any photography would likely be desaturated slightly or treated with a cool color grade to harmonize with the dominant blue temperature of the system.
- Imagery dominance: Text and layout lead; imagery supports. This is not a visual-first portfolio — it's a process-first portfolio where the marquee text and motion design carry as much weight as project visuals.
Icon System
- Library: Custom (no icon library detected in
framework.iconLibs;tailwindLikely: falsesuggests hand-rolled or utility-class icons). - Specific icons observed: The captured viewport contains no visible icons (purely typographic hero). Based on portfolio conventions and DOM structure, expected icons include: external-link arrows (project cards), social handles (footer), mail/contact envelope (CTA), menu hamburger (mobile nav), close/X (modals), and possibly a custom logo mark.
- Treatment: Inferred as stroke-based (consistent with the lightweight aesthetic), colored
currentColorinheriting from parent{colors.ink}or{colors.muted}. Size likely 16px (inline with text) or 20px (standalone). - Alignment: Icons sit on the same baseline as adjacent text with
vertical-align: middleor flex alignment, maintaining the system's precise horizontal rhythm. - Custom recommendations: If building the icon set, use 1.5px stroke width,
stroke-linecap: roundandstroke-linejoin: roundfor the soft geometric feel matching{rounded.lg}and{rounded.pill}values.
Recommended Frontend Stack
- Framework: Next.js 14+ (App Router) or Astro (static portfolio optimization)
- Styling: Vanilla CSS or CSS Modules (no Tailwind detected; custom properties for tokens)
- Fonts: Self-host HitMePunk (/hitmepunk.ttf); load Zalando Sans Expanded via @font-face or CDN
- Animation: CSS keyframes (marquee, grain-shift, spin, pulse already defined); JS for cursor tracking
- Icons: Custom SVG sprites or inline SVG (no library dependency detected)
- Component lib: None (hand-rolled components matching this spec)
- Cursor glow: Vanilla JS mousemove listener updating transform: translate() on two fixed divs
- Grain effect: SVG feTurbulence filter with CSS animation on baseFrequency or background-position
Responsive Behavior
Breakpoints
| Name | Width | Key Changes |
|---|---|---|
| Mobile | < 768px | Single-column layout; marquee text scales down to {typography.display-lg} (40px); cursor glow disabled (hidden class detected on glow elements below md:); navigation collapses to hamburger |
| Tablet | 768px – 1024px | Two-column project grid; cursor glow enabled; full navigation visible |
| Desktop | 1024px – 1440px | Three-column project grid; {typography.display-xl} (56px) hero text restored |
| Wide | > 1440px | Max-width container (~1400px) centers content; canvas extends to viewport edges |
Touch Targets
{component.button-primary}: ~152px × 44px(padding 12px 28px plus text width) — exceeds WCAG AAA 44×44 minimum comfortably.{component.button-outline}: Same dimensions as primary — touch-friendly on mobile.{component.text-input}: Height inferred at 44px (standard input height) — meets minimum; width stretches to container.{component.nav-link}: Tap area should expand to minimum 44×44px via padding despite 14px font size — critical for mobile header usability.{component.project-card}: Entire card surface is tappable (link wrapper) — effective touch target is full card dimensions.
Collapsing Strategy
- Navigation: Below 768px, the horizontal nav bar (
{component.nav-link}items) collapses into a hamburger menu triggering a slide-down or off-canvas drawer. Thehidden md:blockclass detected on cursor-glow elements suggests Tailwind-like responsive utilities are in use despitetailwindLikely: false— may indicate a custom utility framework or manual responsive classes. - Hero text:
{typography.display-xl}(56px) scales down to{typography.display-lg}(40px) or{typography.title-md}(24px) on mobile to prevent overflow. The marquee animation may pause or slow on mobile to reduce motion for smaller screens. - Project grid: Transitions from 3-column (desktop) → 2-column (tablet) → 1-column (mobile). Card padding may compress from
{spacing.lg}(24px) to{spacing.md}(14px) on mobile. - Cursor glow: Explicitly disabled on mobile via
hidden md:blockclasses — touch devices have no cursor to track, and the performance cost of position listeners is unwarranted. - Section padding: Vertical
{spacing.section}(80px) may reduce to{spacing.xl}(48px) on mobile to fit more content above the fold.
Image Behavior
- Project thumbnails: Within
{component.project-card}, images likely useaspect-ratioCSS property or intrinsic dimensions withobject-fit: coverto fill the card's image area without distortion. On mobile (single-column), images may increase vertical height to maintain visual impact. - Hero imagery (if added beyond captured viewport): Would use
width: 100%; height: autowith potentialobject-fit: coverand fixed height on mobile to prevent excessive scrolling before content. - Avatar/profile imagery (if present in about section): Circular crop via
{rounded.full}, fixed size (80-120px desktop, 64px mobile), centered withmargin: 0 auto.
Iteration Guide
-
Build a single-page portfolio using Next.js or Astro with CSS Modules (or vanilla CSS with custom properties). Avoid Tailwind — the extracted data shows no Tailwind fingerprint despite some utility-like class name fragments.
-
Define CSS custom properties for every token in the YAML frontmatter following the
{section.token}paths exactly. Example:--colors-surface-soft: #90c5e8; --typography-display-xl-font-size: 56px;. Reference these variables in all component styles — never hard-code a hex or pixel value that has a token. -
Implement the four keyframe animations first (marquee, grain-shift, spin, pulse) as named
@keyframesblocks. The marquee is mission-critical for the hero; grain-shift is essential for the canvas texture. Test these in isolation before integrating into components. -
Build the cursor glow system as two fixed-position
divelements withborder-radius: 9999px,pointer-events: none, andz-index: 9999. Attach amousemovelistener that updates theirtransform: translate()values withrequestAnimationFrameand ~80ms easing delay. Disable entirely below 768px viewport width. -
Create the
{component.hero-canvas}as a full-viewportmin-h-screensection withbackground-color: var(--colors-surface-soft)anddisplay: flex; align-items: center; justify-content: center. Nest the marquee text container and CTA button pair inside. Apply thegrain-overlayas a siblingdivwithposition: fixed; inset: 0; pointer-events: none; z-index: 999. -
When adding new components, decide which sub-system they belong to: (a) Editorial/Hero — uses sky-blue canvas, large type, motion; (b) Work Grid — uses elevated surface cards, tighter spacing; (c) Meta/Footer — uses caption type, muted colors; (d) Identity — reserves HitMePunk typeface. Each sub-system has distinct density and tone.
-
State variants (hover, active, focus, disabled) must be separate top-level entries in your component CSS or JS objects — never nest them. Follow the pattern:
.button-primary { }and.button-primary-active { }as peers, not.button-primary:active { }unless your framework specifically prefers pseudo-classes. -
Run contrast checks on every text/background combination. The
{colors.ink}on{colors.surface-soft}pairing delivers ~13:1 ratio (excellent). Verify{colors.muted}on{colors.surface-soft}stays above 4.5:1 (AA threshold). -
Font setup: Host
hitmepunk.ttfat/public/fonts/hitmepunk.ttf. Define:
@font-face {
font-family: 'HitMePunk';
src: url('/fonts/hitmepunk.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}
For Zalando Sans Expanded, obtain the licensed files and define similarly, or substitute with Inter (variable font, font-stretch: expanded) via Google Fonts as fallback.
- When in doubt about emphasis: choose larger type before bolder type, color shift before shadow, motion (marquee/grain) before static decoration, and the sky-blue canvas before any other background. The portfolio's power lies in restraint — every pixel earns its place.
Known Gaps
- Hover state styling: Per the no-hover policy, specific
:hoverpseudo-class styles are not documented. Implement hover as intermediate between Default and Active states (e.g., button hover = slight background lighten; card hover ={component.project-card-hover}properties). - Loading/skeleton states: No loading indicators, skeleton screens, or pending-state styles were extractable from the static capture. Design these using
{colors.surface-elevated}backgrounds with pulse animation for content loading. - Form validation states: Beyond
{component.text-input-focus}, no error ({colors.error}) or success ({colors.success}) input states were visible. Implement red border + message for errors, green checkmark for success. - Dark mode / theme toggle: The DOM contains a
spider-themelocalStorage reference suggesting a theme-switching mechanism exists, but the light (sky-blue) variant is the only one captured. Dark mode likely inverts to{colors.canvas}(#00090f) as background with{colors.surface-soft}as text/accent. - Project detail modal/view: Individual project pages or expandable modals were not visible. These would need dedicated typography (possibly
{typography.display-lg}for project titles), image galleries, and next/prev navigation. - Mobile navigation pattern: While collapse behavior is inferred, the exact slide-down drawer, off-canvas panel, or fullscreen overlay pattern for mobile nav is undetermined. Choose based on content volume (drawer for few links, overlay for many).
- Accessibility focus-visible styles: Beyond the inferred focus ring on
{component.text-input-focus}, comprehensive:focus-visibleoutlines for all interactive elements (buttons, links, cards) should be implemented using{colors.border-strong}at 2-3px offset. - Pixel-exact marquee speed and direction: The marquee animation's
animation-duration,direction(normal/reverse/alternate), and whether it pauses on hover are not extractable from CSS alone without seeing the animation in motion. Start with20s linear infiniteand adjust for reading comfort. - Grain overlay opacity and filter parameters: The
grain-shiftkeyframe's specific SVGfeTurbulencevalues (baseFrequency, numOctaves) or CSS background-image noise parameters are implementation details not fully captured. Begin withbaseFrequency="0.65"andopacity: 0.04. - Cursor glow color stops and blend mode: The exact radial-gradient colors (likely
{colors.accent}to transparent), opacities (outer: 0.15, inner: 0.25 estimated), andmix-blend-mode(probablyscreenoroverlay) for the cursor glow circles require aesthetic tuning during implementation.