dev #20
+57
-25
@@ -16,15 +16,22 @@ Cozy Den is a landing page for hiddenden.cafe built with Astro. The site feature
|
||||
```
|
||||
cozy-den/
|
||||
├── src/
|
||||
│ ├── content/
|
||||
│ │ ├── config.ts # Content collection schema
|
||||
│ │ └── blog/
|
||||
│ │ └── *.md # Blog posts (Markdown)
|
||||
│ ├── layouts/
|
||||
│ │ └── BaseLayout.astro # Base HTML layout with global styles
|
||||
│ ├── pages/
|
||||
│ │ ├── index.astro # Main landing page
|
||||
│ │ └── 404.astro # Custom 404 error page
|
||||
│ └── components/ # (Empty - ready for future components)
|
||||
│ │ ├── 404.astro # Custom 404 error page
|
||||
│ │ └── blog/
|
||||
│ │ ├── index.astro # Blog listing page (/blog)
|
||||
│ │ └── [...slug].astro # Individual post pages (/blog/<slug>)
|
||||
├── public/
|
||||
│ ├── favicon.svg # Site favicon (coffee emoji)
|
||||
│ └── robots.txt # Search engine directives
|
||||
│ ├── robots.txt # Search engine directives
|
||||
│ └── blog/ # Blog post images (per-post subdirectory)
|
||||
├── astro.config.mjs # Astro configuration with sitemap
|
||||
├── package.json # Node dependencies
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
@@ -40,13 +47,15 @@ cozy-den/
|
||||
The site uses CSS custom properties for theming. All colors are defined in `src/layouts/BaseLayout.astro`:
|
||||
|
||||
```css
|
||||
--color-bg: #1a1410 /* Dark background (deep coffee) */
|
||||
--color-bg-light: #2a1f18 /* Lighter background for cards */
|
||||
--color-text: #f4e9d8 /* Cream text */
|
||||
--color-text-dim: #c4b5a0 /* Dimmed text for less emphasis */
|
||||
--color-accent: #d4a574 /* Warm accent (coffee with cream) */
|
||||
--color-accent-bright: #e8bf8e /* Brighter accent for highlights */
|
||||
--color-warm: #8b6f47 /* Warm brown for borders/accents */
|
||||
--color-bg: #1e1e2e /* Dark background (Catppuccin Mocha base) */
|
||||
--color-text: #cdd6f4 /* Light text */
|
||||
--color-text-dim: #a6adc8 /* Dimmed text for less emphasis */
|
||||
--color-accent: #cba6f7 /* Mauve accent */
|
||||
--color-accent-bright: #f5c2e7 /* Pink accent for highlights */
|
||||
--color-surface: #313244 /* Surface color for borders/cards */
|
||||
--color-blue: #89b4fa /* Blue for links */
|
||||
--color-green: #a6e3a1 /* Green for code */
|
||||
--color-peach: #fab387 /* Peach for labels */
|
||||
```
|
||||
|
||||
## Component Architecture
|
||||
@@ -62,12 +71,31 @@ The base layout provides:
|
||||
### index.astro
|
||||
|
||||
The main page includes these sections:
|
||||
1. **Hero** - Title and subtitle
|
||||
2. **About Hidden Den** - Information about the site/space
|
||||
3. **About Me** - Information about Latte
|
||||
4. **Services** - List of self-hosted services
|
||||
5. **Support** - Ways to help/contribute
|
||||
6. **Footer** - Links and credits
|
||||
1. **Header** - Title
|
||||
2. **About** - Introduction, age, status
|
||||
3. **Cryptographic Keys** - PGP fingerprint and SSH public key
|
||||
4. **Games** - Games Latte plays
|
||||
5. **Socials** - Links to social platforms
|
||||
6. **Services** - Self-hosted services
|
||||
7. **Support** - Crypto donation addresses
|
||||
8. **Blog** - Link to `/blog`
|
||||
9. **Footer** - Credits
|
||||
|
||||
### blog/index.astro
|
||||
|
||||
Blog listing page at `/blog`. Fetches all non-draft posts from the `blog` content collection, sorted by date descending.
|
||||
|
||||
### blog/[...slug].astro
|
||||
|
||||
Individual blog post page. Renders Markdown content using Astro's `<Content />` component. Styles for prose content (headings, paragraphs, code blocks, images, etc.) are scoped within `.content :global(...)`.
|
||||
|
||||
### src/content/config.ts
|
||||
|
||||
Defines the `blog` content collection schema:
|
||||
- `title` (string) — post title
|
||||
- `date` (date) — publish date, used for sorting
|
||||
- `description` (string) — shown on the listing page
|
||||
- `draft` (boolean, optional) — set to `true` to hide from listing
|
||||
|
||||
### 404.astro
|
||||
|
||||
@@ -120,9 +148,15 @@ npm run preview
|
||||
2. Follow the existing structure with h3 link and description
|
||||
|
||||
**Adding images:**
|
||||
1. Place images in `public/` directory
|
||||
2. Reference them with `/image.jpg` in your code
|
||||
3. They'll be served as static assets
|
||||
1. Place images in `public/blog/<post-slug>/` directory
|
||||
2. Reference in Markdown with `/blog/<post-slug>/image.jpg`
|
||||
3. They'll be served as static assets by nginx
|
||||
|
||||
**Adding a blog post:**
|
||||
1. Create `src/content/blog/my-post-slug.md`
|
||||
2. Add frontmatter: `title`, `date`, `description` (and optionally `draft: true`)
|
||||
3. Write content in Markdown below the frontmatter
|
||||
4. The post appears automatically at `/blog/my-post-slug`
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
@@ -273,14 +307,12 @@ import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
## Future Enhancements
|
||||
|
||||
Ideas for future development:
|
||||
- [ ] Add blog section using Astro's content collections
|
||||
- [ ] Create reusable components for service items
|
||||
- [ ] Add dark/light theme toggle
|
||||
- [ ] Implement contact form
|
||||
- [x] Add blog section using Astro's content collections
|
||||
- [x] Create custom 404 page
|
||||
- [ ] Add RSS feed for blog
|
||||
- [ ] Create custom 404 page
|
||||
- [ ] Create reusable components for service items
|
||||
- [ ] Add more animations and transitions
|
||||
- [ ] Integrate with analytics (privacy-friendly)
|
||||
- [ ] Integrate with analytics (privacy-friendly, self-hosted)
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -9,10 +9,11 @@ Cozy Den is a warm, self-hosted corner of the internet. This landing page repres
|
||||
## Features
|
||||
|
||||
- ☕ Cozy, warm aesthetic with hidden den theme
|
||||
- 🎨 Custom color palette inspired by coffee and warmth
|
||||
- 🎨 Catppuccin Mocha color palette
|
||||
- 📱 Responsive design
|
||||
- ⚡ Built with Astro for blazing fast performance
|
||||
- 🐳 Docker support for easy deployment
|
||||
- 📝 Blog powered by Astro Content Collections (hardcoded Markdown, no CMS)
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -55,7 +56,7 @@ docker build -t cozy-den .
|
||||
### Running the Container
|
||||
|
||||
```bash
|
||||
docker run -d -p 3000:3000 --name cozy-den cozy-den
|
||||
docker run -d -p 3000:80 --name cozy-den cozy-den
|
||||
```
|
||||
|
||||
Or with docker-compose:
|
||||
@@ -84,6 +85,8 @@ The site is built to be easily customizable:
|
||||
- **Colors**: Edit the CSS variables in `src/layouts/BaseLayout.astro`
|
||||
- **Content**: Update sections in `src/pages/index.astro`
|
||||
- **Favicon**: Replace `public/favicon.svg`
|
||||
- **Blog posts**: Add `.md` files to `src/content/blog/` — they appear automatically at `/blog`
|
||||
- **Blog images**: Place images in `public/blog/<post-slug>/` and reference with `/blog/<post-slug>/image.jpg`
|
||||
|
||||
## Technology Stack
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
✅ Sitemap integration
|
||||
✅ robots.txt
|
||||
✅ Accessibility improvements (ARIA labels, semantic HTML)
|
||||
✅ Blog with Astro Content Collections (Markdown, no CMS)
|
||||
|
||||
## Immediate Next Steps
|
||||
|
||||
@@ -28,11 +29,11 @@
|
||||
- [ ] Consider adding subtle background patterns or textures
|
||||
|
||||
#### Medium Term
|
||||
- [ ] Create a blog section using Astro Content Collections
|
||||
- [x] Create a blog section using Astro Content Collections
|
||||
- [ ] Add RSS feed for blog
|
||||
- [ ] Add a projects page that pulls from Gitea API
|
||||
- [ ] Create reusable components for repeated elements
|
||||
- [ ] Add a contact form (self-hosted solution)
|
||||
- [ ] Add RSS feed if blog is implemented
|
||||
|
||||
#### Long Term
|
||||
- [ ] Theme toggle (dark/light, or alternate color schemes)
|
||||
@@ -45,7 +46,7 @@
|
||||
|
||||
- Network is required for npm install (packages not included in repo)
|
||||
- Gitea registry requires authentication (document login process)
|
||||
- No CMS - content updates require code changes (intentional for now)
|
||||
- No CMS - blog posts are Markdown files committed to the repo (intentional — no attack surface)
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ server {
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
absolute_redirect off;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
---
|
||||
title: "Love Without Access"
|
||||
date: 2026-03-01
|
||||
description: "A reflection on a first love — what it meant, what it cost, and why distance was the most loving thing left."
|
||||
---
|
||||
|
||||
*by LATTE*
|
||||
|
||||
There was a time when I thought love was mostly about intensity.
|
||||
|
||||
Waking up next to someone and feeling like the world aligned.
|
||||
Skin against skin.
|
||||
Breathing in sync.
|
||||
Power and surrender that were, underneath it all, just different shapes of trust.
|
||||
|
||||
This isn't a story about blame.
|
||||
And it's not a story about anger.
|
||||
|
||||
It's a story about something that stayed real…
|
||||
even after it stopped being reachable.
|
||||
|
||||
---
|
||||
|
||||
## How It Began
|
||||
|
||||
We were friends first.
|
||||
|
||||
Maybe that's what made it so deep. It didn't explode into existence — it grew. Slowly. Safely.
|
||||
From gaming together to talking for hours.
|
||||
From talking to tension.
|
||||
From tension to touch.
|
||||
|
||||
And then, somehow, we became a home.
|
||||
|
||||
Not the perfect kind.
|
||||
Not the easy kind.
|
||||
But the kind your body recognizes.
|
||||
|
||||
---
|
||||
|
||||
## What We Were
|
||||
|
||||
From the outside, we looked like a relationship.
|
||||
|
||||
On the inside, we were a system of trust.
|
||||
|
||||
I was the one who brought structure.
|
||||
Who checked in.
|
||||
Who asked, "Are you still okay?"
|
||||
Who carried responsibility for safety.
|
||||
|
||||
What some people would call dominance
|
||||
was, for me, care.
|
||||
|
||||
And for him, surrender wasn't weakness.
|
||||
It was relief.
|
||||
|
||||
But what I only understood later was this:
|
||||
|
||||
Closeness regulated me.
|
||||
Distance regulated him.
|
||||
|
||||
And that difference doesn't show up at the start.
|
||||
You only feel it when life gets heavy and love gets real.
|
||||
|
||||
---
|
||||
|
||||
## The Fracture
|
||||
|
||||
There wasn't an explosion. No dramatic collapse.
|
||||
|
||||
There was fatigue.
|
||||
Doubt.
|
||||
Silence.
|
||||
|
||||
"I don't feel it anymore."
|
||||
|
||||
And something in me went very quiet — and very loud — at the same time.
|
||||
|
||||
Because I could still feel myself holding on.
|
||||
And carrying someone who no longer carries back
|
||||
exhausts you in a way no one sees.
|
||||
|
||||
---
|
||||
|
||||
## The After That Kept Me Stuck
|
||||
|
||||
What made it hardest wasn't just the ending.
|
||||
|
||||
It was the ambiguity after.
|
||||
|
||||
Still sleeping next to each other.
|
||||
Still laughing.
|
||||
Still touching.
|
||||
|
||||
But no longer together.
|
||||
|
||||
A body that says yes.
|
||||
Words that say no.
|
||||
|
||||
That contradiction doesn't just hurt emotionally — it destabilizes you.
|
||||
Hope becomes a reflex.
|
||||
And every time hope collapses, you fracture a little with it.
|
||||
|
||||
I tried to understand. I tried to fix.
|
||||
I thought if I could articulate my love clearly enough, we could find safety again.
|
||||
|
||||
But love is not code you can debug
|
||||
when one of you has already logged out.
|
||||
|
||||
---
|
||||
|
||||
## The Part Where Safety Changed
|
||||
|
||||
And then there was the part people don't like to talk about:
|
||||
|
||||
When something private stops being safe.
|
||||
|
||||
When the inner room gets opened.
|
||||
|
||||
I'm not writing this to accuse you.
|
||||
I'm writing this because it matters.
|
||||
|
||||
Because trust isn't just "did you mean well."
|
||||
Trust is "did I feel protected."
|
||||
|
||||
And after a certain point, I didn't.
|
||||
|
||||
That's when I understood something important:
|
||||
|
||||
Even love needs a locked door
|
||||
when the inside of you keeps getting exposed.
|
||||
|
||||
---
|
||||
|
||||
## What It Did to Me
|
||||
|
||||
There were days my body shook.
|
||||
|
||||
Days where seeing your name online
|
||||
made my chest tighten.
|
||||
|
||||
Where I felt cold,
|
||||
then numb,
|
||||
then flooded.
|
||||
|
||||
I don't write that for drama.
|
||||
I write it because endings are not abstract.
|
||||
|
||||
When attachment breaks,
|
||||
the body reacts.
|
||||
|
||||
I wasn't weak.
|
||||
I was grieving a nervous system bond.
|
||||
|
||||
And at some point, I understood something else:
|
||||
|
||||
Loving you was real.
|
||||
But staying in reach of you
|
||||
was slowly undoing me.
|
||||
|
||||
So I chose distance.
|
||||
|
||||
Not because I stopped loving you.
|
||||
But because I had to protect myself.
|
||||
|
||||
No access wasn't punishment.
|
||||
|
||||
It was survival.
|
||||
|
||||
---
|
||||
|
||||
## What I Understand Now
|
||||
|
||||
Some people are not cruel.
|
||||
Not cold.
|
||||
Not heartless.
|
||||
|
||||
They're overwhelmed.
|
||||
|
||||
And when overwhelm becomes someone's default response to emotional depth,
|
||||
distance becomes their survival strategy.
|
||||
|
||||
For a long time, I wondered if I was "too much."
|
||||
Too intense. Too deep. Too present.
|
||||
|
||||
Now I know:
|
||||
|
||||
I wasn't too much.
|
||||
We were mismatched in emotional capacity.
|
||||
|
||||
I wanted co-regulation.
|
||||
He needed self-regulation.
|
||||
|
||||
That's not villain and victim.
|
||||
That's architecture.
|
||||
|
||||
---
|
||||
|
||||
## On Being Replaced
|
||||
|
||||
Yes — he found someone new quickly.
|
||||
|
||||
That hurt.
|
||||
|
||||
Not because he doesn't deserve happiness.
|
||||
But because what we had felt quiet and private,
|
||||
and what came after looked public and bright.
|
||||
|
||||
I questioned whether I was replaceable.
|
||||
Whether I had simply been a phase.
|
||||
|
||||
But love isn't a competition.
|
||||
|
||||
What we had lasted years.
|
||||
It was layered. It was real.
|
||||
It mattered.
|
||||
|
||||
Something ending does not mean it was nothing.
|
||||
|
||||
---
|
||||
|
||||
## For You
|
||||
|
||||
If you ever read this —
|
||||
|
||||
I want you to know that what we had was real to me.
|
||||
Not experimental. Not temporary. Not a placeholder.
|
||||
|
||||
Real.
|
||||
|
||||
You weren't just someone I loved.
|
||||
You were my first love.
|
||||
|
||||
My first true one.
|
||||
|
||||
The first person I chose fully.
|
||||
The first person I built a life around.
|
||||
The first person I learned love with.
|
||||
|
||||
We were figuring things out together.
|
||||
Trying. Failing. Adjusting.
|
||||
Discovering what intimacy meant.
|
||||
Discovering what we meant.
|
||||
|
||||
You weren't just someone who entered my life —
|
||||
you were part of my becoming.
|
||||
|
||||
That matters.
|
||||
|
||||
Not in a way that traps either of us.
|
||||
But in a way that leaves a mark.
|
||||
|
||||
And I need to say this clearly:
|
||||
|
||||
I was never angry at you.
|
||||
Not truly. Not even in the hardest moments.
|
||||
|
||||
I was hurt.
|
||||
I was overwhelmed.
|
||||
I was trying to hold something I didn't yet know how to let go of.
|
||||
|
||||
But even then, I wasn't against you.
|
||||
|
||||
I saw you as someone struggling — not someone malicious.
|
||||
|
||||
There were moments when you softened completely with me.
|
||||
Moments where you rested your full weight without guarding yourself.
|
||||
|
||||
Those moments were real.
|
||||
You were there.
|
||||
You chose me then.
|
||||
|
||||
And I don't erase that.
|
||||
|
||||
Here is the honest truth:
|
||||
|
||||
The love didn't vanish.
|
||||
Access did.
|
||||
|
||||
And choosing no access
|
||||
was the hardest loving decision I've ever made.
|
||||
|
||||
Because access requires safety.
|
||||
And safety requires consistency.
|
||||
And consistency requires a kind of staying that you couldn't give.
|
||||
|
||||
So no, I'm not angry that you couldn't stay.
|
||||
|
||||
I still love you.
|
||||
|
||||
The love I had doesn't disappear just because access is gone.
|
||||
|
||||
It changes shape.
|
||||
It becomes quieter.
|
||||
It becomes something I carry instead of something I reach for.
|
||||
|
||||
And yes — I'm going to be a little playful about it:
|
||||
|
||||
You're on my website. Do you get that?
|
||||
|
||||
Not as a spectacle.
|
||||
Not as a wound.
|
||||
|
||||
As a chapter.
|
||||
|
||||
I don't delete chapters.
|
||||
I archive them properly.
|
||||
|
||||
You were my first true love.
|
||||
The first person I learned how to love with.
|
||||
|
||||
That doesn't give you access anymore.
|
||||
But it does give you permanence.
|
||||
|
||||
That love still exists.
|
||||
|
||||
It just no longer has a door.
|
||||
|
||||
---
|
||||
|
||||
## For Me
|
||||
|
||||
I'm still learning.
|
||||
|
||||
That giving doesn't have to be my identity.
|
||||
That caring doesn't mean abandoning myself.
|
||||
That intensity isn't a flaw.
|
||||
|
||||
I don't need to shrink
|
||||
to be worthy of being held.
|
||||
|
||||
The wolf in me was never meant to become smaller.
|
||||
Only to find the right pack.
|
||||
|
||||
— LATTE
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z, defineCollection } from 'astro:content';
|
||||
|
||||
const blog = defineCollection({
|
||||
type: 'content',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
date: z.coerce.date(),
|
||||
description: z.string(),
|
||||
draft: z.boolean().optional().default(false),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { blog };
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
import BaseLayout from "../../layouts/BaseLayout.astro";
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection("blog", ({ data }) => !data.draft);
|
||||
return posts.map((post) => ({
|
||||
params: { slug: post.slug },
|
||||
props: { post },
|
||||
}));
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = await post.render();
|
||||
|
||||
function formatDate(date: Date) {
|
||||
return date.toISOString().split("T")[0];
|
||||
}
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title={`${post.data.title} — Hidden Den Cafe`}
|
||||
description={post.data.description}
|
||||
>
|
||||
<div class="matrix-bg" aria-hidden="true"></div>
|
||||
|
||||
<main class="main">
|
||||
<div class="container">
|
||||
<header class="header fade-in">
|
||||
<p class="back"><a href="/blog">← back to blog</a></p>
|
||||
<div class="divider">══════════════════════════════</div>
|
||||
<h1 class="title">{post.data.title}</h1>
|
||||
<p class="date">{formatDate(post.data.date)}</p>
|
||||
<div class="divider">══════════════════════════════</div>
|
||||
</header>
|
||||
|
||||
<article class="content fade-in">
|
||||
<Content />
|
||||
</article>
|
||||
|
||||
<footer class="footer fade-in">
|
||||
<div class="divider">══════════════════════════════</div>
|
||||
<p>Made with love by Latte</p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.matrix-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
opacity: 0.03;
|
||||
background:
|
||||
linear-gradient(var(--color-accent) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--color-accent) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
animation: grid-move 20s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes grid-move {
|
||||
0% { transform: translate(0, 0); }
|
||||
100% { transform: translate(50px, 50px); }
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-lg) var(--space-md);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 700px;
|
||||
width: 100%;
|
||||
background: rgba(30, 30, 46, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--color-surface);
|
||||
border-radius: 8px;
|
||||
padding: var(--space-xl);
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.back {
|
||||
margin-bottom: var(--space-sm);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.back a {
|
||||
color: var(--color-text-dim);
|
||||
}
|
||||
|
||||
.back a:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.divider {
|
||||
color: var(--color-surface);
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
margin: var(--space-md) 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.date {
|
||||
color: var(--color-text-dim);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Prose styles for markdown content */
|
||||
.content {
|
||||
color: var(--color-text);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.content :global(h2) {
|
||||
font-size: 1.1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
color: var(--color-accent);
|
||||
margin: var(--space-lg) 0 var(--space-sm);
|
||||
}
|
||||
|
||||
.content :global(h3) {
|
||||
font-size: 1rem;
|
||||
color: var(--color-accent-bright);
|
||||
margin: var(--space-md) 0 var(--space-xs);
|
||||
}
|
||||
|
||||
.content :global(p) {
|
||||
margin-bottom: var(--space-md);
|
||||
color: var(--color-text-dim);
|
||||
}
|
||||
|
||||
.content :global(a) {
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
.content :global(a:hover) {
|
||||
color: var(--color-accent-bright);
|
||||
}
|
||||
|
||||
.content :global(code) {
|
||||
font-family: inherit;
|
||||
background: var(--color-bg);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85em;
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
.content :global(pre) {
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-surface);
|
||||
border-radius: 6px;
|
||||
padding: var(--space-md);
|
||||
overflow-x: auto;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.content :global(pre code) {
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.content :global(ul),
|
||||
.content :global(ol) {
|
||||
padding-left: var(--space-lg);
|
||||
margin-bottom: var(--space-md);
|
||||
color: var(--color-text-dim);
|
||||
}
|
||||
|
||||
.content :global(li) {
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.content :global(blockquote) {
|
||||
border-left: 3px solid var(--color-accent);
|
||||
padding-left: var(--space-md);
|
||||
margin: var(--space-md) 0;
|
||||
color: var(--color-text-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.content :global(hr) {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-surface);
|
||||
margin: var(--space-lg) 0;
|
||||
}
|
||||
|
||||
.content :global(img) {
|
||||
max-width: 100%;
|
||||
border-radius: 6px;
|
||||
margin: var(--space-md) 0;
|
||||
border: 1px solid var(--color-surface);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content :global(figure) {
|
||||
margin: var(--space-md) 0;
|
||||
}
|
||||
|
||||
.content :global(figcaption) {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-dim);
|
||||
text-align: center;
|
||||
margin-top: var(--space-xs);
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: var(--space-lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
color: var(--color-text-dim);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.6s ease-out forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-in:nth-child(1) { animation-delay: 0.1s; }
|
||||
.fade-in:nth-child(2) { animation-delay: 0.2s; }
|
||||
.fade-in:nth-child(3) { animation-delay: 0.3s; }
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.divider {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.matrix-bg {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,241 @@
|
||||
---
|
||||
import BaseLayout from "../../layouts/BaseLayout.astro";
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
const posts = (await getCollection("blog", ({ data }) => !data.draft))
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||
|
||||
function formatDate(date: Date) {
|
||||
return date.toISOString().split("T")[0];
|
||||
}
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Blog — Hidden Den Cafe"
|
||||
description="Latte's blog. Thoughts, technical things, and whatever else is on my mind."
|
||||
>
|
||||
<div class="matrix-bg" aria-hidden="true"></div>
|
||||
|
||||
<main class="main">
|
||||
<div class="container">
|
||||
<header class="header fade-in">
|
||||
<p class="back"><a href="/">← back to home</a></p>
|
||||
<h1 class="title">Blog</h1>
|
||||
<div class="divider">══════════════════════════════</div>
|
||||
</header>
|
||||
|
||||
{posts.length === 0 ? (
|
||||
<section class="section fade-in">
|
||||
<p class="empty">No posts yet. Will get around to it.</p>
|
||||
</section>
|
||||
) : (
|
||||
<section class="section fade-in">
|
||||
<ul class="post-list">
|
||||
{posts.map((post) => (
|
||||
<li class="post-item">
|
||||
<span class="post-date">{formatDate(post.data.date)}</span>
|
||||
<div class="post-info">
|
||||
<a href={`/blog/${post.slug}`} class="post-title">{post.data.title}</a>
|
||||
<p class="post-desc">{post.data.description}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<footer class="footer fade-in">
|
||||
<div class="divider">══════════════════════════════</div>
|
||||
<p>Made with love by Latte</p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.matrix-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
opacity: 0.03;
|
||||
background:
|
||||
linear-gradient(var(--color-accent) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--color-accent) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
animation: grid-move 20s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes grid-move {
|
||||
0% { transform: translate(0, 0); }
|
||||
100% { transform: translate(50px, 50px); }
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-lg) var(--space-md);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 700px;
|
||||
width: 100%;
|
||||
background: rgba(30, 30, 46, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--color-surface);
|
||||
border-radius: 8px;
|
||||
padding: var(--space-xl);
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.back {
|
||||
margin-bottom: var(--space-sm);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.back a {
|
||||
color: var(--color-text-dim);
|
||||
}
|
||||
|
||||
.back a:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.divider {
|
||||
color: var(--color-surface);
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
margin: var(--space-md) 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin: var(--space-md) 0;
|
||||
}
|
||||
|
||||
.post-list {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.post-item {
|
||||
display: flex;
|
||||
gap: var(--space-md);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.post-date {
|
||||
color: var(--color-text-dim);
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
padding-top: 2px;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.post-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.post-title {
|
||||
color: var(--color-blue);
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.post-title:hover {
|
||||
color: var(--color-accent-bright);
|
||||
}
|
||||
|
||||
.post-desc {
|
||||
color: var(--color-text-dim);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: var(--color-text-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: var(--space-lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
color: var(--color-text-dim);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.6s ease-out forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-in:nth-child(1) { animation-delay: 0.1s; }
|
||||
.fade-in:nth-child(2) { animation-delay: 0.2s; }
|
||||
.fade-in:nth-child(3) { animation-delay: 0.3s; }
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.divider {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.post-item {
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.post-date {
|
||||
min-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.matrix-bg {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -111,6 +111,16 @@ if (now < new Date(now.getFullYear(), 6, 8)) age--;
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider">══════════════════════════════</div>
|
||||
|
||||
<!-- Blog -->
|
||||
<section class="section fade-in">
|
||||
<h2>Blog</h2>
|
||||
<ul class="links">
|
||||
<li><a href="/blog">blog</a> — thoughts and things</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer fade-in">
|
||||
<div class="divider">══════════════════════════════</div>
|
||||
|
||||
Reference in New Issue
Block a user