dev #5

Merged
Latte merged 8 commits from dev into main 2026-01-01 14:04:36 +00:00
9 changed files with 622 additions and 351 deletions
Showing only changes of commit 6ed0eee514 - Show all commits
+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!
+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>
+33 -6
View File
@@ -10,7 +10,7 @@ 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;
@@ -48,13 +48,33 @@ const fullOgImage = new URL(ogImage, Astro.site).href;
<!-- 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
name="keywords"
content="self-hosted, privacy, open-source, furry, developer, cozy, hidden den"
/>
<meta name="theme-color" content="#d4a574" /> <meta name="theme-color" content="#d4a574" />
<meta name="color-scheme" content="dark" /> <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 /> -->
@@ -83,7 +103,9 @@ const fullOgImage = new URL(ogImage, Astro.site).href;
--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;
} }
* { * {
@@ -104,7 +126,12 @@ const fullOgImage = new URL(ogImage, Astro.site).href;
line-height: 1.6; line-height: 1.6;
} }
h1, h2, h3, h4, h5, h6 { h1,
h2,
h3,
h4,
h5,
h6 {
color: var(--color-accent-bright); color: var(--color-accent-bright);
line-height: 1.2; line-height: 1.2;
} }
+93 -189
View File
@@ -1,5 +1,9 @@
--- ---
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
@@ -18,137 +22,95 @@ import BaseLayout from "../layouts/BaseLayout.astro";
</section> </section>
<!-- About Hidden Den Section --> <!-- About Hidden Den Section -->
<section class="section about-den" aria-labelledby="about-den-heading"> <Section class="about-den" ariaLabelledby="about-den-heading">
<div class="container"> <Card>
<div class="card fade-in">
<h2 id="about-den-heading">About Hidden Den</h2> <h2 id="about-den-heading">About Hidden Den</h2>
<p> <p>
Welcome to Hidden Den Cafe - a warm, self-hosted space Welcome to Hidden Den Cafe - a warm, self-hosted space where
where technology meets comfort. This is a personal technology meets comfort. This is a personal corner of the
corner of the internet built with love, care, and a internet built with love, care, and a strong belief in
strong belief in privacy and open-source values. privacy and open-source values.
</p> </p>
<p> <p>
Everything here runs on self-hosted infrastructure, Everything here runs on self-hosted infrastructure, giving
giving complete control over data and services. No cloud complete control over data and services. No cloud
dependencies, no tracking, just a cozy digital home. dependencies, no tracking, just a cozy digital home.
</p> </p>
</div> </Card>
</div> </Section>
</section>
<!-- About Latte Section --> <!-- About Latte Section -->
<section class="section about-me" aria-labelledby="about-me-heading"> <Section class="about-me" ariaLabelledby="about-me-heading">
<div class="container"> <Card>
<div class="card fade-in">
<h2 id="about-me-heading">About Me</h2> <h2 id="about-me-heading">About Me</h2>
<p> <p>
Hey there! I'm Latte, a gay furry developer who loves Hey there! I'm Latte, a gay furry developer who loves
building things and being part of the warm, welcoming building things and being part of the warm, welcoming furry
furry community. I'm passionate about self-hosting, community. I'm passionate about self-hosting, open-source
open-source software, and creating cozy spaces both software, and creating cozy spaces both online and off.
online and off.
</p> </p>
<p> <p>
I work primarily with Python and Flask, though I'm I work primarily with Python and Flask, though I'm always
always learning new things (currently exploring learning new things (currently exploring JavaScript and the
JavaScript and the C stack). When I get those surges of C stack). When I get those surges of creative energy, I love
creative energy, I love diving into new projects - from diving into new projects - from Reddit downloaders to
Reddit downloaders to Telegram sticker tools, and Telegram sticker tools, and everything in between.
everything in between.
</p> </p>
<p> <p>Coffee enthusiast • Linux lover • Self-hosting advocate</p>
Coffee enthusiast • Linux lover • Self-hosting advocate </Card>
</p> </Section>
</div>
</div>
</section>
<!-- Services Section --> <!-- Services Section -->
<section class="section services" aria-labelledby="services-heading"> <Section class="services" ariaLabelledby="services-heading">
<div class="container"> <Card>
<div class="card fade-in">
<h2 id="services-heading">Services</h2> <h2 id="services-heading">Services</h2>
<p>Here are the services currently running in the den:</p> <p>Here are the services currently running in the den:</p>
<div class="service-list" role="list"> <div class="service-list" role="list">
<div class="service-item" role="listitem"> <ServiceItem
<h3> title="Gitea"
<a description="Self-hosted Git service for all my projects and code repositories."
href="https://git.hiddenden.cafe" url="https://git.hiddenden.cafe"
target="_blank" />
rel="noopener noreferrer" <ServiceItem
aria-label="Visit Gitea - Self-hosted Git service" title="More Coming Soon"
> description="The den is always growing! More services will be added as they're developed and deployed."
Gitea comingSoon={true}
</a> />
</h3>
<p>
Self-hosted Git service for all my projects and
code repositories.
</p>
</div> </div>
</Card>
<div class="service-item coming-soon" role="listitem"> </Section>
<h3>More Coming Soon</h3>
<p>
The den is always growing! More services will be
added as they're developed and deployed.
</p>
</div>
</div>
</div>
</div>
</section>
<!-- Support Section --> <!-- Support Section -->
<section class="section support" aria-labelledby="support-heading"> <Section class="support" ariaLabelledby="support-heading">
<div class="container"> <Card>
<div class="card fade-in">
<h2 id="support-heading">How to Help Out</h2> <h2 id="support-heading">How to Help Out</h2>
<p> <p>
If you'd like to support the Hidden Den and help keep If you'd like to support the Hidden Den and help keep the
the lights on, here are some ways you can contribute: lights on, here are some ways you can contribute:
</p> </p>
<div class="support-list" role="list"> <div class="support-list" role="list">
<div class="support-item" role="listitem"> <SupportItem
<h3>Share & Spread the Word</h3> title="Share & Spread the Word"
<p> description="Tell others about the projects and services hosted here!"
Tell others about the projects and services />
hosted here! <SupportItem
</p> 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> </div>
</Card>
<div class="support-item" role="listitem"> </Section>
<h3>Report Issues</h3>
<p>
Found a bug or have a suggestion? Let me know!
</p>
</div>
<div class="support-item" role="listitem">
<h3>Buy Me a Coffee</h3>
<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>Contribute</h3>
<p>
Check out the projects on Gitea - contributions
are always welcome!
</p>
</div>
</div>
</div>
</div>
</section>
<!-- Footer --> <!-- Footer -->
<footer class="footer" role="contentinfo"> <footer class="footer" role="contentinfo">
@@ -166,6 +128,32 @@ import BaseLayout from "../layouts/BaseLayout.astro";
</div> </div>
</footer> </footer>
</main> </main>
<!-- Scroll Animation Script -->
<script>
// Intersection Observer for scroll animations
const observerOptions = {
threshold: 0.1,
rootMargin: "0px 0px -50px 0px",
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
// Optionally unobserve after animation
observer.unobserve(entry.target);
}
});
}, observerOptions);
// Observe all elements with scroll-animate class
document.addEventListener("DOMContentLoaded", () => {
const animateElements =
document.querySelectorAll(".scroll-animate");
animateElements.forEach((el) => observer.observe(el));
});
</script>
</BaseLayout> </BaseLayout>
<style> <style>
@@ -204,91 +192,15 @@ import BaseLayout from "../layouts/BaseLayout.astro";
color: var(--color-text-dim); color: var(--color-text-dim);
} }
/* Sections */ /* Service and Support Lists */
.section { .service-list,
padding: var(--space-lg) 0; .support-list {
}
.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); margin-top: var(--space-md);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-md); 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 */
.footer { .footer {
margin-top: auto; margin-top: auto;
@@ -310,10 +222,6 @@ import BaseLayout from "../layouts/BaseLayout.astro";
gap: var(--space-sm); gap: var(--space-sm);
} }
.separator {
color: var(--color-warm);
}
/* Responsive */ /* Responsive */
@media (max-width: 768px) { @media (max-width: 768px) {
.hero-title { .hero-title {
@@ -323,9 +231,5 @@ import BaseLayout from "../layouts/BaseLayout.astro";
.hero-subtitle { .hero-subtitle {
font-size: 1rem; font-size: 1rem;
} }
.card h2 {
font-size: 1.5rem;
}
} }
</style> </style>