dev #60
@@ -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" },
|
||||
|
||||
@@ -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'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>
|
||||
Reference in New Issue
Block a user