Frontend Development Skills & Lessons Learned (Portfolio v5.0)
Purpose: Portfolio-specific patterns, component implementations, and session learnings.
Scope: Project-specific implementation details unique to the portfolio v5.0 Night Sky theme. For universal frontend patterns (CSS gotchas, accessibility guidelines, performance techniques), see ~/.claude/skills/frontend-dev/SKILL.md.
Last Updated: January 24, 2026
Global Skills Reference
Universal patterns have been extracted to the global skills repository. See:
- •
~/.claude/skills/frontend-dev/SKILL.mdfor:- •CSS Transforms Are Atomic
- •Transform Order Matters
- •Aspect Ratio Correction for Circles
- •Staggered Animation with Cleanup
- •Reduced Motion Accessibility
- •Mathematical Circle Placement
- •Props with Sensible Defaults
- •useMemo for Expensive Calculations
- •Simple Icons integration
- •next-intl Setup Pattern
- •Mobile-First Breakpoints
- •Dynamic Viewport Units
- •GPU-Accelerated Animations
- •SVG Over PNG for Icons
- •Accessibility Checklist
- •Common Gotchas (Intervals, Hydration, Z-Index, SVG viewBox)
- •Intersection Observer Pattern
CSS & Styling
CSS Transforms Are Atomic (Not Additive)
Problem: CSS transforms override each other instead of combining.
Wrong:
.element {
transform: translate(-50%, -50%);
}
.element:hover {
transform: scale(1.15); /* This removes translate! */
}
Correct:
.element {
transform: translate(-50%, -50%) rotate(var(--rotation, 0deg));
}
.element:hover {
transform: translate(-50%, -50%) rotate(var(--rotation, 0deg)) scale(1.15);
}
Key Insight: Always include ALL transforms in every transform declaration. Use CSS custom properties for dynamic values.
Transform Order Matters
The order of transform operations affects the final result:
/* Order: translate → rotate → scale */ transform: translate(-50%, -50%) rotate(15deg) scale(1.15);
- •translate - Move the element first
- •rotate - Rotate around the (new) center
- •scale - Scale last to avoid position drift
Aspect Ratio Correction for Circles
When positioning elements in a circle on non-square containers, X-axis needs compensation:
// Without correction: ellipse const x = centerX + (radius * Math.cos(angle)); // With correction: perfect circle const radiusX = radius * 0.7; // Aspect ratio correction const x = centerX + (radiusX * Math.cos(angle));
When to use: Any circular positioning on rectangular containers (hero sections, cards, etc.)
Animation Patterns
Staggered Animation with Cleanup
useEffect(() => {
if (!animation) {
setVisibleLogos(new Set(logos.map((_, i) => i)));
return;
}
const interval = setInterval(() => {
setVisibleLogos((prev) => {
const next = new Set(prev);
if (next.size < logos.length) {
next.add(next.size);
}
return next;
});
}, DELAY_INTERVAL);
return () => clearInterval(interval); // Critical: cleanup!
}, [animation, logos.length]);
Key Points:
- •Always return cleanup function from useEffect
- •Use Set for O(1) visibility checks
- •Handle non-animated case (instant display)
Reduced Motion Accessibility
Always respect user preferences:
@media (prefers-reduced-motion: reduce) {
.animated-element {
animation: fadeInStatic 0.01ms forwards !important;
}
}
@keyframes fadeInStatic {
to {
opacity: 1;
transform: none;
}
}
Why: Users with vestibular disorders need instant display instead of animations.
Circular/Orbital Positioning
Mathematical Circle Placement
const calculatePositions = (totalItems: number, radiusPercent: number) => {
const angleStep = (2 * Math.PI) / totalItems;
const startAngle = -Math.PI / 2; // Start at 12 o'clock
return Array.from({ length: totalItems }, (_, index) => {
const angle = startAngle + index * angleStep;
return {
x: 50 + (radiusPercent * 0.7 * Math.cos(angle)), // 0.7 = aspect correction
y: 50 + (radiusPercent * Math.sin(angle)),
rotation: (angle * 180 / Math.PI) + 90, // Convert to degrees
};
});
};
Parameters:
- •
startAngle = -Math.PI / 2→ Starts at top (12 o'clock) - •
angleStep = 2π / n→ Even distribution - •Multiply X by 0.7 for aspect ratio correction
Component Architecture
Props with Sensible Defaults
interface TechStackLogosProps {
animation?: boolean; // default: true
radius?: number; // default: 35 (%)
centerX?: number; // default: 50 (%)
centerY?: number; // default: 50 (%)
}
Make components configurable but usable out-of-the-box.
useMemo for Expensive Calculations
const logoPositions = useMemo(() => {
return calculateCircularPositions(techLogos.length, radius, centerX, centerY);
}, [radius, centerX, centerY]); // Only recalculate when these change
When to use: Trigonometric calculations, array transformations, complex mappings.
Third-Party Libraries
Simple Icons for Tech Logos
Best source for official brand logos:
npm install simple-icons
import { siReact, siTypescript } from 'simple-icons';
// Usage with SVG string
<div dangerouslySetInnerHTML={{ __html: siReact.svg }} />
Benefits:
- •MIT Licensed (copyright safe)
- •2800+ brand logos
- •Consistent style
- •Small file sizes
Flag Icons for Locale Switcher
npm install flag-icons
@import 'flag-icons/css/flag-icons.min.css';
<span className={`fi fi-${countryCode}`} />
Note: Country codes are ISO 3166-1-alpha-2 (e.g., us, kr, jp).
Internationalization (i18n)
next-intl Setup Pattern
// i18n/routing.ts
export const routing = defineRouting({
locales: ['en', 'ko'],
defaultLocale: 'en',
});
// i18n/request.ts
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as Locale)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});
Locale Switcher Accessibility
Always include:
- •
langattribute on locale buttons - •
aria-labeldescribing the action - •Visual indicator of current locale (not just color)
<button
lang={locale}
aria-label={`Switch to ${langName}`}
aria-pressed={isActive}
>
<span className={`fi fi-${countryCode}`} aria-hidden="true" />
<span className="sr-only">{langName}</span>
</button>
Responsive Design
Mobile-First Breakpoints
/* Base: Mobile (< 768px) */
.element { width: 100%; }
/* Tablet (≥ 768px) */
@media (min-width: 768px) {
.element { width: 50%; }
}
/* Desktop (≥ 1024px) */
@media (min-width: 1024px) {
.element { width: 33%; }
}
Tailwind:
<div className="w-full md:w-1/2 lg:w-1/3" />
Dynamic Viewport Units
Problem: 100vh doesn't account for mobile browser chrome.
Solution: Use dynamic viewport units:
.hero {
height: 100dvh; /* Dynamic viewport height */
}
Tailwind:
<section className="h-dvh" />
Performance
GPU-Accelerated Animations
.animated {
will-change: transform, opacity;
backface-visibility: hidden;
}
Remove after animation:
.animation-complete {
will-change: auto;
}
SVG Over PNG for Icons
Prefer SVGs because:
- •Infinitely scalable
- •Smaller file size
- •CSS-styleable
- •No retina considerations
Accessibility Checklist
Decorative Elements
<div aria-hidden="true">
{/* Decorative content hidden from screen readers */}
</div>
Focus Management
/* Visible focus indicator */
:focus-visible {
outline: 2px solid var(--pastel-green);
outline-offset: 2px;
}
/* Remove outline for mouse users */
:focus:not(:focus-visible) {
outline: none;
}
Color Contrast Minimums
| Context | WCAG AA | WCAG AAA |
|---|---|---|
| Normal text | 4.5:1 | 7:1 |
| Large text (18px+) | 3:1 | 4.5:1 |
| UI components | 3:1 | N/A |
Common Gotchas
1. Interval Memory Leaks
Always clean up intervals:
useEffect(() => {
const id = setInterval(() => {}, 1000);
return () => clearInterval(id); // Required!
}, []);
2. Hydration Mismatches
Avoid:
- •
Math.random()in render - •
new Date()without useEffect - •Browser-only APIs during SSR
Solution: Use useEffect for client-only code.
3. Z-Index Wars
Use consistent z-index scale:
:root {
--z-base: 0;
--z-dropdown: 100;
--z-modal: 200;
--z-tooltip: 300;
--z-toast: 400;
}
4. SVG viewBox Case Sensitivity
// Wrong (JSX) <svg viewbox="0 0 24 24"> // lowercase 'b' won't work // Correct <svg viewBox="0 0 24 24"> // capital 'B' required
Quick Reference Commands
See .claude/CLAUDE.md (Development Commands section) for all project commands.
Common git and testing commands:
git status git diff git log --oneline -10 npx playwright screenshot http://localhost:3000
Document Maintenance
When to update this document:
- •After solving a tricky bug
- •When discovering a better pattern
- •After learning a new library quirk
- •When onboarding notes could help future sessions
Format:
- •Problem description
- •Wrong approach (if applicable)
- •Correct solution
- •Key insight/takeaway
Session Learnings - January 20, 2026
Mistakes & Fixes
- •
Issue: Initial Live Projects Section had info (title, tech stack, description) positioned inside iPhone frame
- •Root Cause: Misinterpretation of Figma design specification during implementation
- •Fix: Moved info section outside phone frame using flexbox column layout (
flex flex-col items-center) - •Prevention: Always compare implementation with actual Figma mockup, not just verbal specs. Use ui-ux-designer agent to validate layout placement before coding
- •
Issue: Gradient overlay on iPhone frame was too opaque (100% opacity)
- •Root Cause: Initial implementation used full-strength gradient without checking visual impact
- •Fix: Reduced overlay opacity to 30% using
rgba(51,51,51,0.3)for subtle effect - •Prevention: Test visual effects at various opacity levels; start conservative with overlays
Patterns Discovered
- •
Pattern: Canvas 2D Twinkling Animation with Accessibility
- •Context: Creating performant star field background that respects
prefers-reduced-motion - •Implementation:
- •Use Canvas 2D API with
requestAnimationFramefor smooth 60fps performance - •Store stars in
useRefto avoid re-generation on every render - •Check
prefers-reduced-motionusingwindow.matchMedia()and listen for changes viaaddEventListener('change') - •When reduced motion: Cancel animation, draw static stars once
- •When motion restored: Restart
requestAnimationFrameloop - •Always clean up:
cancelAnimationFrame(),removeEventListener()in cleanup function
- •Use Canvas 2D API with
- •Key Code Pattern:
typescript
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); isReducedMotionRef.current = mediaQuery.matches; const handleMotionChange = (e: MediaQueryListEvent) => { isReducedMotionRef.current = e.matches; if (e.matches) { cancelAnimationFrame(animationFrameRef.current); // Draw static version } else { animationFrameRef.current = requestAnimationFrame(animate); } }; mediaQuery.addEventListener('change', handleMotionChange);
- •Context: Creating performant star field background that respects
- •
Pattern: High-DPI Canvas Rendering
- •Context: Making canvas sharp on retina/high-DPI displays
- •Implementation:
- •Get
devicePixelRatiofrom window - •Set canvas internal size to
width * dprandheight * dpr - •Set CSS size to logical pixels (unscaled)
- •Scale context:
ctx.scale(dpr, dpr)
- •Get
- •Code:
typescript
const dpr = window.devicePixelRatio || 1; canvas.width = width * dpr; canvas.height = height * dpr; canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr);
- •
Pattern: Sine Wave Opacity Animation for Twinkling Effect
- •Context: Creating natural-looking star twinkling without CSS keyframes
- •Implementation:
- •Use
Math.sin(timestamp * twinkleSpeed + offset)to create oscillation - •Result ranges from -1 to 1, map to opacity range:
0.3 + 0.7 * Math.sin(...) - •Vary
twinkleSpeedper star (0.0005 to 0.002) for natural variation - •Use random
twinkleOffsetso stars don't twinkle in sync
- •Use
- •Benefit: Smooth, continuous animation without discrete keyframes
- •
Pattern: Lazy-Loading iframes with Intersection Observer
- •Context: Load live website previews only when scrolled into view
- •Implementation:
- •Use
IntersectionObserverwithrootMargin: '100px'to start loading before visible - •Set
threshold: 0.1for early detection - •Disconnect observer after first intersection to avoid repeated loads
- •Track loading states:
'idle' | 'loading' | 'loaded' | 'error' - •Show loading spinner while state is loading
- •Use
- •Code:
typescript
const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { setIsInView(true); setLoadingState('loading'); observer.disconnect(); // Load only once } }); }, { rootMargin: '100px', threshold: 0.1 } );
- •
Pattern: 400% Scale Technique for iframe Content Scaling
- •Context: Display responsive website inside small iPhone frame while maintaining readability
- •Implementation:
- •Render iframe at 400% size:
w-[400%] h-[400%] - •Scale down to 25%:
scale-[0.25] - •Set origin to top-left:
origin-top-left - •This makes content 4x smaller but sharp and readable
- •Render iframe at 400% size:
- •Why it works: Scaling at 25% (1/4) shows entire page content, better than clipping
- •Alternative: Could also use CSS zoom property, but transform is more compatible
- •
Pattern: iPhone Pro Frame Design with Detailed Bezels
- •Context: Create realistic iPhone 15 Pro appearance for project previews
- •Design Details:
- •Aspect ratio:
9:19.5(exact iPhone 15 Pro proportions) - •Outer radius:
rounded-[12%]for smooth edges - •Screen radius:
rounded-[10%]with small margin (2%) - •Dynamic Island:
w-[28%] h-[3.5%]centered at top - •Side buttons positioned at specific percentages for visual accuracy
- •Home indicator: thin bar at bottom (
h-[0.6%]) with transparency
- •Aspect ratio:
- •Color scheme: Space Black
#1a1a1awith subtle highlights
Debugging Wins
- •
Problem: Frame info section placement confusion
- •Approach: Compared Figma screenshot with implementation using visual inspection. Used ui-ux-designer agent to analyze design and clarify spec
- •Tool/Technique: Figma image analysis + visual diff between design and code
- •
Problem: Gradient overlay intensity mismatch
- •Approach: Tested different opacity values (100%, 50%, 30%) visually to match Figma design
- •Tool/Technique: Browser DevTools opacity testing, direct color value adjustment
Performance Notes
Session-specific optimizations:
- •Canvas Stars: Validated 150-star rendering more efficient than DOM elements (single RAF loop)
- •Intersection Observer:
rootMargin: '100px'provides optimal iframe loading buffer - •Lazy Iframe: Deferred iframe creation saves ~2MB memory per off-screen preview
- •Reduced Motion: RAF cancellation prevents unnecessary CPU cycles when animations disabled
Session Learnings - January 21, 2026
Mistakes & Fixes
- •
Issue: Type mismatch in LiveProjectsSection - passed
string[]to component expectingSimpleIcon[]- •Root Cause: Tech stack data was fetched as string identifiers, but component required SimpleIcon objects with SVG data
- •Fix: Map string identifiers to actual SimpleIcon objects before passing as props
- •Prevention: Always verify third-party library types (especially simple-icons) match component prop requirements before implementation
- •
Issue: CertificationCard title placement - initially placed inside glass card container
- •Root Cause: Misinterpreted design specification for card layout hierarchy
- •Fix: Moved h3 title OUTSIDE and ABOVE the glassCardBaseStyle container; created separate flex-col wrapper
- •Prevention: Extract explicit placement rules from design specs: "title outside" vs "title inside" should be codified in design system docs
Patterns Discovered
- •
Pattern: Reusable Style Composition via baseStyles.ts
- •Context: Multiple cards need consistent glassmorphic styling, hover effects, and sizing
- •Implementation: See
.claude/CLAUDE.md(Reusable Styles System, lines 206-258) for complete baseStyles.ts documentation - •Key exports:
glassCardBaseStyle(with v5.1 responsive padding),hoverLiftStyle,baseWidth - •Files:
/app/styles/baseStyles.ts - •Session Application: Applied to SmallProjectCard, CertificationCard, LiveProjectCard
- •
Pattern: Title Outside / Card Inside Hierarchy
- •Context: Cards with glass effect need visual separation between heading and content
- •Implementation: See
.claude/CLAUDE.md(Component Patterns, lines 263-289) for complete card pattern specification - •Key Rule: h3 title placed OUTSIDE glassCardBaseStyle container, never inside
- •Session Application: Validated in CertificationCard, themed project cards, certification displays
- •
Pattern: CSS clip-path Animation for Writing Reveal Effect
- •Context: Create animated signature or text reveal that simulates writing from left to right
- •Core Technique:
clip-path: inset()animation from right to left with prefers-reduced-motion support - •Implementation Details:
- •Duration: 2-2.5s for natural writing appearance
- •Easing:
cubic-bezier(0.4, 0, 0.2, 1)for smooth deceleration - •Accessibility: mediaQuery listener for live preference updates
- •Session Application: Implemented in CalligraphySignature component with full accessibility support
- •Key Insight: clip-path is GPU-accelerated, provides smoother animation than width/opacity
- •
Pattern: Semantic HTML with ARIA for Links
- •Context: Improve accessibility for external links and navigation
- •Implementation: See
.claude/CLAUDE.md(Accessibility Patterns section) for comprehensive ARIA and semantic HTML guidelines - •Session Application: Applied to CertificationCard external verification links with proper aria-labels
- •
Pattern: i18n Message Structure with next-intl
- •Context: Support multiple languages (English, Korean) with centralized message management
- •Implementation: See
.claude/CLAUDE.md(Accessibility Patterns - Internationalization section) for next-intl usage patterns - •Session Application: Applied translation hooks to CertificationCard with proper message key structure
Debugging Wins
- •
Problem: LiveProjectsSection type error when integrating tech stack data
- •Approach: Traced type from component prop definition → SimpleIcon interface → actual data structure. Identified mismatch between string identifiers and SimpleIcon objects
- •Tool/Technique: TypeScript compiler errors guided investigation; simple-icons package documentation clarified required object structure
- •
Problem: CertificationCard layout not matching design (title placement)
- •Approach: Created visual mockup in mind from design spec, compared with implementation, identified title was nested inside card when it should be outside
- •Tool/Technique: Read component JSX structure against design hierarchy; made targeted DOM restructuring
- •
Problem: Reduce motion preference handling for CalligraphySignature
- •Approach: Added mediaQuery listener with state tracking; tested by changing system accessibility settings; verified animation stops and signature displays immediately
- •Tool/Technique: System settings change testing, useRef for state persistence across renders, cleanup function verification
Performance Notes
Session-specific optimizations:
- •Centralized Styles: Validated that
baseStyles.tsexport pattern reduces bundle redundancy - •clip-path Animation: Confirmed 60fps performance on CalligraphySignature across devices
- •Reduced Motion: Verified animation cancellation reduces CPU usage when preference enabled
- •Image Loading: Next.js Image with
priorityflag for above-fold CalligraphySignature
Automation Opportunities (Jan 21)
- •Component Generator: Scaffold generator for card components with baseStyles.ts imports
- •i18n Key Validator: Linter to ensure en.json/ko.json key parity
- •Design System Auditor: Find hardcoded classes that should use baseStyles exports
- •Accessibility Scanner: Pre-commit hook for missing ARIA labels
Session Learnings - January 22, 2026
Mistakes & Fixes
- •
Issue: Used
<img>instead ofnext/imagein LiveProjectCard screenshot fallback for mobile- •Root Cause: Didn't consider Next.js Image optimization benefits initially
- •Fix: Replaced
<img>withnext/imageusing remotePatterns configuration - •Prevention: Always reach for
next/imagefirst for any image rendering, especially for performance-critical sections
- •
Issue: React.FC type annotation on
useSimpleIconshook- •Root Cause: Following outdated React typing patterns
- •Fix: Removed React.FC, used modern function return type with proper typing
- •Prevention: Stay current with React 18+ typing patterns; avoid React.FC for new components
- •
Issue: Missing
useMemooptimization for icon rendering inuseSimpleIconshook- •Root Cause: Didn't consider re-render performance impact of creating icon elements on every render
- •Fix: Added
useMemofor bothrenderedIconsarray andIconContainercomponent with proper dependency arrays - •Prevention: Memoize any computed values that are passed as dependencies or could cause expensive re-renders
- •
Issue: Suspense boundary wrapped too much content in
loading.tsx- •Root Cause: Misunderstood that persistent UI (header/nav) should stay outside Suspense
- •Fix: Moved Suspense to only wrap
{children}, keeping persistent elements outside - •Prevention: Suspense should only wrap content that can be replaced; persistent UI remains outside
Patterns Discovered
- •
Pattern: useSimpleIcons Hook for Icon Rendering
- •Context: Multiple components need to render SimpleIcon objects with consistent sizing, tooltips, and accessibility
- •Implementation: See
.claude/CLAUDE.md(Component Patterns, lines 293-330) for complete hook documentation with sm/md/lg sizing - •Location:
/src/hooks/useSimpleIcons.tsx - •Returns:
{ icons: SimpleIcon[]; IconContainer: React.FC }with responsive sizing and tooltip support - •Session Application: Integrated into SmallProjectCard and LiveProjectCard with useMemo optimization
- •
Pattern: Mobile Fallback with useBreakpoint Hook
- •Context: Heavy components (iframes) crash on mobile; need lightweight alternative
- •Implementation: Documented in CLAUDE.md under v5.1 Polish (Issue #460)
- •Key Technique:
useBreakpoint()hook for conditional rendering based on viewport - •Session Application: Successfully resolved mobile crashes in LiveProjectIframe with screenshot fallback
- •Performance Impact: ~75% memory reduction on mobile devices
- •
Pattern: PR Review Workflow with GitHub API
- •Context: Iterating on code based on review feedback
- •Implementation:
- •Fetch PR comments:
gh api repos/:owner/:repo/pulls/:pr_number/comments - •Parse issues/suggestions from comment text
- •Create fixes in same branch
- •Repush without creating new PR
- •Fetch PR comments:
- •Benefits: Keeps PR discussion in one place, single commit history, cleaner GitHub record
- •Command Reference:
bash
gh api repos/owner/repo/pulls/463/comments # Make fixes locally git add . git commit --amend --no-edit git push origin branch-name --force-with-lease
- •
Pattern: Batch Milestone & Issue Creation
- •Context: Organizing work for v5.1 with multiple related issues
- •Implementation:
- •Create milestone first:
gh milestone create --title "v5.1" --description "..." - •Create issues with template batch commands
- •Link issues to milestone via
--milestoneflag - •Organize by epic categories (Animations, Bugs, Polish, etc.)
- •Create milestone first:
- •Benefits: Clear project organization, trackable progress, grouped related work
- •Example:
bash
gh issue create --title "iFrame crashes on mobile" --body "..." --milestone "v5.1" --label "bug"
- •
Pattern: Next.js Image with Remote Patterns
- •Context: Loading external images (screenshots) from URLs
- •Implementation:
- •Configure
remotePatternsinnext.config.tsto allow external domains - •Use
next/imagecomponent with full URL - •Set
priorityfor critical images, lazy-load others
- •Configure
- •Configuration:
typescript
const nextConfig = { images: { remotePatterns: [ { protocol: 'https', hostname: 'screenshots.example.com', pathname: '/**', }, ], }, };
Debugging Wins
- •
Problem: iFrame crashing on mobile viewport
- •Approach: Analyzed viewport size constraints, identified memory overhead of rendering live website inside small frame. Tested
useBreakpointhook to conditionally render screenshot fallback on mobile breakpoints - •Tool/Technique: Mobile breakpoint detection, screenshot fallback as lightweight alternative
- •Approach: Analyzed viewport size constraints, identified memory overhead of rendering live website inside small frame. Tested
- •
Problem: Icon rendering performance and styling consistency
- •Approach: Created dedicated
useSimpleIconshook with memoization. Tested hook across components to ensure consistent sizing and tooltips - •Tool/Technique: React DevTools profiler to verify memoization working; component testing at different breakpoints
- •Approach: Created dedicated
- •
Problem: PR review feedback iteration workflow
- •Approach: Used
gh apito fetch PR comments, parsed feedback, implemented fixes locally, amended commits and force-pushed with--force-with-lease(safe option) - •Tool/Technique: GitHub CLI for automated PR interaction;
--force-with-leaseprevents accidental overwrites
- •Approach: Used
Performance Notes
Session-specific optimizations (v5.1):
- •Mobile Fallback: Screenshot strategy reduces memory 75% vs live iframe on mobile (Issue #460)
- •useMemo Icons: Prevents re-rendering tech stack on parent state changes (Issue #461)
- •Breakpoint Hook: Component tree pruning more efficient than CSS media queries for conditionals
- •Suspense Scope: Narrowed to {children} only, prevents header/nav re-render on route change (Issue #459)
- •Image Optimization: Next.js srcset + remotePatterns for external screenshots (Issue #460)
Accessibility Wins
See .claude/CLAUDE.md (Accessibility section in Quick Reference) for project-wide accessibility standards.
Session-specific validations:
- •Touch Target Sizing: Verified 44x44px minimum on v5.1 components (Issue #457)
- •Focus States: Implemented
focus-visiblewith cyan outline for keyboard navigation - •Icon Tooltips: Integrated MUI Tooltip in
useSimpleIconshook - •Screenshot Fallback: Alt text on mobile fallback images (Issue #460)
Automation Opportunities (Jan 22 - v5.1)
- •PR Comment Bot: Fetch review comments, suggest fixes, auto-label
- •Mobile Optimization Linter: Flag heavy components needing useBreakpoint
- •Icon Validator: Verify SimpleIcon sizing consistency
- •remotePatterns Validator: Check external image URLs before build
- •Visual Regression: Automated screenshots at 375/393/768/1024/1440px breakpoints
Session Learnings - January 23, 2026
Mistakes & Fixes
- •
Issue: Hardcoded icon sizes in SmallProjectCard not responding to breakpoints
- •Root Cause: Fixed 24px sizing ignored responsive design requirements; same size on mobile, tablet, desktop
- •Fix: Updated
useSimpleIconshook to support responsive sizing directly; removedgetResponsiveSizeClassfunction complexity - •Prevention: Always consider responsive variants when creating hooks; simplify by using direct Tailwind class composition with clsx
- •
Issue: SVG icons from SimpleIcons not scaling within flex containers on responsive layouts
- •Root Cause: SVG had fixed dimensions that weren't responsive to container width
- •Fix: Applied
[&>svg]:w-full [&>svg]:h-fullto force SVG to fill parent container dimensions - •Prevention: When rendering SVGs in containers, explicitly set width/height to 100% using child selector in Tailwind
- •
Issue: Arbitrary Tailwind values used for responsive sizing instead of standard classes
- •Root Cause: Created
w-[20px]instead of using standardw-5(20px divisible by 4) - •Fix: Switched to standard Tailwind classes (w-4, w-6, w-8) for all responsive sizing
- •Prevention: Always use standard Tailwind utility classes when values are divisible by 4 (4, 8, 12, 16, 20, 24, 32, etc.)
- •Root Cause: Created
Patterns Discovered
- •
Pattern: 3-Tier Responsive Typography System
- •Context: Consistent text sizing across mobile, tablet, desktop
- •See
.claude/CLAUDE.md(lines 398-428) for complete responsive typography documentation - •Implementation: CSS custom properties in globals.css with media queries
- •Usage: Apply
.text-title,.text-h1,.text-h2, etc. classes - •Key Principle: Mobile-first scaling with md: and lg: breakpoint overrides
- •Session Application: Applied to Hero, Section Headings, Card Titles in v5.1
- •
Pattern: Responsive baseStyles with glassCardBaseStyle Updates
- •Context: Reusable styles need responsive padding and gap variants
- •See
.claude/CLAUDE.md(lines 206-258) for current glassCardBaseStyle specification - •Current Value:
bg-dark-gray/50 backdrop-blur-md rounded-3xl max-sm:p-3 md:p-2.5 lg:p-3 - •Pattern: Mobile-first base value, then md: and lg: overrides in single string
- •Session Note: Use standard Tailwind classes (p-2.5) over arbitrary values (p-[10px])
- •
Pattern: Scroll-Triggered Animation Hooks
- •Context: Multiple sections need unified animation pattern for revealing content on scroll
- •See
.claude/CLAUDE.md(lines 332-397) for complete hook documentation:- •
useStaggeredAnimation- Staggered reveal with configurable delay (default 500ms) - •
useSectionAnimation- Simple fade-in for entire section
- •
- •Location:
/src/hooks/useStaggeredAnimation.tsand/src/hooks/useSectionAnimation.ts - •Features: Intersection Observer API, prefers-reduced-motion support, configurable timing
- •Session Application: Applied to SmallProjectCard, CertificationCard, LiveProjectCard sections (staggered); Hero/Footer (simple fade)
- •
Pattern: Vercel Build Ignore Script for Branch Filtering
- •Context: Restrict production builds to main branches (develop/master), skip builds on feature branches
- •Implementation:
vercel_build_ignore_step.shscript - •Location: Project root, referenced in
vercel.jsonbuild settings - •Script Logic:
bash
#!/bin/bash if [[ "$VERCEL_GIT_COMMIT_REF" != "master" && "$VERCEL_GIT_COMMIT_REF" != "develop" ]]; then echo "Skipping build on branch: $VERCEL_GIT_COMMIT_REF" exit 0 fi
- •Configuration in vercel.json:
json
{ "buildCommand": "bash vercel_build_ignore_step.sh && npm run build" } - •Benefits: Prevents unnecessary builds on feature branches, saves build minutes, faster feedback on main branches
Debugging Wins
- •
Problem: Icon sizing inconsistency across responsive breakpoints
- •Approach: Tested
useSimpleIconshook at 375px, 768px, 1440px using Playwright; identified fixed sizing - •Tool/Technique: Playwright viewport testing + component visual inspection
- •Approach: Tested
- •
Problem: SVG icons not filling containers properly
- •Approach: Inspected SVG in DevTools; found inline width/height attributes overriding container flex
- •Tool/Technique: Browser DevTools CSS inspector to identify conflicting styles
- •
Problem: Determining correct standard Tailwind classes for arbitrary sizes
- •Approach: Referenced Tailwind spacing scale; identified all base-4 multiples (4, 8, 12, 16, 20, 24, 32, 48, 64)
- •Tool/Technique: Tailwind documentation + manual verification of divisibility by 4
- •
Problem: Section animations not triggering consistently on scroll
- •Approach: Added console logging to useStaggeredAnimation; verified Intersection Observer was detecting visibility
- •Tool/Technique: React DevTools profiler + console debugging for observer callbacks
Performance Notes
Session-specific optimizations (v5.1 completion):
- •useSimpleIcons with clsx: Direct object composition more efficient than function-based
getResponsiveSizeClass - •SVG Child Selectors:
[&>svg]:w-fullcompiles to single CSS rule vs. per-component styling - •Standard Tailwind Classes: Enables aggressive tree-shaking; arbitrary values
w-[20px]create larger CSS output - •Staggered Animations: Intersection Observer only for visible elements; hidden items don't trigger updates
- •Vercel Build Ignore: Prevents unnecessary build time on feature branches (typical: 2-5 min saved per PR)
Responsive Testing Workflow Validated
- •5-Breakpoint Strategy: Test at 375px, 393px, 768px, 1024px, 1440px covers 95% of user devices
- •Playwright Viewport Automation: Setting exact viewport sizes more reliable than browser zoom
- •Component-Level Testing: Test individual components (cards, buttons) before full page screenshots
- •Regression Tracking: Save baseline screenshots to catch future changes quickly
- •Mobile-First Validation: Always start at 375px, verify then expand to larger viewports
Accessibility Wins
- •Icon Accessibility: useSimpleIcons hook integrates MUI Tooltip for screen reader announcements
- •Touch Targets: Verified 44x44px minimum maintained at all breakpoints (even with responsive sizing)
- •Color Contrast: Validated all text maintains WCAG AA compliance at responsive text sizes
- •Animation Respect: useStaggeredAnimation respects prefers-reduced-motion by showing all items immediately
Automation Opportunities (Jan 23 - v5.1 completion)
- •Responsive Testing Bot: CLI tool to generate Playwright screenshots at all 5 breakpoints automatically
- •Tailwind Validator: Pre-commit linter to flag arbitrary values (w-[20px]) in favor of standard classes
- •Icon Size Auditor: Scan codebase for inconsistent icon sizing; suggest standardization
- •Breakpoint Consistency: Linter to ensure all components use consistent md: and lg: breakpoints
- •Build Script Validator: Verify vercel.json references correct build ignore script
Session Learnings - January 22, 2026
GitHub CLI Workflow Automation
Session-specific patterns for v5.1 milestone and issue management:
- •Batch Milestone & Issue Creation: Create milestone with
gh milestone create, then batch issues with--milestoneflag - •Structured Issue Templates: Include Type, Affected Breakpoints, Current/Expected Behavior, Screenshots
- •PR Review Iteration: Use
gh api repos/:owner/:repo/pulls/:pr/commentsto fetch feedback, amend commits with--force-with-lease
Key Commands:
gh milestone create "v5.1" -d "Description" gh issue create --title "..." --milestone "v5.1" --label "bug,mobile" gh api repos/owner/repo/pulls/463/comments git commit --amend --no-edit && git push origin branch --force-with-lease
Mobile Responsive Design Debugging
- •
Issue: iframe Content Overflow on Mobile (375px-393px)
- •Root Cause: 400% scale technique doesn't account for mobile viewport constraints; content width exceeds container
- •Investigation: Playwright screenshots at exact mobile breakpoints revealed horizontal scroll
- •Fix Strategy:
- •Implement CSS
overflow: hiddenwith clipping on mobile - •Consider reducing scale percentage on mobile (e.g., 200% instead of 400%)
- •Alternative: Use viewport-aware scale calculation
- •Implement CSS
- •Prevention: Test iframes at 375px during development, not just desktop
- •Related Issue: #461 (GitHub mobile crashes)
- •
Issue: SmallProjectCard Tech Icons Overlap on Mobile
- •Root Cause: Fixed icon size (24px) + fixed gap (8px) don't reflow in narrow columns
- •Investigation: Tested at 375px showed icons compressing and overlapping text
- •Fix Strategy:
- •Add
flex-wrapfor icon container to allow wrapping - •Reduce icon size on mobile:
w-4 h-4 md:w-6 md:h-6 - •Or reduce gap on mobile:
gap-1 md:gap-2
- •Add
- •Prevention: Test all card components at 375px and 393px explicitly
- •Related Issue: #462 (SmallProjectCard icons mobile)
Responsive Testing Best Practices
- •
Breakpoint Testing Checklist
- •Test at exact breakpoints: 375px, 393px, 768px, 1024px, 1440px
- •Use Playwright for consistent viewport capture:
await page.setViewportSize({ width: 375, height: 667 }) - •Compare against previous screenshots to catch regressions
- •Document findings by component (LiveProjectCard, SmallProjectCard, IPhoneProFrame, etc.)
- •
Mobile-Specific Component Testing
- •Verify touch targets remain 44x44px at smaller viewports
- •Check text wrapping and line breaks at narrow widths
- •Test scrolling performance on actual mobile devices (canvas animations, iframe scrolling)
- •Verify images load correctly and don't cause layout shift
- •
Responsive CSS Gotchas
- •Using
w-fullon container doesn't guarantee content fits (check children widths) - •
gridlayouts can overflow on mobile; useflexor stack on mobile instead - •
fixedpositioning may behave differently on mobile (test with actual device) - •Negative margins (
-mt-12) can cause overflow on narrow screens
- •Using
Issue Tracking & Prioritization
- •
Categorization System
- •Bugs: Crashes, rendering errors, broken functionality → Fix immediately
- •Polish: Spacing, alignment, sizing refinements → Plan for release milestone
- •Enhancements: New features, additional states → Consider for future phases
- •Blockers: Breaks deployment or user experience → Highest priority
- •
Milestone Planning
- •Group related issues by feature/section (Section Animations, GitHub Activity, etc.)
- •Mark blocking dependencies with labels or issue references
- •Plan phases to enable parallel work (e.g., mobile fixes don't block desktop enhancements)
Pattern: Responsive Component Development Workflow
- •Design specification → Review at all 5 breakpoints
- •Initial implementation → Test at desktop (1440px)
- •Breakpoint testing → Capture screenshots at 375px, 768px, 1024px
- •Mobile debugging → Fix issues found at 375px-393px
- •Tablet validation → Ensure transitions are smooth at 768px
- •Documentation → Add breakpoint notes to component patterns
Mistakes & Fixes
- •
Issue: Developed components without testing on mobile; discovered iframe/icon issues only during design review
- •Root Cause: Focused on desktop-first development without validating mobile early
- •Fix: Created comprehensive breakpoint testing workflow before marking features complete
- •Prevention: Add "Test at 375px and 1440px" to component implementation checklist
- •
Issue: Issue documentation lacked breakpoint specificity; made triage harder
- •Root Cause: Didn't use structured template for capturing findings
- •Fix: Created GitHub issue template with "Affected Breakpoints" field
- •Prevention: Use template for all design review findings
Performance Notes
- •Mobile debugging with Playwright: Exact viewport sizes (375, 393) are critical; approximate sizes miss edge cases
- •Batch GitHub operations: Using CLI is 5x faster than web UI for 9+ issues
- •Screenshot comparison: Save with breakpoint labels for quick reference during implementation
Debugging Wins
- •
Problem: Understanding why SmallProjectCard icons overlap on mobile
- •Approach: Captured Playwright screenshots at 375px, compared to 1440px version, identified fixed sizing conflict
- •Tool/Technique: Playwright viewport sizing + screenshot comparison
- •
Problem: Identifying all mobile-specific issues in one systematic review
- •Approach: Tested all major components at each breakpoint (375, 393, 768, 1024, 1440px) using Playwright
- •Tool/Technique: Documented findings by component in structured issue format
Automation Opportunities (Updated)
- •Playwright Breakpoint Testing Script: Create reusable script that tests all components at standard breakpoints
- •GitHub CLI Batch Issues: Template for creating milestone with related issues from findings
- •Screenshot Comparison Tool: Automated visual diff between breakpoints to catch regressions
- •Mobile Audit Checklist: Linter rule to flag fixed-size components that should be responsive
- •Issue Template Generator: CLI tool to create properly formatted issues with breakpoint metadata