Skip to content

Commit cddfa51

Browse files
committed
polishing
1 parent 3cca71b commit cddfa51

37 files changed

Lines changed: 285 additions & 68 deletions

src/components/Apply.astro

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ const recaptchaNotice = apply.recaptchaNotice;
4141
data-apply-form
4242
data-endpoint={formEndpoint}
4343
data-error={apply.errorBody}
44+
data-fallback-email={site.email}
45+
data-mailto-label={apply.errorMailtoLabel}
46+
data-mailto-subject={apply.errorMailtoSubject}
4447
data-sending={apply.sendingLabel}
4548
data-verifying={apply.verifyingLabel}
4649
data-recaptcha-api-url={recaptchaApiUrl}
@@ -185,7 +188,14 @@ const recaptchaNotice = apply.recaptchaNotice;
185188
</noscript>
186189
</form>
187190

188-
<div class="apply__success card" data-apply-success hidden tabindex="-1">
191+
<div
192+
class="apply__success card"
193+
data-apply-success
194+
hidden
195+
tabindex="-1"
196+
role="status"
197+
aria-live="polite"
198+
>
189199
<p class="apply__success-mark font-pixel" aria-hidden="true">✓</p>
190200
<h3 class="apply__success-h font-display">{apply.successHeading}</h3>
191201
<p class="apply__success-body font-serif">{apply.successBody}</p>
@@ -351,6 +361,17 @@ const recaptchaNotice = apply.recaptchaNotice;
351361
:global(:root[data-theme='dark']) .apply__status[data-state='error'] {
352362
color: var(--accent-2);
353363
}
364+
.apply__status-mailto {
365+
display: inline-block;
366+
margin-top: 0.4rem;
367+
color: var(--accent-2-text);
368+
text-decoration: underline;
369+
text-underline-offset: 2px;
370+
font-weight: 600;
371+
}
372+
.apply__status-mailto:hover {
373+
text-decoration: none;
374+
}
354375
.apply__disabled {
355376
font-size: var(--step--2);
356377
line-height: 1.5;
@@ -746,6 +767,37 @@ const recaptchaNotice = apply.recaptchaNotice;
746767
if (status) {
747768
status.dataset.state = 'error';
748769
status.textContent = errorMsg;
770+
// One-tap escape hatch for the highest-intent action: a mailto
771+
// pre-filled with what they already typed, so a script-blocked /
772+
// reCAPTCHA-down / offline CTO never dead-ends on a generic error.
773+
const fallbackEmail = form.dataset.fallbackEmail;
774+
const mailtoLabel = form.dataset.mailtoLabel;
775+
if (fallbackEmail && mailtoLabel) {
776+
const fd = new FormData(form);
777+
const get = (k: string) => String(fd.get(k) ?? '').trim();
778+
const subject = `${form.dataset.mailtoSubject || 'Open-source patronage'}${
779+
get('company') ? ` — ${get('company')}` : ''
780+
}`;
781+
const body = [
782+
get('company') && `Company: ${get('company')}`,
783+
get('email') && `Work email: ${get('email')}`,
784+
get('name') && `Name: ${get('name')}`,
785+
get('grade') && `Grade: ${get('grade')}`,
786+
get('budget') && `Funding shape: ${get('budget')}`,
787+
get('timeline') && `Timeline: ${get('timeline')}`,
788+
get('stack') && `Open source we depend on:\n${get('stack')}`,
789+
get('notes') && `Notes: ${get('notes')}`,
790+
]
791+
.filter(Boolean)
792+
.join('\n\n');
793+
const link = document.createElement('a');
794+
link.href = `mailto:${fallbackEmail}?subject=${encodeURIComponent(
795+
subject
796+
)}&body=${encodeURIComponent(body)}`;
797+
link.className = 'apply__status-mailto';
798+
link.textContent = mailtoLabel;
799+
status.append(document.createElement('br'), link);
800+
}
749801
}
750802
if (submit) {
751803
submit.disabled = false;

src/components/Header.astro

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import Logo from './Logo.astro';
6262
</button>
6363
</div>
6464
</div>
65+
<div class="nav-scrim" data-nav-scrim aria-hidden="true"></div>
6566
</header>
6667

6768
<style>
@@ -225,6 +226,36 @@ import Logo from './Logo.astro';
225226
display: block;
226227
}
227228

229+
/* Backdrop scrim behind the mobile drawer — gives the open menu a real modal
230+
affordance and a click-away target. Sits below the header bar (so the close
231+
button stays tappable) and below the drawer, above the page. Desktop: off. */
232+
.nav-scrim {
233+
display: none;
234+
position: fixed;
235+
inset: 4.25rem 0 0 0;
236+
z-index: 1;
237+
background: color-mix(in srgb, var(--bg) 55%, transparent);
238+
-webkit-backdrop-filter: blur(2px);
239+
backdrop-filter: blur(2px);
240+
opacity: 0;
241+
visibility: hidden;
242+
transition:
243+
opacity 0.35s var(--ease-out),
244+
visibility 0s linear 0.35s;
245+
}
246+
.nav-scrim[data-open='true'] {
247+
opacity: 1;
248+
visibility: visible;
249+
transition:
250+
opacity 0.35s var(--ease-out),
251+
visibility 0s;
252+
}
253+
@media (prefers-reduced-motion: reduce) {
254+
.nav-scrim {
255+
transition: none;
256+
}
257+
}
258+
228259
.nav-toggle {
229260
display: none;
230261
}
@@ -260,6 +291,9 @@ import Logo from './Logo.astro';
260291
.nav-toggle {
261292
display: inline-grid;
262293
}
294+
.nav-scrim {
295+
display: block;
296+
}
263297
.brand {
264298
gap: 0;
265299
min-width: 0;
@@ -272,6 +306,7 @@ import Logo from './Logo.astro';
272306
top: 4.25rem;
273307
right: 0;
274308
left: 0;
309+
z-index: 2;
275310
width: auto;
276311
flex-direction: column;
277312
align-items: stretch;
@@ -345,6 +380,9 @@ import Logo from './Logo.astro';
345380
top: 4rem;
346381
max-height: calc(100svh - 4rem);
347382
}
383+
.nav-scrim {
384+
top: 4rem;
385+
}
348386
}
349387

