From 7c4c46c8076c65c4b9c70f0878ee889df3627a2c Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 4 Apr 2026 22:43:09 -0400 Subject: [PATCH 01/36] chore(ci): force deploy From 58617c13702b9313c1d62ee41003aaea2b7061d5 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 4 Apr 2026 23:10:25 -0400 Subject: [PATCH 02/36] fix(examples): add auth (#34) --- src/components/WebContainer/WebContainer.astro | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/WebContainer/WebContainer.astro b/src/components/WebContainer/WebContainer.astro index 337bee2..07a54d7 100644 --- a/src/components/WebContainer/WebContainer.astro +++ b/src/components/WebContainer/WebContainer.astro @@ -14,13 +14,17 @@ const { name } = Astro.props; + + diff --git a/src/starlightOverrides/Header.astro b/src/starlightOverrides/Header.astro new file mode 100644 index 0000000..8238e26 --- /dev/null +++ b/src/starlightOverrides/Header.astro @@ -0,0 +1,115 @@ +--- +import Search from '@astrojs/starlight/components/Search.astro'; +import SocialIcons from '@astrojs/starlight/components/SocialIcons.astro'; +import SiteTitle from './SiteTitle.astro'; +--- + +
+
+ +
+
+ +
+
+ + +
+
+ + diff --git a/src/starlightOverrides/MarkdownContent.astro b/src/starlightOverrides/MarkdownContent.astro new file mode 100644 index 0000000..8fdb56c --- /dev/null +++ b/src/starlightOverrides/MarkdownContent.astro @@ -0,0 +1,10 @@ +--- +import "@astrojs/starlight/style/markdown.css"; +--- + +{/* Identical to Starlight's default MarkdownContent, plus the `content` class: + Starlight's TOC scrollspy observes `main .content > *`, but this site's + heading-wrapper markup leaves headings with no observable siblings, so + without this class `aria-current` only updates when a heading itself + crosses the intersection band and goes stale on jump-scrolls. */} +
diff --git a/src/starlightOverrides/MobileMenuFooter.astro b/src/starlightOverrides/MobileMenuFooter.astro new file mode 100644 index 0000000..1befb64 --- /dev/null +++ b/src/starlightOverrides/MobileMenuFooter.astro @@ -0,0 +1,30 @@ +--- +import SocialIcons from "@astrojs/starlight/components/SocialIcons.astro"; +--- + +{/* Like Starlight's default, minus the theme select — the site is dark-only. */} +
+ +
+ + diff --git a/src/starlightOverrides/SiteTitle.astro b/src/starlightOverrides/SiteTitle.astro new file mode 100644 index 0000000..cce6fa3 --- /dev/null +++ b/src/starlightOverrides/SiteTitle.astro @@ -0,0 +1,58 @@ +--- +--- + + + + + + + + + diff --git a/src/styles/starlight.css b/src/styles/starlight.css index c62b8f3..3f818de 100644 --- a/src/styles/starlight.css +++ b/src/styles/starlight.css @@ -2,19 +2,29 @@ @layer user.overrides { :root { - --sl-font: system-ui, sans-serif; + --font-fallback: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", + sans-serif; + --sl-font: "Inter Variable", var(--font-fallback); --sl-font-heading: "polymath-display", sans-serif; + --sl-font-mono: "dm-mono", ui-monospace, monospace; --sl-color-text-heading: var(--heading-fill); - --sl-text-h1: var(--sl-text-6xl); + + /* Heading baseline: one notch smaller than Starlight's wide-viewport + scale; the full-size scale only applies at desktop widths (below) */ + --sl-text-h1: var(--sl-text-5xl); + --sl-text-h2: var(--sl-text-2xl); + --sl-text-h3: var(--sl-text-xl); + --sl-text-h4: var(--sl-text-lg); --sl-color-accent-hsl: 311 100% 50%; - --sl-color-accent-low: hsl(var(--sl-color-accent-hsl) / 0.5); + --sl-color-accent-low: hsl(var(--sl-color-accent-hsl) / 0.1); --sl-color-accent: hsl(var(--sl-color-accent-hsl) / 0.9); --sl-color-accent-high: hsl(var(--sl-color-accent-hsl) / 1); - --sl-color-bg: var(--container-fill); - --sl-color-bg-nav: var(--container-fill); - --sl-color-bg-sidebar: var(--container-fill); + --sl-color-bg: var(--background); + --sl-color-bg-nav: var(--background); + --sl-color-bg-sidebar: var(--background); --sl-color-bg-inline-code: var(--surface-inner-fill); --sl-color-bg-accent: var(--sl-color-accent-high); --sl-color-hairline-light: var(--surface-border-fill); @@ -22,6 +32,92 @@ --sl-color-hairline-shade: var(--surface-border-fill); --sl-icon-color: var(--heading-fill); + + /* extra room for the hatched separator strip under the navbar */ + --hatch-height: 0.75rem; + --sl-nav-height: 4.25rem; + + scrollbar-width: thin; + scrollbar-gutter: stable; + scrollbar-color: hsl(var(--sl-color-accent-hsl)) var(--surface-fill); + } + + ::selection { + background-color: var(--color-pink-100); + color: var(--color-gray-100); + } + + /* bomb.sh viewport frame: 8px background strips + 1px grid border, drawn + over everything (pointer-events: none) so scrolled content passes + "behind" the frame exactly like on the marketing site */ + body::before { + content: ""; + position: fixed; + inset: 0; + z-index: 101; + pointer-events: none; + background-image: /* fill strips */ + linear-gradient(var(--background), var(--background)), + linear-gradient(var(--background), var(--background)), + linear-gradient(var(--background), var(--background)), + linear-gradient(var(--background), var(--background)), + /* border lines */ linear-gradient(var(--c-grid), var(--c-grid)), + linear-gradient(var(--c-grid), var(--c-grid)), + linear-gradient(var(--c-grid), var(--c-grid)), + linear-gradient(var(--c-grid), var(--c-grid)); + background-size: + /* fill: top, bottom, left, right */ + 100% var(--s-grid-padding), + 100% var(--s-grid-padding), + var(--s-grid-padding) 100%, + var(--s-grid-padding) 100%, + /* border: top, bottom, left, right */ 100% var(--s-grid-width), + 100% var(--s-grid-width), + var(--s-grid-width) 100%, + var(--s-grid-width) 100%; + background-position: + top left, + bottom left, + top left, + top right, + 0 var(--s-grid-padding), + 0 calc(100% - var(--s-grid-padding)), + var(--s-grid-padding) 0, + calc(100% - var(--s-grid-padding)) 0; + background-repeat: no-repeat; + } + + /* bomb.sh hatched separator strip along the bottom of the navbar. + Nav content stays centered across the full nav height; the strip + overlays the bottom edge like the homepage separator. */ + header.header::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: var(--hatch-height); + border-top: var(--s-grid-width) solid var(--c-grid); + --_size: var(--s-grid-width); + --_space: 6px; + background-image: repeating-linear-gradient( + 45deg, + transparent, + transparent var(--_space), + var(--c-grid-muted) var(--_space), + var(--c-grid-muted) calc(var(--_space) + var(--_size)) + ); + pointer-events: none; + } + + /* Full-size heading scale on desktop viewports only */ + @media (min-width: 72rem) { + :root { + --sl-text-h1: var(--sl-text-6xl); + --sl-text-h2: var(--sl-text-4xl); + --sl-text-h3: var(--sl-text-3xl); + --sl-text-h4: var(--sl-text-2xl); + } } :is(h1, h2, h3, h4, h5, h6) { @@ -55,16 +151,17 @@ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-image: linear-gradient(to right, var(--c-start), var(--c-end)); - letter-spacing: 1px; + letter-spacing: 0.1ch; } :is(h2, h3, h4, h5, h6) { --c-start: var(--heading-fill); - --c-end: var(--heading-muted); + --c-end: color-mix(in srgb, var(--heading-fill) 35%, transparent); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-image: linear-gradient(to right, var(--c-start), var(--c-end)); + letter-spacing: 0.1ch; } :is(h1, h2, h3, h4, h5, h6) code { @@ -86,9 +183,16 @@ color: var(--heading-fill); } + /* Right sidebar (TOC) background gradient */ + .right-sidebar { + background: linear-gradient( + to bottom, + var(--panel-grad-start) 0%, + var(--background) 100% + ); + } + starlight-toc nav > ul { - --c-start: var(--heading-muted); - --c-end: var(--separator-fill); border-left: 1px solid transparent; position: relative; @@ -99,7 +203,11 @@ bottom: 0; left: 0; width: var(--border-050); - background: linear-gradient(to bottom, var(--c-start), var(--c-end)); + background: linear-gradient( + to bottom, + var(--surface-border-highlight), + var(--surface-border-fill) + ); } a { @@ -108,7 +216,32 @@ } [aria-current] { - border-left-color: var(--sl-color-accent); + border-left-color: hsl(var(--sl-color-accent-hsl) / 0.9); + font-weight: 600; + color: var(--separator-fill); + } + + /* When the animated marker is active, the static per-link border is + replaced by a single sliding indicator (injected by Head.astro). */ + &.has-marker [aria-current] { + border-left-color: transparent; + } + + .toc-marker { + position: absolute; + left: 0; + width: 2px; + background: hsl(var(--sl-color-accent-hsl) / 0.9); + transition: + top 250ms cubic-bezier(0.23, 1, 0.32, 1), + height 250ms cubic-bezier(0.23, 1, 0.32, 1); + pointer-events: none; + } + + @media (prefers-reduced-motion: reduce) { + .toc-marker { + transition: none; + } } } @@ -121,33 +254,173 @@ sans-serif; } + /* Buttons — match the bomb.sh homepage hero CTAs */ + .sl-link-button { + border-radius: 0.33em; + corner-shape: bevel; + font-family: var(--sl-font); + font-weight: 500; + transition: + color 120ms cubic-bezier(0.23, 1, 0.32, 1), + background-color 120ms cubic-bezier(0.23, 1, 0.32, 1), + border-color 120ms cubic-bezier(0.23, 1, 0.32, 1); + } + .sl-link-button.primary { - background: linear-gradient( - to right in oklab, - var(--color-pink-90), - var(--color-pink-100) - ); + background: var(--color-white-100); color: var(--color-gray-100); - border-color: var(--color-pink-100); - border-radius: 0px; + border: var(--s-grid-width) solid var(--c-grid); + + &:is(:hover, :focus-visible) { + background: color-mix(in srgb, var(--color-pink-100) 30%, transparent); + color: var(--color-pink-100); + border-color: var(--color-pink-100); + } + } + + .sl-link-button:not(.primary) { + background: transparent; + color: var(--color-gray-40); + border: var(--s-grid-width) solid var(--color-gray-40); + + &:is(:hover, :focus-visible) { + background: var(--color-gray-80); + color: var(--color-white-100); + border-color: var(--color-white-100); + } } .sl-link-button > svg { - fill: var(--color-gray-100); + fill: currentColor; + } + + /* Content links — white at rest, topic accent on hover */ + .sl-markdown-content a:not(:where(.not-content *)) { + color: var(--color-white-100); + transition: color 120ms cubic-bezier(0.23, 1, 0.32, 1); + + &:is(:hover, :focus-visible) { + color: hsl(var(--sl-color-accent-hsl)); + } + + /* Links wrapping code chips: accent at rest (so the underline matches + the chip), flipping to white on hover — the inverse of plain links */ + &:has(> code) { + color: hsl(var(--sl-color-accent-hsl)); + + &:is(:hover, :focus-visible) { + color: var(--color-white-100); + + & > code { + --color: var(--color-white-100); + } + } + } + } + + /* Inline code — bomb.sh gradient chip, tinted by the topic accent */ + .sl-markdown-content code:not(:where(.not-content *)):not(pre *) { + --color: hsl(var(--sl-color-accent-hsl)); + font-family: var(--sl-font-mono); + color: var(--color); + transition: + color 120ms cubic-bezier(0.23, 1, 0.32, 1), + background 120ms cubic-bezier(0.23, 1, 0.32, 1); + font-size-adjust: 0.5; + background: linear-gradient( + -45deg, + color-mix(in srgb, var(--color) 25%, transparent) 0%, + color-mix(in srgb, var(--color) 12%, transparent) 100% + ); + padding-inline: 0.25em; + padding-block: 0.1em; + border-radius: 4px; + } + + /* Search — a full-height section of the nav, delimited by vertical + hairline bars, aligned with the main content column */ + site-search { + width: 100%; + height: 100%; + display: flex; + align-items: center; + } + + /* Desktop with a TOC column: cap the search at the main content area so + its right bar aligns with the content/TOC boundary (measured value from + Head.astro; the calc is a close fallback that ignores scrollbar width) */ + @media (min-width: 72rem) { + :root[data-has-toc] site-search { + max-width: var( + --search-clamp, + calc((100vw - 2 * var(--sl-sidebar-width) + var(--sl-content-width)) / 2) + ); + } + } + + @media (min-width: 50rem) { + site-search button[data-open-modal] { + border-radius: 0; + max-width: 100%; + height: 100%; + border-block: none; + border-inline: 1px solid var(--surface-border-fill); + /* No fill — the nav's hatch strip stays visible through the section */ + background-color: transparent; + /* Stack above the nav's hatch strip so the bars stay unbroken, and + nudge 1px left so the left bar overlaps the sidebar border exactly */ + position: relative; + z-index: 1; + translate: -1px 0; + } } .cli-preview { padding: 1em; - border: 1px solid #292929; + border: 1px solid var(--surface-border-fill); border-radius: 0.5em; white-space: pre; line-height: 1.2em; - font-family: monospace; - background-color: #181818; + font-family: var(--sl-font-mono); + background-color: var(--container-fill); color: var(--color-white-100); } + /* Code frames — subtle gradient fade on terminal/editor title bars, + matching the hatch-and-hairline chrome of the rest of the site */ + .expressive-code .frame.is-terminal .header, + .expressive-code .frame.has-title:not(.is-terminal) .header { + background: linear-gradient( + to bottom, + transparent, + color-mix(in srgb, var(--heading-fill) 7%, transparent) + ); + border-bottom: 1px solid var(--surface-border-fill); + } + + /* Twoslash hovercards — same panel language as the rest of the chrome */ + .expressive-code .twoslash-popup-container { + border-radius: var(--radius-100); + box-shadow: 0 8px 24px color-mix(in srgb, #000 35%, transparent); + } + + .expressive-code .twoslash-popup-docs, + .expressive-code .twoslash-popup-docs-tags { + font-family: var(--sl-font); + font-size: var(--sl-text-xs); + } + + .expressive-code .twoslash-popup-code { + font-family: var(--sl-font-mono); + } + .sidebar-pane { + background: linear-gradient( + to bottom, + var(--panel-grad-start) 0%, + var(--background) 100% + ); + a { border: 1px solid transparent; } @@ -208,14 +481,28 @@ --sl-icon-color: var(--color-gray-100); - .sl-link-button { - background-color: var(--color-gray-100); + .sl-link-button.primary { + background: var(--color-gray-100); color: var(--color-white-100); border-color: var(--color-gray-100); + + &:is(:hover, :focus-visible) { + background: color-mix(in srgb, var(--color-pink-100) 12%, transparent); + color: var(--color-gray-100); + border-color: var(--color-pink-100); + } } - .sl-link-button > svg { - fill: var(--color-white-100); + .sl-link-button:not(.primary) { + background: transparent; + color: var(--color-gray-70); + border-color: var(--color-gray-40); + + &:is(:hover, :focus-visible) { + background: var(--color-gray-20); + color: var(--color-gray-100); + border-color: var(--color-gray-100); + } } } @@ -344,3 +631,52 @@ ul.starlight-sidebar-topics .starlight-sidebar-topics li + li { margin-top: 0; } + +/* Run the topics separator on the same horizontal line as the divider under + the page title, so it reads as one continuous rule across sidebar and + content. The min-height mirrors the first content panel's box: mobile-toc + bar + panel padding (2 × 1.5rem) + h1 margin (1rem) + one h1 line + (--sl-text-6xl × 1.2 line-height), minus the sidebar's own 1rem top + padding. Applies wherever the sidebar is visible. */ +@media (min-width: 50rem) { + .starlight-sidebar-topics { + display: flex; + flex-direction: column; + /* Measured offset set by Head.astro (no static fallback — it would act + as a floor and defeat the natural-height measurement; without JS the + two rules are simply compact and unaligned). */ + min-height: var(--topics-rule-offset, auto); + } + + .starlight-sidebar-topics::after { + margin-top: auto; + } + + /* When the title panel grows to meet a taller topics column, anchor the + title to the bottom of the available space, just above the shared rule */ + main .content-panel:first-of-type { + display: flex; + flex-direction: column; + justify-content: flex-end; + + /* As a flex item the inner container would otherwise shrink to fit and + its auto margins would center it */ + & > .sl-container { + width: 100%; + } + } +} + +/* Tablet only: drop the title panel's top padding and the h1's default top + margin — they only add dead space above the title and force the topics + list to stretch further to keep the shared rule aligned. Desktop keeps + its larger title spacing. */ +@media (min-width: 50rem) and (max-width: 71.9375rem) { + main .content-panel:first-of-type { + padding-block-start: 0; + } + + main h1#_top { + margin-top: 0; + } +} diff --git a/src/styles/tint.css b/src/styles/tint.css index 11c32f2..199296a 100644 --- a/src/styles/tint.css +++ b/src/styles/tint.css @@ -130,6 +130,14 @@ --space-card-gap: var(--space-400); --space-card-padding: var(--space-400); --space-cell-padding: var(--space-150); + + /* bomb.sh page chrome: page background + viewport frame grid */ + --background: var(--color-white-100); + --c-grid: var(--color-gray-30); + --c-grid-muted: var(--color-gray-20); + --s-grid-width: 1px; + --s-grid-padding: 8px; + --panel-grad-start: var(--color-gray-20); } :root[data-theme=dark] { @@ -148,6 +156,11 @@ --accent-fill: var(--color-pink-100); --accent-muted: var(--color-pink-90); --accent-highlight: var(--color-pink-100); + + --background: #000; + --c-grid: var(--color-gray-80); + --c-grid-muted: var(--color-gray-90); + --panel-grad-start: var(--color-gray-90); } :root[data-theme=light] { From dae1060489835974d275e97f314a082ec4ea49cf Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 14 Aug 2026 15:08:17 -0400 Subject: [PATCH 36/36] feat: bombshell card asides (#70) --- astro.config.mjs | 3 +- src/styles/asides.css | 268 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 src/styles/asides.css diff --git a/astro.config.mjs b/astro.config.mjs index b54e3ed..c4f0589 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -38,7 +38,8 @@ export default defineConfig({ light: "./src/assets/light.svg", }, customCss: [ - './src/styles/tint.css' + './src/styles/tint.css', + './src/styles/asides.css' ], components: { Head: "./src/starlightOverrides/Head.astro", diff --git a/src/styles/asides.css b/src/styles/asides.css new file mode 100644 index 0000000..6acc648 --- /dev/null +++ b/src/styles/asides.css @@ -0,0 +1,268 @@ +/* Bombshell card asides. + Restyles Starlight's aside markup (`:::note` directives and the `