Files
Evandro Fonseca Junior 08dd046887 feat: add animated components for enhanced UI/UX
- Introduced DrawLine component for drawing animations.
- Added LazyRevealImage component for lazy loading images with reveal animations.
- Implemented TextDiffuseReveal for animated text reveals with staggered effects.
- Created TopProgressBar for a dynamic loading indicator.
- Added PointCloud mock for testing animations.
- Developed useReducedMotion hook to handle user preferences for reduced motion.
- Centralized animation configurations in config.ts for easier management.
- Defined animation variants in variants.ts for consistent animation effects.
- Established AnimationProvider to supply global animation settings.
- Created tests for AnimationProvider and useReducedMotion hook.
- Added SVG assets for branding and UI elements.
- Documented frontend review plan and hero circuit board animation plan.
2026-03-15 21:06:43 -03:00

538 lines
16 KiB
Markdown

# Design Document: Remove Preloader and Adjust Hero
## Overview
This design addresses improvements to the initial user experience by removing the Preloader component and optimizing the Hero section for better readability and visual balance. The changes focus on immediate content access while maintaining the sophisticated animation system and responsive behavior.
### Goals
- Eliminate loading delay by removing the Preloader component entirely
- Improve text readability by reducing Hero font sizes across all breakpoints
- Enhance visual balance by adjusting Logo positioning and sizing
- Preserve all existing animation timing and responsive behaviors
- Maintain accessibility and reduced motion preferences
### Non-Goals
- Modifying the Hero typing animation logic or phase transitions
- Changing the color scheme or design system variables
- Altering other components beyond Page, Hero, and removing Preloader
- Implementing new animation effects or transitions
## Architecture
### Component Structure
The implementation involves three main areas:
1. **Page Component (app/page.tsx)**
- Remove Preloader import and rendering
- Remove body overflow manipulation side effects
- Maintain existing component hierarchy for other sections
2. **Hero Component (app/components/Hero.tsx)**
- Adjust text size classes for all responsive breakpoints
- Modify Logo container sizing and positioning
- Preserve animation state machine and timing logic
3. **Preloader Component (app/components/Preloader.tsx)**
- Delete file entirely from codebase
### Animation Flow
The Hero animation sequence remains unchanged:
```
typing → idle → glitch → logo → (loop back to typing)
```
Timing preserved:
- Typing: 120ms per character
- Idle pause: 1200ms
- Glitch transition: 100ms
- Glitch duration: 600ms
- Logo display: 3500ms
### Responsive Breakpoints
The design maintains Tailwind's standard breakpoints:
- Mobile (default): < 640px
- sm: ≥ 640px
- md: ≥ 768px
- lg: ≥ 1024px
- xl: ≥ 1280px
## Components and Interfaces
### Page Component Changes
**Current Implementation:**
```typescript
export default function Home() {
return (
<div className="flex min-h-screen flex-col overflow-x-hidden">
<Preloader />
<main className="ds-main">
<Hero />
{/* other components */}
</main>
</div>
);
}
```
**New Implementation:**
```typescript
export default function Home() {
return (
<div className="flex min-h-screen flex-col overflow-x-hidden">
<main className="ds-main">
<Hero />
{/* other components */}
</main>
</div>
);
}
```
**Changes:**
- Remove `import { Preloader } from "./components/Preloader";`
- Remove `<Preloader />` from JSX
- No other modifications needed
### Hero Component Changes
#### Text Size Adjustments
**Current Classes:**
```typescript
className="absolute leading-[0.9] tracking-[-0.01em] text-[13vw] sm:text-7xl md:text-[6rem] lg:text-[7rem] xl:text-[8rem]"
```
**New Classes:**
```typescript
className="absolute leading-[0.9] tracking-[-0.01em] text-[10vw] sm:text-[4rem] md:text-[4.5rem] lg:text-[5rem] xl:text-[5.5rem]"
```
**Size Comparison:**
| Breakpoint | Current | New | Reduction |
|------------|---------|-----|-----------|
| Mobile | 13vw | 10vw | ~23% |
| sm | 4.5rem (72px) | 4rem (64px) | ~11% |
| md | 6rem (96px) | 4.5rem (72px) | ~25% |
| lg | 7rem (112px) | 5rem (80px) | ~29% |
| xl | 8rem (128px) | 5.5rem (88px) | ~31% |
#### Logo Positioning Adjustments
**Current Implementation:**
```typescript
<Logo
variant="mark"
size="xl"
theme="dark"
className="h-auto w-[80%] max-w-[320px] drop-shadow-[0_0_20px_rgba(204,255,0,0.34)]"
/>
```
**New Implementation:**
```typescript
<Logo
variant="mark"
size="xl"
theme="dark"
className="h-auto w-[65%] max-w-[280px] drop-shadow-[0_0_20px_rgba(204,255,0,0.34)]"
/>
```
**Changes:**
- Width: 80% → 65% (better proportion to reduced text)
- Max-width: 320px → 280px (improved visual balance)
- All other properties preserved (drop-shadow, theme, variant)
#### Container Height Adjustments
**Current Implementation:**
```typescript
className="relative mb-sp-6 flex min-h-[40vw] items-center sm:min-h-[220px] md:min-h-[280px] lg:min-h-[350px]"
```
**Recommended New Implementation:**
```typescript
className="relative mb-sp-6 flex min-h-[35vw] items-center sm:min-h-[200px] md:min-h-[240px] lg:min-h-[280px]"
```
**Rationale:**
- Proportional reduction to match smaller text sizes
- Maintains vertical rhythm and spacing
- Prevents excessive whitespace around smaller text
### Preloader Component Removal
**Actions:**
1. Delete `app/components/Preloader.tsx`
2. Remove all imports of Preloader from other files
3. No replacement component needed
**Side Effects Eliminated:**
- Body overflow manipulation (`document.body.style.overflow = "hidden"`)
- Window scroll reset (`window.scrollTo(0, 0)`)
- Progress tracking state and timers
- AnimatePresence exit animations
## Data Models
No data model changes required. This feature only modifies:
- Component rendering logic
- CSS classes for styling
- File system structure (deletion)
### State Management
Hero component state remains unchanged:
```typescript
const [phase, setPhase] = useState<"typing" | "idle" | "glitch" | "logo">("typing");
const [typedCount, setTypedCount] = useState(0);
const [charStagger, setCharStagger] = useState(0.03);
```
No new state introduced. Preloader state is removed entirely.
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Animation Phase Sequence Preservation
*For any* Hero component render, the animation phases should transition in the correct sequence: typing → idle → glitch → logo → (loop back to typing), with each phase completing before the next begins.
**Validates: Requirements 4.1, 4.2**
### Property 2: Reduced Motion Adaptation
*For any* user with reduced motion preferences enabled, the Hero component should render with simplified animations that exclude blur and scale effects while maintaining opacity-based transitions.
**Validates: Requirements 4.3, 4.4**
### Property 3: Responsive Character Stagger Timing
*For any* viewport width, the Hero component should apply character stagger timing of 0.015s for mobile viewports (< 768px) and 0.03s for desktop viewports (≥ 768px).
**Validates: Requirements 5.3**
### Property 4: Responsive Text Sizing
*For any* viewport width, the Hero text should apply the appropriate font size class corresponding to the active breakpoint: 10vw (mobile), 4rem (sm), 4.5rem (md), 5rem (lg), or 5.5rem (xl).
**Validates: Requirements 2.2, 2.3, 2.4, 2.5, 2.6**
## Error Handling
### Component Rendering Errors
**Scenario:** Hero component fails to render due to missing dependencies or runtime errors.
**Handling:**
- React error boundaries should catch rendering errors
- Fallback UI should display gracefully
- Console errors should provide clear debugging information
**Implementation:**
```typescript
// No changes to error handling needed
// Existing React error boundary patterns apply
```
### Animation State Errors
**Scenario:** Animation phase transitions fail or get stuck in an invalid state.
**Handling:**
- useEffect cleanup functions prevent memory leaks from dangling timers
- Phase state is constrained to valid values via TypeScript union type
- Component remounts reset state to initial "typing" phase
**Prevention:**
```typescript
// Existing cleanup in Hero component
useEffect(() => {
let timeout: NodeJS.Timeout;
// ... animation logic
return () => clearTimeout(timeout); // Prevents memory leaks
}, [phase, typedCount]);
```
### Media Query Errors
**Scenario:** window.matchMedia is unavailable or returns unexpected values.
**Handling:**
- Default to desktop stagger timing (0.03s) if media query fails
- useEffect cleanup removes event listeners properly
- Component continues to function with default timing
**Robustness:**
```typescript
// Existing implementation handles errors gracefully
const media = window.matchMedia("(max-width: 768px)");
const updateStagger = () => setCharStagger(media.matches ? 0.015 : 0.03);
```
### Missing Component Errors
**Scenario:** Logo component import fails or is unavailable.
**Handling:**
- TypeScript compilation will catch missing imports
- Build process will fail before deployment
- No runtime handling needed (build-time error)
## Testing Strategy
### Dual Testing Approach
This feature requires both unit tests and property-based tests to ensure comprehensive coverage:
- **Unit tests**: Verify specific rendering outputs, CSS classes, and component structure
- **Property tests**: Verify animation behavior, state transitions, and responsive adaptations across many inputs
### Unit Testing
**Focus Areas:**
1. Page component structure (Preloader removal)
2. Hero text size classes at each breakpoint
3. Logo sizing and positioning classes
4. Animation timing constants
5. Reduced motion configuration
**Example Unit Tests:**
```typescript
describe('Page Component', () => {
it('should not render Preloader component', () => {
render(<Home />);
expect(screen.queryByRole('img', { name: /Evandro Dev Logo/i })).not.toBeInTheDocument();
});
it('should render Hero component immediately', () => {
render(<Home />);
expect(screen.getByRole('region', { name: /home/i })).toBeInTheDocument();
});
it('should not set body overflow to hidden', () => {
render(<Home />);
expect(document.body.style.overflow).not.toBe('hidden');
});
});
describe('Hero Text Sizing', () => {
it('should apply reduced font sizes across all breakpoints', () => {
render(<Hero />);
const heading = screen.getByRole('heading', { level: 1 });
const classes = heading.className;
expect(classes).toContain('text-[10vw]');
expect(classes).toContain('sm:text-[4rem]');
expect(classes).toContain('md:text-[4.5rem]');
expect(classes).toContain('lg:text-[5rem]');
expect(classes).toContain('xl:text-[5.5rem]');
});
it('should maintain line-height and tracking', () => {
render(<Hero />);
const heading = screen.getByRole('heading', { level: 1 });
const classes = heading.className;
expect(classes).toContain('leading-[0.9]');
expect(classes).toContain('tracking-[-0.01em]');
});
});
describe('Logo Positioning', () => {
it('should apply reduced width and max-width to Logo', () => {
render(<Hero />);
// Wait for logo phase
act(() => {
jest.advanceTimersByTime(5000);
});
const logo = screen.getByRole('img', { name: /Evandro mark logo/i });
const classes = logo.className;
expect(classes).toContain('w-[65%]');
expect(classes).toContain('max-w-[280px]');
});
});
```
### Property-Based Testing
**Library:** fast-check (for TypeScript/JavaScript)
**Configuration:**
- Minimum 100 iterations per property test
- Each test tagged with feature name and property reference
**Property Test 1: Animation Phase Sequence**
```typescript
import fc from 'fast-check';
describe('Property 1: Animation Phase Sequence Preservation', () => {
it('should transition through phases in correct order', async () => {
// Feature: remove-preloader-adjust-hero, Property 1: Animation phases should transition typing → idle → glitch → logo → typing
await fc.assert(
fc.asyncProperty(
fc.nat(10), // Random starting iteration
async (startIteration) => {
const { result } = renderHook(() => {
const [phase, setPhase] = useState<"typing" | "idle" | "glitch" | "logo">("typing");
// ... hook implementation
return phase;
});
const phases: string[] = [];
// Capture phase transitions over time
for (let i = 0; i < 4; i++) {
phases.push(result.current);
await act(async () => {
jest.advanceTimersByTime(6000); // Full cycle time
});
}
// Verify sequence pattern
const expectedPattern = ["typing", "idle", "glitch", "logo"];
expect(phases.slice(0, 4)).toEqual(expectedPattern);
}
),
{ numRuns: 100 }
);
});
});
```
**Property Test 2: Reduced Motion Adaptation**
```typescript
describe('Property 2: Reduced Motion Adaptation', () => {
it('should simplify animations when reduced motion is preferred', async () => {
// Feature: remove-preloader-adjust-hero, Property 2: Reduced motion should exclude blur and scale effects
await fc.assert(
fc.asyncProperty(
fc.boolean(), // Random reduced motion preference
async (shouldReduce) => {
jest.mock('framer-motion', () => ({
...jest.requireActual('framer-motion'),
useReducedMotion: () => shouldReduce,
}));
const { container } = render(<Hero />);
const motionElements = container.querySelectorAll('[style*="filter"], [style*="scale"]');
if (shouldReduce) {
// Should not have blur or scale in inline styles
motionElements.forEach(el => {
const style = el.getAttribute('style') || '';
expect(style).not.toContain('blur');
expect(style).not.toContain('scale');
});
}
}
),
{ numRuns: 100 }
);
});
});
```
**Property Test 3: Responsive Character Stagger**
```typescript
describe('Property 3: Responsive Character Stagger Timing', () => {
it('should apply correct stagger timing based on viewport width', async () => {
// Feature: remove-preloader-adjust-hero, Property 3: Character stagger should be 0.015s mobile, 0.03s desktop
await fc.assert(
fc.asyncProperty(
fc.integer({ min: 320, max: 1920 }), // Random viewport width
async (viewportWidth) => {
global.innerWidth = viewportWidth;
const { result } = renderHook(() => {
const [charStagger, setCharStagger] = useState(0.03);
useEffect(() => {
const media = window.matchMedia("(max-width: 768px)");
const updateStagger = () => setCharStagger(media.matches ? 0.015 : 0.03);
updateStagger();
media.addEventListener("change", updateStagger);
return () => media.removeEventListener("change", updateStagger);
}, []);
return charStagger;
});
const expectedStagger = viewportWidth <= 768 ? 0.015 : 0.03;
expect(result.current).toBe(expectedStagger);
}
),
{ numRuns: 100 }
);
});
});
```
**Property Test 4: Responsive Text Sizing**
```typescript
describe('Property 4: Responsive Text Sizing', () => {
it('should apply appropriate font size class for any viewport', async () => {
// Feature: remove-preloader-adjust-hero, Property 4: Text size should match breakpoint
await fc.assert(
fc.asyncProperty(
fc.integer({ min: 320, max: 1920 }), // Random viewport width
async (viewportWidth) => {
global.innerWidth = viewportWidth;
render(<Hero />);
const heading = screen.getByRole('heading', { level: 1 });
const classes = heading.className;
// All responsive classes should be present
expect(classes).toContain('text-[10vw]');
expect(classes).toContain('sm:text-[4rem]');
expect(classes).toContain('md:text-[4.5rem]');
expect(classes).toContain('lg:text-[5rem]');
expect(classes).toContain('xl:text-[5.5rem]');
// Tailwind will apply the correct one based on viewport
// This verifies all breakpoint classes are present
}
),
{ numRuns: 100 }
);
});
});
```
### Test Coverage Goals
- **Unit Test Coverage**: 100% of modified components (Page, Hero)
- **Property Test Coverage**: All 4 correctness properties implemented
- **Integration Testing**: Manual verification of visual appearance and animation smoothness
- **Accessibility Testing**: Verify reduced motion preferences work correctly
### Testing Tools
- **Unit Testing**: Jest + React Testing Library
- **Property Testing**: fast-check
- **Visual Testing**: Manual review in browser at all breakpoints
- **Accessibility**: Manual testing with reduced motion system preference