350388
@media (max-width: 22rem) {

src/components/Hero.astro

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
import { hero, motto } from '../data/site';
3-
import GitHubMark from './GitHubMark.astro';
2+
import { hero, motto, miniGame } from '../data/site';
3+
import Logo from './Logo.astro';
44
55
// Precompute the headline as a trusted HTML string. Keep the H1 visible at
66
// first paint: this text is the LCP candidate, so it must not wait for JS reveal.
@@ -64,11 +64,18 @@ const mottoHalf = Array.from({ length: 4 })
6464
<ul class="hero__badges" role="list" data-reveal style="--reveal-delay:720ms">
6565
{hero.badges.map((badge) => <li class="chip chip--dot">{badge}</li>)}
6666
</ul>
67+
68+
<button class="hero__play" type="button" data-game-launch data-reveal style="--reveal-delay:820ms">
69+
<span class="hero__play-glyph" aria-hidden="true">▶</span>
70+
<span class="hero__play-text font-pixel">{miniGame.launchCta}</span>
71+
<span class="hero__play-tag font-mono" aria-hidden="true">{miniGame.footerLabel} · 60s</span>
72+
<span class="visually-hidden"> — {miniGame.footerLabel}, {miniGame.launchHint}</span>
73+
</button>
6774
</div>
6875

6976
<aside class="hero__crest" data-reveal style="--reveal-delay:320ms">
7077
<div class="crest">
71-
<GitHubMark size={160} class="crest__github-mark" />
78+
<Logo size={160} decorative class="crest__mark" />
7279
</div>
7380
<pre
7481
class="crest__status terminal__body font-mono"
@@ -212,6 +219,51 @@ const mottoHalf = Array.from({ length: 4 })
212219
gap: 0.5rem;
213220
margin-top: var(--space-m);
214221
}
222+
223+
/* Arcade launcher — surfaces the signature mini-game in the first viewport
224+
without competing with the primary CTAs. "Insert coin" energy. */
225+
.hero__play {
226+
margin-top: var(--space-m);
227+
display: inline-flex;
228+
align-items: center;
229+
gap: 0.7em;
230+
padding: 0.55em 0.9em;
231+
background: transparent;
232+
color: var(--fg);
233+
border: var(--border-px) solid var(--hairline-strong);
234+
box-shadow: var(--shadow-px-sm);
235+
cursor: pointer;
236+
transition:
237+
transform 0.12s var(--ease-out),
238+
box-shadow 0.12s var(--ease-out),
239+
border-color 0.2s;
240+
}
241+
.hero__play:hover,
242+
.hero__play:focus-visible {
243+
transform: translate(-2px, -2px);
244+
box-shadow: 5px 5px 0 var(--fg);
245+
border-color: var(--fg);
246+
}
247+
.hero__play-glyph {
248+
color: var(--accent);
249+
font-size: 0.8em;
250+
animation: blink 1.1s steps(2, start) infinite;
251+
}
252+
.hero__play-text {
253+
font-size: clamp(0.5rem, 0.42rem + 0.35vw, 0.6rem);
254+
color: var(--fg);
255+
}
256+
.hero__play-tag {
257+
font-size: var(--step--2);
258+
color: var(--fg-faint);
259+
padding-left: 0.7em;
260+
border-left: 1.5px solid var(--hairline-strong);
261+
}
262+
@media (prefers-reduced-motion: reduce) {
263+
.hero__play-glyph {
264+
animation: none;
265+
}
266+
}
215267
.chip--dot::before {
216268
content: '';
217269
width: 7px;
@@ -220,8 +272,9 @@ const mottoHalf = Array.from({ length: 4 })
220272
flex: none;
221273
}
222274

