@cratis/cast 0.0.0-stage → 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 +12 -2
- 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 +75 -4
- 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,412 @@
|
|
|
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 { careerEvents, firstName } 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 = 'Career, replayed from the *event log.*', label = 'Event stream', intro } = Astro.props;
|
|
15
|
+
import { project, formatMonth, yearsBetween, alsoText } from '../lib/career';
|
|
16
|
+
import SectionHeader from './SectionHeader.astro';
|
|
17
|
+
|
|
18
|
+
const total = careerEvents.length;
|
|
19
|
+
const final = project(careerEvents);
|
|
20
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
21
|
+
const years = yearsBetween(final.shippingSince, final.clock);
|
|
22
|
+
const icon = {
|
|
23
|
+
prev: 'M10 3 5 8l5 5',
|
|
24
|
+
next: 'M6 3l5 5-5 5',
|
|
25
|
+
};
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<section id="career" class="relative py-24 md:py-36" aria-labelledby="career-title" data-replay data-total={total}>
|
|
29
|
+
<div class="container-x">
|
|
30
|
+
<SectionHeader index={index} label={label} title={title} id="career-title">
|
|
31
|
+
<p class="mt-6 max-w-[60ch] text-lg text-fg-muted" data-reveal>
|
|
32
|
+
{intro ? <Fragment set:html={renderInline(intro)} /> : <>Every role, skill and organization below is a projection, computed by folding these {total} events in order.
|
|
33
|
+
Scroll, scrub or press play to watch the state rebuild itself.</>}
|
|
34
|
+
</p>
|
|
35
|
+
</SectionHeader>
|
|
36
|
+
|
|
37
|
+
<div class="grid grid-cols-1 gap-8 lg:grid-cols-12 lg:gap-12">
|
|
38
|
+
<!-- Projection -->
|
|
39
|
+
<div class="lg:order-2 lg:col-span-5">
|
|
40
|
+
<div class="lg:sticky lg:top-[calc(var(--nav-h)+1.5rem)]">
|
|
41
|
+
<div class="panel flex flex-col overflow-hidden lg:max-h-[calc(100svh-var(--nav-h)-5rem)]" data-reveal>
|
|
42
|
+
<div class="flex items-center justify-between gap-3 border-b border-line px-5 py-3.5">
|
|
43
|
+
<p class="font-mono text-sm">
|
|
44
|
+
<span class="text-fg-faint">Projection:</span> <span class="text-view">{firstName}.Career</span>
|
|
45
|
+
</p>
|
|
46
|
+
<p class="font-mono text-xs text-fg-muted">
|
|
47
|
+
position <span class="text-fg tabular-nums" data-out="position">{pad(total)}</span><span class="text-fg-faint"> / {pad(total)}</span>
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="h-px bg-line" aria-hidden="true">
|
|
51
|
+
<div class="h-px origin-left bg-gradient-to-r from-event/40 to-event transition-transform duration-500 ease-[var(--ease-out-expo)]" data-out="progress" style="transform:scaleX(1)"></div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="flex min-h-0 flex-1 flex-col gap-6 p-5 md:p-6">
|
|
55
|
+
<div>
|
|
56
|
+
<p class="mb-2 font-mono text-[0.6875rem] tracking-wider text-fg-faint uppercase">Last applied</p>
|
|
57
|
+
<p class="flex flex-wrap items-baseline gap-x-2 font-mono text-sm">
|
|
58
|
+
<span class="text-fg-faint" data-out="lastSeq">#{pad(total)}</span>
|
|
59
|
+
<span class="text-event" data-out="lastType">{final.lastEvent?.type}</span>
|
|
60
|
+
<span class="text-fg-faint" data-out="lastDate">{final.lastEvent && formatMonth(final.lastEvent.date)}</span>
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div>
|
|
65
|
+
<p class="mb-2 font-mono text-[0.6875rem] tracking-wider text-fg-faint uppercase">Current role</p>
|
|
66
|
+
<p class="font-display text-2xl leading-tight font-[620] tracking-[-0.02em]" data-out="role">{final.currentRole?.title ?? '—'}</p>
|
|
67
|
+
<p class="mt-1 text-sm text-fg-muted" data-out="roleOrg">
|
|
68
|
+
{final.currentRole ? `${final.currentRole.org} · since ${formatMonth(final.currentRole.from)}` : 'No role yet'}
|
|
69
|
+
</p>
|
|
70
|
+
<p class="mt-1 text-sm text-fg-faint" data-out="roleAlso">{alsoText(final)}</p>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="grid grid-cols-3 gap-px overflow-hidden rounded-xl border border-line bg-line">
|
|
74
|
+
<div class="bg-ink-900 p-3.5">
|
|
75
|
+
<p class="font-display text-2xl font-[650] tabular-nums" data-out="years">{years.toFixed(1)}</p>
|
|
76
|
+
<p class="mt-0.5 text-xs text-fg-muted">years in the field</p>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="bg-ink-900 p-3.5">
|
|
79
|
+
<p class="font-display text-2xl font-[650] tabular-nums text-command" data-out="affCount">{final.affiliations.length}</p>
|
|
80
|
+
<p class="mt-0.5 text-xs text-fg-muted">organizations</p>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="bg-ink-900 p-3.5">
|
|
83
|
+
<p class="font-display text-2xl font-[650] tabular-nums text-view" data-out="skillCount">{final.skills.length}</p>
|
|
84
|
+
<p class="mt-0.5 text-xs text-fg-muted">skills</p>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div>
|
|
89
|
+
<p class="mb-2.5 font-mono text-[0.6875rem] tracking-wider text-fg-faint uppercase">Organizations</p>
|
|
90
|
+
<ul class="flex flex-wrap gap-1.5" data-out="affiliations">
|
|
91
|
+
{final.affiliations.map((a) => <li class="chip chip-org" data-key={a}>{a}</li>)}
|
|
92
|
+
</ul>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div class="flex min-h-0 flex-1 flex-col">
|
|
96
|
+
<p class="mb-2.5 font-mono text-[0.6875rem] tracking-wider text-fg-faint uppercase">Skill cloud</p>
|
|
97
|
+
<ul class="skills flex max-h-[13.5rem] min-h-[4rem] flex-wrap content-start gap-1.5 overflow-y-auto pr-1 lg:max-h-none lg:flex-1" data-out="skills">
|
|
98
|
+
{final.skills.map((s) => <li class="chip" data-key={s}>{s}</li>)}
|
|
99
|
+
</ul>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<!-- Controls -->
|
|
104
|
+
<div class="flex items-center gap-2 border-t border-line bg-ink-950/40 px-4 py-3" role="group" aria-label="Replay controls" data-controls hidden>
|
|
105
|
+
<button type="button" class="ctl" data-action="prev" aria-label="Step back one event">
|
|
106
|
+
<svg viewBox="0 0 16 16" class="size-4" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d={icon.prev} stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
|
107
|
+
</button>
|
|
108
|
+
<button type="button" class="ctl ctl-play" data-action="play" aria-label="Play replay" aria-pressed="false">
|
|
109
|
+
<svg viewBox="0 0 16 16" class="icon-play size-4" fill="currentColor" aria-hidden="true"><path d="M4.5 2.8v10.4a.6.6 0 0 0 .9.5l8.2-5.2a.6.6 0 0 0 0-1L5.4 2.3a.6.6 0 0 0-.9.5Z"></path></svg>
|
|
110
|
+
<svg viewBox="0 0 16 16" class="icon-pause size-4" fill="currentColor" aria-hidden="true"><rect x="3.5" y="2.5" width="3" height="11" rx=".8"></rect><rect x="9.5" y="2.5" width="3" height="11" rx=".8"></rect></svg>
|
|
111
|
+
</button>
|
|
112
|
+
<button type="button" class="ctl" data-action="next" aria-label="Step forward one event">
|
|
113
|
+
<svg viewBox="0 0 16 16" class="size-4" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d={icon.next} stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
|
114
|
+
</button>
|
|
115
|
+
<label class="ml-2 flex min-w-0 flex-1 items-center">
|
|
116
|
+
<span class="sr-only">Stream position</span>
|
|
117
|
+
<input type="range" min="0" max={total} step="1" value={total} class="scrubber w-full" data-scrubber />
|
|
118
|
+
</label>
|
|
119
|
+
<span class="w-12 text-right font-mono text-xs text-fg-muted tabular-nums" data-out="positionShort" aria-hidden="true">{total}/{total}</span>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
<p class="mt-3 hidden font-mono text-xs text-fg-faint lg:block" data-controls-hint hidden>
|
|
123
|
+
Tip: focus the replay and use <kbd class="kbd">←</kbd> <kbd class="kbd">→</kbd> to step.
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<!-- Stream -->
|
|
129
|
+
<div class="lg:order-1 lg:col-span-7">
|
|
130
|
+
<!-- Compact HUD keeps the projection visible on small screens -->
|
|
131
|
+
<div class="hud sticky top-[calc(var(--nav-h)+0.5rem)] z-20 mb-6 lg:hidden" data-hud hidden>
|
|
132
|
+
<div class="panel flex items-center gap-2 overflow-hidden !bg-ink-900/95 px-3 py-2.5 shadow-2xl shadow-black/60">
|
|
133
|
+
<button type="button" class="ctl" data-action="prev" aria-label="Step back one event">
|
|
134
|
+
<svg viewBox="0 0 16 16" class="size-4" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d={icon.prev} stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
|
135
|
+
</button>
|
|
136
|
+
<button type="button" class="ctl ctl-play" data-action="play" aria-label="Play replay" aria-pressed="false">
|
|
137
|
+
<svg viewBox="0 0 16 16" class="icon-play size-4" fill="currentColor" aria-hidden="true"><path d="M4.5 2.8v10.4a.6.6 0 0 0 .9.5l8.2-5.2a.6.6 0 0 0 0-1L5.4 2.3a.6.6 0 0 0-.9.5Z"></path></svg>
|
|
138
|
+
<svg viewBox="0 0 16 16" class="icon-pause size-4" fill="currentColor" aria-hidden="true"><rect x="3.5" y="2.5" width="3" height="11" rx=".8"></rect><rect x="9.5" y="2.5" width="3" height="11" rx=".8"></rect></svg>
|
|
139
|
+
</button>
|
|
140
|
+
<button type="button" class="ctl" data-action="next" aria-label="Step forward one event">
|
|
141
|
+
<svg viewBox="0 0 16 16" class="size-4" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d={icon.next} stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
|
142
|
+
</button>
|
|
143
|
+
<div class="ml-1 min-w-0 flex-1 font-mono text-[0.6875rem] leading-4">
|
|
144
|
+
<p class="truncate"><span class="text-fg-faint" data-out="hudPos"></span> <span class="text-event" data-out="hudType"></span></p>
|
|
145
|
+
<p class="truncate text-fg-muted" data-out="hudRole"></p>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="absolute inset-x-0 bottom-0 h-0.5 bg-line" aria-hidden="true">
|
|
148
|
+
<div class="h-full origin-left bg-event transition-transform duration-500" data-out="hudProgress"></div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<ol class="stream relative" data-stream aria-label="Career events">
|
|
154
|
+
{
|
|
155
|
+
careerEvents.map((e) => (
|
|
156
|
+
<li class="event relative pb-3 pl-9 md:pl-12" data-seq={e.seq} data-state="applied">
|
|
157
|
+
<span class="rail" aria-hidden="true" />
|
|
158
|
+
<span class="node" aria-hidden="true" />
|
|
159
|
+
<article class="card glow relative rounded-2xl border border-transparent p-4 md:p-5">
|
|
160
|
+
<h3 class="flex flex-wrap items-baseline gap-x-3 gap-y-1 font-mono text-xs">
|
|
161
|
+
<button type="button" class="jump text-left" data-jump={e.seq} aria-label={`Replay to event ${e.seq}: ${e.type}, ${e.title}`}>
|
|
162
|
+
<span class="text-fg-faint">#{pad(e.seq)}</span>
|
|
163
|
+
<span class="ml-2 font-medium text-event">{e.type}</span>
|
|
164
|
+
</button>
|
|
165
|
+
<span class="text-fg-faint">
|
|
166
|
+
<time datetime={e.date}>{formatMonth(e.date)}</time>
|
|
167
|
+
{e.endDate && (
|
|
168
|
+
<>
|
|
169
|
+
{' – '}
|
|
170
|
+
<time datetime={e.endDate}>{formatMonth(e.endDate)}</time>
|
|
171
|
+
</>
|
|
172
|
+
)}
|
|
173
|
+
</span>
|
|
174
|
+
</h3>
|
|
175
|
+
<p class="mt-2.5 font-display text-xl leading-snug font-[620] tracking-[-0.015em] text-fg md:text-[1.4rem]">{e.title}</p>
|
|
176
|
+
{e.org && (
|
|
177
|
+
<p class="mt-1 text-sm text-fg-muted">
|
|
178
|
+
{e.orgUrl ? (
|
|
179
|
+
<a href={e.orgUrl} class="underline decoration-line-strong hover:text-fg hover:decoration-event" rel="noopener">
|
|
180
|
+
{e.org}
|
|
181
|
+
</a>
|
|
182
|
+
) : (
|
|
183
|
+
e.org
|
|
184
|
+
)}
|
|
185
|
+
{e.location && <span class="text-fg-faint"> · {e.location}</span>}
|
|
186
|
+
</p>
|
|
187
|
+
)}
|
|
188
|
+
<p class="mt-3 text-[0.95rem] leading-relaxed text-fg-muted" set:html={renderInline(e.summary)} />
|
|
189
|
+
{e.skills && (
|
|
190
|
+
<ul class="mt-4 flex flex-wrap gap-1.5" aria-label="Skills">
|
|
191
|
+
{e.skills.map((s) => (
|
|
192
|
+
<li class="rounded-md border border-line px-2 py-0.5 font-mono text-[0.6875rem] text-fg-muted">+ {s}</li>
|
|
193
|
+
))}
|
|
194
|
+
</ul>
|
|
195
|
+
)}
|
|
196
|
+
</article>
|
|
197
|
+
</li>
|
|
198
|
+
))
|
|
199
|
+
}
|
|
200
|
+
</ol>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<p class="sr-only" aria-live="polite" aria-atomic="true" data-live></p>
|
|
206
|
+
</section>
|
|
207
|
+
|
|
208
|
+
<style>
|
|
209
|
+
.rail {
|
|
210
|
+
position: absolute;
|
|
211
|
+
left: 0.6875rem;
|
|
212
|
+
top: 0;
|
|
213
|
+
bottom: 0;
|
|
214
|
+
width: 1px;
|
|
215
|
+
background: var(--color-line-strong);
|
|
216
|
+
}
|
|
217
|
+
.event:first-child .rail {
|
|
218
|
+
top: 1.75rem;
|
|
219
|
+
}
|
|
220
|
+
.event:last-child .rail {
|
|
221
|
+
bottom: auto;
|
|
222
|
+
height: 1.75rem;
|
|
223
|
+
}
|
|
224
|
+
@media (min-width: 768px) {
|
|
225
|
+
.rail {
|
|
226
|
+
left: 0.9375rem;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
.node {
|
|
230
|
+
position: absolute;
|
|
231
|
+
left: calc(0.6875rem - 5px);
|
|
232
|
+
top: 1.5rem;
|
|
233
|
+
width: 11px;
|
|
234
|
+
height: 11px;
|
|
235
|
+
border-radius: 3px;
|
|
236
|
+
background: var(--color-ink-950);
|
|
237
|
+
border: 1px solid var(--color-line-strong);
|
|
238
|
+
transition: all 400ms var(--ease-out-expo);
|
|
239
|
+
}
|
|
240
|
+
@media (min-width: 768px) {
|
|
241
|
+
.node {
|
|
242
|
+
left: calc(0.9375rem - 5px);
|
|
243
|
+
top: 1.75rem;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
.event[data-state='applied'] .node,
|
|
247
|
+
.event[data-state='current'] .node {
|
|
248
|
+
background: var(--color-event);
|
|
249
|
+
border-color: var(--color-event);
|
|
250
|
+
}
|
|
251
|
+
.event[data-state='current'] .node {
|
|
252
|
+
box-shadow: 0 0 0 4px rgb(242 153 74 / 0.18), 0 0 18px rgb(242 153 74 / 0.7);
|
|
253
|
+
transform: scale(1.15);
|
|
254
|
+
}
|
|
255
|
+
.event[data-state='applied'] .rail,
|
|
256
|
+
.event[data-state='current'] .rail {
|
|
257
|
+
background: linear-gradient(to bottom, rgb(242 153 74 / 0.55), rgb(242 153 74 / 0.25));
|
|
258
|
+
}
|
|
259
|
+
.event[data-state='current'] .rail {
|
|
260
|
+
background: linear-gradient(to bottom, rgb(242 153 74 / 0.55), var(--color-line-strong) 60%);
|
|
261
|
+
}
|
|
262
|
+
.card {
|
|
263
|
+
transition:
|
|
264
|
+
opacity 500ms var(--ease-out-expo),
|
|
265
|
+
background-color 400ms ease,
|
|
266
|
+
border-color 400ms ease;
|
|
267
|
+
}
|
|
268
|
+
.event[data-state='future'] .card {
|
|
269
|
+
opacity: 0.6;
|
|
270
|
+
}
|
|
271
|
+
.event[data-state='future'] .card:hover {
|
|
272
|
+
opacity: 0.85;
|
|
273
|
+
}
|
|
274
|
+
.event[data-state='current'] .card {
|
|
275
|
+
background: linear-gradient(180deg, rgb(242 153 74 / 0.07), rgb(242 153 74 / 0.015));
|
|
276
|
+
border-color: rgb(242 153 74 / 0.28);
|
|
277
|
+
}
|
|
278
|
+
:global(.js) .event .card {
|
|
279
|
+
cursor: pointer;
|
|
280
|
+
}
|
|
281
|
+
.jump::after {
|
|
282
|
+
content: '';
|
|
283
|
+
position: absolute;
|
|
284
|
+
inset: 0;
|
|
285
|
+
border-radius: 1rem;
|
|
286
|
+
}
|
|
287
|
+
.jump:focus-visible {
|
|
288
|
+
outline: none;
|
|
289
|
+
}
|
|
290
|
+
.jump:focus-visible::after {
|
|
291
|
+
outline: 2px solid var(--color-command);
|
|
292
|
+
outline-offset: 2px;
|
|
293
|
+
}
|
|
294
|
+
.card :global(a) {
|
|
295
|
+
position: relative;
|
|
296
|
+
z-index: 1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.skills {
|
|
300
|
+
scrollbar-width: thin;
|
|
301
|
+
scrollbar-color: color-mix(in srgb, var(--color-fg) 15%, transparent) transparent;
|
|
302
|
+
}
|
|
303
|
+
:global(.chip) {
|
|
304
|
+
display: inline-flex;
|
|
305
|
+
align-items: center;
|
|
306
|
+
border-radius: 0.45rem;
|
|
307
|
+
border: 1px solid var(--color-line);
|
|
308
|
+
background: color-mix(in srgb, var(--color-fg) 2%, transparent);
|
|
309
|
+
padding: 0.2rem 0.5rem;
|
|
310
|
+
font-size: 0.75rem;
|
|
311
|
+
line-height: 1.1rem;
|
|
312
|
+
color: var(--color-fg-muted);
|
|
313
|
+
transition:
|
|
314
|
+
background-color 1.4s ease,
|
|
315
|
+
border-color 1.4s ease,
|
|
316
|
+
color 1.4s ease;
|
|
317
|
+
}
|
|
318
|
+
:global(.chip-org) {
|
|
319
|
+
color: var(--color-command-soft);
|
|
320
|
+
border-color: rgb(91 134 242 / 0.22);
|
|
321
|
+
background: rgb(91 134 242 / 0.05);
|
|
322
|
+
}
|
|
323
|
+
:global(.chip.is-new) {
|
|
324
|
+
animation: var(--animate-pop-in);
|
|
325
|
+
color: #fff;
|
|
326
|
+
border-color: rgb(242 153 74 / 0.6);
|
|
327
|
+
background: rgb(242 153 74 / 0.18);
|
|
328
|
+
transition: none;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.ctl {
|
|
332
|
+
display: inline-grid;
|
|
333
|
+
place-items: center;
|
|
334
|
+
width: 2.25rem;
|
|
335
|
+
height: 2.25rem;
|
|
336
|
+
flex-shrink: 0;
|
|
337
|
+
border-radius: 0.6rem;
|
|
338
|
+
border: 1px solid var(--color-line);
|
|
339
|
+
color: var(--color-fg-muted);
|
|
340
|
+
transition: all 160ms ease;
|
|
341
|
+
cursor: pointer;
|
|
342
|
+
}
|
|
343
|
+
.ctl:hover {
|
|
344
|
+
color: var(--color-fg);
|
|
345
|
+
border-color: var(--color-line-strong);
|
|
346
|
+
background: color-mix(in srgb, var(--color-fg) 5%, transparent);
|
|
347
|
+
}
|
|
348
|
+
.ctl-play {
|
|
349
|
+
background: var(--color-event);
|
|
350
|
+
border-color: transparent;
|
|
351
|
+
color: #1a0d03;
|
|
352
|
+
}
|
|
353
|
+
.ctl-play:hover {
|
|
354
|
+
background: var(--color-event-soft);
|
|
355
|
+
color: #1a0d03;
|
|
356
|
+
}
|
|
357
|
+
.ctl-play .icon-pause,
|
|
358
|
+
.ctl-play[aria-pressed='true'] .icon-play {
|
|
359
|
+
display: none;
|
|
360
|
+
}
|
|
361
|
+
.ctl-play[aria-pressed='true'] .icon-pause {
|
|
362
|
+
display: block;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.scrubber {
|
|
366
|
+
appearance: none;
|
|
367
|
+
height: 1.5rem;
|
|
368
|
+
background: transparent;
|
|
369
|
+
cursor: pointer;
|
|
370
|
+
--fill: 100%;
|
|
371
|
+
}
|
|
372
|
+
.scrubber::-webkit-slider-runnable-track {
|
|
373
|
+
height: 4px;
|
|
374
|
+
border-radius: 99px;
|
|
375
|
+
background: linear-gradient(to right, var(--color-event) var(--fill), color-mix(in srgb, var(--color-fg) 10%, transparent) var(--fill));
|
|
376
|
+
}
|
|
377
|
+
.scrubber::-moz-range-track {
|
|
378
|
+
height: 4px;
|
|
379
|
+
border-radius: 99px;
|
|
380
|
+
background: linear-gradient(to right, var(--color-event) var(--fill), color-mix(in srgb, var(--color-fg) 10%, transparent) var(--fill));
|
|
381
|
+
}
|
|
382
|
+
.scrubber::-webkit-slider-thumb {
|
|
383
|
+
appearance: none;
|
|
384
|
+
width: 14px;
|
|
385
|
+
height: 14px;
|
|
386
|
+
margin-top: -5px;
|
|
387
|
+
border-radius: 4px;
|
|
388
|
+
background: #fff;
|
|
389
|
+
box-shadow: 0 0 0 4px rgb(242 153 74 / 0.25);
|
|
390
|
+
}
|
|
391
|
+
.scrubber::-moz-range-thumb {
|
|
392
|
+
width: 14px;
|
|
393
|
+
height: 14px;
|
|
394
|
+
border: 0;
|
|
395
|
+
border-radius: 4px;
|
|
396
|
+
background: #fff;
|
|
397
|
+
box-shadow: 0 0 0 4px rgb(242 153 74 / 0.25);
|
|
398
|
+
}
|
|
399
|
+
:global(html[data-theme='light']) .ctl-play,
|
|
400
|
+
:global(html[data-theme='light']) .ctl-play:hover {
|
|
401
|
+
color: #fff;
|
|
402
|
+
}
|
|
403
|
+
</style>
|
|
404
|
+
|
|
405
|
+
<script>
|
|
406
|
+
import { mountReplay } from '../scripts/replay';
|
|
407
|
+
import { onPageLoad } from '../lib/lifecycle';
|
|
408
|
+
onPageLoad(() => {
|
|
409
|
+
const root = document.querySelector<HTMLElement>('[data-replay]');
|
|
410
|
+
return root ? mountReplay(root) : undefined;
|
|
411
|
+
});
|
|
412
|
+
</script>
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
// The Cast badge. On a member's site it is a claim that links to the directory, which is the authority.
|
|
6
|
+
import type { Tier } from '../../badges/Tier';
|
|
7
|
+
import { tierStyles } from '../../badges/badgeSvg';
|
|
8
|
+
import { verificationUrl } from '../../badges/constants';
|
|
9
|
+
import CratisMark from './CratisMark.astro';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
tier: Tier | undefined;
|
|
13
|
+
handle: string;
|
|
14
|
+
size?: 'compact' | 'full';
|
|
15
|
+
class?: string;
|
|
16
|
+
/**
|
|
17
|
+
* True on a member's own site: the tier is what that site's build resolved, a claim. The directory, which
|
|
18
|
+
* Cratis builds from the registry and live GitHub data, passes false.
|
|
19
|
+
*/
|
|
20
|
+
claim?: boolean;
|
|
21
|
+
}
|
|
22
|
+
const { tier, handle, size = 'full', class: className = '', claim = true } = Astro.props;
|
|
23
|
+
const style = tier ? tierStyles[tier] : undefined;
|
|
24
|
+
const href = verificationUrl(handle);
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
style && (
|
|
29
|
+
<a
|
|
30
|
+
href={href}
|
|
31
|
+
rel="noopener"
|
|
32
|
+
class:list={['cast-badge group', `cast-${tier}`, size === 'compact' ? 'is-compact' : 'is-full', className]}
|
|
33
|
+
style={`--tier-from:${style.from};--tier-to:${style.to};--tier-ink:${style.ink}`}
|
|
34
|
+
aria-label={claim ? `Cratis Cast ${style.label}, as claimed by this site. Verify at cast.cratis.io` : `Cratis Cast ${style.label}: ${style.description}. Verified by the Cast directory`}
|
|
35
|
+
title={claim ? `Cratis Cast ${style.label} · verify at cast.cratis.io` : `Cratis Cast ${style.label} · verified by the directory`}
|
|
36
|
+
>
|
|
37
|
+
<span class="cast-badge-left">
|
|
38
|
+
<CratisMark class="cast-badge-mark" />
|
|
39
|
+
<span>Cast</span>
|
|
40
|
+
</span>
|
|
41
|
+
<span class="cast-badge-tier">
|
|
42
|
+
{style.label}
|
|
43
|
+
{size === 'full' && claim && (
|
|
44
|
+
<svg viewBox="0 0 16 16" class="cast-badge-check" aria-hidden="true">
|
|
45
|
+
<path d="M5.5 10.5 10.5 5.5M6.5 5.5h4v4" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" />
|
|
46
|
+
</svg>
|
|
47
|
+
)}
|
|
48
|
+
{size === 'full' && !claim && (
|
|
49
|
+
<svg viewBox="0 0 16 16" class="cast-badge-check" aria-hidden="true">
|
|
50
|
+
<path d="M8 1.2 9.6 2.6l2.1-.2.6 2 1.8 1.1-.7 2 .7 2-1.8 1.1-.6 2-2.1-.2L8 14.8l-1.6-1.4-2.1.2-.6-2-1.8-1.1.7-2-.7-2 1.8-1.1.6-2 2.1.2Z" fill="currentColor" opacity=".9" />
|
|
51
|
+
<path d="m5.4 8.2 1.8 1.8 3.5-3.6" fill="none" stroke="var(--tier-from)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
|
|
52
|
+
</svg>
|
|
53
|
+
)}
|
|
54
|
+
</span>
|
|
55
|
+
</a>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
<style>
|
|
60
|
+
.cast-badge {
|
|
61
|
+
position: relative;
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: stretch;
|
|
64
|
+
border-radius: 0.55rem;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
font-family: var(--font-sans);
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
line-height: 1;
|
|
69
|
+
white-space: nowrap;
|
|
70
|
+
box-shadow:
|
|
71
|
+
0 0 0 1px rgb(255 255 255 / 0.1) inset,
|
|
72
|
+
0 8px 24px -12px color-mix(in srgb, var(--tier-from) 70%, transparent);
|
|
73
|
+
transition: transform 200ms var(--ease-out-expo), box-shadow 200ms ease;
|
|
74
|
+
isolation: isolate;
|
|
75
|
+
}
|
|
76
|
+
.cast-badge::after {
|
|
77
|
+
content: '';
|
|
78
|
+
position: absolute;
|
|
79
|
+
inset: 0;
|
|
80
|
+
background: linear-gradient(180deg, rgb(255 255 255 / 0.16), transparent 55%);
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
}
|
|
83
|
+
.cast-badge:hover {
|
|
84
|
+
transform: translateY(-1px);
|
|
85
|
+
box-shadow:
|
|
86
|
+
0 0 0 1px rgb(255 255 255 / 0.16) inset,
|
|
87
|
+
0 12px 30px -10px color-mix(in srgb, var(--tier-from) 85%, transparent);
|
|
88
|
+
}
|
|
89
|
+
.cast-badge-left {
|
|
90
|
+
display: inline-flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 0.35rem;
|
|
93
|
+
padding-inline: 0.5rem 0.45rem;
|
|
94
|
+
background: #0c0c15;
|
|
95
|
+
color: #f5f3f0;
|
|
96
|
+
}
|
|
97
|
+
.cast-badge-tier {
|
|
98
|
+
display: inline-flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 0.3rem;
|
|
101
|
+
padding-inline: 0.5rem;
|
|
102
|
+
background: linear-gradient(100deg, var(--tier-from), var(--tier-to));
|
|
103
|
+
color: var(--tier-ink);
|
|
104
|
+
}
|
|
105
|
+
.is-compact {
|
|
106
|
+
height: 1.375rem;
|
|
107
|
+
font-size: 0.6875rem;
|
|
108
|
+
}
|
|
109
|
+
.is-full {
|
|
110
|
+
height: 1.75rem;
|
|
111
|
+
font-size: 0.8125rem;
|
|
112
|
+
}
|
|
113
|
+
:global(.cast-badge-mark) {
|
|
114
|
+
width: 0.85rem;
|
|
115
|
+
height: 0.85rem;
|
|
116
|
+
}
|
|
117
|
+
.is-full :global(.cast-badge-mark) {
|
|
118
|
+
width: 1rem;
|
|
119
|
+
height: 1rem;
|
|
120
|
+
}
|
|
121
|
+
.cast-badge-check {
|
|
122
|
+
width: 0.95rem;
|
|
123
|
+
height: 0.95rem;
|
|
124
|
+
color: var(--tier-ink);
|
|
125
|
+
}
|
|
126
|
+
@media print {
|
|
127
|
+
.cast-badge {
|
|
128
|
+
box-shadow: none;
|
|
129
|
+
border: 1px solid #ccc;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
@@ -0,0 +1,116 @@
|
|
|
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 { config } from '../data';
|
|
6
|
+
const host = new URL(config.site.url).host;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<dialog class="palette" data-palette aria-label="Command palette" transition:persist="palette">
|
|
10
|
+
<div class="sheet panel overflow-hidden">
|
|
11
|
+
<div class="flex items-center gap-3 border-b border-line px-4">
|
|
12
|
+
<svg viewBox="0 0 20 20" class="size-4 shrink-0 text-fg-faint" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
|
13
|
+
<circle cx="9" cy="9" r="5.5"></circle><path d="m13.2 13.2 3.3 3.3" stroke-linecap="round"></path>
|
|
14
|
+
</svg>
|
|
15
|
+
<input
|
|
16
|
+
type="text"
|
|
17
|
+
class="h-14 min-w-0 flex-1 bg-transparent text-base text-fg placeholder:text-fg-faint focus:outline-none"
|
|
18
|
+
placeholder="Type a command or search…"
|
|
19
|
+
role="combobox"
|
|
20
|
+
aria-expanded="true"
|
|
21
|
+
aria-controls="palette-list"
|
|
22
|
+
aria-autocomplete="list"
|
|
23
|
+
aria-label="Search commands"
|
|
24
|
+
autocomplete="off"
|
|
25
|
+
spellcheck="false"
|
|
26
|
+
data-palette-input
|
|
27
|
+
/>
|
|
28
|
+
<button type="button" class="kbd cursor-pointer" data-palette-close aria-label="Close command palette">esc</button>
|
|
29
|
+
</div>
|
|
30
|
+
<ul id="palette-list" role="listbox" aria-label="Commands" class="max-h-[min(60vh,26rem)] overflow-y-auto p-2" data-palette-list></ul>
|
|
31
|
+
<div class="flex items-center justify-between gap-4 border-t border-line px-4 py-2.5 font-mono text-[0.6875rem] text-fg-faint">
|
|
32
|
+
<span class="flex items-center gap-1.5"><kbd class="kbd">↑</kbd><kbd class="kbd">↓</kbd> navigate <kbd class="kbd ml-2">↵</kbd> run</span>
|
|
33
|
+
<span class="hidden sm:inline">{host}</span>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</dialog>
|
|
37
|
+
|
|
38
|
+
<style>
|
|
39
|
+
.palette {
|
|
40
|
+
position: fixed;
|
|
41
|
+
inset: 0;
|
|
42
|
+
margin: 0;
|
|
43
|
+
padding: 12vh 1rem 1rem;
|
|
44
|
+
width: 100%;
|
|
45
|
+
max-width: none;
|
|
46
|
+
height: 100%;
|
|
47
|
+
max-height: none;
|
|
48
|
+
background: transparent;
|
|
49
|
+
color: inherit;
|
|
50
|
+
border: 0;
|
|
51
|
+
}
|
|
52
|
+
.palette:not([open]) {
|
|
53
|
+
display: none;
|
|
54
|
+
}
|
|
55
|
+
.palette::backdrop {
|
|
56
|
+
background: rgb(4 4 8 / 0.6);
|
|
57
|
+
backdrop-filter: blur(6px);
|
|
58
|
+
}
|
|
59
|
+
.sheet {
|
|
60
|
+
margin-inline: auto;
|
|
61
|
+
max-width: 38rem;
|
|
62
|
+
background: color-mix(in srgb, var(--color-ink-900) 92%, transparent);
|
|
63
|
+
box-shadow:
|
|
64
|
+
inset 0 1px 0 color-mix(in srgb, var(--color-fg) 6%, transparent),
|
|
65
|
+
0 50px 120px -20px rgb(0 0 0 / 0.8),
|
|
66
|
+
0 0 0 1px color-mix(in srgb, var(--color-fg) 4%, transparent);
|
|
67
|
+
}
|
|
68
|
+
:global(.js-motion) .palette[open] .sheet {
|
|
69
|
+
animation: sheet-in 260ms var(--ease-out-expo);
|
|
70
|
+
}
|
|
71
|
+
@keyframes sheet-in {
|
|
72
|
+
from {
|
|
73
|
+
opacity: 0;
|
|
74
|
+
transform: translateY(-8px) scale(0.98);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
.palette :global([role='option']) {
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 0.75rem;
|
|
81
|
+
padding: 0.6rem 0.75rem;
|
|
82
|
+
border-radius: 0.65rem;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
font-size: 0.9375rem;
|
|
85
|
+
color: var(--color-fg-muted);
|
|
86
|
+
}
|
|
87
|
+
.palette :global([role='option'][aria-selected='true']) {
|
|
88
|
+
background: color-mix(in srgb, var(--color-fg) 6%, transparent);
|
|
89
|
+
color: var(--color-fg);
|
|
90
|
+
}
|
|
91
|
+
.palette :global(.grp) {
|
|
92
|
+
padding: 0.75rem 0.75rem 0.35rem;
|
|
93
|
+
font-family: var(--font-mono);
|
|
94
|
+
font-size: 0.6875rem;
|
|
95
|
+
letter-spacing: 0.08em;
|
|
96
|
+
text-transform: uppercase;
|
|
97
|
+
color: var(--color-fg-faint);
|
|
98
|
+
}
|
|
99
|
+
.palette :global(.sw) {
|
|
100
|
+
width: 0.55rem;
|
|
101
|
+
height: 0.55rem;
|
|
102
|
+
border-radius: 2px;
|
|
103
|
+
flex-shrink: 0;
|
|
104
|
+
}
|
|
105
|
+
.palette :global(.hint) {
|
|
106
|
+
margin-left: auto;
|
|
107
|
+
font-family: var(--font-mono);
|
|
108
|
+
font-size: 0.6875rem;
|
|
109
|
+
color: var(--color-fg-faint);
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
112
|
+
|
|
113
|
+
<script>
|
|
114
|
+
import { mountPalette } from '../scripts/palette';
|
|
115
|
+
mountPalette();
|
|
116
|
+
</script>
|