jb-website/src/pages/index.astro

240 lines
6.5 KiB
Text

---
import BaseLayout from "~/layouts/BaseLayout.astro";
import Eyebrow from "~/components/atoms/Eyebrow.astro";
import Mark from "~/components/atoms/Mark.astro";
import LatestLine from "~/components/molecules/LatestLine.astro";
import IndexRow from "~/components/molecules/IndexRow.astro";
import HeroZone from "~/components/islands/HeroZone";
import { blog, art, code } from "~/lib/urls";
import { getCollection } from "astro:content";
// Pull the most recent published post for the "selected writing" line.
const posts = await getCollection("posts", ({ data }) => !data.draft);
posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
const latest = posts[0];
const dateFmt = new Intl.DateTimeFormat("en-AU", {
month: "short",
year: "numeric",
});
// Subdomain index. Each row points at the right destination. Some entries are
// out-of-repo containers; flagging external just signals intent for now (rel attrs).
const indexItems = [
{ num: "01", name: "Blog", href: blog("/"), desc: "Writing", external: false, soon: false },
{ num: "02", name: "Code", href: code("/"), desc: "Experiments & tools", external: false, soon: false },
{ num: "03", name: "Art", href: art("/"), desc: "Generative & physical", external: false, soon: false },
{ num: "04", name: "Signage", href: undefined, desc: undefined, external: true, soon: true },
{ num: "05", name: "Keycaps", href: undefined, desc: undefined, external: true, soon: true },
{ num: "06", name: "Social", href: undefined, desc: undefined, external: true, soon: true },
];
---
<BaseLayout title="Josh Bairstow" current="home" showCoordInTopbar={true}>
<main class="home" data-layout="stack">
<section class="intro">
<Mark kind="ringdots" width={84} top="-10px" left="-24px" rotate={-5} />
<Eyebrow>engineer errant · Sydney</Eyebrow>
<h2 class="statement">
Deliberate work.<br /><em>Restless inquiry</em><span class="accent">.</span>
</h2>
<p class="lead">
Software, pondering, and the occasional ink mark in my quiet corner of the internet.
</p>
</section>
<section class="hero">
<Mark kind="dots" width={52} top="-16px" right="-12px" />
<Mark kind="pipes" width={28} bottom="-8px" left="-6px" rotate={8} />
<HeroZone client:load />
</section>
{
latest && (
<section class="latest">
<Eyebrow>Selected writing</Eyebrow>
<LatestLine
title={latest.data.title}
href={blog(`/posts/${latest.id}/`)}
date={dateFmt.format(latest.data.date)}
/>
</section>
)
}
<section class="index">
<Mark kind="concentric" width={42} bottom="-14px" right="-10px" />
<Eyebrow>Index</Eyebrow>
<nav>
{indexItems.map((item) => (
<IndexRow
num={item.num}
name={item.name}
href={item.href}
desc={item.desc}
soon={item.soon}
external={item.external}
/>
))}
</nav>
</section>
</main>
</BaseLayout>
<style>
/* Stack layout — editorial, centered. The canonical landing per docs/planning.md. */
.home {
display: grid;
grid-template-columns: minmax(0, 880px);
grid-template-areas: "intro" "hero" "latest" "index";
justify-content: center;
gap: clamp(28px, 4.6vh, 52px);
align-content: start;
padding-top: clamp(40px, 7vh, 96px);
text-align: center;
flex: 1;
}
.intro {
grid-area: intro;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.intro :global(.eyebrow) {
margin-bottom: clamp(18px, 3.4vh, 30px);
}
.statement {
font-family: var(--font-display);
font-weight: 500;
font-size: clamp(40px, 5.4vw, 82px);
line-height: 0.97;
letter-spacing: -0.02em;
margin: 0;
color: var(--fg-on-dark-1);
text-wrap: balance;
}
:global(body:not(.dark)) .statement {
color: var(--fg-1);
}
.statement em {
font-style: italic;
font-weight: 400;
}
.statement .accent {
color: var(--accent-deep);
}
.lead {
font-family: var(--font-body);
font-weight: 300;
font-size: clamp(15px, 1.45vw, 18px);
line-height: 1.6;
color: var(--fg-on-dark-2);
max-width: 56ch;
margin: clamp(20px, 3.6vh, 32px) auto 0;
text-align: center;
}
:global(body:not(.dark)) .lead {
color: var(--fg-2);
}
.hero {
grid-area: hero;
position: relative;
display: flex;
}
/* Styles for .hzframe + its children come from the React island's class names */
.hero :global(.hzframe) {
position: relative;
width: 100%;
overflow: hidden;
border-radius: var(--radius-cutout);
background: radial-gradient(
120% 95% at 64% 18%,
#c9b299 0%,
#8a6a47 30%,
#4a3826 60%,
#221e18 92%
);
box-shadow: var(--shadow-2);
cursor: crosshair;
transition: background 700ms var(--ease-out);
aspect-ratio: 16 / 7;
}
.hero :global(.hzframe .grain) {
position: absolute;
inset: 0;
background-image: url(/assets/texture-grain.svg);
background-size: 220px;
opacity: 0.12;
mix-blend-mode: overlay;
pointer-events: none;
}
.hero :global(.hzframe .hzaccent) {
position: absolute;
right: 9%;
top: 12%;
width: 76px;
opacity: 0.18;
filter: invert(1);
pointer-events: none;
transition: transform 900ms var(--ease-out);
}
.hero :global(.hzframe .hzwm) {
position: absolute;
left: 8%;
bottom: 9%;
width: 128px;
opacity: 0.1;
filter: invert(1);
pointer-events: none;
}
.hero :global(.hzframe .hztag) {
position: absolute;
right: 8%;
bottom: 8%;
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: rgba(239, 231, 218, 0.5);
}
.latest {
grid-area: latest;
max-width: 640px;
margin: 0 auto;
width: 100%;
text-align: left;
}
.latest :global(.eyebrow) {
margin-bottom: 14px;
}
.index {
grid-area: index;
position: relative;
text-align: left;
}
.index :global(.eyebrow) {
margin-bottom: 4px;
}
@media (max-width: 860px) {
.home {
grid-template-columns: 1fr;
text-align: left;
}
.intro {
align-items: flex-start;
}
.lead {
text-align: left;
margin-left: 0;
margin-right: 0;
}
.hero :global(.hzframe) {
aspect-ratio: 16 / 9;
}
}
</style>