223-
/* ── The hero emblem — the GitHub octocat mark on a soft gold halo, with a
224-
slim 3-line mono "mission status" caption below it. */
275+
/* ── The hero emblem — the ManagedCode `< >` brand mark on a soft gold halo,
276+
with a slim 3-line mono "mission status" caption below it. Using our own
277+
mark (not GitHub's) keeps the most-judged frame authored and on-brand. */
225278
.hero__crest {
226279
--reveal-y: 0px;
227280
align-self: center;
@@ -237,20 +290,29 @@ const mottoHalf = Array.from({ length: 4 })
237290
max-width: clamp(13rem, 21vw, 18rem);
238291
margin-inline: auto;
239292
}
240-
:global(.crest__github-mark) {
293+
:global(.crest__mark) {
241294
position: absolute;
242295
top: 50%;
243296
left: 50%;
244297
z-index: 2;
245-
width: 54%;
298+
width: 70%;
246299
height: auto;
247300
color: var(--fg);
248301
transform: translate(-50%, -50%);
249302
pointer-events: none;
250303
filter: drop-shadow(0 6px 16px color-mix(in srgb, var(--gold) 30%, transparent));
304+
/* smooths the desktop cursor-magnet parallax (app.ts initMagnetic) */
305+
transition: transform 0.3s var(--ease-out);
306+
will-change: transform;
307+
}
308+
@media (prefers-reduced-motion: reduce) {
309+
:global(.crest__mark) {
310+
transition: none;
311+
}
251312
}
252-
/* a faint patron-gold halo BEHIND the canvas — a separate layer, so it never
253-
blurs the pixel emblem itself */
313+
/* a faint patron-gold halo BEHIND the mark — a separate layer, so it never
314+
blurs the emblem itself. It breathes slowly (composited scale/opacity only):
315+
"the lights are on" — the one subtle, on-brand kinetic beat on the hero. */
254316
.crest::before {
255317
content: '';
256318
position: absolute;
@@ -262,6 +324,24 @@ const mottoHalf = Array.from({ length: 4 })
262324
transparent 68%
263325
);
264326
pointer-events: none;
327+
will-change: transform, opacity;
328+
animation: crest-breathe 5s ease-in-out infinite;
329+
}
330+
@keyframes crest-breathe {
331+
0%,
332+
100% {
333+
transform: scale(0.94);
334+
opacity: 0.7;
335+
}
336+
50% {
337+
transform: scale(1.06);
338+
opacity: 1;
339+
}
340+
}
341+
@media (prefers-reduced-motion: reduce) {
342+
.crest::before {
343+
animation: none;
344+
}
265345
}
266346
:global(:root[data-theme='dark']) .crest::before {
267347
background: radial-gradient(
@@ -356,8 +436,8 @@ const mottoHalf = Array.from({ length: 4 })
356436
max-width: none;
357437
margin-inline: 0;
358438
}
359-
:global(.crest__github-mark) {
360-
width: 66%;
439+
:global(.crest__mark) {
440+
width: 78%;
361441
}
362442
.crest__status {
363443
flex: 1 1 auto;

src/components/Sla.astro

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ import { sla } from '../data/site';
6969
))
7070
}
7171
</ul>
72+
73+
<p class="sla__sources font-mono" data-reveal>{sla.sources}</p>
7274
</div>
7375
</section>
7476

