docs/future-enhancement-plan #4

Merged
Latte merged 3 commits from docs/future-enhancement-plan into dev 2026-01-01 14:04:02 +00:00
13 changed files with 6943 additions and 541 deletions
+590
View File
@@ -0,0 +1,590 @@
# Cozy Den - Future Enhancement Plan
## Executive Summary
This plan outlines potential future enhancements for the Cozy Den landing page (hiddenden.cafe). The current site is well-built with a solid foundation - clean Astro architecture, cozy coffee-themed aesthetic, Docker deployment, and strong accessibility. This document organizes future possibilities by priority and complexity.
---
## Current State Assessment
**Strengths:**
- ✅ Clean, minimal Astro structure with proper TypeScript configuration
- ✅ Strong accessibility (ARIA labels, semantic HTML, keyboard navigation)
- ✅ Production-ready Docker deployment with Nginx
- ✅ Responsive design with mobile support
- ✅ Custom 404 page with themed styling
- ✅ SEO foundations (sitemap, robots.txt, meta tags)
- ✅ Privacy-first (no tracking, no external dependencies)
- ✅ Well-documented (CLAUDE.md, DEVELOPMENT.md, PROJECT_CONTEXT.md)
**Current Limitations:**
- Single-page site (only 2 routes: index, 404)
- No dynamic content (all hardcoded)
- No reusable components (empty components directory)
- Limited animations (only fade-in on load)
- No image optimization system
- Static service list with "More Coming Soon" placeholder
- Missing OG image for social sharing
- No form handling for contact/donations
---
## Phase 1: Quick Wins (Low Effort, High Impact)
### 1.1 Visual Polish
**Effort:** 1-2 hours | **Impact:** Medium
- **Add social sharing image** - Create og-image.png (1200x630px) with coffee theme
- **Favicon variants** - Add apple-touch-icon.png, favicon-16x16.png, favicon-32x32.png
- **Scroll animations** - Implement Intersection Observer for sections to fade/slide in on scroll
- **Hover effects** - Add subtle transitions for links, buttons, and cards
- **Background texture** - Subtle coffee stain or paper texture overlay on background
**Files to modify:**
- `public/` (add new image assets)
- `src/layouts/BaseLayout.astro` (add meta tags for OG image, favicon variants)
- `src/pages/index.astro` (add scroll animation JavaScript)
### 1.2 Content Updates
**Effort:** 30 minutes | **Impact:** High
- **Update service list** - Replace "More Coming Soon" with actual services
- **Add donation/payment links** - Ko-fi, Liberapay, or crypto addresses
- **Personalize About Me** - Review and update personal section
- **Add social links** - Mastodon, GitHub, etc. if desired
**Files to modify:**
- `src/pages/index.astro` (update content in Services and Support sections)
### 1.3 Component Extraction
**Effort:** 2-3 hours | **Impact:** Medium (code quality)
Extract repeated patterns into reusable Astro components:
- **ServiceItem.astro** - Reusable service card component
- **SupportItem.astro** - Reusable support/help card component
- **Card.astro** - Generic card wrapper component
- **Section.astro** - Section wrapper with consistent spacing
**Files to create:**
- `src/components/ServiceItem.astro`
- `src/components/SupportItem.astro`
- `src/components/Card.astro`
- `src/components/Section.astro`
**Files to modify:**
- `src/pages/index.astro` (replace hardcoded HTML with component calls)
---
## Phase 2: Medium Complexity Enhancements
### 2.1 Blog Implementation
**Effort:** 1-2 days | **Impact:** High
Implement a blog using Astro Content Collections for project updates, tutorials, and thoughts.
**Features:**
- Markdown-based blog posts with frontmatter (title, date, tags, description)
- Blog listing page with pagination
- Individual post pages with reading time estimate
- Tag filtering system
- RSS feed generation
- Code syntax highlighting with Shiki
- Consistent coffee theme styling
**Files to create:**
- `src/content/config.ts` (Content Collections schema)
- `src/content/blog/*.md` (blog posts)
- `src/pages/blog/index.astro` (blog listing)
- `src/pages/blog/[slug].astro` (individual post template)
- `src/pages/blog/tags/[tag].astro` (tag filtering)
- `src/pages/rss.xml.js` (RSS feed)
- `src/components/BlogCard.astro` (blog post preview card)
**Files to modify:**
- `astro.config.mjs` (enable Content Collections)
- `package.json` (add @astrojs/rss if needed)
- `src/pages/index.astro` (add link to blog section)
### 2.2 Projects Showcase
**Effort:** 2-3 days | **Impact:** High
Create a projects page showcasing your work, optionally pulling from Gitea API.
**Features:**
- Projects grid/list with filtering by language/topic
- Project cards with description, tech stack, links
- Option to manually curate or pull from Gitea API
- Search functionality
- "Featured projects" section
**Implementation Options:**
1. **Static** - Manually maintain project list in JSON/frontmatter
2. **Dynamic** - Fetch from Gitea API at build time (SSG)
3. **Hybrid** - Curated featured projects + API for rest
**Files to create:**
- `src/pages/projects.astro` (projects listing)
- `src/components/ProjectCard.astro` (project preview)
- `src/utils/gitea.ts` (Gitea API integration if dynamic)
- `src/data/projects.json` (static project data if manual)
**Files to modify:**
- `src/pages/index.astro` (add link to projects page)
### 2.3 Contact Form
**Effort:** 2-4 days | **Impact:** Medium
Self-hosted contact form with server-side handling (no third-party services).
**Implementation Options:**
1. **Astro SSR + Email** - Use Astro SSR mode with nodemailer
2. **External API** - Simple Node.js/Python microservice on separate port
3. **Static form service** - Self-hosted FormSpree alternative
**Features:**
- Name, email, message fields with validation
- Spam protection (honeypot field, rate limiting)
- Email notification to your address
- Success/error feedback to user
- Maintains privacy-first approach (no tracking)
**Files to create:**
- `src/pages/contact.astro` (contact page)
- `src/pages/api/contact.ts` (API endpoint if using SSR)
- Or separate microservice in `/contact-service/`
**Configuration changes:**
- May need to switch Astro to SSR mode or hybrid mode
- Docker setup updates for email service
### 2.4 Theme Toggle
**Effort:** 1-2 days | **Impact:** Medium
Add light/dark theme toggle or alternate color schemes.
**Implementation:**
- Toggle button in header/footer
- LocalStorage persistence
- CSS custom property switching
- Smooth transition animations
- Respect `prefers-color-scheme` system preference
- Alternate schemes: light mode, extra dark, seasonal themes
**Files to modify:**
- `src/layouts/BaseLayout.astro` (add theme toggle script, alternate color variables)
- `src/pages/index.astro` (add toggle button)
- Consider creating `src/utils/theme.ts` for theme management
---
## Phase 3: Advanced Features
### 3.1 Service Status Dashboard
**Effort:** 3-5 days | **Impact:** Medium-High
Dashboard showing status of self-hosted services with uptime monitoring.
**Features:**
- Real-time or periodic status checks
- Uptime percentage display
- Incident history
- Visual indicators (green/yellow/red)
- Manual status override capability
**Implementation:**
- Build-time health checks for SSG
- Or client-side status checks (fetch API)
- Or separate monitoring service (self-hosted Uptime Kuma integration)
**Files to create:**
- `src/pages/status.astro` (status dashboard)
- `src/components/ServiceStatus.astro` (status indicator)
- `src/utils/healthCheck.ts` (service health checking)
### 3.2 Art/Badge Gallery
**Effort:** 2-4 days | **Impact:** Medium
Showcase furry art, badges, stickers, or commission work.
**Features:**
- Grid layout with lightbox modal
- Categories/tags for filtering
- Artist attribution and links
- Image optimization with Astro Image component
- Lazy loading for performance
- Alt text for accessibility
**Files to create:**
- `src/pages/gallery.astro` (gallery page)
- `src/components/Gallery.astro` (grid component)
- `src/components/Lightbox.astro` (modal viewer)
- `public/gallery/` (image storage)
**Configuration:**
- Add @astrojs/image integration
- Optimize build for image processing
### 3.3 Interactive Hero Section
**Effort:** 2-3 days | **Impact:** Medium
Enhanced hero section with animated coffee cup, steam, or other cozy elements.
**Features:**
- Animated SVG coffee cup with steam
- Parallax scrolling effect
- Subtle particle effects (coffee beans, steam wisps)
- Time-based greeting (morning/evening)
- Optional: cursor trail effect (paw prints)
**Implementation:**
- SVG animations with CSS or GSAP
- Minimal JavaScript for interactivity
- Maintain performance (no heavy libraries)
**Files to modify:**
- `src/pages/index.astro` (enhance hero section)
- `public/` (add animated SVG assets)
### 3.4 Guest Book / Community Section
**Effort:** 4-7 days | **Impact:** Medium
Community guest book for visitors to leave messages.
**Features:**
- Message submission form
- Moderation queue (approve before publishing)
- Optional: user avatars or default furry icons
- Pagination
- Rate limiting and spam protection
- Self-hosted storage (SQLite or JSON files)
**Implementation:**
- Requires backend (Astro SSR or separate microservice)
- Database: SQLite, PostgreSQL, or JSON files
- Admin interface for moderation
- Consider: WebMentions integration for federated comments
**Files to create:**
- `src/pages/guestbook.astro` (guest book display)
- `src/pages/api/guestbook.ts` (API endpoints)
- `src/pages/admin/guestbook.astro` (moderation interface)
- Database schema and migration scripts
### 3.5 Privacy-Friendly Analytics
**Effort:** 2-4 days | **Impact:** Low-Medium
Self-hosted analytics to understand traffic without compromising privacy.
**Options:**
1. **Plausible Analytics** (self-hosted)
2. **GoAccess** (log analysis)
3. **Umami** (self-hosted, lightweight)
4. **Custom solution** (minimal logging in Nginx)
**Features:**
- Page view counts
- Referrer tracking
- No cookies or personal data
- Public dashboard option
- No IP address storage
**Implementation:**
- Deploy separate analytics service in Docker
- Add tracking script to BaseLayout
- Create dashboard page or embed analytics UI
---
## Phase 4: Long-Term Vision
### 4.1 Self-Hosting Hub
**Effort:** Ongoing | **Impact:** High (community value)
Transform site into a resource hub for self-hosting enthusiasts.
**Features:**
- Tutorial blog posts (Docker, Nginx, services)
- Docker Compose examples repository
- Service recommendations and reviews
- Troubleshooting guides
- Link directory of self-hosting resources
**Content needed:**
- Write tutorials based on your experience
- Document your homelab setup
- Create reusable Docker configurations
- Build community through shared knowledge
### 4.2 Multilingual Support (i18n)
**Effort:** 3-5 days + translation time | **Impact:** Medium
Add support for multiple languages.
**Implementation:**
- Use Astro's i18n routing
- Translation files (JSON or YAML)
- Language switcher component
- SEO considerations (hreflang tags)
**Languages to consider:**
- English (current)
- Additional languages based on audience
### 4.3 Furry Community Features
**Effort:** Variable | **Impact:** Medium (niche audience)
Features specifically for furry community engagement.
**Ideas:**
- Fursona information page
- Commission status/prices page
- Convention schedule/attendance
- Furry resource links
- Badge collection showcase
- Art trades or collaborations section
### 4.4 Seasonal Themes
**Effort:** 2-3 days per theme | **Impact:** Low-Medium (delight factor)
Automatic theme changes based on season or holidays.
**Themes:**
- Fall: Orange/brown palette, falling leaves animation
- Winter: Cool tones, snow particles
- Spring: Pastel colors, flower accents
- Summer: Bright colors, sunny vibes
- Special: Pride month, Halloween, winter holidays
**Implementation:**
- CSS custom property switching
- JavaScript date detection
- Optional manual theme selector
- Maintain core cozy aesthetic across all themes
### 4.5 API Integrations
**Effort:** Variable | **Impact:** Medium
Integrate with various services you self-host.
**Possible integrations:**
- **Gitea**: Recent commits, repository stats, contribution graph
- **Mastodon**: Recent toots, follower count (if you run instance)
- **Media server**: Recently watched/listened
- **RSS reader**: Shared articles or reading list
- **Bookmarks**: Public bookmark collection
---
## Phase 5: Performance & Code Quality
### 5.1 Performance Optimization
**Effort:** 2-3 days | **Impact:** Medium
Optimize for speed and efficiency.
**Tasks:**
- Implement lazy loading for images (when added)
- Add preload/prefetch hints for critical resources
- Optimize SVG assets (SVGO)
- Implement service worker for offline support
- Critical CSS inlining
- Font loading optimization (if custom fonts added)
- Reduce bundle size analysis
**Tools:**
- Lighthouse audits
- WebPageTest
- Bundle analyzer
### 5.2 Testing Infrastructure
**Effort:** 3-5 days | **Impact:** Medium (quality)
Add automated testing as site grows.
**Testing types:**
- **Unit tests**: Component logic (Vitest)
- **Integration tests**: Page rendering
- **E2E tests**: User flows (Playwright)
- **Accessibility tests**: axe-core automated checks
- **Visual regression**: Screenshot comparisons
**Files to create:**
- `tests/` directory structure
- `vitest.config.ts` or `playwright.config.ts`
- CI/CD pipeline configuration
### 5.3 Documentation Expansion
**Effort:** Ongoing | **Impact:** Medium
Enhance documentation for contributors and future maintainability.
**Documentation needs:**
- Inline code comments for complex logic
- API documentation (if backend added)
- Component usage examples
- Deployment troubleshooting guide
- Contributing guidelines
- Architecture decision records (ADRs)
---
## Implementation Priority Matrix
### High Priority, Quick Wins
1. ✅ Add social sharing OG image
2. ✅ Update service list content
3. ✅ Add donation/payment links
4. ✅ Implement scroll animations
5. ✅ Add favicon variants
### High Priority, Medium Effort
1. ✅ Blog implementation (Content Collections)
2. ✅ Projects showcase page
3. ✅ Component extraction (ServiceItem, Card, etc.)
### Medium Priority, Nice to Have
1. ✅ Contact form
2. ✅ Theme toggle
3. ✅ Service status dashboard
4. ✅ Art/badge gallery
### Low Priority, Future Exploration
1. ✅ Guest book / community features
2. ✅ Privacy-friendly analytics
3. ✅ Multilingual support
4. ✅ Seasonal themes
---
## Technical Considerations
### Astro Mode Decision
Current: **Static Site Generation (SSG)**
Consider switching to **Hybrid** or **Server (SSR)** if adding:
- Contact forms
- Guest book
- Real-time service status
- User authentication
**Trade-offs:**
- SSG: Fast, cheap hosting, better privacy, limited interactivity
- SSR: Dynamic features, server costs, more complexity
- Hybrid: Best of both (some pages static, some dynamic)
### Database Decisions
If dynamic features are added:
**Options:**
- **SQLite**: Simple, file-based, good for small-medium traffic
- **PostgreSQL**: Robust, scalable, self-hostable
- **JSON files**: Simplest, version-controllable, limited scale
- **Redis**: Fast, good for caching and simple data
**Recommendation:** Start with SQLite or JSON, migrate to PostgreSQL if traffic grows.
### Deployment Architecture Evolution
**Current:** Static site → Nginx container
**Future options:**
- **Hybrid**: Astro SSR + Nginx reverse proxy
- **Microservices**: Static site + separate API services
- **Monolith**: Astro SSR handling everything
**Recommendation:** Stick with static as long as possible, add microservices for specific dynamic needs.
---
## Resource Estimates
### Time Investment by Phase
- **Phase 1 (Quick Wins):** 3-5 hours total
- **Phase 2 (Medium):** 1-2 weeks part-time
- **Phase 3 (Advanced):** 2-4 weeks part-time
- **Phase 4 (Long-term):** Ongoing, months
- **Phase 5 (Quality):** 1 week part-time
### Infrastructure Costs
Current: Minimal (static hosting)
Potential additions:
- Blog: No additional cost (static)
- Contact form: May need email service (self-hosted = free)
- Database: SQLite = free, PostgreSQL = minimal RAM
- Analytics: Self-hosted = minimal resources
- Guest book: Requires backend (modest VPS resources)
---
## Next Steps & Recommendations
### Immediate Actions (This Week)
1. **Create OG image** - Design 1200x630px social sharing image with coffee theme
2. **Update service list** - Replace placeholder with actual services
3. **Add donation links** - Set up Ko-fi/Liberapay and add to Support section
4. **Scroll animations** - Implement Intersection Observer for section reveals
### Short Term (This Month)
1. **Extract components** - Refactor ServiceItem and SupportItem into reusable components
2. **Plan blog structure** - Decide on categories, write first 2-3 posts
3. **Implement blog** - Set up Content Collections and blog pages
4. **Favicon variants** - Generate and add multiple favicon sizes
### Medium Term (Next 3 Months)
1. **Projects showcase** - Decide on Gitea API integration or manual curation
2. **Contact form** - Evaluate SSR vs microservice approach
3. **Theme toggle** - Implement light/dark mode switching
4. **Service status** - Add health checks for self-hosted services
### Long Term (6-12 Months)
1. **Community features** - Guest book or comment system if desired
2. **Analytics** - Self-hosted Plausible or Umami instance
3. **Content creation** - Regular blog posts, tutorials, documentation
4. **Seasonal themes** - Create at least 2 alternate seasonal palettes
---
## Success Metrics
Track progress with these goals:
**Technical:**
- ✅ Lighthouse score >95 (all categories)
- ✅ Zero accessibility violations (axe-core)
- ✅ Build time <30 seconds
- ✅ Page load time <2 seconds
**Content:**
- ✅ 10+ blog posts published
- ✅ 5+ projects showcased
- ✅ All services documented
- ✅ Complete personal information
**Community (if applicable):**
- ✅ Guest book messages
- ✅ GitHub stars/forks
- ✅ Visitor traffic (privacy-respecting measurement)
- ✅ Positive feedback
---
## Conclusion
Cozy Den has a solid foundation and many exciting possibilities for growth. The beauty of this project is its flexibility - you can implement features at your own pace based on your needs and available time.
**Core Philosophy to Maintain:**
- ✅ Cozy, warm aesthetic (coffee theme)
- ✅ Privacy-first (no tracking, self-hosted)
- ✅ Lightweight (fast loading, minimal bloat)
- ✅ Accessible (WCAG compliance)
- ✅ Personal (authentic, not corporate)
**Recommended Approach:**
Start with Phase 1 quick wins to polish the existing site, then move to Phase 2 based on what excites you most (blog for writing, projects for showcasing work, contact form for engagement). Don't feel pressured to implement everything - pick what adds value to your goals.
This is your cozy corner of the internet. Build it in a way that makes you happy! ☕🦊
+5506
View File
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
# Favicon Generation Instructions
The following favicon files are referenced in the site but need to be generated:
- `favicon-16x16.png` - 16x16px PNG favicon
- `favicon-32x32.png` - 32x32px PNG favicon
- `apple-touch-icon.png` - 180x180px PNG for Apple devices
- `og-image.png` - 1200x630px PNG for social media sharing (optional, currently using SVG)
## How to Generate
### Option 1: Use an Online Tool
Visit https://realfavicongenerator.net/ and upload your source image (coffee emoji or custom design).
### Option 2: Use ImageMagick (if available)
If you have a source PNG image:
```bash
# 16x16
convert source.png -resize 16x16 favicon-16x16.png
# 32x32
convert source.png -resize 32x32 favicon-32x32.png
# Apple touch icon
convert source.png -resize 180x180 apple-touch-icon.png
# OG image
convert source.png -resize 1200x630 og-image.png
```
### Option 3: Use the SVG favicon
The current `favicon.svg` (coffee emoji) works in modern browsers. The PNG variants are fallbacks for older browsers and specific platforms.
## Current Status
-`favicon.svg` - Exists (coffee emoji)
-`favicon-16x16.png` - Needs to be created
-`favicon-32x32.png` - Needs to be created
-`apple-touch-icon.png` - Needs to be created
-`og-image.svg` - Created (SVG placeholder)
- ⚠️ `og-image.png` - Optional (browsers support SVG, but PNG is more compatible)
## Design Recommendations
Use the cozy coffee theme colors:
- Background: `#1a1410` (dark coffee)
- Accent: `#e8bf8e` (light coffee/cream)
- Text: `#f4e9d8` (cream)
The favicon should be simple and recognizable at small sizes. The coffee emoji ☕ is perfect!
+2 -1
View File
@@ -1,4 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" fill="#1a1410"/> <rect width="100" height="100" fill="#1a1410"/>
<text x="50" y="70" font-size="60" text-anchor="middle" fill="#d4a574"></text> <circle cx="50" cy="50" r="35" fill="#d4a574"/>
<circle cx="50" cy="50" r="25" fill="#2a1f18"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 204 B

