feat/finishing-touch #59

Merged
Latte merged 3 commits from feat/finishing-touch into dev 2026-03-08 09:32:55 +00:00
5 changed files with 0 additions and 2270 deletions
Showing only changes of commit 8b78ee20f5 - Show all commits
-388
View File
@@ -1,388 +0,0 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout
title="AI - Hidden Den Cafe"
description="A calm note about how AI is used at Hidden Den: where it helps, where judgment stays human, and why the boundaries matter."
>
<div class="matrix-bg" aria-hidden="true"></div>
<main class="main">
<div class="container">
<header class="header fade-in">
<p class="eyebrow">How this place is built</p>
<h1 class="title">AI</h1>
<div class="divider">==============================</div>
<p class="lead">
This page exists as a simple note about how I use AI around
Hidden Den. Not as a grand policy statement, just as a clear
explanation of where it helps and where the site is still
very obviously built by a person.
</p>
</header>
<section class="section fade-in">
<h2>AI As A Tool</h2>
<p class="desc">
I use AI in the same spirit that I use a terminal, an
editor, or a good piece of reference documentation: as a
tool that can help me think faster, test ideas, or get
through implementation work with less friction.
</p>
<p class="desc">
In practice that can mean refining copy, brainstorming page
structure, shaping prompts for coding agents, working
through technical edge cases, or getting help with a first
draft of a solution before I reshape it into something that
actually fits this site.
</p>
<p class="desc">
I still write and shape the code myself. AI sometimes helps
explain things, suggest an approach, or help me get unstuck,
but the actual building still happens in my editor and
terminal.
</p>
<div class="note">
<p>
The useful part is assistance, not delegation. AI helps
with scaffolding, iteration, and technical momentum.
</p>
</div>
</section>
<section class="section fade-in">
<h2>What Stays Human</h2>
<p class="desc">
The final direction still comes from me. I decide what gets
published, what tone feels right, what belongs here, and
what does not. The site is meant to reflect a real person,
not an auto-generated personality layer pretending to be
one.
</p>
<p class="desc">
That applies to writing, structure, visual choices, and the
general shape of the project. AI can help me move, but it
does not get to replace judgment, taste, or authorship.
</p>
<div class="value-grid">
<article class="value-card">
<h3>Assistance</h3>
<p>
Drafts, implementation help, idea pressure-testing,
and faster iteration.
</p>
</article>
<article class="value-card">
<h3>Not Replacement</h3>
<p>
Final voice, publishing choices, priorities, and the
actual perspective behind the site stay mine.
</p>
</article>
</div>
</section>
<section class="section fade-in">
<h2>Privacy And Boundaries</h2>
<p class="desc">
Privacy matters here, so AI use is deliberate rather than
casual. I do not think every problem should be handed to an
external system by default, and I do not want tooling habits
that quietly erode ownership just because convenience is
available.
</p>
<p class="desc">
That means using AI where it is practically helpful, keeping
boundaries in mind, and staying aware that these systems are
tools with tradeoffs. They should serve the builder, not
become the environment the builder disappears into.
</p>
<ul class="values compact">
<li>
<span class="value-key">intentional use:</span> I use it where
it helps, not everywhere by reflex.
</li>
<li>
<span class="value-key">privacy matters:</span> Not everything
belongs in a prompt window.
</li>
<li>
<span class="value-key">bounded scope:</span> AI supports
the workflow instead of becoming the workflow.
</li>
</ul>
</section>
<section class="section fade-in">
<h2>Why This Fits Hidden Den</h2>
<p class="desc">
Hidden Den is built around understandable systems, practical
ownership, and tools that earn their place. AI fits that
philosophy when it is used calmly and with limits. It can be
useful without becoming an identity, and productive without
being treated like magic.
</p>
<p class="desc">
That balance matters to me. I like tools that help me build
better and think more clearly, but I still want the site to
feel inhabited, specific, and human-scaled. AI can help with
the scaffolding, but the den is still built by a person.
</p>
</section>
<footer class="footer fade-in">
<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);
padding-top: calc(var(--space-lg) + 3rem);
}
.container {
max-width: 700px;
width: 100%;
background: var(--color-glass);
backdrop-filter: blur(10px);
border: 1px solid var(--color-surface);
border-radius: 8px;
padding: var(--space-xl);
}
.header {
text-align: center;
margin-bottom: var(--space-lg);
}
.eyebrow {
color: var(--color-text-dim);
text-transform: uppercase;
letter-spacing: 0.24em;
font-size: 0.75rem;
margin-bottom: var(--space-sm);
}
.title {
font-size: 2rem;
font-weight: 700;
letter-spacing: 2px;
}
.divider {
color: var(--color-surface);
text-align: center;
font-size: 0.75rem;
margin: var(--space-md) 0;
user-select: none;
}
.lead {
color: var(--color-text);
line-height: 1.8;
max-width: 38rem;
margin: 0 auto;
}
.section {
margin: var(--space-lg) 0;
}
.section h2 {
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: var(--space-sm);
color: var(--color-accent);
}
.desc {
color: var(--color-text-dim);
line-height: 1.7;
margin-bottom: var(--space-sm);
}
.desc:last-child {
margin-bottom: 0;
}
.note {
margin-top: var(--space-md);
padding: var(--space-md);
border: 1px solid
color-mix(in srgb, var(--color-accent) 30%, transparent);
background:
linear-gradient(
135deg,
color-mix(in srgb, var(--color-accent) 10%, transparent),
transparent 70%
),
color-mix(in srgb, var(--color-bg-light) 84%, transparent);
border-radius: 8px;
}
.note p {
color: var(--color-text);
line-height: 1.7;
}
.value-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-md);
margin-top: var(--space-md);
}
.value-card {
padding: var(--space-md);
border-radius: 8px;
background: color-mix(in srgb, var(--color-bg-light) 88%, transparent);
border: 1px solid
color-mix(in srgb, var(--color-surface) 70%, transparent);
}
.value-card h3 {
color: var(--color-accent-bright);
font-size: 0.95rem;
text-transform: uppercase;
letter-spacing: 0.12em;
margin-bottom: var(--space-xs);
}
.value-card p {
color: var(--color-text-dim);
line-height: 1.7;
}
.values {
list-style: none;
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.values li {
color: var(--color-text-dim);
line-height: 1.6;
}
.value-key {
color: var(--color-accent);
font-weight: 600;
}
.compact {
margin-top: var(--space-md);
}
.footer {
margin-top: var(--space-xl);
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;
}
.fade-in:nth-child(4) {
animation-delay: 0.4s;
}
.fade-in:nth-child(5) {
animation-delay: 0.5s;
}
@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;
}
.lead {
font-size: 0.95rem;
}
.value-grid {
grid-template-columns: 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
.matrix-bg {
animation: none;
}
.fade-in {
animation: none;
opacity: 1;
}
}
</style>
-313
View File
@@ -1,313 +0,0 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
const changelogEntries = [
{
date: "2026-03-07",
changes: [
"Added this changelog page as a quiet logbook for how the den grows.",
"Published Coffee & Code #1 and added series support to the blog.",
"Published Things I Learned From Loving Deeply.",
"Added a Q&A page for the site's philosophy and tone.",
"Expanded the About page and added a gentler Start page for new visitors.",
],
},
{
date: "2026-03-06",
changes: [
"Added the Now page as a living snapshot of current focus.",
"Added a Uses page for the tools, systems, and infrastructure behind the site.",
"Refined navigation and layout behavior to support a growing set of pages.",
],
},
{
date: "2026-03-04",
changes: [
"Published After the Silence.",
"Polished the homepage avatar and project card interactions.",
"Added dedicated About and Projects pages, along with some nginx hardening.",
],
},
{
date: "2026-03-01",
changes: [
"Published Welcome to the Den and Love Without Access.",
"Expanded the homepage with socials, age, and cryptographic keys.",
"Added repository workflows and supporting site infrastructure.",
],
},
{
date: "2026-01-03",
changes: [
"Reshaped the site into a more personal page with a warmer identity.",
"Moved the project further away from a placeholder site and closer to Hidden Den as an actual home.",
],
},
{
date: "2025-12-23",
changes: [
"Started the Astro-based site with Docker deployment in mind.",
"Set up the first pieces of the den: project structure, documentation, and hosting groundwork.",
],
},
];
---
<BaseLayout
title="Changelog - Hidden Den Cafe"
description="A quiet logbook of how Cozy Den changes over time."
>
<div class="matrix-bg" aria-hidden="true"></div>
<main class="main">
<div class="container">
<header class="header fade-in">
<h1 class="title">Changelog</h1>
<div class="divider">==============================</div>
<p class="lead">
This page keeps a quiet record of changes to the site. New
pages, new writing, design shifts, structural cleanup, and
the slow work of making the den feel more lived in all land
here.
</p>
</header>
<section class="section fade-in" aria-labelledby="changelog-list">
<h2 id="changelog-list" class="sr-only">Changelog entries</h2>
<ol class="changelog-list">
{
changelogEntries.map((entry) => (
<li class="changelog-item">
<time class="entry-date" datetime={entry.date}>
{entry.date}
</time>
<article
class="entry-card"
aria-label={`Changelog entry for ${entry.date}`}
>
<ul class="entry-changes">
{entry.changes.map((change) => (
<li>{change}</li>
))}
</ul>
</article>
</li>
))
}
</ol>
</section>
<section class="section fade-in">
<p class="closing-note">
The goal is not to announce polished releases. It is simply
to leave a trail showing how this place changes over time.
</p>
</section>
<footer class="footer fade-in">
<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);
padding-top: calc(var(--space-lg) + 3rem);
}
.container {
max-width: 700px;
width: 100%;
background: var(--color-glass);
backdrop-filter: blur(10px);
border: 1px solid var(--color-surface);
border-radius: 8px;
padding: var(--space-xl);
}
.header {
margin-bottom: var(--space-lg);
}
.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;
}
.lead {
color: var(--color-text-dim);
line-height: 1.75;
max-width: 40rem;
margin: 0 auto;
text-align: center;
}
.section {
margin: var(--space-md) 0;
}
.changelog-list {
list-style: none;
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.changelog-item {
display: flex;
gap: var(--space-md);
align-items: flex-start;
}
.entry-date {
color: var(--color-text-dim);
font-size: 0.8rem;
white-space: nowrap;
padding-top: 2px;
min-width: 90px;
}
.entry-card {
flex: 1;
padding: var(--space-md);
border-radius: 8px;
background: color-mix(in srgb, var(--color-bg-light) 88%, transparent);
border: 1px solid
color-mix(in srgb, var(--color-surface) 70%, transparent);
}
.entry-changes {
display: grid;
gap: var(--space-xs);
padding-left: 1.15rem;
}
.entry-changes li {
color: var(--color-text-dim);
line-height: 1.7;
}
.closing-note {
color: var(--color-text-dim);
line-height: 1.7;
text-align: center;
max-width: 42rem;
margin: 0 auto;
}
.footer {
margin-top: var(--space-xl);
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;
}
.fade-in:nth-child(4) {
animation-delay: 0.4s;
}
@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;
}
.changelog-item {
flex-direction: column;
gap: var(--space-xs);
}
.entry-date {
min-width: unset;
}
}
@media (prefers-reduced-motion: reduce) {
.matrix-bg {
animation: none;
}
.fade-in {
animation: none;
opacity: 1;
}
}
</style>
-289
View File
@@ -1,289 +0,0 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout
title="Q&A - Hidden Den Cafe"
description="A quiet Q&A about Cozy Den, why it exists, and the values behind it."
>
<div class="matrix-bg" aria-hidden="true"></div>
<main class="main">
<div class="container">
<header class="header fade-in">
<p class="eyebrow">Quiet answers</p>
<h1 class="title">Q&amp;A</h1>
<div class="divider">==============================</div>
<p class="lead">
A few short answers for anyone wondering what Cozy Den is,
why it exists, and what kind of space it is trying to be.
</p>
</header>
<section class="section fade-in" aria-labelledby="faq-heading">
<h2 id="faq-heading" class="sr-only">Questions and answers</h2>
<div class="faq-list">
<article class="faq-item">
<h3>What is Cozy Den?</h3>
<p>
Cozy Den is a personal website and quiet corner of
the web. It is part writing space, part workshop,
and part home for ideas about self-hosting,
privacy, infrastructure, and building things with
care.
</p>
</article>
<article class="faq-item">
<h3>
Why build a personal website instead of using social
media?
</h3>
<p>
Social platforms are useful for discovery, but they
are not good homes. A personal site is calmer, more
durable, and shaped by the person making it instead
of by feeds, algorithms, or platform incentives.
</p>
</article>
<article class="faq-item">
<h3>What does privacy-first mean for this site?</h3>
<p>
It means the site tries to ask for as little as
possible. No trackers, no ads, no unnecessary
external scripts, and no design choices built around
surveillance. Visitors are guests here, not data to
be collected.
</p>
</article>
<article class="faq-item">
<h3>Who is behind Hidden Den?</h3>
<p>
Hidden Den is built by Latte: a gay furry developer,
homelab enthusiast, and privacy-minded internet
dweller who still believes personal websites should
feel warm, human, and fully their own.
</p>
</article>
</div>
</section>
<section class="section fade-in">
<div class="note">
<p>
If the short version helps: Cozy Den exists to be a
small, thoughtful place on the internet that belongs to
the person building it.
</p>
</div>
</section>
<footer class="footer fade-in">
<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);
padding-top: calc(var(--space-lg) + 3rem);
}
.container {
max-width: 700px;
width: 100%;
background: var(--color-glass);
backdrop-filter: blur(10px);
border: 1px solid var(--color-surface);
border-radius: 8px;
padding: var(--space-xl);
}
.header {
text-align: center;
margin-bottom: var(--space-lg);
}
.eyebrow {
color: var(--color-text-dim);
text-transform: uppercase;
letter-spacing: 0.24em;
font-size: 0.75rem;
margin-bottom: var(--space-sm);
}
.title {
font-size: 2rem;
font-weight: 700;
letter-spacing: 2px;
}
.divider {
color: var(--color-surface);
text-align: center;
font-size: 0.75rem;
margin: var(--space-md) 0;
user-select: none;
}
.lead {
color: var(--color-text);
line-height: 1.8;
max-width: 38rem;
margin: 0 auto;
}
.section {
margin: var(--space-lg) 0;
}
.faq-list {
display: grid;
gap: var(--space-md);
}
.faq-item {
padding: var(--space-md);
border-radius: 8px;
background: color-mix(in srgb, var(--color-bg-light) 88%, transparent);
border: 1px solid color-mix(in srgb, var(--color-surface) 70%, transparent);
}
.faq-item h3 {
font-size: 0.98rem;
color: var(--color-accent-bright);
margin-bottom: var(--space-xs);
line-height: 1.5;
}
.faq-item p {
color: var(--color-text-dim);
line-height: 1.75;
}
.note {
padding: var(--space-md);
border: 1px solid color-mix(in srgb, var(--color-accent) 30%, transparent);
background:
linear-gradient(
135deg,
color-mix(in srgb, var(--color-accent) 12%, transparent),
transparent 70%
),
color-mix(in srgb, var(--color-bg-light) 84%, transparent);
border-radius: 8px;
}
.note p {
color: var(--color-text);
line-height: 1.7;
}
.footer {
margin-top: var(--space-xl);
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;
}
.fade-in:nth-child(4) {
animation-delay: 0.4s;
}
@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;
}
.lead {
font-size: 0.95rem;
}
.faq-item {
padding: var(--space-sm);
}
}
@media (prefers-reduced-motion: reduce) {
.matrix-bg {
animation: none;
}
.fade-in {
animation: none;
opacity: 1;
}
}
</style>
-610
View File
@@ -1,610 +0,0 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import { getCollection } from "astro:content";
import { formatBlogDate } from "../lib/blog";
import { getReadingTime } from "../lib/readingTime";
const featuredEssays = (
await getCollection("blog", ({ data }) => !data.draft && data.featuredEssay)
).sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
const curatedEssays = featuredEssays.map((post) => ({
...post,
readingTime: getReadingTime(post.body),
}));
const recommendedPath = curatedEssays
.filter((post) => typeof post.data.readingOrder === "number")
.sort(
(a, b) =>
(a.data.readingOrder ?? Number.MAX_SAFE_INTEGER) -
(b.data.readingOrder ?? Number.MAX_SAFE_INTEGER),
);
const categoryMap = new Map<string, (typeof curatedEssays)[number][]>();
for (const post of curatedEssays) {
if (!post.data.category) continue;
const current = categoryMap.get(post.data.category) ?? [];
current.push(post);
categoryMap.set(post.data.category, current);
}
const categoryGroups = [...categoryMap.entries()].sort(([a], [b]) =>
a.localeCompare(b),
);
function formatCategory(category: string) {
return category.replace(/\b\w/g, (char) => char.toUpperCase());
}
---
<BaseLayout
title="Library - Hidden Den Cafe"
description="A quiet reading room for deeper writing, reflections, and longer posts from Hidden Den."
>
<div class="matrix-bg" aria-hidden="true"></div>
<main class="main">
<div class="container">
<header class="header fade-in">
<p class="eyebrow">Deeper writing</p>
<h1 class="title">Library</h1>
<div class="divider">==============================</div>
<p class="lead">
This is the quieter shelf of the den: a place for longer
posts, reflective essays, and writing worth revisiting. The
blog keeps the full archive. The library is a smaller,
curated reading room.
</p>
</header>
{
curatedEssays.length === 0 ? (
<section class="section fade-in">
<div class="note">
<p>
The shelf is still being arranged. For now, the
deeper writing still lives in the blog until
more posts are marked for the library.
</p>
</div>
</section>
) : (
<>
<section
class="section fade-in"
aria-labelledby="highlighted-essays"
>
<div class="section-head">
<h2 id="highlighted-essays">
Highlighted Essays
</h2>
<a class="section-link" href="/blog">
Browse all posts
</a>
</div>
<p class="section-intro">
A small curated shelf of the more substantial
writing on the site.
</p>
<ul class="essay-list">
{curatedEssays.map((post) => (
<li class="essay-item">
<article class="essay-card">
<div class="card-topline">
<div class="meta-row">
<time
datetime={formatBlogDate(
post.data.pubDate,
)}
>
{formatBlogDate(
post.data.pubDate,
)}
</time>
<span
class="meta-sep"
aria-hidden="true"
>
·
</span>
<span>
{post.readingTime.text}
</span>
</div>
{post.data.category && (
<span class="category-pill">
{formatCategory(
post.data.category,
)}
</span>
)}
</div>
<h3>
<a href={`/blog/${post.slug}`}>
{post.data.title}
</a>
</h3>
<p>{post.data.description}</p>
<a
class="essay-link"
href={`/blog/${post.slug}`}
>
Read essay
</a>
</article>
</li>
))}
</ul>
</section>
{recommendedPath.length >= 2 && (
<section
class="section fade-in"
aria-labelledby="reading-path"
>
<h2 id="reading-path">
Recommended Reading Path
</h2>
<p class="section-intro">
If you want a gentle path through the longer
writing, start here.
</p>
<ol class="path-list">
{recommendedPath.map((post) => (
<li class="path-item">
<span class="path-number">
{post.data.readingOrder}
</span>
<article class="path-card">
<h3>
<a
href={`/blog/${post.slug}`}
>
{post.data.title}
</a>
</h3>
<p>{post.data.description}</p>
<div class="meta-row path-meta">
<span>
{formatBlogDate(
post.data.pubDate,
)}
</span>
<span
class="meta-sep"
aria-hidden="true"
>
·
</span>
<span>
{post.readingTime.text}
</span>
</div>
</article>
</li>
))}
</ol>
</section>
)}
{categoryGroups.length > 0 && (
<section
class="section fade-in"
aria-labelledby="browse-by-theme"
>
<h2 id="browse-by-theme">Browse By Theme</h2>
<p class="section-intro">
The same shelf, grouped more loosely by what
each piece leans toward.
</p>
<div class="theme-grid">
{categoryGroups.map(([category, posts]) => (
<section
class="theme-group"
aria-labelledby={`theme-${category}`}
>
<h3 id={`theme-${category}`}>
{formatCategory(category)}
</h3>
<ul class="theme-list">
{posts.map((post) => (
<li>
<a
href={`/blog/${post.slug}`}
>
{post.data.title}
</a>
<span class="theme-meta">
{formatBlogDate(
post.data
.pubDate,
)}{" "}
·{" "}
{
post.readingTime
.text
}
</span>
</li>
))}
</ul>
</section>
))}
</div>
</section>
)}
</>
)
}
<footer class="footer fade-in">
<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);
padding-top: calc(var(--space-lg) + 3rem);
}
.container {
max-width: 760px;
width: 100%;
background: var(--color-glass);
backdrop-filter: blur(10px);
border: 1px solid var(--color-surface);
border-radius: 8px;
padding: var(--space-xl);
}
.header {
text-align: center;
margin-bottom: var(--space-lg);
}
.eyebrow {
color: var(--color-text-dim);
text-transform: uppercase;
letter-spacing: 0.24em;
font-size: 0.75rem;
margin-bottom: var(--space-sm);
}
.title {
font-size: 2rem;
font-weight: 700;
letter-spacing: 2px;
}
.divider {
color: var(--color-surface);
text-align: center;
font-size: 0.75rem;
margin: var(--space-md) 0;
user-select: none;
}
.lead {
color: var(--color-text);
line-height: 1.8;
max-width: 40rem;
margin: 0 auto;
}
.section {
margin: var(--space-xl) 0;
}
.section h2 {
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: var(--space-sm);
color: var(--color-accent);
}
.section-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-sm);
margin-bottom: var(--space-sm);
}
.section-head h2 {
margin-bottom: 0;
}
.section-link {
color: var(--color-blue);
font-size: 0.85rem;
white-space: nowrap;
}
.section-link:hover {
color: var(--color-accent-bright);
}
.section-intro {
color: var(--color-text-dim);
line-height: 1.7;
margin-bottom: var(--space-md);
}
.essay-list {
list-style: none;
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.essay-card,
.path-card,
.theme-group {
padding: var(--space-md);
border-radius: 8px;
background: color-mix(in srgb, var(--color-bg-light) 88%, transparent);
border: 1px solid
color-mix(in srgb, var(--color-surface) 70%, transparent);
}
.card-topline {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-sm);
margin-bottom: var(--space-sm);
flex-wrap: wrap;
}
.meta-row {
display: flex;
align-items: center;
gap: var(--space-xs);
color: var(--color-text-dim);
font-size: 0.8rem;
}
.meta-sep {
color: var(--color-surface);
}
.category-pill {
display: inline-flex;
align-items: center;
color: var(--color-accent);
border: 1px solid
color-mix(in srgb, var(--color-surface) 80%, transparent);
padding: 1px 8px;
border-radius: 999px;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.12em;
}
.essay-card h3,
.path-card h3,
.theme-group h3 {
font-size: 1rem;
margin-bottom: var(--space-xs);
color: var(--color-accent-bright);
}
.essay-card p,
.path-card p {
color: var(--color-text-dim);
line-height: 1.7;
}
.essay-link {
display: inline-flex;
margin-top: var(--space-sm);
color: var(--color-blue);
}
.essay-link:hover,
.essay-card h3 a:hover,
.path-card h3 a:hover,
.theme-list a:hover {
color: var(--color-accent-bright);
}
.note {
padding: var(--space-md);
border: 1px solid
color-mix(in srgb, var(--color-accent) 30%, transparent);
background:
linear-gradient(
135deg,
color-mix(in srgb, var(--color-accent) 12%, transparent),
transparent 70%
),
color-mix(in srgb, var(--color-bg-light) 84%, transparent);
border-radius: 8px;
}
.note p {
color: var(--color-text);
line-height: 1.7;
}
.path-list {
list-style: none;
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.path-item {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-md);
align-items: flex-start;
}
.path-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 50%;
border: 1px solid
color-mix(in srgb, var(--color-accent) 35%, transparent);
color: var(--color-accent);
font-size: 0.9rem;
flex-shrink: 0;
}
.path-meta {
margin-top: var(--space-sm);
}
.theme-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-md);
}
.theme-list {
list-style: none;
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.theme-list li {
display: flex;
flex-direction: column;
gap: 2px;
}
.theme-meta {
color: var(--color-text-dim);
font-size: 0.78rem;
}
.footer {
margin-top: var(--space-xl);
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;
}
.fade-in:nth-child(4) {
animation-delay: 0.4s;
}
.fade-in:nth-child(5) {
animation-delay: 0.5s;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 700px) {
.theme-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 600px) {
.container {
padding: var(--space-md);
}
.title {
font-size: 1.5rem;
}
.divider {
font-size: 0.6rem;
}
.lead {
font-size: 0.95rem;
}
.section-head {
flex-direction: column;
align-items: flex-start;
}
.path-item {
grid-template-columns: 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
.matrix-bg {
animation: none;
}
.fade-in {
animation: none;
opacity: 1;
}
}
</style>
-670
View File
@@ -1,670 +0,0 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import projects from "../data/projects.json";
import { getCollection, type CollectionEntry } from "astro:content";
type Project = {
name: string;
description: string;
tags: string[];
status: "stable" | "wip" | "concept";
links: {
site?: string;
gitea?: string;
github?: string;
};
};
const posts = (await getCollection("blog", ({ data }) => !data.draft)).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const recommendedPosts = posts.slice(0, 3);
const typedProjects = projects as Project[];
const highlightedProjectNames = ["GuardDen", "openrabbit", "DevDen"];
const highlightedProjects = highlightedProjectNames
.map((name) => typedProjects.find((project) => project.name === name))
.filter((project): project is Project => Boolean(project));
function formatDate(date: CollectionEntry<"blog">["data"]["pubDate"]) {
return date.toISOString().split("T")[0];
}
function getProjectLink(project: Project) {
return (
project.links.site ||
project.links.gitea ||
project.links.github ||
"/projects"
);
}
function getProjectLinkLabel(project: Project) {
if (project.links.site) return "visit";
if (project.links.gitea) return "gitea";
if (project.links.github) return "github";
return "projects";
}
---
<BaseLayout
title="Start Here - Hidden Den Cafe"
description="A gentle starting point for new visitors to Hidden Den: what this place is, what to read first, and where to explore next."
>
<div class="matrix-bg" aria-hidden="true"></div>
<main class="main">
<div class="container">
<header class="header fade-in">
<p class="eyebrow">New here?</p>
<h1 class="title">Start Here</h1>
<div class="divider">==============================</div>
<p class="lead">
This page is a small orientation point for first-time
visitors. If you have just found Hidden Den, this is the
quickest way to get a feel for what kind of place it is and
where you might want to wander next.
</p>
</header>
<section
class="section fade-in"
aria-labelledby="what-this-place-is"
>
<h2 id="what-this-place-is">What Hidden Den Is</h2>
<p class="desc">
Hidden Den is Latte&apos;s personal corner of the internet:
part writing space, part workshop, part quiet place to think
out loud. It holds projects, experiments,
infrastructure-minded notes, and the kind of personal web
presence that does not need to behave like a brand.
</p>
<p class="desc">
The site leans toward privacy, ownership, and human-scale
spaces online. It is built to feel warm and readable rather
than optimized, noisy, or extractive. More cozy tech wizard
than cyberpunk hacker.
</p>
<div class="note">
<p>
If you want the short version: this is a personal site
for writing, building, self-hosting, and keeping a small
piece of the web genuinely personal.
</p>
</div>
</section>
<section
class="section fade-in"
aria-labelledby="recommended-reading"
>
<div class="section-head">
<h2 id="recommended-reading">Recommended Reading</h2>
<a class="section-link" href="/blog">See all posts</a>
</div>
<p class="desc section-intro">
A few good starting points from the blog. These give a feel
for the den so far without asking you to dig through
everything first.
</p>
{
recommendedPosts.length === 0 ? (
<p class="empty">
The den is still quiet on the writing front.
</p>
) : (
<ul class="reading-list">
{recommendedPosts.map((post) => (
<li class="reading-item">
<article class="content-card">
<div class="meta-row">
<span class="meta-label">
posted
</span>
<time
datetime={formatDate(
post.data.pubDate,
)}
>
{formatDate(post.data.pubDate)}
</time>
</div>
<h3>
<a href={`/blog/${post.slug}`}>
{post.data.title}
</a>
</h3>
<p>{post.data.description}</p>
</article>
</li>
))}
</ul>
)
}
</section>
<section class="section fade-in" aria-labelledby="explore-the-site">
<h2 id="explore-the-site">Explore The Site</h2>
<p class="desc section-intro">
If you would rather browse by section, these are the best
places to continue.
</p>
<div class="explore-grid">
<a class="explore-card" href="/about">
<span class="card-kicker">About</span>
<h3>Meet the person behind the den</h3>
<p>
A fuller introduction to Latte, the philosophy
behind the site, and the kind of internet this place
is trying to make room for.
</p>
</a>
<a class="explore-card" href="/blog">
<span class="card-kicker">Blog</span>
<h3>Read the writing</h3>
<p>
Thoughts, personal pieces, and technical reflections
gathered in one place without feeds, tracking, or
platform noise.
</p>
</a>
<a class="explore-card" href="/projects">
<span class="card-kicker">Projects</span>
<h3>See what is being built</h3>
<p>
Bots, tools, experiments, and den-adjacent systems
that show the practical side of the site.
</p>
</a>
</div>
</section>
<section
class="section fade-in"
aria-labelledby="project-highlights"
>
<div class="section-head">
<h2 id="project-highlights">Projects Worth Seeing</h2>
<a class="section-link" href="/projects"
>Browse all projects</a
>
</div>
<p class="desc section-intro">
A small curated subset, just enough to sketch the shape of
the work without turning this page into a full catalog.
</p>
<ul class="project-list">
{
highlightedProjects.map((project) => (
<li class="project-item">
<article class="content-card project-card">
<div class="project-topline">
<h3>{project.name}</h3>
<span
class={`status status-${project.status}`}
>
{project.status}
</span>
</div>
<p>{project.description}</p>
<div class="project-footer">
<div
class="tag-list"
aria-label={`${project.name} tags`}
>
{project.tags.map((tag) => (
<span class="tag">{tag}</span>
))}
</div>
<a
class="project-link"
href={getProjectLink(project)}
target={
getProjectLink(
project,
).startsWith("http")
? "_blank"
: undefined
}
rel={
getProjectLink(
project,
).startsWith("http")
? "noopener noreferrer"
: undefined
}
>
{getProjectLinkLabel(project)}
</a>
</div>
</article>
</li>
))
}
</ul>
</section>
<footer class="footer fade-in">
<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);
padding-top: calc(var(--space-lg) + 3rem);
}
.container {
max-width: 700px;
width: 100%;
background: var(--color-glass);
backdrop-filter: blur(10px);
border: 1px solid var(--color-surface);
border-radius: 8px;
padding: var(--space-xl);
}
.header {
text-align: center;
margin-bottom: var(--space-lg);
}
.eyebrow {
color: var(--color-text-dim);
text-transform: uppercase;
letter-spacing: 0.24em;
font-size: 0.75rem;
margin-bottom: var(--space-sm);
}
.title {
font-size: 2rem;
font-weight: 700;
letter-spacing: 2px;
}
.divider {
color: var(--color-surface);
text-align: center;
font-size: 0.75rem;
margin: var(--space-md) 0;
user-select: none;
}
.lead {
color: var(--color-text);
line-height: 1.8;
max-width: 38rem;
margin: 0 auto;
}
.section {
margin: var(--space-lg) 0;
}
.section h2 {
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: var(--space-sm);
color: var(--color-accent);
}
.section-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-sm);
margin-bottom: var(--space-sm);
}
.section-head h2 {
margin-bottom: 0;
}
.section-link {
color: var(--color-blue);
font-size: 0.85rem;
white-space: nowrap;
}
.section-link:hover {
color: var(--color-accent-bright);
}
.section-intro {
margin-bottom: var(--space-md);
}
.desc {
color: var(--color-text-dim);
line-height: 1.7;
margin-bottom: var(--space-sm);
}
.desc:last-child {
margin-bottom: 0;
}
.note {
margin-top: var(--space-md);
padding: var(--space-md);
border: 1px solid
color-mix(in srgb, var(--color-accent) 30%, transparent);
background:
linear-gradient(
135deg,
color-mix(in srgb, var(--color-accent) 12%, transparent),
transparent 70%
),
color-mix(in srgb, var(--color-bg-light) 84%, transparent);
border-radius: 8px;
}
.note p {
color: var(--color-text);
line-height: 1.7;
}
.reading-list,
.project-list {
list-style: none;
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.content-card {
padding: var(--space-md);
border-radius: 8px;
background: color-mix(in srgb, var(--color-bg-light) 88%, transparent);
border: 1px solid
color-mix(in srgb, var(--color-surface) 70%, transparent);
}
.content-card h3 {
font-size: 1rem;
margin-bottom: var(--space-xs);
color: var(--color-accent-bright);
}
.content-card p {
color: var(--color-text-dim);
line-height: 1.7;
}
.meta-row {
display: flex;
align-items: center;
gap: var(--space-xs);
color: var(--color-text-dim);
font-size: 0.8rem;
margin-bottom: var(--space-xs);
}
.meta-label {
color: var(--color-accent);
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 0.72rem;
}
.explore-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-md);
}
.explore-card {
display: block;
padding: var(--space-md);
border-radius: 8px;
border: 1px solid
color-mix(in srgb, var(--color-surface) 70%, transparent);
background: color-mix(in srgb, var(--color-bg-light) 88%, transparent);
transition:
border-color 0.2s ease,
transform 0.2s ease,
box-shadow 0.2s ease;
}
.explore-card:hover {
border-color: color-mix(in srgb, var(--color-accent) 55%, transparent);
transform: translateY(-2px);
box-shadow: 0 0 0 1px
color-mix(in srgb, var(--color-accent) 35%, transparent);
}
.card-kicker {
display: inline-block;
color: var(--color-accent);
text-transform: uppercase;
letter-spacing: 0.16em;
font-size: 0.72rem;
margin-bottom: var(--space-xs);
}
.explore-card h3 {
font-size: 0.95rem;
margin-bottom: var(--space-xs);
color: var(--color-accent-bright);
}
.explore-card p {
color: var(--color-text-dim);
line-height: 1.7;
font-size: 0.9rem;
}
.project-card h3 {
margin-bottom: 0;
}
.project-topline {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-sm);
margin-bottom: var(--space-xs);
}
.status {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.12em;
}
.status-stable {
color: var(--color-green);
}
.status-wip {
color: var(--color-peach);
}
.status-concept {
color: var(--color-text-dim);
}
.project-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-sm);
margin-top: var(--space-sm);
flex-wrap: wrap;
}
.tag-list {
display: flex;
gap: var(--space-xs);
flex-wrap: wrap;
}
.tag {
font-size: 0.72rem;
color: var(--color-accent);
border: 1px solid var(--color-surface);
padding: 1px 6px;
border-radius: 999px;
}
.project-link {
color: var(--color-blue);
font-size: 0.85rem;
white-space: nowrap;
}
.project-link:hover {
color: var(--color-accent-bright);
}
.empty {
color: var(--color-text-dim);
font-style: italic;
}
.footer {
margin-top: var(--space-xl);
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;
}
.fade-in:nth-child(4) {
animation-delay: 0.4s;
}
.fade-in:nth-child(5) {
animation-delay: 0.5s;
}
.fade-in:nth-child(6) {
animation-delay: 0.6s;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 700px) {
.explore-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 600px) {
.container {
padding: var(--space-md);
}
.title {
font-size: 1.5rem;
}
.divider {
font-size: 0.6rem;
}
.lead {
font-size: 0.95rem;
}
.section-head {
flex-direction: column;
align-items: flex-start;
}
.project-topline,
.project-footer,
.meta-row {
flex-direction: column;
align-items: flex-start;
}
}
@media (prefers-reduced-motion: reduce) {
.matrix-bg {
animation: none;
}
.fade-in {
animation: none;
opacity: 1;
}
.explore-card {
transition: none;
}
.explore-card:hover {
transform: none;
box-shadow: none;
}
}
</style>