@@ -268,6 +270,15 @@ import { sla } from '../data/site';
268270
color: var(--accent);
269271
}
270272

273+
/* ---- Source footnote (matches the stat-card rigor elsewhere) ---- */
274+
.sla__sources {
275+
margin-top: clamp(1rem, 0.7rem + 1vw, 1.6rem);
276+
max-width: 70ch;
277+
font-size: var(--step--2);
278+
line-height: 1.6;
279+
color: var(--fg-faint);
280+
}
281+
271282
/* =========================================================
272283
RESPONSIVE SWAP
273284
========================================================= */

src/components/Tiers.astro

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { tiers } from '../data/site';
88
const meterCells = (fill: number) =>
99
Array.from(
1010
{ length: tiers.meterTotal },
11-
(_, i) => `<i class="plan__cell"${i < fill ? ' data-on' : ''}></i>`
11+
(_, i) => `<i class="plan__cell"${i < fill ? ' data-on' : ''} style="--i:${i}"></i>`
1212
).join('');
1313
---
1414

@@ -383,12 +383,35 @@ const meterCells = (fill: number) =>
383383
height: 0.62rem;
384384
border: 1.5px solid color-mix(in srgb, var(--plan-tone) 32%, transparent);
385385
background: color-mix(in srgb, var(--plan-tone) 8%, transparent);
386+
transition:
387+
background-color 0.32s var(--ease-out),
388+
border-color 0.32s var(--ease-out);
386389
}
387390
.plan__meter-bar :global(.plan__cell[data-on]) {
388391
border-color: var(--plan-tone);
389392
background: var(--plan-tone);
390393
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bg) 30%, transparent);
391394
}
395+
/* Signature motion: the capacity meter "charges" left→right as the card
396+
reveals — motion that communicates the reserved band filling up. Gated on
397+
:root.js so a no-JS / crawler paint shows the meter already full; the
398+
visual-test helper reveals every card, so frames settle on the lit state. */
399+
:root.js .tiers__plan[data-reveal]:not(.is-visible) .plan__meter-bar :global(.plan__cell[data-on]) {
400+
border-color: color-mix(in srgb, var(--plan-tone) 30%, transparent);
401+
background: color-mix(in srgb, var(--plan-tone) 8%, transparent);
402+
box-shadow: none;
403+
}
404+
.tiers__plan.is-visible .plan__meter-bar :global(.plan__cell[data-on]) {
405+
transition-delay: calc(0.22s + var(--i, 0) * 0.055s);
406+
}
407+
@media (prefers-reduced-motion: reduce) {
408+
.plan__meter-bar :global(.plan__cell) {
409+
transition: none;
410+
}
411+
.tiers__plan.is-visible .plan__meter-bar :global(.plan__cell[data-on]) {
412+
transition-delay: 0s;
413+
}
414+
}
392415

393416
.plan__rule {
394417
margin-block: 0.1rem;

0 commit comments

Comments
 (0)