240 lines
6.5 KiB
Text
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>
|