dev #60

Merged
Latte merged 42 commits from dev into main 2026-03-08 09:41:15 +00:00
2 changed files with 617 additions and 0 deletions
Showing only changes of commit 9d929be386 - Show all commits
+1
View File
@@ -3,6 +3,7 @@ const currentPath = Astro.url.pathname;
const links = [
{ href: "/", label: "home" },
{ href: "/start", label: "start" },
{ href: "/about", label: "about" },
{ href: "/projects", label: "projects" },
{ href: "/now", label: "now" },
+616
View File
@@ -0,0 +1,616 @@
---
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.date.valueOf() - a.data.date.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"]["date"]) {
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.date)}>
{formatDate(post.data.date)}
</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>