After

Width:  |  Height:  |  Size: 220 B

+30
View File
@@ -0,0 +1,30 @@
<svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
<!-- Background gradient -->
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1a1410;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2a1f18;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="1200" height="630" fill="url(#bgGradient)"/>
<!-- Coffee cup emoji (approximate) -->
<text x="600" y="250" font-size="120" text-anchor="middle"></text>
<!-- Title -->
<text x="600" y="380" font-family="system-ui, sans-serif" font-size="72" font-weight="bold" fill="#e8bf8e" text-anchor="middle">
Hidden Den Cafe
</text>
<!-- Subtitle -->
<text x="600" y="450" font-family="system-ui, sans-serif" font-size="36" fill="#c4b5a0" text-anchor="middle">
A cozy corner of the internet
</text>
<!-- Bottom text -->
<text x="600" y="550" font-family="system-ui, sans-serif" font-size="24" fill="#8b6f47" text-anchor="middle">
Self-hosted • Privacy-focused • Furry-friendly
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+32
View File
@@ -0,0 +1,32 @@
{
"name": "Hidden Den Cafe",
"short_name": "Hidden Den",
"description": "A cozy, self-hosted corner of the internet",
"icons": [
{
"src": "/favicon.svg",
"sizes": "any",
"type": "image/svg+xml"
},
{
"src": "/favicon-16x16.png",
"sizes": "16x16",
"type": "image/png"
},
{
"src": "/favicon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "/apple-touch-icon.png",
"sizes": "180x180",
"type": "image/png"
}
],
"theme_color": "#d4a574",
"background_color": "#1a1410",
"display": "standalone",
"start_url": "/",
"orientation": "portrait"
}
+74
View File
@@ -0,0 +1,74 @@
---
interface Props {
class?: string;
}
const { class: className } = Astro.props;
---
<div class:list={["card", "scroll-animate", className]}>
<slot />
</div>
<style>
.card {
background: var(--color-bg-light);
border-radius: 12px;
padding: var(--space-lg);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(212, 165, 116, 0.1);
transition: box-shadow 0.3s ease;
}
.card:hover {
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4);
}
.card :global(h2) {
font-size: 2rem;
margin-bottom: var(--space-md);
}
.card :global(p) {
margin-bottom: var(--space-md);
color: var(--color-text);
}
.card :global(p:last-child) {
margin-bottom: 0;
}
/* Scroll animation states */
.scroll-animate {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.scroll-animate.visible {
opacity: 1;
transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
.card {
transition: none;
}
.card:hover {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
}
.scroll-animate {
opacity: 1;
transform: none;
transition: none;
}
}
@media (max-width: 768px) {
.card :global(h2) {
font-size: 1.5rem;
}
}
</style>
+27
View File
@@ -0,0 +1,27 @@
---
interface Props {
class?: string;
ariaLabelledby?: string;
}
const { class: className, ariaLabelledby } = Astro.props;
---
<section class:list={["section", className]} aria-labelledby={ariaLabelledby}>
<div class="container">
<slot />
</div>
</section>
<style>
.section {
padding: var(--space-lg) 0;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 0 var(--space-md);
width: 100%;
}
</style>
+68
View File
@@ -0,0 +1,68 @@
---
interface Props {
title: string;
description: string;
url?: string;
comingSoon?: boolean;
}
const { title, description, url, comingSoon = false } = Astro.props;
---
<div class="service-item" class:list={{ "coming-soon": comingSoon }} role="listitem">
<h3>
{url ? (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
aria-label={`Visit ${title}`}
>
{title}
</a>
) : (
title
)}
</h3>
<p>{description}</p>
</div>
<style>
.service-item {
background: var(--color-bg);
padding: var(--space-md);
border-radius: 8px;
border-left: 4px solid var(--color-accent);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.service-item:hover {
transform: translateX(4px);
box-shadow: 0 4px 8px rgba(212, 165, 116, 0.2);
}
.service-item.coming-soon {
border-left-color: var(--color-warm);
opacity: 0.8;
}
.service-item h3 {
margin-bottom: var(--space-xs);
font-size: 1.25rem;
}
.service-item p {
color: var(--color-text-dim);
margin: 0;
}
@media (prefers-reduced-motion: reduce) {
.service-item {
transition: none;
}
.service-item:hover {
transform: none;
}
}
</style>
+58
View File
@@ -0,0 +1,58 @@
---
interface Props {
title: string;
description: string;
comingSoonNote?: string;
}
const { title, description, comingSoonNote } = Astro.props;
---
<div class="support-item" role="listitem">
<h3>{title}</h3>
<p>
{description}
{comingSoonNote && (
<span class="coming-soon-text">({comingSoonNote})</span>
)}
</p>
</div>
<style>
.support-item {
background: var(--color-bg);
padding: var(--space-md);
border-radius: 8px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.support-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(212, 165, 116, 0.15);
}
.support-item h3 {
margin-bottom: var(--space-xs);
font-size: 1.25rem;
}
.support-item p {
color: var(--color-text-dim);
margin: 0;
}
.coming-soon-text {
font-style: italic;
color: var(--color-warm);
}
@media (prefers-reduced-motion: reduce) {
.support-item {
transition: none;
}
.support-item:hover {
transform: none;
}
}
</style>
+170 -143
View File
@@ -1,16 +1,16 @@
--- ---
interface Props { interface Props {
title: string; title: string;
description?: string; description?: string;
ogImage?: string; ogImage?: string;
canonicalURL?: string; canonicalURL?: string;
} }
const { const {
title, title,
description = "Hidden Den Cafe - A cozy, self-hosted corner of the internet. Privacy-focused, furry-friendly, and built with love.", description = "Hidden Den Cafe - A cozy, self-hosted corner of the internet. Privacy-focused, furry-friendly, and built with love.",
ogImage = "/og-image.png", ogImage = "/og-image.png",
canonicalURL = Astro.url.pathname canonicalURL = Astro.url.pathname,
} = Astro.props; } = Astro.props;
const fullCanonicalURL = new URL(canonicalURL, Astro.site).href; const fullCanonicalURL = new URL(canonicalURL, Astro.site).href;
@@ -19,156 +19,183 @@ const fullOgImage = new URL(ogImage, Astro.site).href;
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<!-- Primary Meta Tags --> <!-- Primary Meta Tags -->
<title>{title}</title> <title>{title}</title>
<meta name="title" content={title} /> <meta name="title" content={title} />
<meta name="description" content={description} /> <meta name="description" content={description} />
<link rel="canonical" href={fullCanonicalURL} /> <link rel="canonical" href={fullCanonicalURL} />
<!-- Open Graph / Facebook --> <!-- Open Graph / Facebook -->
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content={fullCanonicalURL} /> <meta property="og:url" content={fullCanonicalURL} />
<meta property="og:title" content={title} /> <meta property="og:title" content={title} />
<meta property="og:description" content={description} /> <meta property="og:description" content={description} />
<meta property="og:image" content={fullOgImage} /> <meta property="og:image" content={fullOgImage} />
<meta property="og:site_name" content="Hidden Den Cafe" /> <meta property="og:site_name" content="Hidden Den Cafe" />
<meta property="og:locale" content="en_US" /> <meta property="og:locale" content="en_US" />
<!-- Twitter Card --> <!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content={fullCanonicalURL} /> <meta name="twitter:url" content={fullCanonicalURL} />
<meta name="twitter:title" content={title} /> <meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} /> <meta name="twitter:description" content={description} />
<meta name="twitter:image" content={fullOgImage} /> <meta name="twitter:image" content={fullOgImage} />
<!-- Additional Meta Tags --> <!-- Additional Meta Tags -->
<meta name="author" content="Latte" /> <meta name="author" content="Latte" />
<meta name="keywords" content="self-hosted, privacy, open-source, furry, developer, cozy, hidden den" /> <meta
<meta name="theme-color" content="#d4a574" /> name="keywords"
<meta name="color-scheme" content="dark" /> content="self-hosted, privacy, open-source, furry, developer, cozy, hidden den"
/>
<meta name="theme-color" content="#d4a574" />
<meta name="color-scheme" content="dark" />
<!-- Favicon --> <!-- Favicons -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/favicon.svg" /> <link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<!-- Preconnect for performance (if needed for future external resources) --> <!-- Preconnect for performance (if needed for future external resources) -->
<!-- <link rel="preconnect" href="https://example.com" crossorigin /> --> <!-- <link rel="preconnect" href="https://example.com" crossorigin /> -->
</head> </head>
<body> <body>
<slot /> <slot />
</body> </body>
</html> </html>
<style is:global> <style is:global>
:root { :root {
/* Cozy Den Color Palette */ /* Cozy Den Color Palette */
--color-bg: #1a1410; --color-bg: #1a1410;
--color-bg-light: #2a1f18; --color-bg-light: #2a1f18;
--color-text: #f4e9d8; --color-text: #f4e9d8;
--color-text-dim: #c4b5a0; --color-text-dim: #c4b5a0;
--color-accent: #d4a574; --color-accent: #d4a574;
--color-accent-bright: #e8bf8e; --color-accent-bright: #e8bf8e;
--color-warm: #8b6f47; --color-warm: #8b6f47;
/* Spacing */ /* Spacing */
--space-xs: 0.5rem; --space-xs: 0.5rem;
--space-sm: 1rem; --space-sm: 1rem;
--space-md: 1.5rem; --space-md: 1.5rem;
--space-lg: 2rem; --space-lg: 2rem;
--space-xl: 3rem; --space-xl: 3rem;
/* Typography */ /* Typography */
--font-body: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-body:
} system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
sans-serif;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-family: var(--font-body);
background: var(--color-bg);
color: var(--color-text);
scroll-behavior: smooth;
}
body {
min-height: 100vh;
line-height: 1.6;
}
h1, h2, h3, h4, h5, h6 {
color: var(--color-accent-bright);
line-height: 1.2;
}
a {
color: var(--color-accent-bright);
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: var(--color-accent);
}
a:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
border-radius: 2px;
}
/* Smooth animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
} }
to {
opacity: 1; * {
transform: translateY(0); margin: 0;
padding: 0;
box-sizing: border-box;
} }
}
.fade-in { html {
animation: fadeIn 0.6s ease-out; font-family: var(--font-body);
} background: var(--color-bg);
color: var(--color-text);
/* Respect user's motion preferences */ scroll-behavior: smooth;
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
} }
}
/* Improve focus visibility for keyboard navigation */ body {
*:focus-visible { min-height: 100vh;
outline: 2px solid var(--color-accent); line-height: 1.6;
outline-offset: 2px; }
}
/* Screen reader only content */ h1,
.sr-only { h2,
position: absolute; h3,
width: 1px; h4,
height: 1px; h5,
padding: 0; h6 {
margin: -1px; color: var(--color-accent-bright);
overflow: hidden; line-height: 1.2;
clip: rect(0, 0, 0, 0); }
white-space: nowrap;
border-width: 0; a {
} color: var(--color-accent-bright);
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: var(--color-accent);
}
a:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
border-radius: 2px;
}
/* Smooth animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.6s ease-out;
}
/* Respect user's motion preferences */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Improve focus visibility for keyboard navigation */
*:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
/* Screen reader only content */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
</style> </style>
+125 -121
View File
@@ -1,150 +1,154 @@
--- ---
import BaseLayout from '../layouts/BaseLayout.astro'; import BaseLayout from "../layouts/BaseLayout.astro";
--- ---
<BaseLayout <BaseLayout
title="404 - Page Not Found | Hidden Den Cafe" title="404 - Page Not Found | Hidden Den Cafe"
description="This page doesn't exist in the Hidden Den. Let's get you back to somewhere cozy." description="This page doesn't exist in the Hidden Den. Let's get you back to somewhere cozy."
> >
<main class="not-found-page"> <main class="not-found-page">
<div class="container"> <div class="container">
<div class="card fade-in"> <div class="card fade-in">
<div class="not-found-content"> <div class="not-found-content">
<h1 class="error-code">404</h1> <h1 class="error-code">404</h1>
<h2 class="error-title">Lost in the Den?</h2> <h2 class="error-title">Lost in the Den?</h2>
<p class="error-message"> <p class="error-message">
Oops! This cozy corner doesn't seem to exist. Maybe it's still being built, Oops! This cozy corner doesn't seem to exist. Maybe it's
or perhaps you've wandered into a part of the den that hasn't been opened yet. still being built, or perhaps you've wandered into a
</p> part of the den that hasn't been opened yet.
<div class="actions"> </p>
<a href="/" class="btn-primary"> <div class="actions">
<span aria-hidden="true">🏡</span> Back to Home <a href="/" class="btn-primary"> Back to Home </a>
</a> <a
<a href="https://git.hiddenden.cafe" class="btn-secondary" target="_blank" rel="noopener noreferrer"> href="https://git.hiddenden.cafe"
<span aria-hidden="true">📦</span> Visit Gitea class="btn-secondary"
</a> target="_blank"
</div> rel="noopener noreferrer"
>
Visit Gitea
</a>
</div>
</div>
</div>
</div> </div>
</div> </main>
</div>
</main>
</BaseLayout> </BaseLayout>
<style> <style>
.not-found-page { .not-found-page {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: 100vh; min-height: 100vh;
padding: var(--space-md); padding: var(--space-md);
} }
.container { .container {
max-width: 600px; max-width: 600px;
width: 100%; width: 100%;
} }
.card { .card {
background: var(--color-bg-light); background: var(--color-bg-light);
border-radius: 12px; border-radius: 12px;
padding: var(--space-xl); padding: var(--space-xl);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(212, 165, 116, 0.1); border: 1px solid rgba(212, 165, 116, 0.1);
text-align: center; text-align: center;
} }
.not-found-content { .not-found-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-md); gap: var(--space-md);
} }
.error-code {
font-size: 6rem;
font-weight: bold;
color: var(--color-accent);
line-height: 1;
margin: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.error-title {
font-size: 2rem;
margin: 0;
color: var(--color-accent-bright);
}
.error-message {
font-size: 1.1rem;
color: var(--color-text-dim);
line-height: 1.6;
margin: 0;
}
.actions {
display: flex;
gap: var(--space-md);
justify-content: center;
flex-wrap: wrap;
margin-top: var(--space-md);
}
.btn-primary,
.btn-secondary {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-sm) var(--space-lg);
border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
text-decoration: none;
}
.btn-primary {
background: var(--color-accent);
color: var(--color-bg);
}
.btn-primary:hover {
background: var(--color-accent-bright);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(212, 165, 116, 0.3);
}
.btn-secondary {
background: transparent;
color: var(--color-accent-bright);
border: 2px solid var(--color-accent);
}
.btn-secondary:hover {
background: rgba(212, 165, 116, 0.1);
border-color: var(--color-accent-bright);
transform: translateY(-2px);
}
@media (max-width: 768px) {
.error-code { .error-code {
font-size: 4rem; font-size: 6rem;
font-weight: bold;
color: var(--color-accent);
line-height: 1;
margin: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
} }
.error-title { .error-title {
font-size: 1.5rem; font-size: 2rem;
margin: 0;
color: var(--color-accent-bright);
} }
.error-message { .error-message {
font-size: 1rem; font-size: 1.1rem;
color: var(--color-text-dim);
line-height: 1.6;
margin: 0;
} }
.actions { .actions {
flex-direction: column; display: flex;
gap: var(--space-md);
justify-content: center;
flex-wrap: wrap;
margin-top: var(--space-md);
} }
.btn-primary, .btn-primary,
.btn-secondary { .btn-secondary {
width: 100%; display: inline-flex;
justify-content: center; align-items: center;
gap: var(--space-xs);
padding: var(--space-sm) var(--space-lg);
border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
text-decoration: none;
}
.btn-primary {
background: var(--color-accent);
color: var(--color-bg);
}
.btn-primary:hover {
background: var(--color-accent-bright);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(212, 165, 116, 0.3);
}
.btn-secondary {
background: transparent;
color: var(--color-accent-bright);
border: 2px solid var(--color-accent);
}
.btn-secondary:hover {
background: rgba(212, 165, 116, 0.1);
border-color: var(--color-accent-bright);
transform: translateY(-2px);
}
@media (max-width: 768px) {
.error-code {
font-size: 4rem;
}
.error-title {
font-size: 1.5rem;
}
.error-message {
font-size: 1rem;
}
.actions {
flex-direction: column;
}
.btn-primary,
.btn-secondary {
width: 100%;
justify-content: center;
}
} }
}
</style> </style>
+210 -276
View File
@@ -1,301 +1,235 @@
--- ---
import BaseLayout from '../layouts/BaseLayout.astro'; import BaseLayout from "../layouts/BaseLayout.astro";
import Card from "../components/Card.astro";
import Section from "../components/Section.astro";
import ServiceItem from "../components/ServiceItem.astro";
import SupportItem from "../components/SupportItem.astro";
--- ---
<BaseLayout <BaseLayout
title="Hidden Den Cafe - Cozy Self-Hosted Services" title="Hidden Den Cafe - Cozy Self-Hosted Services"
description="Welcome to Hidden Den Cafe - a warm, self-hosted space where technology meets comfort. Privacy-focused, furry-friendly, and built with love by Latte." description="Welcome to Hidden Den Cafe - a warm, self-hosted space where technology meets comfort. Privacy-focused, furry-friendly, and built with love by Latte."
> >
<main id="main-content"> <main id="main-content">
<!-- Hero Section --> <!-- Hero Section -->
<section class="hero" aria-labelledby="hero-title"> <section class="hero" aria-labelledby="hero-title">
<div class="container"> <div class="container">
<div class="hero-content fade-in"> <div class="hero-content fade-in">
<h1 id="hero-title" class="hero-title"> <h1 id="hero-title" class="hero-title">Hidden Den Cafe</h1>
<span aria-hidden="true">🏡</span> Hidden Den Cafe <p class="hero-subtitle">A cozy corner of the internet</p>
</h1> </div>
<p class="hero-subtitle">A cozy corner of the internet</p>
</div>
</div>
</section>
<!-- About Hidden Den Section -->
<section class="section about-den" aria-labelledby="about-den-heading">
<div class="container">
<div class="card fade-in">
<h2 id="about-den-heading"><span aria-hidden="true">☕</span> About Hidden Den</h2>
<p>
Welcome to Hidden Den Cafe - a warm, self-hosted space where technology
meets comfort. This is a personal corner of the internet built with love,
care, and a strong belief in privacy and open-source values.
</p>
<p>
Everything here runs on self-hosted infrastructure, giving complete control
over data and services. No cloud dependencies, no tracking, just a cozy
digital home.
</p>
</div>
</div>
</section>
<!-- About Latte Section -->
<section class="section about-me" aria-labelledby="about-me-heading">
<div class="container">
<div class="card fade-in">
<h2 id="about-me-heading"><span aria-hidden="true">🦊</span> About Me</h2>
<p>
Hey there! I'm Latte, a gay furry developer who loves building things
and being part of the warm, welcoming furry community. I'm passionate
about self-hosting, open-source software, and creating cozy spaces
both online and off.
</p>
<p>
I work primarily with Python and Flask, though I'm always learning new
things (currently exploring JavaScript and the C stack). When I get those
surges of creative energy, I love diving into new projects - from Reddit
downloaders to Telegram sticker tools, and everything in between.
</p>
<p>
Coffee enthusiast ☕ • Linux lover 🐧 • Self-hosting advocate 🏠
</p>
</div>
</div>
</section>
<!-- Services Section -->
<section class="section services" aria-labelledby="services-heading">
<div class="container">
<div class="card fade-in">
<h2 id="services-heading"><span aria-hidden="true">🛠️</span> Services</h2>
<p>Here are the services currently running in the den:</p>
<div class="service-list" role="list">
<div class="service-item" role="listitem">
<h3>
<a href="https://git.hiddenden.cafe" target="_blank" rel="noopener noreferrer" aria-label="Visit Gitea - Self-hosted Git service">
<span aria-hidden="true">📦</span> Gitea
</a>
</h3>
<p>Self-hosted Git service for all my projects and code repositories.</p>
</div> </div>
</section>
<div class="service-item coming-soon" role="listitem"> <!-- About Hidden Den Section -->
<h3><span aria-hidden="true">🔜</span> More Coming Soon</h3> <Section class="about-den" ariaLabelledby="about-den-heading">
<p>The den is always growing! More services will be added as they're developed and deployed.</p> <Card>
<h2 id="about-den-heading">About Hidden Den</h2>
<p>
Welcome to Hidden Den Cafe - a warm, self-hosted space where
technology meets comfort. This is a personal corner of the
internet built with love, care, and a strong belief in
privacy and open-source values.
</p>
<p>
Everything here runs on self-hosted infrastructure, giving
complete control over data and services. No cloud
dependencies, no tracking, just a cozy digital home.
</p>
</Card>
</Section>
<!-- About Latte Section -->
<Section class="about-me" ariaLabelledby="about-me-heading">
<Card>
<h2 id="about-me-heading">About Me</h2>
<p>
Hey there! I'm Latte, a gay furry developer who loves
building things and being part of the warm, welcoming furry
community. I'm passionate about self-hosting, open-source
software, and creating cozy spaces both online and off.
</p>
<p>
I work primarily with Python and Flask, though I'm always
learning new things (currently exploring JavaScript and the
C stack). When I get those surges of creative energy, I love
diving into new projects - from Reddit downloaders to
Telegram sticker tools, and everything in between.
</p>
<p>Coffee enthusiast • Linux lover • Self-hosting advocate</p>
</Card>
</Section>
<!-- Services Section -->
<Section class="services" ariaLabelledby="services-heading">
<Card>
<h2 id="services-heading">Services</h2>
<p>Here are the services currently running in the den:</p>
<div class="service-list" role="list">
<ServiceItem
title="Gitea"
description="Self-hosted Git service for all my projects and code repositories."
url="https://git.hiddenden.cafe"
/>
<ServiceItem
title="More Coming Soon"
description="The den is always growing! More services will be added as they're developed and deployed."
comingSoon={true}
/>
</div>
</Card>
</Section>
<!-- Support Section -->
<Section class="support" ariaLabelledby="support-heading">
<Card>
<h2 id="support-heading">How to Help Out</h2>
<p>
If you'd like to support the Hidden Den and help keep the
lights on, here are some ways you can contribute:
</p>
<div class="support-list" role="list">
<SupportItem
title="Share & Spread the Word"
description="Tell others about the projects and services hosted here!"
/>
<SupportItem
title="Report Issues"
description="Found a bug or have a suggestion? Let me know!"
/>
<SupportItem
title="Buy Me a Coffee"
description="Donations help cover server costs and keep the den cozy."
comingSoonNote="Payment links coming soon!"
/>
<SupportItem
title="Contribute"
description="Check out the projects on Gitea - contributions are always welcome!"
/>
</div>
</Card>
</Section>
<!-- Footer -->
<footer class="footer" role="contentinfo">
<div class="container">
<p>Made with love by Latte</p>
<nav aria-label="Footer navigation">
<p class="footer-links">
<a
href="https://git.hiddenden.cafe"
target="_blank"
rel="noopener noreferrer">Gitea</a
>
</p>
</nav>
</div> </div>
</div> </footer>
</div> </main>
</div>
</section>
<!-- Support Section --> <!-- Scroll Animation Script -->
<section class="section support" aria-labelledby="support-heading"> <script>
<div class="container"> // Intersection Observer for scroll animations
<div class="card fade-in"> const observerOptions = {
<h2 id="support-heading"><span aria-hidden="true">💝</span> How to Help Out</h2> threshold: 0.1,
<p> rootMargin: "0px 0px -50px 0px",
If you'd like to support the Hidden Den and help keep the lights on, };
here are some ways you can contribute:
</p>
<div class="support-list" role="list"> const observer = new IntersectionObserver((entries) => {
<div class="support-item" role="listitem"> entries.forEach((entry) => {
<h3><span aria-hidden="true">🌟</span> Share & Spread the Word</h3> if (entry.isIntersecting) {
<p>Tell others about the projects and services hosted here!</p> entry.target.classList.add("visible");
</div> // Optionally unobserve after animation
observer.unobserve(entry.target);
}
});
}, observerOptions);
<div class="support-item" role="listitem"> // Observe all elements with scroll-animate class
<h3><span aria-hidden="true">🐛</span> Report Issues</h3> document.addEventListener("DOMContentLoaded", () => {
<p>Found a bug or have a suggestion? Let me know!</p> const animateElements =
</div> document.querySelectorAll(".scroll-animate");
animateElements.forEach((el) => observer.observe(el));
<div class="support-item" role="listitem"> });
<h3><span aria-hidden="true">☕</span> Buy Me a Coffee</h3> </script>
<p>
Donations help cover server costs and keep the den cozy.
<span class="coming-soon-text">(Payment links coming soon!)</span>
</p>
</div>
<div class="support-item" role="listitem">
<h3><span aria-hidden="true">🤝</span> Contribute</h3>
<p>Check out the projects on Gitea - contributions are always welcome!</p>
</div>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer" role="contentinfo">
<div class="container">
<p>Made with <span aria-hidden="true">💖</span><span class="sr-only">love</span> by Latte</p>
<nav aria-label="Footer navigation">
<p class="footer-links">
<a href="https://git.hiddenden.cafe" target="_blank" rel="noopener noreferrer">Gitea</a>
<span class="separator" aria-hidden="true">•</span>
<a href="https://dmush.cloud" target="_blank" rel="noopener noreferrer">dmush.cloud</a>
</p>
</nav>
</div>
</footer>
</main>
</BaseLayout> </BaseLayout>
<style> <style>
main { main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
} }
.container { .container {
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
padding: 0 var(--space-md); padding: 0 var(--space-md);
width: 100%; width: 100%;
} }
/* Hero Section */ /* Hero Section */
.hero { .hero {
padding: var(--space-xl) 0; padding: var(--space-xl) 0;
text-align: center; text-align: center;
background: linear-gradient(135deg, var(--color-bg) 0%, var(--color-bg-light) 100%); background: linear-gradient(
} 135deg,
var(--color-bg) 0%,
var(--color-bg-light) 100%
);
}
.hero-title {
font-size: 3rem;
margin-bottom: var(--space-sm);
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.hero-subtitle {
font-size: 1.25rem;
color: var(--color-text-dim);
}
/* Sections */
.section {
padding: var(--space-lg) 0;
}
.card {
background: var(--color-bg-light);
border-radius: 12px;
padding: var(--space-lg);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(212, 165, 116, 0.1);
}
.card h2 {
font-size: 2rem;
margin-bottom: var(--space-md);
}
.card p {
margin-bottom: var(--space-md);
color: var(--color-text);
}
.card p:last-child {
margin-bottom: 0;
}
/* Services Section */
.service-list {
margin-top: var(--space-md);
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.service-item {
background: var(--color-bg);
padding: var(--space-md);
border-radius: 8px;
border-left: 4px solid var(--color-accent);
}
.service-item h3 {
margin-bottom: var(--space-xs);
font-size: 1.25rem;
}
.service-item p {
color: var(--color-text-dim);
margin: 0;
}
.service-item.coming-soon {
border-left-color: var(--color-warm);
opacity: 0.8;
}
/* Support Section */
.support-list {
margin-top: var(--space-md);
display: grid;
gap: var(--space-md);
}
.support-item {
background: var(--color-bg);
padding: var(--space-md);
border-radius: 8px;
}
.support-item h3 {
margin-bottom: var(--space-xs);
font-size: 1.25rem;
}
.support-item p {
color: var(--color-text-dim);
margin: 0;
}
.coming-soon-text {
font-style: italic;
color: var(--color-warm);
}
/* Footer */
.footer {
margin-top: auto;
padding: var(--space-lg) 0;
text-align: center;
border-top: 1px solid rgba(212, 165, 116, 0.2);
background: var(--color-bg-light);
}
.footer p {
color: var(--color-text-dim);
margin-bottom: var(--space-xs);
}
.footer-links {
display: flex;
justify-content: center;
align-items: center;
gap: var(--space-sm);
}
.separator {
color: var(--color-warm);
}
/* Responsive */
@media (max-width: 768px) {
.hero-title { .hero-title {
font-size: 2rem; font-size: 3rem;
margin-bottom: var(--space-sm);
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
} }
.hero-subtitle { .hero-subtitle {
font-size: 1rem; font-size: 1.25rem;
color: var(--color-text-dim);
} }
.card h2 { /* Service and Support Lists */
font-size: 1.5rem; .service-list,
.support-list {
margin-top: var(--space-md);
display: flex;
flex-direction: column;
gap: var(--space-md);
}
/* Footer */
.footer {
margin-top: auto;
padding: var(--space-lg) 0;
text-align: center;
border-top: 1px solid rgba(212, 165, 116, 0.2);
background: var(--color-bg-light);
}
.footer p {
color: var(--color-text-dim);
margin-bottom: var(--space-xs);
}
.footer-links {
display: flex;
justify-content: center;
align-items: center;
gap: var(--space-sm);
}
/* Responsive */
@media (max-width: 768px) {
.hero-title {
font-size: 2rem;
}
.hero-subtitle {
font-size: 1rem;
}
} }
}
</style> </style>