diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..a6ab98b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +buy_me_a_coffee: roma321m diff --git a/README.md b/README.md index a3f5cd8..dd8d6e4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![ci](https://github.com/launchcal/launchcal.github.io/actions/workflows/ci.yml/badge.svg)](https://github.com/launchcal/launchcal.github.io/actions/workflows/ci.yml) [![publish](https://github.com/launchcal/launchcal.github.io/actions/workflows/publish.yml/badge.svg)](https://github.com/launchcal/launchcal.github.io/actions/workflows/publish.yml) +[![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-roma321m-FFDD00?logo=buymeacoffee&logoColor=black)](https://buymeacoffee.com/roma321m) Free, self-updating calendars of SpaceX launches. Subscribe once and every new launch, reschedule and scrub shows up in your calendar, in your own time zone. @@ -98,6 +99,11 @@ only print the changes, and `--calendar all=` to sync one calendar i See the [terms of use](https://launchcal.github.io/terms.html) and [privacy policy](https://launchcal.github.io/privacy.html) (no cookies, no tracking). +## Support + +launchcal is free and ad-free. If it helps you catch a launch, you can +[buy me a coffee](https://buymeacoffee.com/roma321m). + ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). Report security issues privately, see [SECURITY.md](SECURITY.md). diff --git a/site/app.js b/site/app.js index 139f6ac..d1cc885 100644 --- a/site/app.js +++ b/site/app.js @@ -10,12 +10,13 @@ document.addEventListener('click', async (event) => { window.prompt('Copy this calendar link:', link.href); return; } - link.dataset.label ??= link.textContent; - link.textContent = 'Copied'; + const label = link.querySelector('span') ?? link; + link.dataset.label ??= label.textContent; + label.textContent = 'Copied'; copyStatus.textContent = 'Link copied'; clearTimeout(link.copyTimer); link.copyTimer = setTimeout(() => { - link.textContent = link.dataset.label; + label.textContent = link.dataset.label; copyStatus.textContent = ''; }, 1500); }); diff --git a/site/privacy.html b/site/privacy.html index cb90a12..234280e 100644 --- a/site/privacy.html +++ b/site/privacy.html @@ -12,6 +12,10 @@
diff --git a/site/style.css b/site/style.css index f29583a..2be6f49 100644 --- a/site/style.css +++ b/site/style.css @@ -104,15 +104,101 @@ h3 { padding: 96px var(--gutter) 48px; } -.wordmark { +/* Top bar over the hero: wordmark on the left, GitHub and Buy Me a Coffee on the right. */ +.topbar { position: absolute; - top: 24px; + z-index: 2; + top: 16px; left: var(--gutter); + right: var(--gutter); + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.wordmark { margin: 0; font-size: 1rem; letter-spacing: 0.35em; } +.project-links { + display: flex; + align-items: center; + gap: 16px; +} + +.github-link { + display: inline-flex; + align-items: center; + gap: 8px; + min-height: 40px; + font-weight: 700; + font-size: 0.8rem; + letter-spacing: 0.14em; + text-transform: uppercase; + text-decoration: none; +} + +.github-link:hover { + text-decoration: underline; + text-underline-offset: 4px; +} + +.github-link .icon { + width: 24px; + height: 24px; +} + +.wide-only { + display: none; +} + +@media (max-width: 599px) { + .topbar .wordmark, + .wordmark-link { + font-size: 0.85rem; + letter-spacing: 0.25em; + } + + .project-links { + gap: 12px; + } +} + +@media (max-width: 359px) { + .btn-small span { + display: none; + } + + .btn-small .icon { + margin-right: 0; + } +} + +@media (min-width: 600px) { + .wide-only { + display: inline; + } +} + +.icon { + flex: none; + width: 18px; + height: 18px; + fill: none; + stroke: currentColor; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; +} + +.icon .fill { + fill: currentColor; + stroke: none; +} + .lede { max-width: 34em; margin: 0 0 32px; @@ -142,9 +228,10 @@ h3 { padding: 96px 48px 96px max(48px, calc((100vw - 1200px) / 2 + 48px)); } - .wordmark { - top: 40px; + .topbar { + top: 28px; left: max(48px, calc((100vw - 1200px) / 2 + 48px)); + right: max(48px, calc((100vw - 1200px) / 2 + 48px)); } } @@ -183,6 +270,22 @@ a:focus-visible { border-width: 2px; } +.btn .icon { + margin-right: 10px; +} + +.btn-small { + min-height: 40px; + padding: 8px 12px; + font-size: 0.75rem; + white-space: nowrap; + background: rgba(0, 0, 0, 0.35); +} + +.btn-small .icon { + margin-right: 8px; +} + .card .btn-primary { background: var(--fg); color: var(--bg); @@ -368,6 +471,10 @@ footer p { /* Terms and privacy pages. */ .page-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; max-width: 1200px; margin: 0 auto; padding: 24px var(--gutter); diff --git a/site/terms.html b/site/terms.html index df9b570..11e9147 100644 --- a/site/terms.html +++ b/site/terms.html @@ -12,6 +12,10 @@
diff --git a/src/page.html b/src/page.html index 96125c1..67b4307 100644 --- a/src/page.html +++ b/src/page.html @@ -18,8 +18,14 @@
A Falcon 9 rocket lifting off at night, its flame reflected in the water in front of the launch pad. -
+

launchcal

+ +
+

SpaceX launches
in your calendar

Free, unofficial calendars that keep themselves up to date. Scrubs and new launch times show up within hours, in your own time zone.

Choose a calendar @@ -51,7 +57,7 @@

What it looks like

Launches sit in your week next to everything else, marked as free time.
-

Screenshots of the public Google calendar, October 2026, with colours inverted to match this page.

+

Screenshots of the public Google calendar, October 2026.

@@ -80,6 +86,7 @@

Good to know

Unofficial. Not affiliated with, endorsed by or connected to SpaceX.

Launch data: The Space Devs (Launch Library 2). Photo: CRS-20 launch, NASA/Tony Gray and Tim Terry; NASA does not endorse this site.

Open source under the MIT license on GitHub. No cookies, no tracking.

+

Free and ad-free. If it helps you catch a launch, you can buy me a coffee.

Terms of use ยท Privacy

diff --git a/src/page.ts b/src/page.ts index 646c446..50d8651 100644 --- a/src/page.ts +++ b/src/page.ts @@ -6,6 +6,15 @@ export const SITE_URL = 'https://launchcal.github.io'; /** The fields of an event the landing page shows in its "next launch" panel. */ export type NextLaunch = Pick; +/** Neutral outline icons for the card buttons; brand logos are deliberately not used. */ +const ICON_CALENDAR_ADD = icon(''); +const ICON_SUBSCRIBE = icon(''); +const ICON_LINK = icon(''); + +function icon(paths: string): string { + return ``; +} + /** Google's own share link: opens "Add this calendar?" for a signed-in user. */ export function googleAddUrl(googleId: string): string { return `https://calendar.google.com/calendar/u/0?cid=${Buffer.from(googleId).toString('base64').replace(/=+$/, '')}`; @@ -21,9 +30,9 @@ export function renderPage(template: string, calendars: CalendarDef[]): string {

${name}

${escapeHtml(calendar.blurb)}

`; }); diff --git a/test/page.test.ts b/test/page.test.ts index e9abcb1..89a7fd0 100644 --- a/test/page.test.ts +++ b/test/page.test.ts @@ -59,6 +59,15 @@ describe('renderPage', () => { assert.ok(html.includes('aria-label="Copy the SpaceX crewed launches ICS link"')); }); + it('puts a decorative icon before each button label, with the label in its own span for the copy feedback', () => { + const html = renderPage(template, [calendar]); + + assert.equal(html.match(/Add to Google Calendar')); + assert.ok(html.includes('Apple / Outlook')); + assert.ok(html.includes('Copy ICS link')); + }); + it('escapes calendar text', () => { const html = renderPage(template, [{ ...calendar, name: 'Crew & "friends"', blurb: 'Dragon & co' }]);