Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions apps/site/src/components/Header.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
---
import NorthGuildMark from "./NorthGuildMark.astro";

interface Props {
/** Plays the aurora entrance (`.ng-enter-pane`, Base.astro). */
entrance?: boolean;
}
const { entrance = false } = Astro.props;

const links = [
{
label: "NorthGuild on GitHub",
Expand All @@ -13,8 +21,10 @@ const links = [
];
---

<header class="gmt-glass site-header">
<a class="wordmark" href="/">NorthGuild</a>
<header
class:list={["gmt-glass", "site-header", { "ng-enter-pane": entrance }]}
>
<div class="wordmark"><NorthGuildMark />NorthGuild</div>
<nav aria-label="NorthGuild community">
{
links.map((l) => (
Expand Down Expand Up @@ -56,15 +66,14 @@ const links = [
display: none;
}
.wordmark {
display: inline-flex;
align-items: center;
/* Matches the space either side of the GMT header's divider. */
gap: var(--gmt-space-6);
font-family: var(--gmt-font-display);
color: var(--gmt-ice);
text-decoration: none;
letter-spacing: 0.04em;
}
.wordmark:focus-visible {
outline: 2px solid var(--gmt-cyan);
outline-offset: 4px;
}
nav {
display: flex;
gap: 1rem;
Expand Down
133 changes: 133 additions & 0 deletions apps/site/src/components/NorthGuildMark.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
/**
* The NorthGuild shield, drawn as frost on glass rather than flat fills.
*
* The shapes are the GMT docs mark (gmt `apps/dox/src/assets/northguild-logo.svg`):
* the shield, its lit left half, the mountain field and the `</>`. Here it is
* kept quiet to sit in the night sky: everything is the wordmark's ice tone at
* low opacity, the panes are washes that fade out towards the point, and only
* the shield's edge, the mountain ridge and the `</>` are drawn as lines.
*
* Inline, not an `<img>`, so the theme tokens reach every path. `aria-hidden`:
* the wordmark beside it already reads "NorthGuild". The gradient ids are
* fixed, so render this once per page.
*/
---

<svg
class="ng-mark"
viewBox="0 0 1000 1317"
aria-hidden="true"
focusable="false"
>
<defs>
<linearGradient id="ng-mark-pane" x1="0" y1="0" x2="0" y2="1">
<stop class="pane-top" offset="0"></stop>
<stop class="pane-bottom" offset="1"></stop>
</linearGradient>
<linearGradient id="ng-mark-sheen" x1="0" y1="0" x2="1" y2="1">
<stop class="sheen-top" offset="0"></stop>
<stop class="sheen-bottom" offset="0.7"></stop>
</linearGradient>
</defs>
<!-- Back to front: the panes of ice, then the lines drawn over them. -->
<path
class="pane"
d="M500 0 L0 179.1 L0 709.1 C0 1018.6 373.1 1206.2 500 1317 C626.9 1206.2 1000 1018.6 1000 709.1 L1000 179.1 Z"
></path>
<path
class="sheen"
d="M500 0 L0 179.1 L0 709.1 C0 1018.6 373.1 1206.2 500 1317 Z"></path>
<path
class="pane"
d="M93.3 243.9 L500 99 L906.7 243.9 L906.7 686.5 L709.3 390.2 L654.9 470.3 L500 239.5 L345.1 470.3 L290.7 390.2 L93.3 686.5 Z"
></path>
<path
class="edge"
d="M500 0 L0 179.1 L0 709.1 C0 1018.6 373.1 1206.2 500 1317 C626.9 1206.2 1000 1018.6 1000 709.1 L1000 179.1 Z"
></path>
<path
class="ridge"
d="M93.3 686.5 L290.7 390.2 L345.1 470.3 L500 239.5 L654.9 470.3 L709.3 390.2 L906.7 686.5"
></path>
<path
class="glyph"
d="M366 640 L212 736.5 L366 833 M634 640 L788 736.5 L634 833 M560 560 L443 910"
></path>
</svg>

<style>
.ng-mark {
display: block;
block-size: 1.75rem;
inline-size: auto;
overflow: visible;
}

/* Faintly lighter at the top, clear at the point. */
.pane-top {
stop-color: var(--gmt-ice);
stop-opacity: 0.07;
}
.pane-bottom {
stop-color: var(--gmt-ice);
stop-opacity: 0;
}
.sheen-top {
stop-color: var(--gmt-ice);
stop-opacity: 0.05;
}
.sheen-bottom {
stop-color: var(--gmt-ice);
stop-opacity: 0;
}
.pane {
fill: url(#ng-mark-pane);
}
.sheen {
fill: url(#ng-mark-sheen);
}

.edge,
.ridge,
.glyph {
fill: none;
stroke: var(--gmt-ice);
stroke-linejoin: round;
stroke-linecap: round;
}
/* Hairlines stay one pixel at any size, like the borders around them. */
.edge,
.ridge {
vector-effect: non-scaling-stroke;
stroke-width: 1px;
}
.edge {
stroke-opacity: 0.5;
}
.ridge {
stroke-opacity: 0.35;
}
/* In viewBox units, so the glyph keeps its weight against the shield. */
.glyph {
stroke-opacity: 0.75;
stroke-width: 36;
}

/* System colours: drop the washes, keep every line at full strength. */
@media (forced-colors: active) {
.ng-mark {
forced-color-adjust: none;
}
.pane,
.sheen {
fill: none;
}
.edge,
.ridge,
.glyph {
stroke: CanvasText;
stroke-opacity: 1;
}
}
</style>
6 changes: 4 additions & 2 deletions apps/site/src/components/RepoCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ interface Props {
description: string;
docsHref: string;
githubHref: string;
/** Position in the list, for the page's staggered entrance. */
order?: number;
}
const { name, description, docsHref, githubHref } = Astro.props;
const { name, description, docsHref, githubHref, order = 0 } = Astro.props;
---

<article class="gmt-glass repo-card">
<article class="gmt-glass repo-card ng-enter-pane" style={`--order: ${order}`}>
<h2>{name}</h2>
<p>{description}</p>
<div class="links">
Expand Down
107 changes: 104 additions & 3 deletions apps/site/src/layouts/Base.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ import Header from "../components/Header.astro";
interface Props {
title: string;
description?: string;
/** Plays the aurora entrance on the header. The page's own blocks opt in
with `.ng-enter-*`, so a page that sets this should stagger them after
it. */
entrance?: boolean;
}
const { title, description = "NorthGuild: open-source tools for developers." } =
Astro.props;
const {
title,
description = "NorthGuild: open-source tools for developers.",
entrance = false,
} = Astro.props;
---

<!doctype html>
Expand All @@ -30,7 +37,7 @@ const { title, description = "NorthGuild: open-source tools for developers." } =
/>
</head>
<body>
<Header />
<Header entrance={entrance} />
<main>
<slot />
</main>
Expand Down Expand Up @@ -73,4 +80,98 @@ const { title, description = "NorthGuild: open-source tools for developers." } =
a {
color: var(--gmt-cyan);
}
/* ---- Entrance ----
* Each block condenses out of the sky like a curtain of aurora drifting
* across: a soft-edged band of light passes left to right, the blur clears
* behind it, and the green-to-cyan tint it carries settles into the usual
* ice. Nothing moves; it only comes into focus. `.ng-enter-text` is for type,
* `.ng-enter-pane` for glass (it tints the border and glow instead). Set
* `--ng-delay` to stagger.
*
* `backwards` only: once it ends every property falls back to the normal
* style, so no filter or mask is left on the glass. The last keyframe is
* that normal look, so there is no jump. */
.ng-enter-text,
.ng-enter-pane {
--ng-aurora: color-mix(in oklab, var(--gmt-spring) 45%, var(--gmt-cyan));
--ng-sweep: linear-gradient(100deg, #000 38%, transparent 62%);
mask-size: 300% 100%;
/* Let the blur and glow spill past the box instead of stopping at a hard
edge: the mask repeats up and down and paints outside the box. */
mask-repeat: repeat-y;
mask-clip: no-clip;
/* Longhands: the build's minifier turns this shorthand into
`animation: none`. */
animation-duration: 2.4s;
animation-timing-function: cubic-bezier(0.25, 0.6, 0.2, 1);
animation-fill-mode: backwards;
animation-delay: var(--ng-delay, 0s);
}
.ng-enter-text {
animation-name: ng-aurora-text;
}
.ng-enter-pane {
animation-name: ng-aurora-pane;
}

/* The clear box-shadow is never seen. Chrome only paints a mask over the
box and its shadows, so it gives the text's blur and glow room to fade
out instead of stopping at the box's edge. */
@keyframes ng-aurora-text {
0% {
mask-image: var(--ng-sweep);
mask-position: 100% 0;
filter: blur(12px);
color: var(--ng-aurora);
text-shadow: 0 0 24px color(from var(--ng-aurora) srgb r g b / 0.7);
box-shadow: 0 0 48px 32px transparent;
}
45% {
filter: blur(3px);
color: var(--ng-aurora);
text-shadow: 0 0 18px color(from var(--ng-aurora) srgb r g b / 0.45);
}
100% {
mask-image: var(--ng-sweep);
mask-position: 0% 0;
filter: blur(0);
text-shadow: 0 0 0 transparent;
box-shadow: 0 0 48px 32px transparent;
}
}

@keyframes ng-aurora-pane {
0% {
mask-image: var(--ng-sweep);
mask-position: 100% 0;
filter: blur(10px);
border-color: color(from var(--ng-aurora) srgb r g b / 0.6);
box-shadow: 0 0 40px -6px color(from var(--ng-aurora) srgb r g b / 0.4);
}
45% {
filter: blur(2px);
border-color: color(from var(--ng-aurora) srgb r g b / 0.4);
box-shadow: 0 0 32px -6px color(from var(--ng-aurora) srgb r g b / 0.25);
}
100% {
mask-image: var(--ng-sweep);
mask-position: 0% 0;
filter: blur(0);
}
}

/* Reduced motion: no drifting light or blur, just a short fade. */
@media (prefers-reduced-motion: reduce) {
.ng-enter-text,
.ng-enter-pane {
animation-name: ng-fade;
animation-duration: 0.6s;
animation-delay: 0s;
}
}
@keyframes ng-fade {
from {
opacity: 0;
}
}
</style>
32 changes: 28 additions & 4 deletions apps/site/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,44 @@ const repos = [
];
---

<Base title="NorthGuild">
<Base title="NorthGuild" entrance>
<Aurora />
<h1>NorthGuild</h1>
<p class="intro">Open-source tools for developers, built in the frigid north.</p>
<h1 class="ng-enter-text">NorthGuild</h1>
<p class="intro ng-enter-text">Open-source tools for developers, built in the frigid north.</p>
<div class="grid">
{repos.map((r) => <RepoCard {...r} />)}
{repos.map((r, i) => <RepoCard {...r} order={i} />)}
</div>
</Base>

<style>
/* The sky only answers the pointer over bare page (Aurora.astro,
`isBareSky`), so each block is only as wide as its own text, and the grid
lets the pointer through its gaps to the sky behind. */
h1,
.intro {
inline-size: fit-content;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
gap: 1rem;
margin-top: 2rem;
pointer-events: none;
}
.grid > :global(*) {
pointer-events: auto;
}

/* The entrance (`.ng-enter-*`, Base.astro): the header leads, then the
title, the line under it, and the cards in turn, landing as the sky's own
fade-in (1.6s) finishes. */
h1 {
--ng-delay: 0.25s;
}
.intro {
--ng-delay: 0.55s;
}
.grid > :global(*) {
--ng-delay: calc(0.9s + var(--order, 0) * 0.25s);
}
</style>
Loading