@cratis/cast 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/badges/RegistryMember.ts +16 -0
- package/badges/ResolvedTier.ts +17 -0
- package/badges/Tier.ts +14 -0
- package/badges/TierSource.ts +14 -0
- package/badges/badgeSvg.ts +137 -0
- package/badges/constants.ts +17 -0
- package/badges/cratisMark.ts +15 -0
- package/badges/index.ts +12 -0
- package/badges/registry.ts +63 -0
- package/badges/resolveTier.ts +24 -0
- package/cast.schema.json +1113 -0
- package/config/CastConfig.ts +17 -0
- package/config/CastConfigError.ts +10 -0
- package/config/LiveStat.ts +17 -0
- package/config/ResolvedSection.ts +13 -0
- package/config/SectionId.ts +21 -0
- package/config/ThemeMode.ts +9 -0
- package/config/castSchema.ts +262 -0
- package/config/index.ts +12 -0
- package/config/parseCast.ts +58 -0
- package/config/resolveSections.ts +48 -0
- package/github/GitHubSnapshot.ts +66 -0
- package/github/fetchGitHub.ts +235 -0
- package/github/parse.ts +63 -0
- package/index.ts +88 -0
- package/integration/CastData.ts +20 -0
- package/integration/CastOptions.ts +17 -0
- package/integration/Snapshot.ts +12 -0
- package/integration/loadRegistry.ts +37 -0
- package/integration/resolveData.ts +138 -0
- package/integration/snapshotFile.ts +23 -0
- package/integration/virtualModule.ts +43 -0
- package/machine/castJson.ts +64 -0
- package/machine/llmsText.ts +69 -0
- package/machine/ogImageSvg.ts +79 -0
- package/machine/resumeJson.ts +87 -0
- package/package.json +76 -8
- package/registry/members.yaml +16 -0
- package/routes/404.astro +40 -0
- package/routes/apple-touch-icon.png.ts +17 -0
- package/routes/badge-card.svg.ts +13 -0
- package/routes/badge.svg.ts +11 -0
- package/routes/cast.json.ts +11 -0
- package/routes/cv.astro +371 -0
- package/routes/favicon.svg.ts +16 -0
- package/routes/index.astro +61 -0
- package/routes/llms.txt.ts +10 -0
- package/routes/og.png.ts +23 -0
- package/routes/resume.json.ts +13 -0
- package/routes/robots.txt.ts +10 -0
- package/site/components/Activity.astro +194 -0
- package/site/components/Agentic.astro +143 -0
- package/site/components/CareerReplay.astro +412 -0
- package/site/components/CastBadge.astro +132 -0
- package/site/components/CommandPalette.astro +116 -0
- package/site/components/Contact.astro +163 -0
- package/site/components/Cratis.astro +233 -0
- package/site/components/CratisMark.astro +18 -0
- package/site/components/EventModel.astro +262 -0
- package/site/components/Footer.astro +28 -0
- package/site/components/Hero.astro +208 -0
- package/site/components/Monogram.astro +25 -0
- package/site/components/Nav.astro +124 -0
- package/site/components/Now.astro +113 -0
- package/site/components/Projects.astro +188 -0
- package/site/components/PullRequestFeed.astro +86 -0
- package/site/components/SectionHeader.astro +27 -0
- package/site/components/Skills.astro +41 -0
- package/site/components/Stats.astro +101 -0
- package/site/components/Talks.astro +73 -0
- package/site/components/Terminal.astro +159 -0
- package/site/components/Writing.astro +126 -0
- package/site/data.ts +48 -0
- package/site/layouts/Base.astro +144 -0
- package/site/lib/accent.ts +52 -0
- package/site/lib/blog.ts +32 -0
- package/site/lib/career.ts +148 -0
- package/site/lib/clipboard.ts +12 -0
- package/site/lib/contributions.ts +63 -0
- package/site/lib/github.ts +55 -0
- package/site/lib/inline.ts +23 -0
- package/site/lib/lifecycle.ts +44 -0
- package/site/lib/motion.ts +43 -0
- package/site/lib/nav.ts +38 -0
- package/site/lib/reltime.ts +33 -0
- package/site/lib/safeJson.ts +15 -0
- package/site/lib/sections.ts +41 -0
- package/site/lib/theme.ts +28 -0
- package/site/scripts/ambient.ts +93 -0
- package/site/scripts/event-board.ts +403 -0
- package/site/scripts/event-model.ts +155 -0
- package/site/scripts/heatmap.ts +94 -0
- package/site/scripts/live.ts +145 -0
- package/site/scripts/palette.ts +296 -0
- package/site/scripts/replay.ts +251 -0
- package/site/scripts/terminal.ts +556 -0
- package/site/styles/global.css +508 -0
- package/virtual.d.ts +15 -0
- package/writing/WritingSnapshot.ts +20 -0
- package/writing/fetchWriting.ts +54 -0
- package/writing/parse.ts +72 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
3
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
4
|
+
|
|
5
|
+
import { person, profileLinks } from '../data';
|
|
6
|
+
import { renderInline } from '../lib/inline';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
index: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
intro?: string;
|
|
13
|
+
}
|
|
14
|
+
const { index, title = 'Append an event to *my stream.*', label = 'Command', intro = 'Questions about event sourcing, Cratis, a project or a role? Issue the command and it lands in my inbox.' } = Astro.props;
|
|
15
|
+
const mailto = person.email ? `mailto:${person.email}` : undefined;
|
|
16
|
+
const channels = [person.email ? 'email' : undefined, ...profileLinks.slice(0, 3).map((link) => link.label)].filter(Boolean).join(' · ');
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<section id="contact" class="relative overflow-hidden py-24 md:py-40" aria-labelledby="contact-title">
|
|
20
|
+
<div class="pointer-events-none absolute inset-0" aria-hidden="true">
|
|
21
|
+
<div class="absolute bottom-0 left-1/2 h-[30rem] w-[70rem] -translate-x-1/2 translate-y-1/2 rounded-full bg-accent/15 blur-[120px]"></div>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="container-x relative">
|
|
24
|
+
<p class="mb-5 flex items-center gap-3 font-mono text-xs text-fg-faint" data-reveal>
|
|
25
|
+
{index && <span class="text-fg-muted">{index}</span>}{index && <span class="h-px w-8 bg-line-strong" aria-hidden="true"></span>}<span class="note note-command">{label}</span>
|
|
26
|
+
</p>
|
|
27
|
+
<h2 id="contact-title" class="max-w-[14ch] text-[clamp(2.75rem,7.5vw,6.5rem)] leading-[0.92] font-[800] tracking-[-0.045em]" data-reveal set:html={renderInline(title)} />
|
|
28
|
+
<p class="mt-6 max-w-[52ch] text-lg text-fg-muted" data-reveal set:html={renderInline(intro)} />
|
|
29
|
+
|
|
30
|
+
<div class="mt-14 grid grid-cols-1 items-center gap-6 lg:grid-cols-[1fr_auto_1fr]" data-reveal data-contact>
|
|
31
|
+
<!-- Command sticky note -->
|
|
32
|
+
<div class="sticky-card cmd">
|
|
33
|
+
<p class="flex items-center justify-between font-mono text-xs">
|
|
34
|
+
<span class="note note-command">Command</span>
|
|
35
|
+
<span class="text-fg-faint">{person.handle}/inbox</span>
|
|
36
|
+
</p>
|
|
37
|
+
<p class="mt-4 font-display text-4xl font-[680] tracking-[-0.03em] text-command-soft">SendMessage</p>
|
|
38
|
+
<dl class="mt-5 grid grid-cols-[5.5rem_1fr] gap-y-2 font-mono text-[0.8125rem]">
|
|
39
|
+
<dt class="text-fg-faint">to</dt><dd class="truncate text-fg">{person.email ?? person.name}</dd>
|
|
40
|
+
<dt class="text-fg-faint">from</dt><dd class="text-fg-muted">you</dd>
|
|
41
|
+
<dt class="text-fg-faint">channel</dt><dd class="text-fg-muted">{channels}</dd>
|
|
42
|
+
</dl>
|
|
43
|
+
<button type="button" class="btn mt-6 w-full border-command/40 bg-command/15 text-command-soft hover:bg-command/25" data-send aria-expanded="false" aria-controls="contact-result" hidden>
|
|
44
|
+
Execute command
|
|
45
|
+
<span class="font-mono text-xs opacity-70" aria-hidden="true">↵</span>
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<svg viewBox="0 0 80 24" class="arrow mx-auto hidden h-6 w-20 text-fg-faint lg:block" fill="none" aria-hidden="true">
|
|
50
|
+
<path d="M2 12h72m-8-7 8 7-8 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4 5"></path>
|
|
51
|
+
</svg>
|
|
52
|
+
|
|
53
|
+
<!-- Resulting event -->
|
|
54
|
+
<div id="contact-result" class="sticky-card evt" data-result>
|
|
55
|
+
<p class="flex items-center justify-between font-mono text-xs">
|
|
56
|
+
<span class="note">Event</span>
|
|
57
|
+
<span class="text-fg-faint" data-result-seq>pending</span>
|
|
58
|
+
</p>
|
|
59
|
+
<p class="mt-4 font-display text-4xl font-[680] tracking-[-0.03em] text-event-soft">MessageRequested</p>
|
|
60
|
+
{person.email && <div class="mt-6 flex flex-wrap gap-2.5">
|
|
61
|
+
<a href={mailto} class="btn btn-primary">
|
|
62
|
+
<svg viewBox="0 0 16 16" class="size-4" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><rect x="2" y="3.5" width="12" height="9" rx="1.5"></rect><path d="m2.5 4.5 5.5 4 5.5-4" stroke-linejoin="round"></path></svg>
|
|
63
|
+
Write an email
|
|
64
|
+
</a>
|
|
65
|
+
<span class="relative inline-flex">
|
|
66
|
+
<button type="button" class="btn" data-copy={person.email} aria-label={`Copy email address ${person.email}`}>
|
|
67
|
+
<svg viewBox="0 0 16 16" class="size-4" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="5" y="5" width="9" height="9" rx="2"></rect><path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5"></path></svg>
|
|
68
|
+
<span data-copy-label>Copy email</span>
|
|
69
|
+
</button>
|
|
70
|
+
<span class="sr-only" aria-live="polite" data-copy-status></span>
|
|
71
|
+
</span>
|
|
72
|
+
</div>}
|
|
73
|
+
<div class="mt-3 flex flex-wrap gap-2.5">
|
|
74
|
+
{profileLinks.map((link) => <a href={link.url} class="btn btn-sm" rel="me noopener">{link.label}</a>)}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
|
|
81
|
+
<style>
|
|
82
|
+
.sticky-card {
|
|
83
|
+
position: relative;
|
|
84
|
+
border-radius: 1.25rem;
|
|
85
|
+
padding: 1.5rem;
|
|
86
|
+
border: 1px solid var(--color-line);
|
|
87
|
+
background: color-mix(in srgb, var(--color-ink-900) 80%, transparent);
|
|
88
|
+
backdrop-filter: blur(12px);
|
|
89
|
+
transition:
|
|
90
|
+
opacity 700ms var(--ease-out-expo),
|
|
91
|
+
transform 700ms var(--ease-out-expo),
|
|
92
|
+
filter 700ms var(--ease-out-expo);
|
|
93
|
+
}
|
|
94
|
+
@media (min-width: 768px) {
|
|
95
|
+
.sticky-card {
|
|
96
|
+
padding: 2rem;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
.cmd {
|
|
100
|
+
border-color: rgb(91 134 242 / 0.3);
|
|
101
|
+
background:
|
|
102
|
+
radial-gradient(120% 100% at 0% 0%, rgb(91 134 242 / 0.16), transparent 60%),
|
|
103
|
+
color-mix(in srgb, var(--color-ink-900) 80%, transparent);
|
|
104
|
+
box-shadow: 0 30px 80px -40px rgb(91 134 242 / 0.5);
|
|
105
|
+
}
|
|
106
|
+
.evt {
|
|
107
|
+
border-color: rgb(242 153 74 / 0.3);
|
|
108
|
+
background:
|
|
109
|
+
radial-gradient(120% 100% at 100% 0%, rgb(242 153 74 / 0.16), transparent 60%),
|
|
110
|
+
color-mix(in srgb, var(--color-ink-900) 80%, transparent);
|
|
111
|
+
box-shadow: 0 30px 80px -40px rgb(242 153 74 / 0.5);
|
|
112
|
+
}
|
|
113
|
+
/* Before the command executes, the event is a ghost. */
|
|
114
|
+
[data-armed] .evt {
|
|
115
|
+
opacity: 0.35;
|
|
116
|
+
filter: blur(1.5px) saturate(0.3);
|
|
117
|
+
transform: translateX(-12px) scale(0.97);
|
|
118
|
+
pointer-events: none;
|
|
119
|
+
border-style: dashed;
|
|
120
|
+
}
|
|
121
|
+
[data-armed] .arrow {
|
|
122
|
+
opacity: 0.4;
|
|
123
|
+
}
|
|
124
|
+
.arrow {
|
|
125
|
+
transition: opacity 600ms ease, color 600ms ease;
|
|
126
|
+
}
|
|
127
|
+
[data-sent] .arrow {
|
|
128
|
+
color: var(--color-event);
|
|
129
|
+
}
|
|
130
|
+
:global(.js-motion) [data-sent] .arrow path {
|
|
131
|
+
animation: dash 1.2s linear infinite;
|
|
132
|
+
}
|
|
133
|
+
@keyframes dash {
|
|
134
|
+
to {
|
|
135
|
+
stroke-dashoffset: -18;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
139
|
+
|
|
140
|
+
<script>
|
|
141
|
+
import { onPageLoad } from '../lib/lifecycle';
|
|
142
|
+
onPageLoad(() => {
|
|
143
|
+
const root = document.querySelector<HTMLElement>('[data-contact]');
|
|
144
|
+
const send = root?.querySelector<HTMLButtonElement>('[data-send]');
|
|
145
|
+
const result = root?.querySelector<HTMLElement>('[data-result]');
|
|
146
|
+
if (root && send && result) {
|
|
147
|
+
send.hidden = false;
|
|
148
|
+
root.toggleAttribute('data-armed', true);
|
|
149
|
+
result.setAttribute('inert', '');
|
|
150
|
+
send.addEventListener('click', () => {
|
|
151
|
+
root.removeAttribute('data-armed');
|
|
152
|
+
root.setAttribute('data-sent', '');
|
|
153
|
+
result.removeAttribute('inert');
|
|
154
|
+
send.setAttribute('aria-expanded', 'true');
|
|
155
|
+
send.disabled = true;
|
|
156
|
+
send.textContent = 'Command accepted';
|
|
157
|
+
const seq = root.querySelector('[data-result-seq]');
|
|
158
|
+
if (seq) seq.textContent = 'appended';
|
|
159
|
+
result.querySelector<HTMLElement>('a')?.focus({ preventScroll: true });
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
</script>
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
3
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
4
|
+
|
|
5
|
+
import { cratis as cratisData } from '../data';
|
|
6
|
+
import SectionHeader from './SectionHeader.astro';
|
|
7
|
+
import PullRequestFeed from './PullRequestFeed.astro';
|
|
8
|
+
import { cratisRepos, mergedPrFloor, mergedPrTotal, snapshotDate, starsFor, type CratisRepo } from '../lib/github';
|
|
9
|
+
import { renderInline } from '../lib/inline';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
index: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
}
|
|
16
|
+
const cratis = cratisData!;
|
|
17
|
+
const { index, title = cratis.title, label = cratis.label } = Astro.props;
|
|
18
|
+
|
|
19
|
+
const fmt = new Intl.NumberFormat('en-US');
|
|
20
|
+
const byName = new Map(cratisRepos.map((r) => [r.name, r]));
|
|
21
|
+
const pipeline = cratis.pipeline.map((n) => byName.get(n)).filter((r): r is CratisRepo => !!r);
|
|
22
|
+
const tooling = cratisRepos.filter((r) => r.group === 'tooling');
|
|
23
|
+
const clients = cratisRepos.filter((r) => r.group === 'client');
|
|
24
|
+
const asOf = snapshotDate();
|
|
25
|
+
|
|
26
|
+
const rankOne = cratisRepos.filter((r) => r.rank === 1).length;
|
|
27
|
+
const founded = cratis.repos.filter((r) => r.founded);
|
|
28
|
+
// Only facts that hold: each figure appears when the data supports it.
|
|
29
|
+
const headline = [
|
|
30
|
+
...(rankOne > 0 ? [{ value: `#1`, label: `committer on ${rankOne} Cratis ${rankOne === 1 ? 'repository' : 'repositories'}`, accent: 'text-command', live: false }] : []),
|
|
31
|
+
...(founded.length > 0 ? [{ value: String(founded.length), label: `${founded.length === 1 ? 'repository' : 'repositories'} founded: ${founded.map((r) => r.name).join(', ')}`, accent: 'text-event', live: false }] : []),
|
|
32
|
+
...(cratis.clientLanguages.length > 0 ? [{ value: String(cratis.clientLanguages.length), label: `Chronicle client languages: ${cratis.clientLanguages.join(', ')}`, accent: 'text-view', live: false }] : []),
|
|
33
|
+
...(mergedPrTotal > 0 ? [{ value: `${fmt.format(mergedPrFloor)}${mergedPrTotal >= 100 ? '+' : ''}`, label: 'merged PRs in the Cratis org', accent: 'text-fg', live: true }] : []),
|
|
34
|
+
];
|
|
35
|
+
const badge = (r: CratisRepo) => (r.founded ? 'b-founded' : r.rank === 1 ? 'b-lead' : 'b-muted');
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
<section id="cratis" class="relative overflow-hidden py-24 md:py-36" aria-labelledby="cratis-title">
|
|
39
|
+
<div class="pointer-events-none absolute top-1/3 -right-40 size-[40rem] rounded-full bg-command/10 blur-[120px]" aria-hidden="true"></div>
|
|
40
|
+
<div class="container-x relative">
|
|
41
|
+
<SectionHeader index={index} kind="command" label={label} title={title} id="cratis-title">
|
|
42
|
+
<div class="mt-6 grid max-w-[64ch] gap-4 text-lg leading-relaxed">
|
|
43
|
+
{cratis.pitch && <p class="text-fg" set:html={renderInline(cratis.pitch)} />}
|
|
44
|
+
<p class="text-fg-muted" set:html={renderInline(cratis.role)} />
|
|
45
|
+
</div>
|
|
46
|
+
<div class="mt-6 flex flex-wrap gap-3">
|
|
47
|
+
{Object.entries(cratis.links).map(([text, href]) => <a href={href} class="btn btn-sm" rel="noopener">{text} <span aria-hidden="true">↗</span></a>)}
|
|
48
|
+
</div>
|
|
49
|
+
</SectionHeader>
|
|
50
|
+
|
|
51
|
+
{headline.length > 0 && <dl class:list={['grid grid-cols-2 gap-px overflow-hidden rounded-2xl border border-line bg-line', headline.length >= 4 ? 'lg:grid-cols-4' : headline.length === 3 ? 'lg:grid-cols-3' : '']} data-reveal>
|
|
52
|
+
{
|
|
53
|
+
headline.map((h) => (
|
|
54
|
+
<div class="flex flex-col bg-ink-900 p-5 md:p-6">
|
|
55
|
+
<dt class="order-2 mt-2 text-sm leading-snug text-fg-muted">
|
|
56
|
+
{h.label}
|
|
57
|
+
{h.live && <> (<span data-merged-prs="exact">{fmt.format(mergedPrTotal)}</span>, <span data-pr-status>as of {asOf}</span>)</>}
|
|
58
|
+
</dt>
|
|
59
|
+
<dd data-merged-prs={h.live ? 'floor' : undefined} class={`order-1 font-display text-[clamp(2.25rem,4.4vw,3.5rem)] leading-none font-[750] tracking-[-0.045em] tabular-nums ${h.accent}`}>{h.value}</dd>
|
|
60
|
+
</div>
|
|
61
|
+
))
|
|
62
|
+
}
|
|
63
|
+
</dl>}
|
|
64
|
+
|
|
65
|
+
{pipeline.length > 0 && <div class="mt-16 md:mt-20" data-reveal>
|
|
66
|
+
<div class="mb-5 flex flex-wrap items-baseline justify-between gap-3">
|
|
67
|
+
<h3 class="font-display text-2xl font-[620] tracking-[-0.02em]">From model to running app</h3>
|
|
68
|
+
<p class="font-mono text-xs text-fg-faint">my commits and merged PRs, all-time, as of {asOf}</p>
|
|
69
|
+
</div>
|
|
70
|
+
<ol class="pipeline grid gap-4 lg:grid-cols-4" aria-label="The Cratis pipeline">
|
|
71
|
+
{
|
|
72
|
+
pipeline.map((r, i) => (
|
|
73
|
+
<li class="relative">
|
|
74
|
+
<a href={r.url} rel="noopener" class="panel glow group flex h-full flex-col p-5" style="--glow: var(--color-command)">
|
|
75
|
+
<span class="flex items-center justify-between gap-2 font-mono text-[0.6875rem]">
|
|
76
|
+
<span class="text-fg-faint">{String(i + 1).padStart(2, '0')} · {r.step}</span>
|
|
77
|
+
<span class={`badge ${badge(r)}`}>{r.role}</span>
|
|
78
|
+
</span>
|
|
79
|
+
<span class="mt-6 font-display text-3xl font-[650] tracking-[-0.03em] transition-colors group-hover:text-command-soft">{r.name}</span>
|
|
80
|
+
<span class="note note-command mt-1">{r.kind}</span>
|
|
81
|
+
<span class="mt-3 text-sm leading-relaxed text-fg-muted">{r.desc}</span>
|
|
82
|
+
<span class="mt-auto flex gap-4 pt-5 font-mono text-xs text-fg-faint tabular-nums">
|
|
83
|
+
<span><span class="text-fg">{fmt.format(r.commits)}</span> commits</span>
|
|
84
|
+
<span><span class="text-fg">{fmt.format(r.prs)}</span> PRs</span>
|
|
85
|
+
{starsFor(`Cratis/${r.name}`) !== undefined && <span class="ml-auto" aria-label={`${starsFor(`Cratis/${r.name}`)} stars on GitHub`}>★ {starsFor(`Cratis/${r.name}`)}</span>}
|
|
86
|
+
</span>
|
|
87
|
+
</a>
|
|
88
|
+
{i < pipeline.length - 1 && <span class="flow" aria-hidden="true"></span>}
|
|
89
|
+
</li>
|
|
90
|
+
))
|
|
91
|
+
}
|
|
92
|
+
</ol>
|
|
93
|
+
</div>}
|
|
94
|
+
|
|
95
|
+
{(tooling.length > 0 || clients.length > 0) && <div class="mt-6 grid gap-4 lg:grid-cols-12" data-reveal>
|
|
96
|
+
<ul class:list={['grid gap-4 sm:grid-cols-2', clients.length > 0 ? 'lg:col-span-8' : 'lg:col-span-12 lg:grid-cols-3']} aria-label="Around the pipeline">
|
|
97
|
+
{
|
|
98
|
+
tooling.map((r) => {
|
|
99
|
+
const Tag = r.url ? 'a' : 'div';
|
|
100
|
+
return (
|
|
101
|
+
<li>
|
|
102
|
+
<Tag href={r.url} rel={r.url ? 'noopener' : undefined} class:list={['panel flex h-full flex-col p-5', r.url && 'glow group']} style="--glow: var(--color-command)">
|
|
103
|
+
<span class="flex items-center justify-between gap-2 font-mono text-[0.6875rem]">
|
|
104
|
+
<span class="note note-command">{r.kind}</span>
|
|
105
|
+
<span class={`badge ${badge(r)}`}>{r.role}</span>
|
|
106
|
+
</span>
|
|
107
|
+
<span class="mt-4 font-display text-xl font-[640] tracking-[-0.02em] transition-colors group-hover:text-command-soft">{r.name}</span>
|
|
108
|
+
<span class="mt-1.5 text-sm leading-relaxed text-fg-muted">{r.desc}</span>
|
|
109
|
+
{r.note && <span class="mt-2 font-mono text-xs text-view">{r.note}</span>}
|
|
110
|
+
<span class="mt-auto flex gap-4 pt-4 font-mono text-xs text-fg-faint tabular-nums">
|
|
111
|
+
<span><span class="text-fg">{fmt.format(r.commits)}</span> commits</span>
|
|
112
|
+
<span><span class="text-fg">{fmt.format(r.prs)}</span> PRs</span>
|
|
113
|
+
</span>
|
|
114
|
+
</Tag>
|
|
115
|
+
</li>
|
|
116
|
+
);
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
</ul>
|
|
120
|
+
{clients.length > 0 && <div class="panel p-5 lg:col-span-4">
|
|
121
|
+
<p class="note">Chronicle clients</p>
|
|
122
|
+
<p class="mt-3 font-display text-xl font-[640] tracking-[-0.02em]">{cratis.clientLanguages.length || clients.length} {cratis.clientLanguages.length ? 'languages' : 'clients'}</p>
|
|
123
|
+
{cratis.clientLanguages.length > 0 && <p class="mt-1 text-sm text-fg-muted">{cratis.clientLanguages.join(' · ')}.</p>}
|
|
124
|
+
<ul class="mt-4 divide-y divide-line border-t border-line">
|
|
125
|
+
{
|
|
126
|
+
clients.map((r) => (
|
|
127
|
+
<li>
|
|
128
|
+
<a href={r.url} rel="noopener" class="group flex items-start justify-between gap-3 py-3">
|
|
129
|
+
<span class="min-w-0">
|
|
130
|
+
<span class="block font-mono text-sm text-fg group-hover:underline">{r.name}</span>
|
|
131
|
+
<span class="block text-xs leading-relaxed text-fg-muted">{r.desc}</span>
|
|
132
|
+
</span>
|
|
133
|
+
<span class="shrink-0 text-right">
|
|
134
|
+
<span class={`badge ${badge(r)}`}>{r.role}</span>
|
|
135
|
+
<span class="mt-1 block font-mono text-[0.6875rem] text-fg-faint tabular-nums">{fmt.format(r.commits)} commits</span>
|
|
136
|
+
</span>
|
|
137
|
+
</a>
|
|
138
|
+
</li>
|
|
139
|
+
))
|
|
140
|
+
}
|
|
141
|
+
</ul>
|
|
142
|
+
</div>}
|
|
143
|
+
</div>}
|
|
144
|
+
|
|
145
|
+
<div class="mt-16 grid gap-6 md:mt-20 lg:grid-cols-12">
|
|
146
|
+
<PullRequestFeed class="lg:col-span-7" />
|
|
147
|
+
|
|
148
|
+
{cratis.highlights.length > 0 && <div class="lg:col-span-5" data-reveal style="--reveal-delay:120ms">
|
|
149
|
+
<h3 class="font-display text-2xl font-[620] tracking-[-0.02em]">Highlights from the log</h3>
|
|
150
|
+
<p class="mt-2 text-fg-muted">A few of the changes I have landed in Cratis.</p>
|
|
151
|
+
<ol class="panel mt-6 divide-y divide-line overflow-hidden font-mono text-sm" aria-label="Highlights">
|
|
152
|
+
{
|
|
153
|
+
cratis.highlights.map((h) => (
|
|
154
|
+
<li class="flex items-start gap-3 px-5 py-3.5 transition-colors hover:bg-fg/[0.02]">
|
|
155
|
+
<svg viewBox="0 0 16 16" class="mt-0.5 size-4 shrink-0 text-[#a371f7] [html[data-theme=light]_&]:text-[#8250df]" fill="currentColor" aria-label="merged" role="img">
|
|
156
|
+
<path d="M5.45 5.154A4.25 4.25 0 0 0 9.25 7.5h1.378a2.251 2.251 0 1 1 0 1.5H9.25A5.734 5.734 0 0 1 5 7.123v3.505a2.25 2.25 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.95-.218ZM4.25 13.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm8.5-4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM5 3.25a.75.75 0 1 0 0 .005V3.25Z" />
|
|
157
|
+
</svg>
|
|
158
|
+
<span class="text-fg">{h}</span>
|
|
159
|
+
</li>
|
|
160
|
+
))
|
|
161
|
+
}
|
|
162
|
+
</ol>
|
|
163
|
+
</div>}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</section>
|
|
167
|
+
|
|
168
|
+
<style>
|
|
169
|
+
.badge {
|
|
170
|
+
display: inline-block;
|
|
171
|
+
border-radius: 999px;
|
|
172
|
+
padding: 0.05rem 0.5rem;
|
|
173
|
+
font-family: var(--font-mono);
|
|
174
|
+
font-size: 0.625rem;
|
|
175
|
+
white-space: nowrap;
|
|
176
|
+
border: 1px solid var(--color-line);
|
|
177
|
+
color: var(--color-fg-muted);
|
|
178
|
+
}
|
|
179
|
+
.b-founded {
|
|
180
|
+
color: var(--color-event-soft);
|
|
181
|
+
border-color: color-mix(in oklab, var(--color-event) 45%, transparent);
|
|
182
|
+
background: color-mix(in oklab, var(--color-event) 10%, transparent);
|
|
183
|
+
}
|
|
184
|
+
.b-lead {
|
|
185
|
+
color: var(--color-command-soft);
|
|
186
|
+
border-color: color-mix(in oklab, var(--color-command) 45%, transparent);
|
|
187
|
+
background: color-mix(in oklab, var(--color-command) 10%, transparent);
|
|
188
|
+
}
|
|
189
|
+
/* Connector between pipeline stages, with a pulse travelling along it. */
|
|
190
|
+
.flow {
|
|
191
|
+
position: absolute;
|
|
192
|
+
z-index: 1;
|
|
193
|
+
left: 50%;
|
|
194
|
+
bottom: -1rem;
|
|
195
|
+
width: 1px;
|
|
196
|
+
height: 1rem;
|
|
197
|
+
background: linear-gradient(var(--color-command), var(--color-event));
|
|
198
|
+
overflow: hidden;
|
|
199
|
+
}
|
|
200
|
+
@media (min-width: 1024px) {
|
|
201
|
+
.flow {
|
|
202
|
+
left: auto;
|
|
203
|
+
right: -1rem;
|
|
204
|
+
bottom: auto;
|
|
205
|
+
top: 50%;
|
|
206
|
+
width: 1rem;
|
|
207
|
+
height: 1px;
|
|
208
|
+
background: linear-gradient(90deg, var(--color-command), var(--color-event));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
.flow::after {
|
|
212
|
+
content: '';
|
|
213
|
+
position: absolute;
|
|
214
|
+
inset: 0;
|
|
215
|
+
background: #fff;
|
|
216
|
+
opacity: 0;
|
|
217
|
+
}
|
|
218
|
+
:global(.js-motion) .flow::after {
|
|
219
|
+
animation: flow-pulse 2.4s ease-in-out infinite;
|
|
220
|
+
}
|
|
221
|
+
.pipeline > li:nth-child(2) .flow::after { animation-delay: 0.6s; }
|
|
222
|
+
.pipeline > li:nth-child(3) .flow::after { animation-delay: 1.2s; }
|
|
223
|
+
@keyframes flow-pulse {
|
|
224
|
+
0%, 100% { opacity: 0; }
|
|
225
|
+
50% { opacity: 0.9; }
|
|
226
|
+
}
|
|
227
|
+
</style>
|
|
228
|
+
|
|
229
|
+
<script>
|
|
230
|
+
import { hydrateRelativeTimes } from '../lib/reltime';
|
|
231
|
+
import { onPageLoad } from '../lib/lifecycle';
|
|
232
|
+
onPageLoad(() => hydrateRelativeTimes(document.querySelector('[data-feed]') ?? document));
|
|
233
|
+
</script>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
3
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
4
|
+
|
|
5
|
+
import { cratisMarkPaths } from '../../badges/cratisMark';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
class?: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
}
|
|
11
|
+
const { class: className = 'size-5', label } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<svg viewBox="0 0 328 328" class={className} role={label ? 'img' : undefined} aria-label={label} aria-hidden={label ? undefined : 'true'}>
|
|
15
|
+
<g transform="translate(0 328) scale(.1 -.1)" fill="currentColor">
|
|
16
|
+
{cratisMarkPaths.map((path) => <path d={path} />)}
|
|
17
|
+
</g>
|
|
18
|
+
</svg>
|