diff --git a/doc/API.md b/doc/API.md index cf524be..1f0368b 100644 --- a/doc/API.md +++ b/doc/API.md @@ -148,15 +148,53 @@ For comprehensive documentation of each endpoint, request/response formats, and --- +## Architecture + +### Route Organization + +All routes and middleware configuration are centralized in `internal/routes/routes.go` for clean separation of concerns: + +```go +// internal/routes/routes.go +func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler) http.Handler { + mux := http.NewServeMux() + + // Public routes + mux.HandleFunc("/", cvHandler.Home) + mux.HandleFunc("/cv", cvHandler.CVContent) + mux.HandleFunc("/health", healthHandler.Check) + + // Protected PDF endpoint (rate limited + origin checked) + // Static files with cache control + // Middleware chain (Recovery → Logger → SecurityHeaders) + + return handler +} +``` + +This architecture provides: +- ✅ Single source of truth for routes +- ✅ Clear middleware chain visibility +- ✅ Easy route management and testing +- ✅ Clean separation from main.go + +### Data Loading + +**Simplified Architecture** (cache removed as of v1.1.0): +- JSON files loaded directly from disk on each request +- No caching layer (over-engineering for static CV data) +- Go's built-in file system caching is sufficient +- Data only changes on deployment/restart + ## Endpoints Overview -| Method | Path | Description | HTMX Support | -|--------|------|-------------|--------------| -| GET | `/` | Full CV page (home) | ❌ No | -| GET | `/cv` | CV content partial | ✅ Yes | -| GET | `/export/pdf` | PDF export | ❌ No | -| GET | `/health` | Health check | ❌ No | -| GET | `/static/*` | Static files (CSS, JS, images) | ❌ No | +| Method | Path | Description | HTMX Support | Protection | +|--------|------|-------------|--------------|------------| +| GET | `/` | Full CV page (home) | ❌ No | None | +| GET | `/cv` | CV content partial | ✅ Yes | None | +| GET | `/export/pdf` | PDF export | ❌ No | ✅ Rate Limited + Origin Check | +| GET | `/health` | Health check | ❌ No | None | +| GET | `/static/*` | Static files (CSS, JS, images) | ❌ No | Cache Control | --- @@ -458,7 +496,7 @@ No special headers required. ```json { "status": "ok", - "timestamp": "2025-11-09T14:32:45.123Z", + "timestamp": "2025-11-12T17:49:00.123Z", "version": "1.0.0" } ``` @@ -471,6 +509,8 @@ No special headers required. | `timestamp` | string | ISO 8601 timestamp of the request | | `version` | string | Application version from main.go | +**Note:** Cache statistics have been removed as of v1.1.0 (caching layer eliminated for simplicity). + #### Examples **curl:** @@ -1253,9 +1293,11 @@ For comprehensive protection documentation, see [SECURITY.md](SECURITY.md#api-pr ## Rate Limiting -**Current State:** Not implemented +**Current State:** ✅ **Fully Implemented** (as of v1.1.0) -### Recommended Implementation +### Current Implementation + +Rate limiting is configured in `internal/routes/routes.go` and applied via middleware: #### Per-Endpoint Limits @@ -1925,6 +1967,7 @@ go tool trace trace.out | Version | Date | Changes | |---------|------|---------| +| 1.1.0 | 2025-11-12 | Route extraction to internal/routes, cache removal, rate limiting implementation | | 1.0.0 | 2025-11-09 | Initial release with Go rewrite | ### Related Documentation @@ -1940,6 +1983,6 @@ go tool trace trace.out --- -**Last Updated:** November 9, 2025 -**API Version:** 1.0.0 -**Documentation Version:** 1.0.0 +**Last Updated:** November 12, 2025 +**API Version:** 1.1.0 +**Documentation Version:** 1.1.0 diff --git a/doc/USER_GUIDE.md b/doc/USER_GUIDE.md new file mode 100644 index 0000000..c2533b9 --- /dev/null +++ b/doc/USER_GUIDE.md @@ -0,0 +1,633 @@ +# User Guide - CV Website Features + +This guide provides comprehensive documentation for all interactive features of the CV website. + +## Table of Contents + +- [Zoom Feature](#zoom-feature) +- [Language Switching](#language-switching) +- [CV Customization](#cv-customization) +- [PDF Export](#pdf-export) +- [Navigation](#navigation) +- [Accessibility](#accessibility) + +--- + +## Zoom Feature + +The CV includes an intelligent zoom control for comfortable viewing at any size. + +### Overview + +The zoom feature allows you to adjust the CV display size from 25% (bird's-eye view) to 175% (enhanced readability) while keeping navigation controls at a consistent, accessible size. + +### How to Use + +#### Visual Control + +The zoom control appears as a draggable widget near the bottom of the screen (desktop only). + +**Components:** +- **Slider**: Drag anywhere between 25% and 175% zoom +- **Min/Max Labels**: Show the range (25% and 175%) +- **Reset Button**: Circular button showing current zoom percentage +- **Close Button**: Small X button (top-right corner) to hide the control + +**Interaction:** +1. **Adjust Zoom**: Click and drag the slider to your preferred zoom level +2. **Reset to Default**: Click the circular button (shows current %) to return to 100% +3. **Reposition Control**: Click and drag the control itself (not the slider/buttons) to move it anywhere on screen +4. **Hide Control**: Click the X button to hide the control +5. **Show Control**: Access from hamburger menu (☰) → Select "Zoom" + +#### Keyboard Shortcuts + +The zoom feature supports standard browser-style keyboard shortcuts: + +| Shortcut | Action | Increment | +|----------|--------|-----------| +| `Ctrl/Cmd + Plus (+)` | Zoom In | +10% | +| `Ctrl/Cmd + Minus (-)` | Zoom Out | -10% | +| `Ctrl/Cmd + 0` | Reset to Default | 100% | + +**Note**: These shortcuts work regardless of whether the zoom control is visible. + +#### Menu Access + +1. Click the **hamburger menu (☰)** in the top-left corner +2. Hover or click **"Zoom"** menu item +3. The zoom control will appear +4. Use the control as described above + +### What Zooms vs. What Stays Fixed + +#### Elements That Zoom (Content) + +The following elements scale according to the zoom level: +- All CV content (text, headings, paragraphs) +- Images and icons within the CV +- CV paper and layout structure +- Section spacing and margins +- All typography and line heights + +**Behavior at >100% zoom:** +- Content extends beyond viewport width +- Horizontal scrollbar appears +- Natural overflow allows full-size viewing +- Matches browser zoom behavior + +#### Elements That Stay Fixed (UI Controls) + +The following elements maintain consistent size regardless of zoom: +- **Action Bar**: Top black bar with all buttons +- **Hamburger Menu**: Navigation menu and all menu items +- **Zoom Control**: The zoom widget itself +- **Back-to-Top Button**: Bottom-right corner button +- **Info Button**: Information modal trigger +- **Footer**: Bottom footer remains at normal size + +**Why fixed UI?** +- **Accessibility**: Controls stay at standard touch target size (44x44px) +- **Usability**: Buttons never become too large or too small +- **Consistency**: Predictable interaction at any zoom level +- **Navigation**: Always accessible regardless of content zoom state + +### Design Rationale + +#### Zoom Range: 25-175% + +**25% - Bird's Eye View:** +- See entire CV structure at once +- Great for navigation and overview +- Check layout balance and spacing +- Quick section location +- Ideal for presentations or large screens + +**100% - Optimal Reading:** +- Default and recommended setting +- Calibrated for comfortable reading +- Optimal font rendering and line length +- Balanced for most screen sizes +- Professional presentation view + +**175% - Enhanced Readability:** +- Enhanced readability for detailed review +- Accessibility support for vision needs +- Close inspection of specific content +- Better for small or high-DPI screens +- Reduced eye strain for long reading sessions + +#### Fixed UI Elements (Inverse Zoom Technique) + +**Technical Implementation:** +Fixed buttons use `zoom: 1/zoomLevel` to counteract content zoom. + +**Example:** +- Content zoom: 150% (`zoom: 1.5`) +- Button inverse: 67% (`zoom: 0.67`) +- Result: Button stays at original size + +**Benefits:** +- Buttons remain at accessibility-compliant size (44x44px) +- Prevents unusably large buttons at 175% zoom +- Prevents unusably small buttons at 25% zoom +- Maintains muscle memory for button positions +- Consistent visual hierarchy + +#### Horizontal Scroll Behavior + +**Why horizontal scroll appears:** +- Content naturally extends beyond viewport at >100% zoom +- Allows viewing full-size content without compression +- Matches expected browser zoom behavior +- Prevents text reflow issues +- Maintains layout integrity + +**User Experience:** +- Smooth scrolling on all devices +- Keyboard navigation supported (arrow keys) +- Mouse wheel scrolling (shift + wheel) +- Trackpad gestures work naturally +- Does not interfere with vertical scrolling + +### Optimal Viewing Tips + +#### Quick Overview (25-75% Zoom) + +**Best For:** +- Getting familiar with CV structure +- Locating specific sections quickly +- Checking overall layout balance +- Comparing multiple sections +- Presentation mode on large screens + +**Recommended Settings:** +- Zoom: 50-75% +- View: Full screen +- Navigation: Use section links + +#### Comfortable Reading (100% Zoom) + +**Best For:** +- Primary reading experience +- Professional presentation +- Standard review and evaluation +- Sharing with others +- PDF export preparation + +**Recommended Settings:** +- Zoom: 100% (default) +- View: Full screen or windowed +- Font rendering: Optimal + +#### Detailed Review (125-175% Zoom) + +**Best For:** +- Enhanced readability needs +- Close inspection of content +- Accessibility requirements +- Small screen viewing +- Detailed proofreading + +**Recommended Settings:** +- Zoom: 125-150% +- Use horizontal scroll to navigate +- Focus on one section at a time +- Combine with section navigation + +### Persistence and Storage + +**Automatic Save:** +- Your zoom level is automatically saved to browser's localStorage +- Returns to your preferred zoom when you revisit +- No manual save required + +**Per-Device Storage:** +- Each device/browser remembers its own zoom preference +- Desktop and mobile can have different settings +- Private/Incognito mode does not persist zoom + +**Storage Keys:** +- `cv-zoom`: Current zoom percentage (25-175) +- `cv-zoom-position`: Custom position if dragged +- `cv-zoom-visible`: Whether control is shown/hidden + +**Clear Storage:** +To reset zoom preferences: +1. Browser DevTools → Application → Local Storage +2. Find keys starting with `cv-zoom` +3. Delete to reset to defaults + +### Mobile Behavior + +**Automatic Handling:** +- Zoom control is **hidden on mobile devices** (screens ≤768px) +- Mobile browsers have native pinch-to-zoom +- Native zoom works better on small screens +- Touch gestures are more intuitive on mobile + +**Why hidden on mobile?** +- Limited screen space for controls +- Native pinch-to-zoom is superior +- Touch targets would be too small +- Desktop-oriented feature +- Mobile viewport already optimized + +**Mobile Alternatives:** +- Use browser's native pinch-to-zoom +- Double-tap to zoom sections +- Use browser zoom in/out buttons + +### Accessibility Features + +#### Keyboard Support + +**Full Keyboard Navigation:** +- `Tab`: Navigate to zoom control +- `Space`/`Enter`: Activate reset button +- `Arrow Keys`: Adjust slider (when focused) +- `Ctrl/Cmd + Plus/Minus/0`: Global shortcuts +- `Esc`: Close menu/modals + +#### Screen Reader Support + +**ARIA Attributes:** +```html + +``` + +**Announcements:** +- Current zoom level announced on change +- Reset button announces "Reset zoom to 100%" +- Slider range clearly communicated +- Interactive elements properly labeled + +#### Visual Accessibility + +**High Contrast:** +- Slider track: Clear contrast ratio +- Button borders: Visible in all modes +- Text labels: White on semi-transparent background +- Focus indicators: 2px white outline + +**Focus Indicators:** +- All interactive elements have visible focus +- Keyboard navigation clearly indicated +- Tab order follows logical flow + +**Touch Targets:** +- All buttons meet 44x44px minimum +- Slider thumb: 18px diameter +- Close button: 24px diameter +- Reset button: 44px diameter + +### Troubleshooting + +#### Zoom Not Working + +**Symptoms:** +- Slider moves but nothing happens +- Keyboard shortcuts don't work +- Page doesn't zoom + +**Solutions:** +1. Check browser console for JavaScript errors +2. Ensure JavaScript is enabled +3. Try hard refresh: `Ctrl/Cmd + Shift + R` +4. Clear browser cache and reload +5. Check if browser zoom is interfering (reset to 100%) + +#### Zoom Control Not Visible + +**Symptoms:** +- Can't find zoom control on page +- Control disappeared + +**Solutions:** +1. Check if you're on mobile (control hidden <768px) +2. Open hamburger menu → Click "Zoom" to show +3. Check localStorage: `cv-zoom-visible` should be `true` +4. Control may be positioned off-screen (drag from edge) +5. Refresh page to reset position + +#### Zoom Resets on Refresh + +**Symptoms:** +- Zoom goes back to 100% every time +- Preference not saving + +**Solutions:** +1. Check if localStorage is enabled in browser +2. Ensure you're not in Private/Incognito mode +3. Check browser privacy settings +4. Try a different browser to isolate issue + +#### Horizontal Scroll Issues + +**Symptoms:** +- Can't scroll horizontally when zoomed +- Content cut off at edges + +**Solutions:** +1. Ensure body overflow-x is set to auto +2. Try Shift + Mouse Wheel for horizontal scroll +3. Use keyboard arrow keys +4. Check if browser extensions are interfering + +#### Fixed Buttons Look Wrong + +**Symptoms:** +- Buttons too large or too small +- Buttons scaling with content + +**Solutions:** +1. Check if CSS zoom property is supported +2. Verify browser compatibility (all modern browsers) +3. Check for conflicting CSS from extensions +4. Try disabling browser themes/extensions + +### Technical Details + +#### Implementation + +**CSS Zoom Property:** +```css +.zoom-wrapper { + zoom: 1.25; /* 125% zoom example */ +} +``` + +**Inverse Zoom for Fixed Elements:** +```css +.fixed-button { + zoom: 0.8; /* 1/1.25 = 0.8 to counteract 125% */ +} +``` + +**Horizontal Overflow:** +```css +body { + overflow-x: auto; /* Allow horizontal scroll */ +} + +.zoom-wrapper { + min-width: 100vw; /* When zoomed > 100% */ +} +``` + +#### JavaScript API + +**Key Functions:** +```javascript +// Apply zoom level +applyZoom(zoomValue, saveToStorage) + +// Increment/decrement zoom +incrementZoom(step) + +// Update display +updateZoomDisplay(zoomValue) + +// Check mobile view +isMobileView() +``` + +**Event Listeners:** +- `input`: Real-time slider updates +- `click`: Reset button handler +- `keydown`: Global keyboard shortcuts +- `resize`: Handle viewport changes + +#### Browser Compatibility + +**Supported Browsers:** +- Chrome/Edge 90+ (Full support) +- Firefox 90+ (Full support) +- Safari 13+ (Full support) +- Opera 76+ (Full support) + +**CSS Zoom Support:** +- Webkit/Blink: Native support +- Firefox: Supported since Firefox 90 +- All modern browsers support CSS zoom + +**Fallback:** +- No fallback needed (all modern browsers) +- For older browsers, zoom would simply not work +- Page remains fully functional without zoom + +--- + +## Language Switching + +The CV supports instant language switching between English and Spanish without page reload. + +### How to Use + +**From Action Bar:** +1. Click language flag icon in top bar +2. Select English or Spanish +3. Content swaps instantly via HTMX + +**From Menu:** +1. Open hamburger menu (☰) +2. Hover over "Language" item +3. Select preferred language + +**Via URL:** +- English: `?lang=en` +- Spanish: `?lang=es` + +**Persistence:** +- Language preference saved to localStorage +- Returns to your preferred language on next visit +- Works across all pages + +--- + +## CV Customization + +### CV Length Toggle + +**Short Version:** +- Concise overview +- Key highlights only +- 1-2 pages when printed +- Quick review + +**Long Version:** +- Complete details +- Full project descriptions +- Comprehensive experience +- In-depth review + +**Toggle:** +1. Action bar toggle switch +2. Hamburger menu → "Long Version" +3. Preference persisted to localStorage + +### Logo Visibility + +**Show Logos:** +- Company and technology logos visible +- Visual brand recognition +- More engaging presentation + +**Hide Logos:** +- Text-only presentation +- Cleaner, more formal look +- Better for print + +**Toggle:** +1. Action bar toggle switch +2. Hamburger menu → "Show Logos" +3. Preference persisted to localStorage + +### Theme Switching + +**Default Theme:** +- Gray background with paper texture +- Sidebars with contact info +- Header and footer visible +- Full visual design + +**Clean Theme:** +- White background +- No sidebars +- Minimal chrome +- Print-optimized + +**Toggle:** +1. Action bar toggle switch +2. Hamburger menu → "Clean Theme" +3. Preference persisted to localStorage + +--- + +## PDF Export + +### Server-Side Generation + +**Recommended Method:** +1. Click "Download as PDF" in action bar +2. Server generates PDF using headless Chrome +3. File downloads automatically +4. Filename: `CV-Juan-Andres-Moreno-Rubio-{lang}.pdf` + +**Advantages:** +- Consistent rendering across platforms +- Perfect font rendering +- No browser compatibility issues +- Professional quality output + +### Browser Print + +**Alternative Method:** +1. Click "Print Friendly" in action bar +2. Browser print dialog opens +3. Select "Save as PDF" +4. Configure print settings + +**Settings:** +- Switches to clean theme +- Forces short version +- Removes interactive elements +- Optimized for printing + +--- + +## Navigation + +### Section Navigation + +**Table of Contents:** +- Auto-generated from CV sections +- Click any section to scroll smoothly +- Header stays visible during navigation + +**Hamburger Menu:** +- Mobile-friendly navigation +- Hover to open on desktop +- Click to open on mobile +- Automatic closing after selection + +**Back to Top:** +- Appears after scrolling 300px +- Smooth scroll to top +- Always accessible +- Bottom-right position + +### Collapsible Sections + +**Details/Summary:** +- Click section headers to expand/collapse +- Mobile accordion style +- Desktop always expanded + +**Expand/Collapse All:** +- Action bar buttons +- Hamburger menu options +- Affects all collapsible sections + +--- + +## Accessibility + +### Keyboard Navigation + +**Tab Order:** +- Logical flow through all interactive elements +- Visible focus indicators +- Skip links for main content +- All features accessible via keyboard + +### Screen Readers + +**ARIA Labels:** +- All interactive elements labeled +- Form controls properly described +- Dynamic content announcements +- Semantic HTML structure + +### Visual Accessibility + +**Color Contrast:** +- WCAG AA compliant +- High contrast mode support +- Focus indicators visible + +**Font Scaling:** +- Responsive typography +- Works with browser zoom +- Readable at all sizes + +**Motion Preferences:** +- Respects prefers-reduced-motion +- Smooth scrolling optional +- Animations can be disabled + +### Touch Targets + +**Minimum Sizes:** +- All buttons: 44x44px +- Touch-friendly spacing +- No overlapping targets +- Adequate margins + +--- + +## Support + +For issues, questions, or feedback: +- GitHub Issues: https://github.com/txemac/cv/issues +- Documentation: See /doc folder +- Live Demo: https://juan.andres.morenorub.io/ + +**Last Updated:** 2025-11-12 diff --git a/internal/cache/cv_cache.go b/internal/cache/cv_cache.go deleted file mode 100644 index 027a38f..0000000 --- a/internal/cache/cv_cache.go +++ /dev/null @@ -1,189 +0,0 @@ -package cache - -import ( - "fmt" - "log" - "sync" - "time" -) - -// CacheEntry represents a cached item with expiration -type cacheEntry struct { - data interface{} - expiration time.Time -} - -// CVCache provides thread-safe caching for CV and UI data -type CVCache struct { - mu sync.RWMutex - entries map[string]*cacheEntry - ttl time.Duration - stats CacheStats -} - -// CacheStats tracks cache performance metrics -type CacheStats struct { - mu sync.RWMutex - Hits int64 - Misses int64 - Size int -} - -// New creates a new CVCache with the specified TTL -func New(ttl time.Duration) *CVCache { - if ttl <= 0 { - ttl = 1 * time.Hour // Default TTL - } - - cache := &CVCache{ - entries: make(map[string]*cacheEntry), - ttl: ttl, - stats: CacheStats{}, - } - - // Start background cleanup goroutine - go cache.cleanupExpired() - - return cache -} - -// Get retrieves an item from the cache -func (c *CVCache) Get(key string) (interface{}, bool) { - c.mu.RLock() - entry, exists := c.entries[key] - c.mu.RUnlock() - - if !exists { - c.recordMiss() - return nil, false - } - - // Check if expired - if time.Now().After(entry.expiration) { - c.mu.Lock() - delete(c.entries, key) - c.mu.Unlock() - c.recordMiss() - return nil, false - } - - c.recordHit() - return entry.data, true -} - -// Set stores an item in the cache with TTL -func (c *CVCache) Set(key string, data interface{}) { - c.mu.Lock() - defer c.mu.Unlock() - - c.entries[key] = &cacheEntry{ - data: data, - expiration: time.Now().Add(c.ttl), - } - - c.updateSize() -} - -// Invalidate removes a specific key from cache -func (c *CVCache) Invalidate(key string) { - c.mu.Lock() - defer c.mu.Unlock() - - delete(c.entries, key) - c.updateSize() -} - -// InvalidateAll clears the entire cache -func (c *CVCache) InvalidateAll() { - c.mu.Lock() - defer c.mu.Unlock() - - c.entries = make(map[string]*cacheEntry) - c.updateSize() - log.Println("🗑️ Cache invalidated") -} - -// Warm preloads cache with specific keys -func (c *CVCache) Warm(key string, loader func() (interface{}, error)) error { - data, err := loader() - if err != nil { - return fmt.Errorf("cache warm failed for key %s: %w", key, err) - } - - c.Set(key, data) - log.Printf("🔥 Cache warmed: %s", key) - return nil -} - -// GetStats returns current cache statistics -func (c *CVCache) GetStats() CacheStats { - c.stats.mu.RLock() - defer c.stats.mu.RUnlock() - - return CacheStats{ - Hits: c.stats.Hits, - Misses: c.stats.Misses, - Size: c.stats.Size, - } -} - -// HitRate returns the cache hit rate as a percentage -func (c *CVCache) HitRate() float64 { - stats := c.GetStats() - total := stats.Hits + stats.Misses - if total == 0 { - return 0 - } - return (float64(stats.Hits) / float64(total)) * 100 -} - -// recordHit increments the hit counter -func (c *CVCache) recordHit() { - c.stats.mu.Lock() - c.stats.Hits++ - c.stats.mu.Unlock() -} - -// recordMiss increments the miss counter -func (c *CVCache) recordMiss() { - c.stats.mu.Lock() - c.stats.Misses++ - c.stats.mu.Unlock() -} - -// updateSize updates the cached size count -func (c *CVCache) updateSize() { - c.stats.mu.Lock() - c.stats.Size = len(c.entries) - c.stats.mu.Unlock() -} - -// cleanupExpired periodically removes expired entries -func (c *CVCache) cleanupExpired() { - ticker := time.NewTicker(5 * time.Minute) - defer ticker.Stop() - - for range ticker.C { - c.mu.Lock() - now := time.Now() - removed := 0 - - for key, entry := range c.entries { - if now.After(entry.expiration) { - delete(c.entries, key) - removed++ - } - } - - if removed > 0 { - c.updateSize() - log.Printf("🧹 Cache cleanup: removed %d expired entries", removed) - } - c.mu.Unlock() - } -} - -// BuildKey creates a consistent cache key -func BuildKey(prefix, lang string) string { - return fmt.Sprintf("%s:%s", prefix, lang) -} diff --git a/internal/handlers/health.go b/internal/handlers/health.go index 79ea1ba..32c9390 100644 --- a/internal/handlers/health.go +++ b/internal/handlers/health.go @@ -5,24 +5,13 @@ import ( "log" "net/http" "time" - - "github.com/juanatsap/cv-site/internal/models" ) // HealthResponse represents the health check response type HealthResponse struct { - Status string `json:"status"` - Timestamp time.Time `json:"timestamp"` - Version string `json:"version"` - Cache *CacheInfo `json:"cache,omitempty"` -} - -// CacheInfo represents cache statistics -type CacheInfo struct { - Hits int64 `json:"hits"` - Misses int64 `json:"misses"` - Size int `json:"size"` - HitRate float64 `json:"hit_rate_percent"` + Status string `json:"status"` + Timestamp time.Time `json:"timestamp"` + Version string `json:"version"` } // HealthHandler handles health check requests @@ -45,21 +34,9 @@ func (h *HealthHandler) Check(w http.ResponseWriter, r *http.Request) { Version: h.version, } - // Include cache stats if available - if cache := models.GetCache(); cache != nil { - stats := cache.GetStats() - response.Cache = &CacheInfo{ - Hits: stats.Hits, - Misses: stats.Misses, - Size: stats.Size, - HitRate: cache.HitRate(), - } - } - w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { - // Log error but don't change response status (already written) log.Printf("ERROR encoding health check response: %v", err) } } diff --git a/internal/middleware/security.go b/internal/middleware/security.go index 39e20bb..bc7467f 100644 --- a/internal/middleware/security.go +++ b/internal/middleware/security.go @@ -221,3 +221,17 @@ func (rl *RateLimiter) cleanup() { rl.mu.Unlock() } } + +// CacheControl adds cache headers to static files +// 1 hour in development, 1 day in production +func CacheControl(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + maxAge := "3600" // 1 hour + if os.Getenv("GO_ENV") == "production" { + maxAge = "86400" // 1 day + } + + w.Header().Set("Cache-Control", "public, max-age="+maxAge) + next.ServeHTTP(w, r) + }) +} diff --git a/internal/models/cv.go b/internal/models/cv.go index 0a4dc93..1df4a6d 100644 --- a/internal/models/cv.go +++ b/internal/models/cv.go @@ -4,11 +4,7 @@ import ( "encoding/json" "fmt" "html/template" - "log" "os" - "time" - - "github.com/juanatsap/cv-site/internal/cache" ) // CV represents the complete curriculum vitae structure @@ -16,7 +12,6 @@ type CV struct { Personal Personal `json:"personal"` Summary string `json:"summary"` Experience []Experience `json:"experience"` - AIDevelopment AIDevelopment `json:"ai_development"` Education []Education `json:"education"` Skills Skills `json:"skills"` Languages []Language `json:"languages"` @@ -62,20 +57,6 @@ type Experience struct { Duration string `json:"-"` // Calculated field, not from JSON } -type AIDevelopment struct { - Title string `json:"title"` - Period string `json:"period"` - Description string `json:"description"` - Skills []AISkill `json:"skills"` - Achievements []string `json:"achievements"` -} - -type AISkill struct { - Category string `json:"category"` - Proficiency string `json:"proficiency"` - Items []string `json:"items"` -} - type Education struct { Degree string `json:"degree"` Institution string `json:"institution"` @@ -193,104 +174,42 @@ type TechStack struct { CSS3 string `json:"css3"` } -// Global cache instance (initialized in main.go) -var cvCache *cache.CVCache - -// InitCache initializes the global cache with specified TTL -func InitCache(ttl time.Duration) { - cvCache = cache.New(ttl) - log.Printf("✓ Cache initialized (TTL: %v)", ttl) -} - -// GetCache returns the cache instance (for stats/management) -func GetCache() *cache.CVCache { - return cvCache -} - // LoadCV loads CV data from a JSON file for the specified language -// Uses cache if available, falls back to disk on cache miss func LoadCV(lang string) (*CV, error) { - // Validate language if lang != "en" && lang != "es" { return nil, fmt.Errorf("unsupported language: %s", lang) } - // Try cache first if available - if cvCache != nil { - cacheKey := cache.BuildKey("cv", lang) - if cached, found := cvCache.Get(cacheKey); found { - if cv, ok := cached.(*CV); ok { - return cv, nil - } - // Invalid cache entry, invalidate it - cvCache.Invalidate(cacheKey) - } - } - - // Cache miss or no cache - load from disk filename := fmt.Sprintf("data/cv-%s.json", lang) - - // Read the JSON file data, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("error reading file %s: %w", filename, err) } - // Parse JSON var cv CV if err := json.Unmarshal(data, &cv); err != nil { return nil, fmt.Errorf("error parsing JSON: %w", err) } - // Store in cache if available - if cvCache != nil { - cacheKey := cache.BuildKey("cv", lang) - cvCache.Set(cacheKey, &cv) - } - return &cv, nil } // LoadUI loads UI translations from a JSON file for the specified language -// Uses cache if available, falls back to disk on cache miss func LoadUI(lang string) (*UI, error) { - // Validate language if lang != "en" && lang != "es" { return nil, fmt.Errorf("unsupported language: %s", lang) } - // Try cache first if available - if cvCache != nil { - cacheKey := cache.BuildKey("ui", lang) - if cached, found := cvCache.Get(cacheKey); found { - if ui, ok := cached.(*UI); ok { - return ui, nil - } - // Invalid cache entry, invalidate it - cvCache.Invalidate(cacheKey) - } - } - - // Cache miss or no cache - load from disk filename := fmt.Sprintf("data/ui-%s.json", lang) - - // Read the JSON file data, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("error reading file %s: %w", filename, err) } - // Parse JSON var ui UI if err := json.Unmarshal(data, &ui); err != nil { return nil, fmt.Errorf("error parsing JSON: %w", err) } - // Store in cache if available - if cvCache != nil { - cacheKey := cache.BuildKey("ui", lang) - cvCache.Set(cacheKey, &ui) - } - return &ui, nil } diff --git a/internal/routes/routes.go b/internal/routes/routes.go new file mode 100644 index 0000000..d75a4cd --- /dev/null +++ b/internal/routes/routes.go @@ -0,0 +1,41 @@ +package routes + +import ( + "net/http" + "time" + + "github.com/juanatsap/cv-site/internal/handlers" + "github.com/juanatsap/cv-site/internal/middleware" +) + +// Setup configures all application routes and middleware +func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler) http.Handler { + mux := http.NewServeMux() + + // Public routes + mux.HandleFunc("/", cvHandler.Home) + mux.HandleFunc("/cv", cvHandler.CVContent) + mux.HandleFunc("/health", healthHandler.Check) + + // Protected PDF endpoint with rate limiting (3 requests/minute per IP) + pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute) + protectedPDFHandler := middleware.OriginChecker( + pdfRateLimiter.Middleware( + http.HandlerFunc(cvHandler.ExportPDF), + ), + ) + mux.Handle("/export/pdf", protectedPDFHandler) + + // Static files with cache control + staticHandler := http.StripPrefix("/static/", http.FileServer(http.Dir("static"))) + mux.Handle("/static/", middleware.CacheControl(staticHandler)) + + // Apply comprehensive middleware chain + handler := middleware.Recovery( + middleware.Logger( + middleware.SecurityHeaders(mux), + ), + ) + + return handler +} diff --git a/main.go b/main.go index 5b4568c..425fc9c 100644 --- a/main.go +++ b/main.go @@ -13,12 +13,11 @@ import ( "github.com/joho/godotenv" "github.com/juanatsap/cv-site/internal/config" "github.com/juanatsap/cv-site/internal/handlers" - "github.com/juanatsap/cv-site/internal/middleware" - "github.com/juanatsap/cv-site/internal/models" + "github.com/juanatsap/cv-site/internal/routes" "github.com/juanatsap/cv-site/internal/templates" ) -const version = "1.0.0" +const version = "1.1.0" func main() { // Initialize logger @@ -36,29 +35,6 @@ func main() { cfg := config.Load() log.Printf("✓ Configuration loaded (env: %s)", os.Getenv("GO_ENV")) - // Initialize cache (1 hour TTL, configurable via env) - cacheTTL := 1 * time.Hour - if ttlEnv := os.Getenv("CACHE_TTL_MINUTES"); ttlEnv != "" { - if minutes, err := time.ParseDuration(ttlEnv + "m"); err == nil { - cacheTTL = minutes - } - } - models.InitCache(cacheTTL) - - // Warm cache with default languages - log.Println("🔥 Warming cache...") - for _, lang := range []string{"en", "es"} { - // Warm CV cache - if _, err := models.LoadCV(lang); err != nil { - log.Printf("⚠️ Failed to warm CV cache for %s: %v", lang, err) - } - // Warm UI cache - if _, err := models.LoadUI(lang); err != nil { - log.Printf("⚠️ Failed to warm UI cache for %s: %v", lang, err) - } - } - log.Printf("✓ Cache warmed (TTL: %v)", cacheTTL) - // Initialize template manager templateMgr, err := templates.NewManager(&cfg.Template) if err != nil { @@ -69,37 +45,8 @@ func main() { cvHandler := handlers.NewCVHandler(templateMgr, cfg.Address()) healthHandler := handlers.NewHealthHandler(version) - // Setup router - mux := http.NewServeMux() - - // Create rate limiter for PDF endpoint - // Allow 3 PDF generations per minute per IP - pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute) - log.Printf("🔒 Rate limiter enabled for PDF endpoint (3 requests/minute)") - - // Routes - mux.HandleFunc("/", cvHandler.Home) - mux.HandleFunc("/cv", cvHandler.CVContent) - mux.HandleFunc("/health", healthHandler.Check) - - // Protected PDF endpoint with origin checking + rate limiting - protectedPDFHandler := middleware.OriginChecker( - pdfRateLimiter.Middleware( - http.HandlerFunc(cvHandler.ExportPDF), - ), - ) - mux.Handle("/export/pdf", protectedPDFHandler) - - // Static files with cache control - staticHandler := http.StripPrefix("/static/", http.FileServer(http.Dir("static"))) - mux.Handle("/static/", cacheControl(staticHandler)) - - // Apply comprehensive middleware chain - handler := middleware.Recovery( - middleware.Logger( - middleware.SecurityHeaders(mux), - ), - ) + // Setup routes and middleware + handler := routes.Setup(cvHandler, healthHandler) // Create server with timeouts server := &http.Server{ @@ -152,17 +99,3 @@ func main() { log.Println("✓ Server stopped gracefully") } } - -// cacheControl adds cache headers to static files -func cacheControl(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Cache static files for 1 hour in development, 1 day in production - maxAge := "3600" // 1 hour - if os.Getenv("GO_ENV") == "production" { - maxAge = "86400" // 1 day - } - - w.Header().Set("Cache-Control", "public, max-age="+maxAge) - h.ServeHTTP(w, r) - }) -} diff --git a/tests/screenshots/badge-measurements.json b/tests/screenshots/badge-measurements.json deleted file mode 100644 index d8fd707..0000000 --- a/tests/screenshots/badge-measurements.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "old": {}, - "new": { - "badge": { - "box": { - "x": 528.046875, - "y": 173.96875, - "width": 164, - "height": 21.609375 - }, - "styles": { - "height": "21.6094px", - "padding": "0px", - "fontSize": "14.4px", - "fontWeight": "400", - "color": "rgb(204, 204, 204)", - "backgroundColor": "rgba(0, 0, 0, 0)", - "borderRadius": "0px", - "display": "block", - "alignItems": "normal" - } - } - } -} \ No newline at end of file diff --git a/tests/screenshots/comparison-report.json b/tests/screenshots/comparison-report.json deleted file mode 100644 index 25149fd..0000000 --- a/tests/screenshots/comparison-report.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "timestamp": "2025-10-31T15:52:05.485Z", - "oldSite": { - "url": "http://localhost:3000", - "hasContent": true, - "classesFound": 0, - "dimensions": { - "width": 1920, - "height": 1080 - }, - "badges": null - }, - "newSite": { - "url": "http://localhost:1999", - "dimensions": { - "width": 1920, - "height": 2195 - }, - "badges": [ - { - "tag": "SPAN", - "class": "title-badge", - "text": "ANALYST PROGRAMMER", - "styles": { - "fontSize": "14.4px", - "fontWeight": "400", - "color": "rgb(204, 204, 204)", - "backgroundColor": "rgba(0, 0, 0, 0)", - "padding": "0px", - "height": "21.6094px" - } - }, - { - "tag": "SPAN", - "class": "title-badge", - "text": "NODEJS + REACTJS DEVELOPER", - "styles": { - "fontSize": "14.4px", - "fontWeight": "400", - "color": "rgb(204, 204, 204)", - "backgroundColor": "rgba(0, 0, 0, 0)", - "padding": "0px", - "height": "21.6094px" - } - }, - { - "tag": "SPAN", - "class": "title-badge", - "text": "WEB DEVELOPER", - "styles": { - "fontSize": "14.4px", - "fontWeight": "400", - "color": "rgb(204, 204, 204)", - "backgroundColor": "rgba(0, 0, 0, 0)", - "padding": "0px", - "height": "21.6094px" - } - }, - { - "tag": "SPAN", - "class": "title-badge", - "text": "JAVA DEVELOPER", - "styles": { - "fontSize": "14.4px", - "fontWeight": "400", - "color": "rgb(204, 204, 204)", - "backgroundColor": "rgba(0, 0, 0, 0)", - "padding": "0px", - "height": "21.6094px" - } - }, - { - "tag": "SPAN", - "class": "title-badge", - "text": "PHP DEVELOPER", - "styles": { - "fontSize": "14.4px", - "fontWeight": "400", - "color": "rgb(204, 204, 204)", - "backgroundColor": "rgba(0, 0, 0, 0)", - "padding": "0px", - "height": "21.6094px" - } - } - ] - }, - "comparison": { - "dimensionsMatch": false, - "pixelPerfect": null - } -} \ No newline at end of file diff --git a/tests/screenshots/critical-elements-full-styles.json b/tests/screenshots/critical-elements-full-styles.json deleted file mode 100644 index e82987f..0000000 --- a/tests/screenshots/critical-elements-full-styles.json +++ /dev/null @@ -1,2501 +0,0 @@ -{ - "old": {}, - "new": { - ".cv-title-badges-header": { - "accent-color": "auto", - "align-content": "normal", - "align-items": "center", - "align-self": "auto", - "alignment-baseline": "auto", - "anchor-name": "none", - "anchor-scope": "none", - "animation-composition": "replace", - "animation-delay": "0s", - "animation-direction": "normal", - "animation-duration": "0s", - "animation-fill-mode": "none", - "animation-iteration-count": "1", - "animation-name": "none", - "animation-play-state": "running", - "animation-range-end": "normal", - "animation-range-start": "normal", - "animation-timeline": "auto", - "animation-timing-function": "ease", - "app-region": "none", - "appearance": "none", - "backdrop-filter": "none", - "backface-visibility": "visible", - "background-attachment": "scroll", - "background-blend-mode": "normal", - "background-clip": "border-box", - "background-color": "rgb(48, 48, 48)", - "background-image": "none", - "background-origin": "padding-box", - "background-position": "0% 0%", - "background-repeat": "repeat", - "background-size": "auto", - "baseline-shift": "0px", - "baseline-source": "auto", - "block-size": "46px", - "border-block-end-color": "rgb(52, 73, 94)", - "border-block-end-style": "solid", - "border-block-end-width": "2px", - "border-block-start-color": "rgb(41, 43, 44)", - "border-block-start-style": "none", - "border-block-start-width": "0px", - "border-bottom-color": "rgb(52, 73, 94)", - "border-bottom-left-radius": "0px", - "border-bottom-right-radius": "0px", - "border-bottom-style": "solid", - "border-bottom-width": "2px", - "border-collapse": "separate", - "border-end-end-radius": "0px", - "border-end-start-radius": "0px", - "border-image-outset": "0", - "border-image-repeat": "stretch", - "border-image-slice": "100%", - "border-image-source": "none", - "border-image-width": "1", - "border-inline-end-color": "rgb(41, 43, 44)", - "border-inline-end-style": "none", - "border-inline-end-width": "0px", - "border-inline-start-color": "rgb(41, 43, 44)", - "border-inline-start-style": "none", - "border-inline-start-width": "0px", - "border-left-color": "rgb(41, 43, 44)", - "border-left-style": "none", - "border-left-width": "0px", - "border-right-color": "rgb(41, 43, 44)", - "border-right-style": "none", - "border-right-width": "0px", - "border-start-end-radius": "0px", - "border-start-start-radius": "0px", - "border-top-color": "rgb(41, 43, 44)", - "border-top-left-radius": "0px", - "border-top-right-radius": "0px", - "border-top-style": "none", - "border-top-width": "0px", - "bottom": "auto", - "box-decoration-break": "slice", - "box-shadow": "none", - "box-sizing": "border-box", - "break-after": "auto", - "break-before": "auto", - "break-inside": "auto", - "buffered-rendering": "auto", - "caption-side": "top", - "caret-animation": "auto", - "caret-color": "rgb(41, 43, 44)", - "clear": "none", - "clip": "auto", - "clip-path": "none", - "clip-rule": "nonzero", - "color": "rgb(41, 43, 44)", - "color-interpolation": "srgb", - "color-interpolation-filters": "linearrgb", - "color-rendering": "auto", - "column-count": "auto", - "column-gap": "0px", - "column-rule-color": "rgb(41, 43, 44)", - "column-rule-style": "none", - "column-rule-width": "0px", - "column-span": "none", - "column-width": "auto", - "contain-intrinsic-block-size": "none", - "contain-intrinsic-height": "none", - "contain-intrinsic-inline-size": "none", - "contain-intrinsic-size": "none", - "contain-intrinsic-width": "none", - "container-name": "none", - "container-type": "normal", - "content": "normal", - "corner-bottom-left-shape": "round", - "corner-bottom-right-shape": "round", - "corner-end-end-shape": "round", - "corner-end-start-shape": "round", - "corner-start-end-shape": "round", - "corner-start-start-shape": "round", - "corner-top-left-shape": "round", - "corner-top-right-shape": "round", - "cursor": "auto", - "cx": "0px", - "cy": "0px", - "d": "none", - "direction": "ltr", - "display": "flex", - "dominant-baseline": "auto", - "dynamic-range-limit": "no-limit", - "empty-cells": "show", - "field-sizing": "fixed", - "fill": "rgb(0, 0, 0)", - "fill-opacity": "1", - "fill-rule": "nonzero", - "filter": "none", - "flex-basis": "auto", - "flex-direction": "row", - "flex-grow": "0", - "flex-shrink": "1", - "flex-wrap": "wrap", - "float": "none", - "flood-color": "rgb(0, 0, 0)", - "flood-opacity": "1", - "font-family": "Quicksand, \"Source Sans Pro\", -apple-system, system-ui, sans-serif", - "font-kerning": "auto", - "font-optical-sizing": "auto", - "font-palette": "normal", - "font-size": "16px", - "font-size-adjust": "none", - "font-stretch": "100%", - "font-style": "normal", - "font-synthesis-small-caps": "auto", - "font-synthesis-style": "auto", - "font-synthesis-weight": "auto", - "font-variant": "normal", - "font-variant-alternates": "normal", - "font-variant-caps": "normal", - "font-variant-east-asian": "normal", - "font-variant-emoji": "normal", - "font-variant-ligatures": "normal", - "font-variant-numeric": "normal", - "font-variant-position": "normal", - "font-weight": "400", - "grid-auto-columns": "auto", - "grid-auto-flow": "row", - "grid-auto-rows": "auto", - "grid-column-end": "-1", - "grid-column-start": "1", - "grid-row-end": "auto", - "grid-row-start": "auto", - "grid-template-areas": "none", - "grid-template-columns": "none", - "grid-template-rows": "none", - "height": "46px", - "hyphenate-character": "auto", - "hyphenate-limit-chars": "auto", - "hyphens": "manual", - "image-orientation": "from-image", - "image-rendering": "auto", - "initial-letter": "normal", - "inline-size": "1200px", - "inset-block-end": "auto", - "inset-block-start": "auto", - "inset-inline-end": "auto", - "inset-inline-start": "auto", - "interactivity": "auto", - "interpolate-size": "numeric-only", - "isolation": "auto", - "justify-content": "center", - "justify-items": "normal", - "justify-self": "auto", - "left": "auto", - "letter-spacing": "normal", - "lighting-color": "rgb(255, 255, 255)", - "line-break": "auto", - "line-height": "24px", - "list-style-image": "none", - "list-style-position": "outside", - "list-style-type": "disc", - "margin-block-end": "0px", - "margin-block-start": "0px", - "margin-bottom": "0px", - "margin-inline-end": "0px", - "margin-inline-start": "0px", - "margin-left": "0px", - "margin-right": "0px", - "margin-top": "0px", - "marker-end": "none", - "marker-mid": "none", - "marker-start": "none", - "mask-clip": "border-box", - "mask-composite": "add", - "mask-image": "none", - "mask-mode": "match-source", - "mask-origin": "border-box", - "mask-position": "0% 0%", - "mask-repeat": "repeat", - "mask-size": "auto", - "mask-type": "luminance", - "math-depth": "0", - "math-shift": "normal", - "math-style": "normal", - "max-block-size": "none", - "max-height": "none", - "max-inline-size": "none", - "max-width": "none", - "min-block-size": "auto", - "min-height": "auto", - "min-inline-size": "auto", - "min-width": "auto", - "mix-blend-mode": "normal", - "object-fit": "fill", - "object-position": "50% 50%", - "object-view-box": "none", - "offset-anchor": "auto", - "offset-distance": "0px", - "offset-path": "none", - "offset-position": "normal", - "offset-rotate": "auto 0deg", - "opacity": "1", - "order": "0", - "orphans": "2", - "outline-color": "rgb(41, 43, 44)", - "outline-offset": "0px", - "outline-style": "none", - "outline-width": "0px", - "overflow-anchor": "auto", - "overflow-block": "visible", - "overflow-clip-margin": "0px", - "overflow-inline": "visible", - "overflow-wrap": "normal", - "overflow-x": "visible", - "overflow-y": "visible", - "overlay": "none", - "overscroll-behavior-block": "auto", - "overscroll-behavior-inline": "auto", - "padding-block-end": "10px", - "padding-block-start": "10px", - "padding-bottom": "10px", - "padding-inline-end": "20px", - "padding-inline-start": "20px", - "padding-left": "20px", - "padding-right": "20px", - "padding-top": "10px", - "paint-order": "normal", - "perspective": "none", - "perspective-origin": "600px 23px", - "pointer-events": "auto", - "position": "static", - "position-anchor": "auto", - "position-area": "none", - "position-try-fallbacks": "none", - "position-try-order": "normal", - "position-visibility": "always", - "print-color-adjust": "economy", - "r": "0px", - "reading-flow": "normal", - "reading-order": "0", - "resize": "none", - "right": "auto", - "rotate": "none", - "row-gap": "0px", - "ruby-align": "space-around", - "ruby-position": "over", - "rx": "auto", - "ry": "auto", - "scale": "none", - "scroll-behavior": "auto", - "scroll-initial-target": "none", - "scroll-margin-block-end": "0px", - "scroll-margin-block-start": "0px", - "scroll-margin-inline-end": "0px", - "scroll-margin-inline-start": "0px", - "scroll-marker-group": "none", - "scroll-padding-block-end": "auto", - "scroll-padding-block-start": "auto", - "scroll-padding-inline-end": "auto", - "scroll-padding-inline-start": "auto", - "scroll-target-group": "none", - "scroll-timeline-axis": "block", - "scroll-timeline-name": "none", - "scrollbar-color": "auto", - "scrollbar-gutter": "auto", - "scrollbar-width": "auto", - "shape-image-threshold": "0", - "shape-margin": "0px", - "shape-outside": "none", - "shape-rendering": "auto", - "speak": "normal", - "stop-color": "rgb(0, 0, 0)", - "stop-opacity": "1", - "stroke": "none", - "stroke-dasharray": "none", - "stroke-dashoffset": "0px", - "stroke-linecap": "butt", - "stroke-linejoin": "miter", - "stroke-miterlimit": "4", - "stroke-opacity": "1", - "stroke-width": "1px", - "tab-size": "8", - "table-layout": "auto", - "text-align": "start", - "text-align-last": "auto", - "text-anchor": "start", - "text-autospace": "no-autospace", - "text-box-edge": "auto", - "text-box-trim": "none", - "text-decoration": "none", - "text-decoration-color": "rgb(41, 43, 44)", - "text-decoration-line": "none", - "text-decoration-skip-ink": "auto", - "text-decoration-style": "solid", - "text-emphasis-color": "rgb(41, 43, 44)", - "text-emphasis-position": "over", - "text-emphasis-style": "none", - "text-indent": "0px", - "text-overflow": "clip", - "text-rendering": "auto", - "text-shadow": "none", - "text-size-adjust": "auto", - "text-spacing-trim": "normal", - "text-transform": "none", - "text-underline-position": "auto", - "text-wrap-mode": "wrap", - "text-wrap-style": "auto", - "timeline-scope": "none", - "top": "auto", - "touch-action": "auto", - "transform": "none", - "transform-origin": "600px 23px", - "transform-style": "flat", - "transition-behavior": "normal", - "transition-delay": "0s", - "transition-duration": "0s", - "transition-property": "all", - "transition-timing-function": "ease", - "translate": "none", - "unicode-bidi": "isolate", - "user-select": "auto", - "vector-effect": "none", - "vertical-align": "baseline", - "view-timeline-axis": "block", - "view-timeline-inset": "auto", - "view-timeline-name": "none", - "view-transition-class": "none", - "view-transition-group": "normal", - "view-transition-name": "none", - "visibility": "visible", - "white-space-collapse": "collapse", - "widows": "2", - "width": "1200px", - "will-change": "auto", - "word-break": "normal", - "word-spacing": "0px", - "writing-mode": "horizontal-tb", - "x": "0px", - "y": "0px", - "z-index": "auto", - "zoom": "1", - "-webkit-border-horizontal-spacing": "0px", - "-webkit-border-image": "none", - "-webkit-border-vertical-spacing": "0px", - "-webkit-box-align": "stretch", - "-webkit-box-decoration-break": "slice", - "-webkit-box-direction": "normal", - "-webkit-box-flex": "0", - "-webkit-box-ordinal-group": "1", - "-webkit-box-orient": "horizontal", - "-webkit-box-pack": "start", - "-webkit-box-reflect": "none", - "-webkit-font-smoothing": "auto", - "-webkit-line-break": "auto", - "-webkit-line-clamp": "none", - "-webkit-locale": "\"en\"", - "-webkit-mask-box-image": "none", - "-webkit-mask-box-image-outset": "0", - "-webkit-mask-box-image-repeat": "stretch", - "-webkit-mask-box-image-slice": "0 fill", - "-webkit-mask-box-image-source": "none", - "-webkit-mask-box-image-width": "auto", - "-webkit-rtl-ordering": "logical", - "-webkit-tap-highlight-color": "rgba(0, 0, 0, 0.18)", - "-webkit-text-combine": "none", - "-webkit-text-decorations-in-effect": "none", - "-webkit-text-fill-color": "rgb(41, 43, 44)", - "-webkit-text-orientation": "vertical-right", - "-webkit-text-security": "none", - "-webkit-text-stroke-color": "rgb(41, 43, 44)", - "-webkit-text-stroke-width": "0px", - "-webkit-user-drag": "auto", - "-webkit-user-modify": "read-only", - "-webkit-writing-mode": "horizontal-tb", - "--bg-gray": "rgb(82, 86, 89)", - "--text-gray": "rgb(51, 51, 51)", - "--accent-blue": "#0066cc", - "--sidebar-gray": "#d1d4d2", - "--black-bar": "#2b2b2b", - "--border-gray": "#dddddd", - "--paper-white": "#ffffff", - "--text-dark": "rgb(0, 0, 0)" - }, - ".title-badge": { - "accent-color": "auto", - "align-content": "normal", - "align-items": "normal", - "align-self": "auto", - "alignment-baseline": "auto", - "anchor-name": "none", - "anchor-scope": "none", - "animation-composition": "replace", - "animation-delay": "0s", - "animation-direction": "normal", - "animation-duration": "0s", - "animation-fill-mode": "none", - "animation-iteration-count": "1", - "animation-name": "none", - "animation-play-state": "running", - "animation-range-end": "normal", - "animation-range-start": "normal", - "animation-timeline": "auto", - "animation-timing-function": "ease", - "app-region": "none", - "appearance": "none", - "backdrop-filter": "none", - "backface-visibility": "visible", - "background-attachment": "scroll", - "background-blend-mode": "normal", - "background-clip": "border-box", - "background-color": "rgba(0, 0, 0, 0)", - "background-image": "none", - "background-origin": "padding-box", - "background-position": "0% 0%", - "background-repeat": "repeat", - "background-size": "auto", - "baseline-shift": "0px", - "baseline-source": "auto", - "block-size": "21.6094px", - "border-block-end-color": "rgb(204, 204, 204)", - "border-block-end-style": "none", - "border-block-end-width": "0px", - "border-block-start-color": "rgb(204, 204, 204)", - "border-block-start-style": "none", - "border-block-start-width": "0px", - "border-bottom-color": "rgb(204, 204, 204)", - "border-bottom-left-radius": "0px", - "border-bottom-right-radius": "0px", - "border-bottom-style": "none", - "border-bottom-width": "0px", - "border-collapse": "separate", - "border-end-end-radius": "0px", - "border-end-start-radius": "0px", - "border-image-outset": "0", - "border-image-repeat": "stretch", - "border-image-slice": "100%", - "border-image-source": "none", - "border-image-width": "1", - "border-inline-end-color": "rgb(204, 204, 204)", - "border-inline-end-style": "none", - "border-inline-end-width": "0px", - "border-inline-start-color": "rgb(204, 204, 204)", - "border-inline-start-style": "none", - "border-inline-start-width": "0px", - "border-left-color": "rgb(204, 204, 204)", - "border-left-style": "none", - "border-left-width": "0px", - "border-right-color": "rgb(204, 204, 204)", - "border-right-style": "none", - "border-right-width": "0px", - "border-start-end-radius": "0px", - "border-start-start-radius": "0px", - "border-top-color": "rgb(204, 204, 204)", - "border-top-left-radius": "0px", - "border-top-right-radius": "0px", - "border-top-style": "none", - "border-top-width": "0px", - "bottom": "auto", - "box-decoration-break": "slice", - "box-shadow": "none", - "box-sizing": "border-box", - "break-after": "auto", - "break-before": "auto", - "break-inside": "auto", - "buffered-rendering": "auto", - "caption-side": "top", - "caret-animation": "auto", - "caret-color": "rgb(204, 204, 204)", - "clear": "none", - "clip": "auto", - "clip-path": "none", - "clip-rule": "nonzero", - "color": "rgb(204, 204, 204)", - "color-interpolation": "srgb", - "color-interpolation-filters": "linearrgb", - "color-rendering": "auto", - "column-count": "auto", - "column-gap": "normal", - "column-rule-color": "rgb(204, 204, 204)", - "column-rule-style": "none", - "column-rule-width": "0px", - "column-span": "none", - "column-width": "auto", - "contain-intrinsic-block-size": "none", - "contain-intrinsic-height": "none", - "contain-intrinsic-inline-size": "none", - "contain-intrinsic-size": "none", - "contain-intrinsic-width": "none", - "container-name": "none", - "container-type": "normal", - "content": "normal", - "corner-bottom-left-shape": "round", - "corner-bottom-right-shape": "round", - "corner-end-end-shape": "round", - "corner-end-start-shape": "round", - "corner-start-end-shape": "round", - "corner-start-start-shape": "round", - "corner-top-left-shape": "round", - "corner-top-right-shape": "round", - "cursor": "auto", - "cx": "0px", - "cy": "0px", - "d": "none", - "direction": "ltr", - "display": "block", - "dominant-baseline": "auto", - "dynamic-range-limit": "no-limit", - "empty-cells": "show", - "field-sizing": "fixed", - "fill": "rgb(0, 0, 0)", - "fill-opacity": "1", - "fill-rule": "nonzero", - "filter": "none", - "flex-basis": "auto", - "flex-direction": "row", - "flex-grow": "0", - "flex-shrink": "1", - "flex-wrap": "nowrap", - "float": "none", - "flood-color": "rgb(0, 0, 0)", - "flood-opacity": "1", - "font-family": "Quicksand, \"Source Sans Pro\", -apple-system, system-ui, sans-serif", - "font-kerning": "auto", - "font-optical-sizing": "auto", - "font-palette": "normal", - "font-size": "14.4px", - "font-size-adjust": "none", - "font-stretch": "100%", - "font-style": "normal", - "font-synthesis-small-caps": "auto", - "font-synthesis-style": "auto", - "font-synthesis-weight": "auto", - "font-variant": "normal", - "font-variant-alternates": "normal", - "font-variant-caps": "normal", - "font-variant-east-asian": "normal", - "font-variant-emoji": "normal", - "font-variant-ligatures": "normal", - "font-variant-numeric": "normal", - "font-variant-position": "normal", - "font-weight": "400", - "grid-auto-columns": "auto", - "grid-auto-flow": "row", - "grid-auto-rows": "auto", - "grid-column-end": "auto", - "grid-column-start": "auto", - "grid-row-end": "auto", - "grid-row-start": "auto", - "grid-template-areas": "none", - "grid-template-columns": "none", - "grid-template-rows": "none", - "height": "21.6094px", - "hyphenate-character": "auto", - "hyphenate-limit-chars": "auto", - "hyphens": "manual", - "image-orientation": "from-image", - "image-rendering": "auto", - "initial-letter": "normal", - "inline-size": "164px", - "inset-block-end": "auto", - "inset-block-start": "auto", - "inset-inline-end": "auto", - "inset-inline-start": "auto", - "interactivity": "auto", - "interpolate-size": "numeric-only", - "isolation": "auto", - "justify-content": "normal", - "justify-items": "normal", - "justify-self": "auto", - "left": "auto", - "letter-spacing": "normal", - "lighting-color": "rgb(255, 255, 255)", - "line-break": "auto", - "line-height": "21.6px", - "list-style-image": "none", - "list-style-position": "outside", - "list-style-type": "disc", - "margin-block-end": "0px", - "margin-block-start": "0px", - "margin-bottom": "0px", - "margin-inline-end": "0px", - "margin-inline-start": "0px", - "margin-left": "0px", - "margin-right": "0px", - "margin-top": "0px", - "marker-end": "none", - "marker-mid": "none", - "marker-start": "none", - "mask-clip": "border-box", - "mask-composite": "add", - "mask-image": "none", - "mask-mode": "match-source", - "mask-origin": "border-box", - "mask-position": "0% 0%", - "mask-repeat": "repeat", - "mask-size": "auto", - "mask-type": "luminance", - "math-depth": "0", - "math-shift": "normal", - "math-style": "normal", - "max-block-size": "none", - "max-height": "none", - "max-inline-size": "none", - "max-width": "none", - "min-block-size": "auto", - "min-height": "auto", - "min-inline-size": "auto", - "min-width": "auto", - "mix-blend-mode": "normal", - "object-fit": "fill", - "object-position": "50% 50%", - "object-view-box": "none", - "offset-anchor": "auto", - "offset-distance": "0px", - "offset-path": "none", - "offset-position": "normal", - "offset-rotate": "auto 0deg", - "opacity": "1", - "order": "0", - "orphans": "2", - "outline-color": "rgb(204, 204, 204)", - "outline-offset": "0px", - "outline-style": "none", - "outline-width": "0px", - "overflow-anchor": "auto", - "overflow-block": "visible", - "overflow-clip-margin": "0px", - "overflow-inline": "visible", - "overflow-wrap": "normal", - "overflow-x": "visible", - "overflow-y": "visible", - "overlay": "none", - "overscroll-behavior-block": "auto", - "overscroll-behavior-inline": "auto", - "padding-block-end": "0px", - "padding-block-start": "0px", - "padding-bottom": "0px", - "padding-inline-end": "0px", - "padding-inline-start": "0px", - "padding-left": "0px", - "padding-right": "0px", - "padding-top": "0px", - "paint-order": "normal", - "perspective": "none", - "perspective-origin": "82px 10.7969px", - "pointer-events": "auto", - "position": "static", - "position-anchor": "auto", - "position-area": "none", - "position-try-fallbacks": "none", - "position-try-order": "normal", - "position-visibility": "always", - "print-color-adjust": "economy", - "r": "0px", - "reading-flow": "normal", - "reading-order": "0", - "resize": "none", - "right": "auto", - "rotate": "none", - "row-gap": "normal", - "ruby-align": "space-around", - "ruby-position": "over", - "rx": "auto", - "ry": "auto", - "scale": "none", - "scroll-behavior": "auto", - "scroll-initial-target": "none", - "scroll-margin-block-end": "0px", - "scroll-margin-block-start": "0px", - "scroll-margin-inline-end": "0px", - "scroll-margin-inline-start": "0px", - "scroll-marker-group": "none", - "scroll-padding-block-end": "auto", - "scroll-padding-block-start": "auto", - "scroll-padding-inline-end": "auto", - "scroll-padding-inline-start": "auto", - "scroll-target-group": "none", - "scroll-timeline-axis": "block", - "scroll-timeline-name": "none", - "scrollbar-color": "auto", - "scrollbar-gutter": "auto", - "scrollbar-width": "auto", - "shape-image-threshold": "0", - "shape-margin": "0px", - "shape-outside": "none", - "shape-rendering": "auto", - "speak": "normal", - "stop-color": "rgb(0, 0, 0)", - "stop-opacity": "1", - "stroke": "none", - "stroke-dasharray": "none", - "stroke-dashoffset": "0px", - "stroke-linecap": "butt", - "stroke-linejoin": "miter", - "stroke-miterlimit": "4", - "stroke-opacity": "1", - "stroke-width": "1px", - "tab-size": "8", - "table-layout": "auto", - "text-align": "start", - "text-align-last": "auto", - "text-anchor": "start", - "text-autospace": "no-autospace", - "text-box-edge": "auto", - "text-box-trim": "none", - "text-decoration": "none", - "text-decoration-color": "rgb(204, 204, 204)", - "text-decoration-line": "none", - "text-decoration-skip-ink": "auto", - "text-decoration-style": "solid", - "text-emphasis-color": "rgb(204, 204, 204)", - "text-emphasis-position": "over", - "text-emphasis-style": "none", - "text-indent": "0px", - "text-overflow": "clip", - "text-rendering": "auto", - "text-shadow": "none", - "text-size-adjust": "auto", - "text-spacing-trim": "normal", - "text-transform": "uppercase", - "text-underline-position": "auto", - "text-wrap-mode": "nowrap", - "text-wrap-style": "auto", - "timeline-scope": "none", - "top": "auto", - "touch-action": "auto", - "transform": "none", - "transform-origin": "82px 10.8047px", - "transform-style": "flat", - "transition-behavior": "normal", - "transition-delay": "0s", - "transition-duration": "0s", - "transition-property": "all", - "transition-timing-function": "ease", - "translate": "none", - "unicode-bidi": "normal", - "user-select": "auto", - "vector-effect": "none", - "vertical-align": "baseline", - "view-timeline-axis": "block", - "view-timeline-inset": "auto", - "view-timeline-name": "none", - "view-transition-class": "none", - "view-transition-group": "normal", - "view-transition-name": "none", - "visibility": "visible", - "white-space-collapse": "collapse", - "widows": "2", - "width": "164px", - "will-change": "auto", - "word-break": "normal", - "word-spacing": "0px", - "writing-mode": "horizontal-tb", - "x": "0px", - "y": "0px", - "z-index": "auto", - "zoom": "1", - "-webkit-border-horizontal-spacing": "0px", - "-webkit-border-image": "none", - "-webkit-border-vertical-spacing": "0px", - "-webkit-box-align": "stretch", - "-webkit-box-decoration-break": "slice", - "-webkit-box-direction": "normal", - "-webkit-box-flex": "0", - "-webkit-box-ordinal-group": "1", - "-webkit-box-orient": "horizontal", - "-webkit-box-pack": "start", - "-webkit-box-reflect": "none", - "-webkit-font-smoothing": "auto", - "-webkit-line-break": "auto", - "-webkit-line-clamp": "none", - "-webkit-locale": "\"en\"", - "-webkit-mask-box-image": "none", - "-webkit-mask-box-image-outset": "0", - "-webkit-mask-box-image-repeat": "stretch", - "-webkit-mask-box-image-slice": "0 fill", - "-webkit-mask-box-image-source": "none", - "-webkit-mask-box-image-width": "auto", - "-webkit-rtl-ordering": "logical", - "-webkit-tap-highlight-color": "rgba(0, 0, 0, 0.18)", - "-webkit-text-combine": "none", - "-webkit-text-decorations-in-effect": "none", - "-webkit-text-fill-color": "rgb(204, 204, 204)", - "-webkit-text-orientation": "vertical-right", - "-webkit-text-security": "none", - "-webkit-text-stroke-color": "rgb(204, 204, 204)", - "-webkit-text-stroke-width": "0px", - "-webkit-user-drag": "auto", - "-webkit-user-modify": "read-only", - "-webkit-writing-mode": "horizontal-tb", - "--bg-gray": "rgb(82, 86, 89)", - "--text-gray": "rgb(51, 51, 51)", - "--accent-blue": "#0066cc", - "--sidebar-gray": "#d1d4d2", - "--black-bar": "#2b2b2b", - "--border-gray": "#dddddd", - "--paper-white": "#ffffff", - "--text-dark": "rgb(0, 0, 0)" - }, - ".badge-separator": { - "accent-color": "auto", - "align-content": "normal", - "align-items": "normal", - "align-self": "auto", - "alignment-baseline": "auto", - "anchor-name": "none", - "anchor-scope": "none", - "animation-composition": "replace", - "animation-delay": "0s", - "animation-direction": "normal", - "animation-duration": "0s", - "animation-fill-mode": "none", - "animation-iteration-count": "1", - "animation-name": "none", - "animation-play-state": "running", - "animation-range-end": "normal", - "animation-range-start": "normal", - "animation-timeline": "auto", - "animation-timing-function": "ease", - "app-region": "none", - "appearance": "none", - "backdrop-filter": "none", - "backface-visibility": "visible", - "background-attachment": "scroll", - "background-blend-mode": "normal", - "background-clip": "border-box", - "background-color": "rgba(0, 0, 0, 0)", - "background-image": "none", - "background-origin": "padding-box", - "background-position": "0% 0%", - "background-repeat": "repeat", - "background-size": "auto", - "baseline-shift": "0px", - "baseline-source": "auto", - "block-size": "24px", - "border-block-end-color": "rgb(204, 204, 204)", - "border-block-end-style": "none", - "border-block-end-width": "0px", - "border-block-start-color": "rgb(204, 204, 204)", - "border-block-start-style": "none", - "border-block-start-width": "0px", - "border-bottom-color": "rgb(204, 204, 204)", - "border-bottom-left-radius": "0px", - "border-bottom-right-radius": "0px", - "border-bottom-style": "none", - "border-bottom-width": "0px", - "border-collapse": "separate", - "border-end-end-radius": "0px", - "border-end-start-radius": "0px", - "border-image-outset": "0", - "border-image-repeat": "stretch", - "border-image-slice": "100%", - "border-image-source": "none", - "border-image-width": "1", - "border-inline-end-color": "rgb(204, 204, 204)", - "border-inline-end-style": "none", - "border-inline-end-width": "0px", - "border-inline-start-color": "rgb(204, 204, 204)", - "border-inline-start-style": "none", - "border-inline-start-width": "0px", - "border-left-color": "rgb(204, 204, 204)", - "border-left-style": "none", - "border-left-width": "0px", - "border-right-color": "rgb(204, 204, 204)", - "border-right-style": "none", - "border-right-width": "0px", - "border-start-end-radius": "0px", - "border-start-start-radius": "0px", - "border-top-color": "rgb(204, 204, 204)", - "border-top-left-radius": "0px", - "border-top-right-radius": "0px", - "border-top-style": "none", - "border-top-width": "0px", - "bottom": "1px", - "box-decoration-break": "slice", - "box-shadow": "none", - "box-sizing": "border-box", - "break-after": "auto", - "break-before": "auto", - "break-inside": "auto", - "buffered-rendering": "auto", - "caption-side": "top", - "caret-animation": "auto", - "caret-color": "rgb(204, 204, 204)", - "clear": "none", - "clip": "auto", - "clip-path": "none", - "clip-rule": "nonzero", - "color": "rgb(204, 204, 204)", - "color-interpolation": "srgb", - "color-interpolation-filters": "linearrgb", - "color-rendering": "auto", - "column-count": "auto", - "column-gap": "normal", - "column-rule-color": "rgb(204, 204, 204)", - "column-rule-style": "none", - "column-rule-width": "0px", - "column-span": "none", - "column-width": "auto", - "contain-intrinsic-block-size": "none", - "contain-intrinsic-height": "none", - "contain-intrinsic-inline-size": "none", - "contain-intrinsic-size": "none", - "contain-intrinsic-width": "none", - "container-name": "none", - "container-type": "normal", - "content": "normal", - "corner-bottom-left-shape": "round", - "corner-bottom-right-shape": "round", - "corner-end-end-shape": "round", - "corner-end-start-shape": "round", - "corner-start-end-shape": "round", - "corner-start-start-shape": "round", - "corner-top-left-shape": "round", - "corner-top-right-shape": "round", - "cursor": "auto", - "cx": "0px", - "cy": "0px", - "d": "none", - "direction": "ltr", - "display": "block", - "dominant-baseline": "auto", - "dynamic-range-limit": "no-limit", - "empty-cells": "show", - "field-sizing": "fixed", - "fill": "rgb(0, 0, 0)", - "fill-opacity": "1", - "fill-rule": "nonzero", - "filter": "none", - "flex-basis": "auto", - "flex-direction": "row", - "flex-grow": "0", - "flex-shrink": "1", - "flex-wrap": "nowrap", - "float": "none", - "flood-color": "rgb(0, 0, 0)", - "flood-opacity": "1", - "font-family": "Quicksand, \"Source Sans Pro\", -apple-system, system-ui, sans-serif", - "font-kerning": "auto", - "font-optical-sizing": "auto", - "font-palette": "normal", - "font-size": "16px", - "font-size-adjust": "none", - "font-stretch": "100%", - "font-style": "normal", - "font-synthesis-small-caps": "auto", - "font-synthesis-style": "auto", - "font-synthesis-weight": "auto", - "font-variant": "normal", - "font-variant-alternates": "normal", - "font-variant-caps": "normal", - "font-variant-east-asian": "normal", - "font-variant-emoji": "normal", - "font-variant-ligatures": "normal", - "font-variant-numeric": "normal", - "font-variant-position": "normal", - "font-weight": "400", - "grid-auto-columns": "auto", - "grid-auto-flow": "row", - "grid-auto-rows": "auto", - "grid-column-end": "auto", - "grid-column-start": "auto", - "grid-row-end": "auto", - "grid-row-start": "auto", - "grid-template-areas": "none", - "grid-template-columns": "none", - "grid-template-rows": "none", - "height": "24px", - "hyphenate-character": "auto", - "hyphenate-limit-chars": "auto", - "hyphens": "manual", - "image-orientation": "from-image", - "image-rendering": "auto", - "initial-letter": "normal", - "inline-size": "33.2812px", - "inset-block-end": "1px", - "inset-block-start": "-1px", - "inset-inline-end": "0px", - "inset-inline-start": "0px", - "interactivity": "auto", - "interpolate-size": "numeric-only", - "isolation": "auto", - "justify-content": "normal", - "justify-items": "normal", - "justify-self": "auto", - "left": "0px", - "letter-spacing": "normal", - "lighting-color": "rgb(255, 255, 255)", - "line-break": "auto", - "line-height": "24px", - "list-style-image": "none", - "list-style-position": "outside", - "list-style-type": "disc", - "margin-block-end": "0px", - "margin-block-start": "0px", - "margin-bottom": "0px", - "margin-inline-end": "0px", - "margin-inline-start": "0px", - "margin-left": "0px", - "margin-right": "0px", - "margin-top": "0px", - "marker-end": "none", - "marker-mid": "none", - "marker-start": "none", - "mask-clip": "border-box", - "mask-composite": "add", - "mask-image": "none", - "mask-mode": "match-source", - "mask-origin": "border-box", - "mask-position": "0% 0%", - "mask-repeat": "repeat", - "mask-size": "auto", - "mask-type": "luminance", - "math-depth": "0", - "math-shift": "normal", - "math-style": "normal", - "max-block-size": "none", - "max-height": "none", - "max-inline-size": "none", - "max-width": "none", - "min-block-size": "auto", - "min-height": "auto", - "min-inline-size": "auto", - "min-width": "auto", - "mix-blend-mode": "normal", - "object-fit": "fill", - "object-position": "50% 50%", - "object-view-box": "none", - "offset-anchor": "auto", - "offset-distance": "0px", - "offset-path": "none", - "offset-position": "normal", - "offset-rotate": "auto 0deg", - "opacity": "1", - "order": "0", - "orphans": "2", - "outline-color": "rgb(204, 204, 204)", - "outline-offset": "0px", - "outline-style": "none", - "outline-width": "0px", - "overflow-anchor": "auto", - "overflow-block": "visible", - "overflow-clip-margin": "0px", - "overflow-inline": "visible", - "overflow-wrap": "normal", - "overflow-x": "visible", - "overflow-y": "visible", - "overlay": "none", - "overscroll-behavior-block": "auto", - "overscroll-behavior-inline": "auto", - "padding-block-end": "0px", - "padding-block-start": "0px", - "padding-bottom": "0px", - "padding-inline-end": "15px", - "padding-inline-start": "15px", - "padding-left": "15px", - "padding-right": "15px", - "padding-top": "0px", - "paint-order": "normal", - "perspective": "none", - "perspective-origin": "16.6406px 12px", - "pointer-events": "auto", - "position": "relative", - "position-anchor": "auto", - "position-area": "none", - "position-try-fallbacks": "none", - "position-try-order": "normal", - "position-visibility": "always", - "print-color-adjust": "economy", - "r": "0px", - "reading-flow": "normal", - "reading-order": "0", - "resize": "none", - "right": "0px", - "rotate": "none", - "row-gap": "normal", - "ruby-align": "space-around", - "ruby-position": "over", - "rx": "auto", - "ry": "auto", - "scale": "none", - "scroll-behavior": "auto", - "scroll-initial-target": "none", - "scroll-margin-block-end": "0px", - "scroll-margin-block-start": "0px", - "scroll-margin-inline-end": "0px", - "scroll-margin-inline-start": "0px", - "scroll-marker-group": "none", - "scroll-padding-block-end": "auto", - "scroll-padding-block-start": "auto", - "scroll-padding-inline-end": "auto", - "scroll-padding-inline-start": "auto", - "scroll-target-group": "none", - "scroll-timeline-axis": "block", - "scroll-timeline-name": "none", - "scrollbar-color": "auto", - "scrollbar-gutter": "auto", - "scrollbar-width": "auto", - "shape-image-threshold": "0", - "shape-margin": "0px", - "shape-outside": "none", - "shape-rendering": "auto", - "speak": "normal", - "stop-color": "rgb(0, 0, 0)", - "stop-opacity": "1", - "stroke": "none", - "stroke-dasharray": "none", - "stroke-dashoffset": "0px", - "stroke-linecap": "butt", - "stroke-linejoin": "miter", - "stroke-miterlimit": "4", - "stroke-opacity": "1", - "stroke-width": "1px", - "tab-size": "8", - "table-layout": "auto", - "text-align": "start", - "text-align-last": "auto", - "text-anchor": "start", - "text-autospace": "no-autospace", - "text-box-edge": "auto", - "text-box-trim": "none", - "text-decoration": "none", - "text-decoration-color": "rgb(204, 204, 204)", - "text-decoration-line": "none", - "text-decoration-skip-ink": "auto", - "text-decoration-style": "solid", - "text-emphasis-color": "rgb(204, 204, 204)", - "text-emphasis-position": "over", - "text-emphasis-style": "none", - "text-indent": "0px", - "text-overflow": "clip", - "text-rendering": "auto", - "text-shadow": "none", - "text-size-adjust": "auto", - "text-spacing-trim": "normal", - "text-transform": "none", - "text-underline-position": "auto", - "text-wrap-mode": "wrap", - "text-wrap-style": "auto", - "timeline-scope": "none", - "top": "-1px", - "touch-action": "auto", - "transform": "none", - "transform-origin": "16.6406px 12px", - "transform-style": "flat", - "transition-behavior": "normal", - "transition-delay": "0s", - "transition-duration": "0s", - "transition-property": "all", - "transition-timing-function": "ease", - "translate": "none", - "unicode-bidi": "normal", - "user-select": "auto", - "vector-effect": "none", - "vertical-align": "baseline", - "view-timeline-axis": "block", - "view-timeline-inset": "auto", - "view-timeline-name": "none", - "view-transition-class": "none", - "view-transition-group": "normal", - "view-transition-name": "none", - "visibility": "visible", - "white-space-collapse": "collapse", - "widows": "2", - "width": "33.2812px", - "will-change": "auto", - "word-break": "normal", - "word-spacing": "0px", - "writing-mode": "horizontal-tb", - "x": "0px", - "y": "0px", - "z-index": "auto", - "zoom": "1", - "-webkit-border-horizontal-spacing": "0px", - "-webkit-border-image": "none", - "-webkit-border-vertical-spacing": "0px", - "-webkit-box-align": "stretch", - "-webkit-box-decoration-break": "slice", - "-webkit-box-direction": "normal", - "-webkit-box-flex": "0", - "-webkit-box-ordinal-group": "1", - "-webkit-box-orient": "horizontal", - "-webkit-box-pack": "start", - "-webkit-box-reflect": "none", - "-webkit-font-smoothing": "auto", - "-webkit-line-break": "auto", - "-webkit-line-clamp": "none", - "-webkit-locale": "\"en\"", - "-webkit-mask-box-image": "none", - "-webkit-mask-box-image-outset": "0", - "-webkit-mask-box-image-repeat": "stretch", - "-webkit-mask-box-image-slice": "0 fill", - "-webkit-mask-box-image-source": "none", - "-webkit-mask-box-image-width": "auto", - "-webkit-rtl-ordering": "logical", - "-webkit-tap-highlight-color": "rgba(0, 0, 0, 0.18)", - "-webkit-text-combine": "none", - "-webkit-text-decorations-in-effect": "none", - "-webkit-text-fill-color": "rgb(204, 204, 204)", - "-webkit-text-orientation": "vertical-right", - "-webkit-text-security": "none", - "-webkit-text-stroke-color": "rgb(204, 204, 204)", - "-webkit-text-stroke-width": "0px", - "-webkit-user-drag": "auto", - "-webkit-user-modify": "read-only", - "-webkit-writing-mode": "horizontal-tb", - "--bg-gray": "rgb(82, 86, 89)", - "--text-gray": "rgb(51, 51, 51)", - "--accent-blue": "#0066cc", - "--sidebar-gray": "#d1d4d2", - "--black-bar": "#2b2b2b", - "--border-gray": "#dddddd", - "--paper-white": "#ffffff", - "--text-dark": "rgb(0, 0, 0)" - }, - ".sidebar-title": { - "accent-color": "auto", - "align-content": "normal", - "align-items": "normal", - "align-self": "auto", - "alignment-baseline": "auto", - "anchor-name": "none", - "anchor-scope": "none", - "animation-composition": "replace", - "animation-delay": "0s", - "animation-direction": "normal", - "animation-duration": "0s", - "animation-fill-mode": "none", - "animation-iteration-count": "1", - "animation-name": "none", - "animation-play-state": "running", - "animation-range-end": "normal", - "animation-range-start": "normal", - "animation-timeline": "auto", - "animation-timing-function": "ease", - "app-region": "none", - "appearance": "none", - "backdrop-filter": "none", - "backface-visibility": "visible", - "background-attachment": "scroll", - "background-blend-mode": "normal", - "background-clip": "border-box", - "background-color": "rgba(0, 0, 0, 0)", - "background-image": "none", - "background-origin": "padding-box", - "background-position": "0% 0%", - "background-repeat": "repeat", - "background-size": "auto", - "baseline-shift": "0px", - "baseline-source": "auto", - "block-size": "38.4688px", - "border-block-end-color": "rgb(51, 51, 51)", - "border-block-end-style": "none", - "border-block-end-width": "0px", - "border-block-start-color": "rgb(51, 51, 51)", - "border-block-start-style": "none", - "border-block-start-width": "0px", - "border-bottom-color": "rgb(51, 51, 51)", - "border-bottom-left-radius": "0px", - "border-bottom-right-radius": "0px", - "border-bottom-style": "none", - "border-bottom-width": "0px", - "border-collapse": "separate", - "border-end-end-radius": "0px", - "border-end-start-radius": "0px", - "border-image-outset": "0", - "border-image-repeat": "stretch", - "border-image-slice": "100%", - "border-image-source": "none", - "border-image-width": "1", - "border-inline-end-color": "rgb(51, 51, 51)", - "border-inline-end-style": "none", - "border-inline-end-width": "0px", - "border-inline-start-color": "rgb(51, 51, 51)", - "border-inline-start-style": "none", - "border-inline-start-width": "0px", - "border-left-color": "rgb(51, 51, 51)", - "border-left-style": "none", - "border-left-width": "0px", - "border-right-color": "rgb(51, 51, 51)", - "border-right-style": "none", - "border-right-width": "0px", - "border-start-end-radius": "0px", - "border-start-start-radius": "0px", - "border-top-color": "rgb(51, 51, 51)", - "border-top-left-radius": "0px", - "border-top-right-radius": "0px", - "border-top-style": "none", - "border-top-width": "0px", - "bottom": "auto", - "box-decoration-break": "slice", - "box-shadow": "none", - "box-sizing": "border-box", - "break-after": "auto", - "break-before": "auto", - "break-inside": "auto", - "buffered-rendering": "auto", - "caption-side": "top", - "caret-animation": "auto", - "caret-color": "rgb(51, 51, 51)", - "clear": "none", - "clip": "auto", - "clip-path": "none", - "clip-rule": "nonzero", - "color": "rgb(51, 51, 51)", - "color-interpolation": "srgb", - "color-interpolation-filters": "linearrgb", - "color-rendering": "auto", - "column-count": "auto", - "column-gap": "normal", - "column-rule-color": "rgb(51, 51, 51)", - "column-rule-style": "none", - "column-rule-width": "0px", - "column-span": "none", - "column-width": "auto", - "contain-intrinsic-block-size": "none", - "contain-intrinsic-height": "none", - "contain-intrinsic-inline-size": "none", - "contain-intrinsic-size": "none", - "contain-intrinsic-width": "none", - "container-name": "none", - "container-type": "normal", - "content": "normal", - "corner-bottom-left-shape": "round", - "corner-bottom-right-shape": "round", - "corner-end-end-shape": "round", - "corner-end-start-shape": "round", - "corner-start-end-shape": "round", - "corner-start-start-shape": "round", - "corner-top-left-shape": "round", - "corner-top-right-shape": "round", - "cursor": "auto", - "cx": "0px", - "cy": "0px", - "d": "none", - "direction": "ltr", - "display": "block", - "dominant-baseline": "auto", - "dynamic-range-limit": "no-limit", - "empty-cells": "show", - "field-sizing": "fixed", - "fill": "rgb(0, 0, 0)", - "fill-opacity": "1", - "fill-rule": "nonzero", - "filter": "none", - "flex-basis": "auto", - "flex-direction": "row", - "flex-grow": "0", - "flex-shrink": "1", - "flex-wrap": "nowrap", - "float": "none", - "flood-color": "rgb(0, 0, 0)", - "flood-opacity": "1", - "font-family": "Quicksand, sans-serif", - "font-kerning": "auto", - "font-optical-sizing": "auto", - "font-palette": "normal", - "font-size": "18.72px", - "font-size-adjust": "none", - "font-stretch": "100%", - "font-style": "normal", - "font-synthesis-small-caps": "auto", - "font-synthesis-style": "auto", - "font-synthesis-weight": "auto", - "font-variant": "normal", - "font-variant-alternates": "normal", - "font-variant-caps": "normal", - "font-variant-east-asian": "normal", - "font-variant-emoji": "normal", - "font-variant-ligatures": "normal", - "font-variant-numeric": "normal", - "font-variant-position": "normal", - "font-weight": "500", - "grid-auto-columns": "auto", - "grid-auto-flow": "row", - "grid-auto-rows": "auto", - "grid-column-end": "auto", - "grid-column-start": "auto", - "grid-row-end": "auto", - "grid-row-start": "auto", - "grid-template-areas": "none", - "grid-template-columns": "none", - "grid-template-rows": "none", - "height": "38.4688px", - "hyphenate-character": "auto", - "hyphenate-limit-chars": "auto", - "hyphens": "manual", - "image-orientation": "from-image", - "image-rendering": "auto", - "initial-letter": "normal", - "inline-size": "252px", - "inset-block-end": "auto", - "inset-block-start": "auto", - "inset-inline-end": "auto", - "inset-inline-start": "auto", - "interactivity": "auto", - "interpolate-size": "numeric-only", - "isolation": "auto", - "justify-content": "normal", - "justify-items": "normal", - "justify-self": "auto", - "left": "auto", - "letter-spacing": "normal", - "lighting-color": "rgb(255, 255, 255)", - "line-break": "auto", - "line-height": "22.464px", - "list-style-image": "none", - "list-style-position": "outside", - "list-style-type": "disc", - "margin-block-end": "8px", - "margin-block-start": "0px", - "margin-bottom": "8px", - "margin-inline-end": "0px", - "margin-inline-start": "0px", - "margin-left": "0px", - "margin-right": "0px", - "margin-top": "0px", - "marker-end": "none", - "marker-mid": "none", - "marker-start": "none", - "mask-clip": "border-box", - "mask-composite": "add", - "mask-image": "none", - "mask-mode": "match-source", - "mask-origin": "border-box", - "mask-position": "0% 0%", - "mask-repeat": "repeat", - "mask-size": "auto", - "mask-type": "luminance", - "math-depth": "0", - "math-shift": "normal", - "math-style": "normal", - "max-block-size": "none", - "max-height": "none", - "max-inline-size": "none", - "max-width": "none", - "min-block-size": "0px", - "min-height": "0px", - "min-inline-size": "0px", - "min-width": "0px", - "mix-blend-mode": "normal", - "object-fit": "fill", - "object-position": "50% 50%", - "object-view-box": "none", - "offset-anchor": "auto", - "offset-distance": "0px", - "offset-path": "none", - "offset-position": "normal", - "offset-rotate": "auto 0deg", - "opacity": "1", - "order": "0", - "orphans": "2", - "outline-color": "rgb(51, 51, 51)", - "outline-offset": "0px", - "outline-style": "none", - "outline-width": "0px", - "overflow-anchor": "auto", - "overflow-block": "visible", - "overflow-clip-margin": "0px", - "overflow-inline": "visible", - "overflow-wrap": "normal", - "overflow-x": "visible", - "overflow-y": "visible", - "overlay": "none", - "overscroll-behavior-block": "auto", - "overscroll-behavior-inline": "auto", - "padding-block-end": "8px", - "padding-block-start": "8px", - "padding-bottom": "8px", - "padding-inline-end": "0px", - "padding-inline-start": "0px", - "padding-left": "0px", - "padding-right": "0px", - "padding-top": "8px", - "paint-order": "normal", - "perspective": "none", - "perspective-origin": "126px 19.2344px", - "pointer-events": "auto", - "position": "static", - "position-anchor": "auto", - "position-area": "none", - "position-try-fallbacks": "none", - "position-try-order": "normal", - "position-visibility": "always", - "print-color-adjust": "economy", - "r": "0px", - "reading-flow": "normal", - "reading-order": "0", - "resize": "none", - "right": "auto", - "rotate": "none", - "row-gap": "normal", - "ruby-align": "space-around", - "ruby-position": "over", - "rx": "auto", - "ry": "auto", - "scale": "none", - "scroll-behavior": "auto", - "scroll-initial-target": "none", - "scroll-margin-block-end": "0px", - "scroll-margin-block-start": "0px", - "scroll-margin-inline-end": "0px", - "scroll-margin-inline-start": "0px", - "scroll-marker-group": "none", - "scroll-padding-block-end": "auto", - "scroll-padding-block-start": "auto", - "scroll-padding-inline-end": "auto", - "scroll-padding-inline-start": "auto", - "scroll-target-group": "none", - "scroll-timeline-axis": "block", - "scroll-timeline-name": "none", - "scrollbar-color": "auto", - "scrollbar-gutter": "auto", - "scrollbar-width": "auto", - "shape-image-threshold": "0", - "shape-margin": "0px", - "shape-outside": "none", - "shape-rendering": "auto", - "speak": "normal", - "stop-color": "rgb(0, 0, 0)", - "stop-opacity": "1", - "stroke": "none", - "stroke-dasharray": "none", - "stroke-dashoffset": "0px", - "stroke-linecap": "butt", - "stroke-linejoin": "miter", - "stroke-miterlimit": "4", - "stroke-opacity": "1", - "stroke-width": "1px", - "tab-size": "8", - "table-layout": "auto", - "text-align": "start", - "text-align-last": "auto", - "text-anchor": "start", - "text-autospace": "no-autospace", - "text-box-edge": "auto", - "text-box-trim": "none", - "text-decoration": "none", - "text-decoration-color": "rgb(51, 51, 51)", - "text-decoration-line": "none", - "text-decoration-skip-ink": "auto", - "text-decoration-style": "solid", - "text-emphasis-color": "rgb(51, 51, 51)", - "text-emphasis-position": "over", - "text-emphasis-style": "none", - "text-indent": "0px", - "text-overflow": "clip", - "text-rendering": "auto", - "text-shadow": "none", - "text-size-adjust": "auto", - "text-spacing-trim": "normal", - "text-transform": "none", - "text-underline-position": "auto", - "text-wrap-mode": "wrap", - "text-wrap-style": "auto", - "timeline-scope": "none", - "top": "auto", - "touch-action": "auto", - "transform": "none", - "transform-origin": "126px 19.2344px", - "transform-style": "flat", - "transition-behavior": "normal", - "transition-delay": "0s", - "transition-duration": "0s", - "transition-property": "all", - "transition-timing-function": "ease", - "translate": "none", - "unicode-bidi": "isolate", - "user-select": "auto", - "vector-effect": "none", - "vertical-align": "baseline", - "view-timeline-axis": "block", - "view-timeline-inset": "auto", - "view-timeline-name": "none", - "view-transition-class": "none", - "view-transition-group": "normal", - "view-transition-name": "none", - "visibility": "visible", - "white-space-collapse": "collapse", - "widows": "2", - "width": "252px", - "will-change": "auto", - "word-break": "normal", - "word-spacing": "0px", - "writing-mode": "horizontal-tb", - "x": "0px", - "y": "0px", - "z-index": "auto", - "zoom": "1", - "-webkit-border-horizontal-spacing": "0px", - "-webkit-border-image": "none", - "-webkit-border-vertical-spacing": "0px", - "-webkit-box-align": "stretch", - "-webkit-box-decoration-break": "slice", - "-webkit-box-direction": "normal", - "-webkit-box-flex": "0", - "-webkit-box-ordinal-group": "1", - "-webkit-box-orient": "horizontal", - "-webkit-box-pack": "start", - "-webkit-box-reflect": "none", - "-webkit-font-smoothing": "auto", - "-webkit-line-break": "auto", - "-webkit-line-clamp": "none", - "-webkit-locale": "\"en\"", - "-webkit-mask-box-image": "none", - "-webkit-mask-box-image-outset": "0", - "-webkit-mask-box-image-repeat": "stretch", - "-webkit-mask-box-image-slice": "0 fill", - "-webkit-mask-box-image-source": "none", - "-webkit-mask-box-image-width": "auto", - "-webkit-rtl-ordering": "logical", - "-webkit-tap-highlight-color": "rgba(0, 0, 0, 0.18)", - "-webkit-text-combine": "none", - "-webkit-text-decorations-in-effect": "none", - "-webkit-text-fill-color": "rgb(51, 51, 51)", - "-webkit-text-orientation": "vertical-right", - "-webkit-text-security": "none", - "-webkit-text-stroke-color": "rgb(51, 51, 51)", - "-webkit-text-stroke-width": "0px", - "-webkit-user-drag": "auto", - "-webkit-user-modify": "read-only", - "-webkit-writing-mode": "horizontal-tb", - "--bg-gray": "rgb(82, 86, 89)", - "--text-gray": "rgb(51, 51, 51)", - "--accent-blue": "#0066cc", - "--sidebar-gray": "#d1d4d2", - "--black-bar": "#2b2b2b", - "--border-gray": "#dddddd", - "--paper-white": "#ffffff", - "--text-dark": "rgb(0, 0, 0)" - }, - ".section-title": { - "accent-color": "auto", - "align-content": "normal", - "align-items": "normal", - "align-self": "auto", - "alignment-baseline": "auto", - "anchor-name": "none", - "anchor-scope": "none", - "animation-composition": "replace", - "animation-delay": "0s", - "animation-direction": "normal", - "animation-duration": "0s", - "animation-fill-mode": "none", - "animation-iteration-count": "1", - "animation-name": "none", - "animation-play-state": "running", - "animation-range-end": "normal", - "animation-range-start": "normal", - "animation-timeline": "auto", - "animation-timing-function": "ease", - "app-region": "none", - "appearance": "none", - "backdrop-filter": "none", - "backface-visibility": "visible", - "background-attachment": "scroll", - "background-blend-mode": "normal", - "background-clip": "border-box", - "background-color": "rgba(0, 0, 0, 0)", - "background-image": "none", - "background-origin": "padding-box", - "background-position": "0% 0%", - "background-repeat": "repeat", - "background-size": "auto", - "baseline-shift": "0px", - "baseline-source": "auto", - "block-size": "24.9531px", - "border-block-end-color": "rgb(51, 51, 51)", - "border-block-end-style": "none", - "border-block-end-width": "0px", - "border-block-start-color": "rgb(51, 51, 51)", - "border-block-start-style": "none", - "border-block-start-width": "0px", - "border-bottom-color": "rgb(51, 51, 51)", - "border-bottom-left-radius": "0px", - "border-bottom-right-radius": "0px", - "border-bottom-style": "none", - "border-bottom-width": "0px", - "border-collapse": "separate", - "border-end-end-radius": "0px", - "border-end-start-radius": "0px", - "border-image-outset": "0", - "border-image-repeat": "stretch", - "border-image-slice": "100%", - "border-image-source": "none", - "border-image-width": "1", - "border-inline-end-color": "rgb(51, 51, 51)", - "border-inline-end-style": "none", - "border-inline-end-width": "0px", - "border-inline-start-color": "rgb(51, 51, 51)", - "border-inline-start-style": "none", - "border-inline-start-width": "0px", - "border-left-color": "rgb(51, 51, 51)", - "border-left-style": "none", - "border-left-width": "0px", - "border-right-color": "rgb(51, 51, 51)", - "border-right-style": "none", - "border-right-width": "0px", - "border-start-end-radius": "0px", - "border-start-start-radius": "0px", - "border-top-color": "rgb(51, 51, 51)", - "border-top-left-radius": "0px", - "border-top-right-radius": "0px", - "border-top-style": "none", - "border-top-width": "0px", - "bottom": "auto", - "box-decoration-break": "slice", - "box-shadow": "none", - "box-sizing": "border-box", - "break-after": "auto", - "break-before": "auto", - "break-inside": "auto", - "buffered-rendering": "auto", - "caption-side": "top", - "caret-animation": "auto", - "caret-color": "rgb(51, 51, 51)", - "clear": "none", - "clip": "auto", - "clip-path": "none", - "clip-rule": "nonzero", - "color": "rgb(51, 51, 51)", - "color-interpolation": "srgb", - "color-interpolation-filters": "linearrgb", - "color-rendering": "auto", - "column-count": "auto", - "column-gap": "normal", - "column-rule-color": "rgb(51, 51, 51)", - "column-rule-style": "none", - "column-rule-width": "0px", - "column-span": "none", - "column-width": "auto", - "contain-intrinsic-block-size": "none", - "contain-intrinsic-height": "none", - "contain-intrinsic-inline-size": "none", - "contain-intrinsic-size": "none", - "contain-intrinsic-width": "none", - "container-name": "none", - "container-type": "normal", - "content": "normal", - "corner-bottom-left-shape": "round", - "corner-bottom-right-shape": "round", - "corner-end-end-shape": "round", - "corner-end-start-shape": "round", - "corner-start-end-shape": "round", - "corner-start-start-shape": "round", - "corner-top-left-shape": "round", - "corner-top-right-shape": "round", - "cursor": "auto", - "cx": "0px", - "cy": "0px", - "d": "none", - "direction": "ltr", - "display": "block", - "dominant-baseline": "auto", - "dynamic-range-limit": "no-limit", - "empty-cells": "show", - "field-sizing": "fixed", - "fill": "rgb(0, 0, 0)", - "fill-opacity": "1", - "fill-rule": "nonzero", - "filter": "none", - "flex-basis": "auto", - "flex-direction": "row", - "flex-grow": "0", - "flex-shrink": "1", - "flex-wrap": "nowrap", - "float": "none", - "flood-color": "rgb(0, 0, 0)", - "flood-opacity": "1", - "font-family": "Quicksand, sans-serif", - "font-kerning": "auto", - "font-optical-sizing": "auto", - "font-palette": "normal", - "font-size": "20.8px", - "font-size-adjust": "none", - "font-stretch": "100%", - "font-style": "normal", - "font-synthesis-small-caps": "auto", - "font-synthesis-style": "auto", - "font-synthesis-weight": "auto", - "font-variant": "normal", - "font-variant-alternates": "normal", - "font-variant-caps": "normal", - "font-variant-east-asian": "normal", - "font-variant-emoji": "normal", - "font-variant-ligatures": "normal", - "font-variant-numeric": "normal", - "font-variant-position": "normal", - "font-weight": "500", - "grid-auto-columns": "auto", - "grid-auto-flow": "row", - "grid-auto-rows": "auto", - "grid-column-end": "auto", - "grid-column-start": "auto", - "grid-row-end": "auto", - "grid-row-start": "auto", - "grid-template-areas": "none", - "grid-template-columns": "none", - "grid-template-rows": "none", - "height": "24.9531px", - "hyphenate-character": "auto", - "hyphenate-limit-chars": "auto", - "hyphens": "manual", - "image-orientation": "from-image", - "image-rendering": "auto", - "initial-letter": "normal", - "inline-size": "820px", - "inset-block-end": "auto", - "inset-block-start": "auto", - "inset-inline-end": "auto", - "inset-inline-start": "auto", - "interactivity": "auto", - "interpolate-size": "numeric-only", - "isolation": "auto", - "justify-content": "normal", - "justify-items": "normal", - "justify-self": "auto", - "left": "auto", - "letter-spacing": "normal", - "lighting-color": "rgb(255, 255, 255)", - "line-break": "auto", - "line-height": "24.96px", - "list-style-image": "none", - "list-style-position": "outside", - "list-style-type": "disc", - "margin-block-end": "10px", - "margin-block-start": "10px", - "margin-bottom": "10px", - "margin-inline-end": "0px", - "margin-inline-start": "0px", - "margin-left": "0px", - "margin-right": "0px", - "margin-top": "10px", - "marker-end": "none", - "marker-mid": "none", - "marker-start": "none", - "mask-clip": "border-box", - "mask-composite": "add", - "mask-image": "none", - "mask-mode": "match-source", - "mask-origin": "border-box", - "mask-position": "0% 0%", - "mask-repeat": "repeat", - "mask-size": "auto", - "mask-type": "luminance", - "math-depth": "0", - "math-shift": "normal", - "math-style": "normal", - "max-block-size": "none", - "max-height": "none", - "max-inline-size": "none", - "max-width": "none", - "min-block-size": "0px", - "min-height": "0px", - "min-inline-size": "0px", - "min-width": "0px", - "mix-blend-mode": "normal", - "object-fit": "fill", - "object-position": "50% 50%", - "object-view-box": "none", - "offset-anchor": "auto", - "offset-distance": "0px", - "offset-path": "none", - "offset-position": "normal", - "offset-rotate": "auto 0deg", - "opacity": "1", - "order": "0", - "orphans": "2", - "outline-color": "rgb(51, 51, 51)", - "outline-offset": "0px", - "outline-style": "none", - "outline-width": "0px", - "overflow-anchor": "auto", - "overflow-block": "visible", - "overflow-clip-margin": "0px", - "overflow-inline": "visible", - "overflow-wrap": "normal", - "overflow-x": "visible", - "overflow-y": "visible", - "overlay": "none", - "overscroll-behavior-block": "auto", - "overscroll-behavior-inline": "auto", - "padding-block-end": "0px", - "padding-block-start": "0px", - "padding-bottom": "0px", - "padding-inline-end": "0px", - "padding-inline-start": "0px", - "padding-left": "0px", - "padding-right": "0px", - "padding-top": "0px", - "paint-order": "normal", - "perspective": "none", - "perspective-origin": "410px 12.4688px", - "pointer-events": "auto", - "position": "static", - "position-anchor": "auto", - "position-area": "none", - "position-try-fallbacks": "none", - "position-try-order": "normal", - "position-visibility": "always", - "print-color-adjust": "economy", - "r": "0px", - "reading-flow": "normal", - "reading-order": "0", - "resize": "none", - "right": "auto", - "rotate": "none", - "row-gap": "normal", - "ruby-align": "space-around", - "ruby-position": "over", - "rx": "auto", - "ry": "auto", - "scale": "none", - "scroll-behavior": "auto", - "scroll-initial-target": "none", - "scroll-margin-block-end": "0px", - "scroll-margin-block-start": "0px", - "scroll-margin-inline-end": "0px", - "scroll-margin-inline-start": "0px", - "scroll-marker-group": "none", - "scroll-padding-block-end": "auto", - "scroll-padding-block-start": "auto", - "scroll-padding-inline-end": "auto", - "scroll-padding-inline-start": "auto", - "scroll-target-group": "none", - "scroll-timeline-axis": "block", - "scroll-timeline-name": "none", - "scrollbar-color": "auto", - "scrollbar-gutter": "auto", - "scrollbar-width": "auto", - "shape-image-threshold": "0", - "shape-margin": "0px", - "shape-outside": "none", - "shape-rendering": "auto", - "speak": "normal", - "stop-color": "rgb(0, 0, 0)", - "stop-opacity": "1", - "stroke": "none", - "stroke-dasharray": "none", - "stroke-dashoffset": "0px", - "stroke-linecap": "butt", - "stroke-linejoin": "miter", - "stroke-miterlimit": "4", - "stroke-opacity": "1", - "stroke-width": "1px", - "tab-size": "8", - "table-layout": "auto", - "text-align": "start", - "text-align-last": "auto", - "text-anchor": "start", - "text-autospace": "no-autospace", - "text-box-edge": "auto", - "text-box-trim": "none", - "text-decoration": "none", - "text-decoration-color": "rgb(51, 51, 51)", - "text-decoration-line": "none", - "text-decoration-skip-ink": "auto", - "text-decoration-style": "solid", - "text-emphasis-color": "rgb(51, 51, 51)", - "text-emphasis-position": "over", - "text-emphasis-style": "none", - "text-indent": "0px", - "text-overflow": "clip", - "text-rendering": "auto", - "text-shadow": "none", - "text-size-adjust": "auto", - "text-spacing-trim": "normal", - "text-transform": "none", - "text-underline-position": "auto", - "text-wrap-mode": "wrap", - "text-wrap-style": "auto", - "timeline-scope": "none", - "top": "auto", - "touch-action": "auto", - "transform": "none", - "transform-origin": "410px 12.4766px", - "transform-style": "flat", - "transition-behavior": "normal", - "transition-delay": "0s", - "transition-duration": "0s", - "transition-property": "all", - "transition-timing-function": "ease", - "translate": "none", - "unicode-bidi": "isolate", - "user-select": "auto", - "vector-effect": "none", - "vertical-align": "baseline", - "view-timeline-axis": "block", - "view-timeline-inset": "auto", - "view-timeline-name": "none", - "view-transition-class": "none", - "view-transition-group": "normal", - "view-transition-name": "none", - "visibility": "visible", - "white-space-collapse": "collapse", - "widows": "2", - "width": "820px", - "will-change": "auto", - "word-break": "normal", - "word-spacing": "0px", - "writing-mode": "horizontal-tb", - "x": "0px", - "y": "0px", - "z-index": "auto", - "zoom": "1", - "-webkit-border-horizontal-spacing": "0px", - "-webkit-border-image": "none", - "-webkit-border-vertical-spacing": "0px", - "-webkit-box-align": "stretch", - "-webkit-box-decoration-break": "slice", - "-webkit-box-direction": "normal", - "-webkit-box-flex": "0", - "-webkit-box-ordinal-group": "1", - "-webkit-box-orient": "horizontal", - "-webkit-box-pack": "start", - "-webkit-box-reflect": "none", - "-webkit-font-smoothing": "auto", - "-webkit-line-break": "auto", - "-webkit-line-clamp": "none", - "-webkit-locale": "\"en\"", - "-webkit-mask-box-image": "none", - "-webkit-mask-box-image-outset": "0", - "-webkit-mask-box-image-repeat": "stretch", - "-webkit-mask-box-image-slice": "0 fill", - "-webkit-mask-box-image-source": "none", - "-webkit-mask-box-image-width": "auto", - "-webkit-rtl-ordering": "logical", - "-webkit-tap-highlight-color": "rgba(0, 0, 0, 0.18)", - "-webkit-text-combine": "none", - "-webkit-text-decorations-in-effect": "none", - "-webkit-text-fill-color": "rgb(51, 51, 51)", - "-webkit-text-orientation": "vertical-right", - "-webkit-text-security": "none", - "-webkit-text-stroke-color": "rgb(51, 51, 51)", - "-webkit-text-stroke-width": "0px", - "-webkit-user-drag": "auto", - "-webkit-user-modify": "read-only", - "-webkit-writing-mode": "horizontal-tb", - "--bg-gray": "rgb(82, 86, 89)", - "--text-gray": "rgb(51, 51, 51)", - "--accent-blue": "#0066cc", - "--sidebar-gray": "#d1d4d2", - "--black-bar": "#2b2b2b", - "--border-gray": "#dddddd", - "--paper-white": "#ffffff", - "--text-dark": "rgb(0, 0, 0)" - }, - ".cv-name": { - "accent-color": "auto", - "align-content": "normal", - "align-items": "normal", - "align-self": "auto", - "alignment-baseline": "auto", - "anchor-name": "none", - "anchor-scope": "none", - "animation-composition": "replace", - "animation-delay": "0s", - "animation-direction": "normal", - "animation-duration": "0s", - "animation-fill-mode": "none", - "animation-iteration-count": "1", - "animation-name": "none", - "animation-play-state": "running", - "animation-range-end": "normal", - "animation-range-start": "normal", - "animation-timeline": "auto", - "animation-timing-function": "ease", - "app-region": "none", - "appearance": "none", - "backdrop-filter": "none", - "backface-visibility": "visible", - "background-attachment": "scroll", - "background-blend-mode": "normal", - "background-clip": "border-box", - "background-color": "rgba(0, 0, 0, 0)", - "background-image": "none", - "background-origin": "padding-box", - "background-position": "0% 0%", - "background-repeat": "repeat", - "background-size": "auto", - "baseline-shift": "0px", - "baseline-source": "auto", - "block-size": "38.7188px", - "border-block-end-color": "rgb(0, 0, 0)", - "border-block-end-style": "none", - "border-block-end-width": "0px", - "border-block-start-color": "rgb(0, 0, 0)", - "border-block-start-style": "none", - "border-block-start-width": "0px", - "border-bottom-color": "rgb(0, 0, 0)", - "border-bottom-left-radius": "0px", - "border-bottom-right-radius": "0px", - "border-bottom-style": "none", - "border-bottom-width": "0px", - "border-collapse": "separate", - "border-end-end-radius": "0px", - "border-end-start-radius": "0px", - "border-image-outset": "0", - "border-image-repeat": "stretch", - "border-image-slice": "100%", - "border-image-source": "none", - "border-image-width": "1", - "border-inline-end-color": "rgb(0, 0, 0)", - "border-inline-end-style": "none", - "border-inline-end-width": "0px", - "border-inline-start-color": "rgb(0, 0, 0)", - "border-inline-start-style": "none", - "border-inline-start-width": "0px", - "border-left-color": "rgb(0, 0, 0)", - "border-left-style": "none", - "border-left-width": "0px", - "border-right-color": "rgb(0, 0, 0)", - "border-right-style": "none", - "border-right-width": "0px", - "border-start-end-radius": "0px", - "border-start-start-radius": "0px", - "border-top-color": "rgb(0, 0, 0)", - "border-top-left-radius": "0px", - "border-top-right-radius": "0px", - "border-top-style": "none", - "border-top-width": "0px", - "bottom": "auto", - "box-decoration-break": "slice", - "box-shadow": "none", - "box-sizing": "border-box", - "break-after": "auto", - "break-before": "auto", - "break-inside": "auto", - "buffered-rendering": "auto", - "caption-side": "top", - "caret-animation": "auto", - "caret-color": "rgb(0, 0, 0)", - "clear": "none", - "clip": "auto", - "clip-path": "none", - "clip-rule": "nonzero", - "color": "rgb(0, 0, 0)", - "color-interpolation": "srgb", - "color-interpolation-filters": "linearrgb", - "color-rendering": "auto", - "column-count": "auto", - "column-gap": "normal", - "column-rule-color": "rgb(0, 0, 0)", - "column-rule-style": "none", - "column-rule-width": "0px", - "column-span": "none", - "column-width": "auto", - "contain-intrinsic-block-size": "none", - "contain-intrinsic-height": "none", - "contain-intrinsic-inline-size": "none", - "contain-intrinsic-size": "none", - "contain-intrinsic-width": "none", - "container-name": "none", - "container-type": "normal", - "content": "normal", - "corner-bottom-left-shape": "round", - "corner-bottom-right-shape": "round", - "corner-end-end-shape": "round", - "corner-end-start-shape": "round", - "corner-start-end-shape": "round", - "corner-start-start-shape": "round", - "corner-top-left-shape": "round", - "corner-top-right-shape": "round", - "cursor": "auto", - "cx": "0px", - "cy": "0px", - "d": "none", - "direction": "ltr", - "display": "block", - "dominant-baseline": "auto", - "dynamic-range-limit": "no-limit", - "empty-cells": "show", - "field-sizing": "fixed", - "fill": "rgb(0, 0, 0)", - "fill-opacity": "1", - "fill-rule": "nonzero", - "filter": "none", - "flex-basis": "auto", - "flex-direction": "row", - "flex-grow": "0", - "flex-shrink": "1", - "flex-wrap": "nowrap", - "float": "none", - "flood-color": "rgb(0, 0, 0)", - "flood-opacity": "1", - "font-family": "Quicksand, sans-serif", - "font-kerning": "auto", - "font-optical-sizing": "auto", - "font-palette": "normal", - "font-size": "35.2px", - "font-size-adjust": "none", - "font-stretch": "100%", - "font-style": "normal", - "font-synthesis-small-caps": "auto", - "font-synthesis-style": "auto", - "font-synthesis-weight": "auto", - "font-variant": "normal", - "font-variant-alternates": "normal", - "font-variant-caps": "normal", - "font-variant-east-asian": "normal", - "font-variant-emoji": "normal", - "font-variant-ligatures": "normal", - "font-variant-numeric": "normal", - "font-variant-position": "normal", - "font-weight": "400", - "grid-auto-columns": "auto", - "grid-auto-flow": "row", - "grid-auto-rows": "auto", - "grid-column-end": "auto", - "grid-column-start": "auto", - "grid-row-end": "auto", - "grid-row-start": "auto", - "grid-template-areas": "none", - "grid-template-columns": "none", - "grid-template-rows": "none", - "height": "38.7188px", - "hyphenate-character": "auto", - "hyphenate-limit-chars": "auto", - "hyphens": "manual", - "image-orientation": "from-image", - "image-rendering": "auto", - "initial-letter": "normal", - "inline-size": "638px", - "inset-block-end": "auto", - "inset-block-start": "auto", - "inset-inline-end": "auto", - "inset-inline-start": "auto", - "interactivity": "auto", - "interpolate-size": "numeric-only", - "isolation": "auto", - "justify-content": "normal", - "justify-items": "normal", - "justify-self": "auto", - "left": "auto", - "letter-spacing": "normal", - "lighting-color": "rgb(255, 255, 255)", - "line-break": "auto", - "line-height": "38.72px", - "list-style-image": "none", - "list-style-position": "outside", - "list-style-type": "disc", - "margin-block-end": "8px", - "margin-block-start": "0px", - "margin-bottom": "8px", - "margin-inline-end": "0px", - "margin-inline-start": "0px", - "margin-left": "0px", - "margin-right": "0px", - "margin-top": "0px", - "marker-end": "none", - "marker-mid": "none", - "marker-start": "none", - "mask-clip": "border-box", - "mask-composite": "add", - "mask-image": "none", - "mask-mode": "match-source", - "mask-origin": "border-box", - "mask-position": "0% 0%", - "mask-repeat": "repeat", - "mask-size": "auto", - "mask-type": "luminance", - "math-depth": "0", - "math-shift": "normal", - "math-style": "normal", - "max-block-size": "none", - "max-height": "none", - "max-inline-size": "none", - "max-width": "none", - "min-block-size": "0px", - "min-height": "0px", - "min-inline-size": "0px", - "min-width": "0px", - "mix-blend-mode": "normal", - "object-fit": "fill", - "object-position": "50% 50%", - "object-view-box": "none", - "offset-anchor": "auto", - "offset-distance": "0px", - "offset-path": "none", - "offset-position": "normal", - "offset-rotate": "auto 0deg", - "opacity": "1", - "order": "0", - "orphans": "2", - "outline-color": "rgb(0, 0, 0)", - "outline-offset": "0px", - "outline-style": "none", - "outline-width": "0px", - "overflow-anchor": "auto", - "overflow-block": "visible", - "overflow-clip-margin": "0px", - "overflow-inline": "visible", - "overflow-wrap": "normal", - "overflow-x": "visible", - "overflow-y": "visible", - "overlay": "none", - "overscroll-behavior-block": "auto", - "overscroll-behavior-inline": "auto", - "padding-block-end": "0px", - "padding-block-start": "0px", - "padding-bottom": "0px", - "padding-inline-end": "0px", - "padding-inline-start": "0px", - "padding-left": "0px", - "padding-right": "0px", - "padding-top": "0px", - "paint-order": "normal", - "perspective": "none", - "perspective-origin": "319px 19.3594px", - "pointer-events": "auto", - "position": "static", - "position-anchor": "auto", - "position-area": "none", - "position-try-fallbacks": "none", - "position-try-order": "normal", - "position-visibility": "always", - "print-color-adjust": "economy", - "r": "0px", - "reading-flow": "normal", - "reading-order": "0", - "resize": "none", - "right": "auto", - "rotate": "none", - "row-gap": "normal", - "ruby-align": "space-around", - "ruby-position": "over", - "rx": "auto", - "ry": "auto", - "scale": "none", - "scroll-behavior": "auto", - "scroll-initial-target": "none", - "scroll-margin-block-end": "0px", - "scroll-margin-block-start": "0px", - "scroll-margin-inline-end": "0px", - "scroll-margin-inline-start": "0px", - "scroll-marker-group": "none", - "scroll-padding-block-end": "auto", - "scroll-padding-block-start": "auto", - "scroll-padding-inline-end": "auto", - "scroll-padding-inline-start": "auto", - "scroll-target-group": "none", - "scroll-timeline-axis": "block", - "scroll-timeline-name": "none", - "scrollbar-color": "auto", - "scrollbar-gutter": "auto", - "scrollbar-width": "auto", - "shape-image-threshold": "0", - "shape-margin": "0px", - "shape-outside": "none", - "shape-rendering": "auto", - "speak": "normal", - "stop-color": "rgb(0, 0, 0)", - "stop-opacity": "1", - "stroke": "none", - "stroke-dasharray": "none", - "stroke-dashoffset": "0px", - "stroke-linecap": "butt", - "stroke-linejoin": "miter", - "stroke-miterlimit": "4", - "stroke-opacity": "1", - "stroke-width": "1px", - "tab-size": "8", - "table-layout": "auto", - "text-align": "start", - "text-align-last": "auto", - "text-anchor": "start", - "text-autospace": "no-autospace", - "text-box-edge": "auto", - "text-box-trim": "none", - "text-decoration": "none", - "text-decoration-color": "rgb(0, 0, 0)", - "text-decoration-line": "none", - "text-decoration-skip-ink": "auto", - "text-decoration-style": "solid", - "text-emphasis-color": "rgb(0, 0, 0)", - "text-emphasis-position": "over", - "text-emphasis-style": "none", - "text-indent": "0px", - "text-overflow": "clip", - "text-rendering": "auto", - "text-shadow": "none", - "text-size-adjust": "auto", - "text-spacing-trim": "normal", - "text-transform": "none", - "text-underline-position": "auto", - "text-wrap-mode": "wrap", - "text-wrap-style": "auto", - "timeline-scope": "none", - "top": "auto", - "touch-action": "auto", - "transform": "none", - "transform-origin": "319px 19.3594px", - "transform-style": "flat", - "transition-behavior": "normal", - "transition-delay": "0s", - "transition-duration": "0s", - "transition-property": "all", - "transition-timing-function": "ease", - "translate": "none", - "unicode-bidi": "isolate", - "user-select": "auto", - "vector-effect": "none", - "vertical-align": "baseline", - "view-timeline-axis": "block", - "view-timeline-inset": "auto", - "view-timeline-name": "none", - "view-transition-class": "none", - "view-transition-group": "normal", - "view-transition-name": "none", - "visibility": "visible", - "white-space-collapse": "collapse", - "widows": "2", - "width": "638px", - "will-change": "auto", - "word-break": "normal", - "word-spacing": "0px", - "writing-mode": "horizontal-tb", - "x": "0px", - "y": "0px", - "z-index": "auto", - "zoom": "1", - "-webkit-border-horizontal-spacing": "0px", - "-webkit-border-image": "none", - "-webkit-border-vertical-spacing": "0px", - "-webkit-box-align": "stretch", - "-webkit-box-decoration-break": "slice", - "-webkit-box-direction": "normal", - "-webkit-box-flex": "0", - "-webkit-box-ordinal-group": "1", - "-webkit-box-orient": "horizontal", - "-webkit-box-pack": "start", - "-webkit-box-reflect": "none", - "-webkit-font-smoothing": "auto", - "-webkit-line-break": "auto", - "-webkit-line-clamp": "none", - "-webkit-locale": "\"en\"", - "-webkit-mask-box-image": "none", - "-webkit-mask-box-image-outset": "0", - "-webkit-mask-box-image-repeat": "stretch", - "-webkit-mask-box-image-slice": "0 fill", - "-webkit-mask-box-image-source": "none", - "-webkit-mask-box-image-width": "auto", - "-webkit-rtl-ordering": "logical", - "-webkit-tap-highlight-color": "rgba(0, 0, 0, 0.18)", - "-webkit-text-combine": "none", - "-webkit-text-decorations-in-effect": "none", - "-webkit-text-fill-color": "rgb(0, 0, 0)", - "-webkit-text-orientation": "vertical-right", - "-webkit-text-security": "none", - "-webkit-text-stroke-color": "rgb(0, 0, 0)", - "-webkit-text-stroke-width": "0px", - "-webkit-user-drag": "auto", - "-webkit-user-modify": "read-only", - "-webkit-writing-mode": "horizontal-tb", - "--bg-gray": "rgb(82, 86, 89)", - "--text-gray": "rgb(51, 51, 51)", - "--accent-blue": "#0066cc", - "--sidebar-gray": "#d1d4d2", - "--black-bar": "#2b2b2b", - "--border-gray": "#dddddd", - "--paper-white": "#ffffff", - "--text-dark": "rgb(0, 0, 0)" - } - } -} \ No newline at end of file diff --git a/tests/screenshots/new-full-rendered.png b/tests/screenshots/new-full-rendered.png deleted file mode 100644 index 964dd6b..0000000 Binary files a/tests/screenshots/new-full-rendered.png and /dev/null differ diff --git a/tests/screenshots/new-fullpage.png b/tests/screenshots/new-fullpage.png deleted file mode 100644 index 1914c71..0000000 Binary files a/tests/screenshots/new-fullpage.png and /dev/null differ diff --git a/tests/screenshots/new-header.png b/tests/screenshots/new-header.png deleted file mode 100644 index 8e9b949..0000000 Binary files a/tests/screenshots/new-header.png and /dev/null differ diff --git a/tests/screenshots/new-sidebar.png b/tests/screenshots/new-sidebar.png deleted file mode 100644 index aefd15a..0000000 Binary files a/tests/screenshots/new-sidebar.png and /dev/null differ diff --git a/tests/screenshots/sidebar-comparison.json b/tests/screenshots/sidebar-comparison.json deleted file mode 100644 index ff7120b..0000000 --- a/tests/screenshots/sidebar-comparison.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "old": {}, - "new": { - "backgroundColor": "rgb(209, 212, 210)", - "padding": "32px 24px", - "width": "300px", - "minWidth": "auto" - } -} \ No newline at end of file diff --git a/tests/screenshots/typography-comparison.json b/tests/screenshots/typography-comparison.json deleted file mode 100644 index 0821b3a..0000000 --- a/tests/screenshots/typography-comparison.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "old": {}, - "new": { - "name": { - "fontFamily": "Quicksand, sans-serif", - "fontSize": "35.2px", - "fontWeight": "400", - "lineHeight": "38.72px", - "color": "rgb(0, 0, 0)", - "letterSpacing": "normal" - }, - "sidebarTitle": { - "fontFamily": "Quicksand, sans-serif", - "fontSize": "18.72px", - "fontWeight": "500", - "lineHeight": "22.464px", - "color": "rgb(51, 51, 51)", - "letterSpacing": "normal" - }, - "sectionTitle": { - "fontFamily": "Quicksand, sans-serif", - "fontSize": "20.8px", - "fontWeight": "500", - "lineHeight": "24.96px", - "color": "rgb(51, 51, 51)", - "letterSpacing": "normal" - }, - "badge": { - "fontFamily": "Quicksand, \"Source Sans Pro\", -apple-system, system-ui, sans-serif", - "fontSize": "14.4px", - "fontWeight": "400", - "lineHeight": "21.6px", - "color": "rgb(204, 204, 204)", - "letterSpacing": "normal" - } - } -} \ No newline at end of file diff --git a/tests/test-bottom-buttons.html b/tests/test-bottom-buttons.html new file mode 100644 index 0000000..19ff575 --- /dev/null +++ b/tests/test-bottom-buttons.html @@ -0,0 +1,216 @@ + + +
+ + +Scroll to the bottom to see the buttons turn green!
+Start scrolling down to test the bottom detection...
+Keep scrolling...
+Just a bit more...
+You should see the buttons turn green now!
+The info button (ℹ️ bottom-left) and back-to-top button (↑ bottom-right) should both be GREEN when you're at the bottom.
+