diff --git a/package-lock.json b/package-lock.json index 6df80d8..a7e495f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.1.0", "dependencies": { "@astrojs/sitemap": "^3.7.2", + "@fontsource-variable/jetbrains-mono": "^5.2.8", + "@fontsource-variable/space-grotesk": "^5.2.10", "@tailwindcss/vite": "^4.3.0", "astro": "^6.3.3", "tailwindcss": "^4.3.0" @@ -888,6 +890,24 @@ "node": ">=18" } }, + "node_modules/@fontsource-variable/jetbrains-mono": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz", + "integrity": "sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/space-grotesk": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/@fontsource-variable/space-grotesk/-/space-grotesk-5.2.10.tgz", + "integrity": "sha512-yJQO/o35/hAP3CFnpdFTwQku2yzJOae2HIpBmqkOVoxhhXJaQP3g+b6Jrz7u+eI7A5ZdCIf88uMWpBJdFiGr5w==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@img/colour": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", @@ -981,9 +1001,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1000,9 +1017,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1019,9 +1033,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1038,9 +1049,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1057,9 +1065,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1076,9 +1081,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1095,9 +1097,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1114,9 +1113,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1133,9 +1129,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1158,9 +1151,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1183,9 +1173,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1208,9 +1195,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1233,9 +1217,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1258,9 +1239,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1283,9 +1261,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1308,9 +1283,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1589,9 +1561,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1605,9 +1574,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1621,9 +1587,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1637,9 +1600,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1653,9 +1613,6 @@ "cpu": [ "loong64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1669,9 +1626,6 @@ "cpu": [ "loong64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1685,9 +1639,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1701,9 +1652,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1717,9 +1665,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1733,9 +1678,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1749,9 +1691,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1765,9 +1704,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1781,9 +1717,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2113,9 +2046,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2132,9 +2062,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2151,9 +2078,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2170,9 +2094,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4024,9 +3945,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4047,9 +3965,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4070,9 +3985,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4093,9 +4005,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ diff --git a/package.json b/package.json index 3a82895..4c5cdd3 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ }, "dependencies": { "@astrojs/sitemap": "^3.7.2", + "@fontsource-variable/jetbrains-mono": "^5.2.8", + "@fontsource-variable/space-grotesk": "^5.2.10", "@tailwindcss/vite": "^4.3.0", "astro": "^6.3.3", "tailwindcss": "^4.3.0" diff --git a/src/components/Hero.astro b/src/components/Hero.astro index 2d26cf8..50e27a7 100644 --- a/src/components/Hero.astro +++ b/src/components/Hero.astro @@ -1,5 +1,11 @@ --- +// Hide the event pill once the date passes (build-time check). +// To re-use this pattern: add a new entry, point Hero at it. +const SLACK_DEV_DAY_HIDE_AFTER = new Date('2026-05-21T00:00:00Z'); +const showSlackDevDay = new Date() < SLACK_DEV_DAY_HIDE_AFTER; +// Must be a PUBLIC GitHub repo — degit downloads anonymous tarballs and 404s on private repos. +const QUICK_START_CMD = 'npx degit tightknitai/slack-hono-template my-slack-app'; ---
@@ -7,7 +13,7 @@
+ + diff --git a/src/components/RepoGrid.astro b/src/components/RepoGrid.astro index 16a5bdf..f6af161 100644 --- a/src/components/RepoGrid.astro +++ b/src/components/RepoGrid.astro @@ -16,6 +16,11 @@ const featuredSet = new Set(featured.map((r) => r.name)); const rest = repos.filter((r) => !featuredSet.has(r.name)); const featuredVariants = ['yellow', 'pink', 'cyan'] as const; + +// Social-proof stat row — only render once the org has earned some real traction. +const totalStars = repos.reduce((sum, r) => sum + r.stars, 0); +const SOCIAL_PROOF_STAR_THRESHOLD = 100; +const showSocialProof = totalStars >= SOCIAL_PROOF_STAR_THRESHOLD; ---
@@ -23,13 +28,33 @@ const featuredVariants = ['yellow', 'pink', 'cyan'] as const;
- // featured + // the stack

The Stack.

+ { + showSocialProof && ( +
+ {repos.length} repos + + + {totalStars.toLocaleString('en-US')} stars + + + open source +
+ ) + }

Block Kit tooling, app frameworks, and validators we use to ship Slack apps faster. Open diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 4f2c82f..0cfc4fd 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -85,25 +85,7 @@ const allJsonLd = [organizationLd, websiteLd, ...jsonLd]; - - - - - + {/* Fonts are self-hosted via @fontsource-variable in src/styles/global.css — no CDN hop. */} { allJsonLd.map((doc) => ( diff --git a/src/lib/github.ts b/src/lib/github.ts index e3f5979..4785f57 100644 --- a/src/lib/github.ts +++ b/src/lib/github.ts @@ -176,6 +176,7 @@ const EXCLUDED_REPOS = new Set(['tightknit-tdx-demo', 'homebrew-tap']); // Repos to surface even when the GitHub org doesn't expose them (yet). // If the live API later returns one of these, the live entry wins. +// IMPORTANT: only list PUBLIC repos here — private repos render cards that 404 for visitors. const MANUAL_REPOS: Repo[] = [ { name: 'slack-hono-template', @@ -213,16 +214,29 @@ export async function loadRepos(org: string): Promise<{ repos: Repo[]; usedFallb } } +// Locale-aware, calendar-correct relative time. `narrow` keeps output compact ("5d ago", "5mo ago"). +const RELATIVE_TIME = new Intl.RelativeTimeFormat('en', { + style: 'narrow', + numeric: 'auto', +}); + export function formatRelativeTime(iso: string): string { - const then = new Date(iso).getTime(); - const now = Date.now(); - const diff = Math.max(0, now - then); - const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + const then = new Date(iso); + const now = new Date(); + const diffMs = now.getTime() - then.getTime(); + if (diffMs < 0) return 'just now'; + + const days = Math.floor(diffMs / 86_400_000); if (days < 1) return 'today'; - if (days === 1) return '1d ago'; - if (days < 30) return `${days}d ago`; - const months = Math.floor(days / 30); - if (months < 12) return `${months}mo ago`; - const years = Math.floor(days / 365); - return `${years}y ago`; + if (days < 30) return RELATIVE_TIME.format(-days, 'day'); + + // Calendar-aware month diff — avoids the floor-by-30 drift the old impl had. + const months = + (now.getFullYear() - then.getFullYear()) * 12 + + (now.getMonth() - then.getMonth()) - + (now.getDate() < then.getDate() ? 1 : 0); + if (months < 12) return RELATIVE_TIME.format(-months, 'month'); + + const years = Math.floor(months / 12); + return RELATIVE_TIME.format(-years, 'year'); } diff --git a/src/styles/global.css b/src/styles/global.css index 446019a..9a03e3a 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,3 +1,7 @@ +/* Self-hosted variable fonts. Replaces the Google Fonts in Layout.astro. */ +@import '@fontsource-variable/space-grotesk'; +@import '@fontsource-variable/jetbrains-mono'; + @import 'tailwindcss'; @theme { @@ -10,9 +14,9 @@ --color-cyan: #00e0ff; --color-lime: #c8ff3a; - --font-display: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif; - --font-mono: 'JetBrains Mono', ui-monospace, monospace; - --font-sans: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif; + --font-display: 'Space Grotesk Variable', 'Space Grotesk', ui-sans-serif, system-ui, sans-serif; + --font-mono: 'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, monospace; + --font-sans: 'Space Grotesk Variable', 'Space Grotesk', ui-sans-serif, system-ui, sans-serif; --radius-none: 0; --shadow-brutal: 8px 8px 0 #0a0a0a; @@ -96,19 +100,46 @@ } } - .float-block { - animation: float-block 6s ease-in-out infinite; + /* Three float variants — different durations + rotation ranges so the blocks don't bob in sync. + Note: animation-delay on the parent wrapper was previously a no-op (animations live here on + .float-block-*, not the wrapper); these negative delays put each block at a different phase. */ + .float-block-a { + animation: float-block-a 6s ease-in-out infinite; + } + .float-block-b { + animation: float-block-b 6.8s ease-in-out -1.5s infinite; + } + .float-block-c { + animation: float-block-c 7.4s ease-in-out -3s infinite; } - @keyframes float-block { + @keyframes float-block-a { 0%, 100% { - transform: translateY(0) rotate(-2deg); + transform: translateY(0) rotate(-3deg); } 50% { transform: translateY(-12px) rotate(2deg); } } + @keyframes float-block-b { + 0%, + 100% { + transform: translateY(0) rotate(5deg); + } + 50% { + transform: translateY(-9px) rotate(-4deg); + } + } + @keyframes float-block-c { + 0%, + 100% { + transform: translateY(0) rotate(8deg); + } + 50% { + transform: translateY(-14px) rotate(16deg); + } + } .brutal-card { background: #ffffff; @@ -222,4 +253,94 @@ transform: translate(-2px, -2px); box-shadow: 6px 6px 0 var(--color-ink); } + + .brutal-snippet { + display: inline-flex; + align-items: center; + gap: 0.65rem; + padding: 0.85rem 0.85rem 0.85rem 1rem; + background: var(--color-ink); + color: var(--color-cream); + border: 3px solid var(--color-ink); + box-shadow: 4px 4px 0 var(--color-pink); + font-family: var(--font-mono); + font-size: 0.875rem; + line-height: 1; + max-width: 100%; + overflow-x: auto; + transition: + transform 120ms ease, + box-shadow 120ms ease; + } + + .brutal-snippet:hover, + .brutal-snippet:focus-within { + transform: translate(-2px, -2px); + box-shadow: 6px 6px 0 var(--color-pink); + } + + .snippet-prompt { + color: var(--color-pink); + user-select: none; + font-weight: 600; + } + + .snippet-cmd { + color: var(--color-cream); + white-space: nowrap; + } + + .snippet-copy { + margin-left: auto; + padding: 0.3rem 0.65rem; + background: var(--color-yellow); + color: var(--color-ink); + border: 2px solid var(--color-cream); + font-family: var(--font-mono); + font-size: 10px; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + cursor: pointer; + transition: + background 120ms ease, + color 120ms ease, + border-color 120ms ease; + } + + .snippet-copy:hover, + .snippet-copy:focus-visible { + background: var(--color-pink); + color: var(--color-cream); + outline: none; + } + + .snippet-copy-ok { + display: none; + } + + .snippet-copy.is-copied { + background: var(--color-cyan); + color: var(--color-ink); + } + + .snippet-copy.is-copied .snippet-copy-default { + display: none; + } + + .snippet-copy.is-copied .snippet-copy-ok { + display: inline; + } +} + +/* Honor user motion preferences. Decorative animations stop; functional transitions soften. */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + scroll-behavior: auto !important; + } }