blume 0.6.2 → 0.6.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "Documentation that's fast, AI-ready, and zero-config.",
5
5
  "keywords": [
6
6
  "astro",
@@ -28,7 +28,7 @@ const { prev, next, strings } = Astro.props;
28
28
  >
29
29
  <Icon class="rtl:-scale-x-100" name="arrow-left" size={16} />
30
30
  <span>
31
- <span class="block text-muted-foreground text-xs">
31
+ <span class="block text-muted-foreground text-xs max-md:hidden">
32
32
  {strings.previous}
33
33
  </span>
34
34
  <span class="block font-medium">{prev.label}</span>
@@ -43,7 +43,7 @@ const { prev, next, strings } = Astro.props;
43
43
  href={next.route}
44
44
  >
45
45
  <span>
46
- <span class="block text-muted-foreground text-xs">
46
+ <span class="block text-muted-foreground text-xs max-md:hidden">
47
47
  {strings.next}
48
48
  </span>
49
49
  <span class="block font-medium">{next.label}</span>
@@ -367,11 +367,15 @@ const bannerScript = banner?.dismissible
367
367
  class:list={["mx-auto grid grid-cols-1 items-start", gridClass]}
368
368
  data-blume-doc-grid
369
369
  >
370
- {
371
- showSidebar && (
372
370
  <aside
373
371
  aria-label="Primary"
374
- class="fixed top-[var(--blume-drawer-top,4rem)] start-0 z-[35] h-[calc(100dvh-var(--blume-drawer-top,4rem))] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:top-16 lg:z-auto lg:h-[calc(100dvh-4rem)] lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4"
372
+ class:list={[
373
+ "fixed top-[var(--blume-drawer-top,4rem)] start-0 z-[35] h-[calc(100dvh-var(--blume-drawer-top,4rem))] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:top-16 lg:z-auto lg:h-[calc(100dvh-4rem)] lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4",
374
+ // A "bare" landing (the changelog index) has no sidebar column on
375
+ // desktop, but the header hamburger still needs a drawer to open on
376
+ // mobile — without it the toggle only locked page scroll.
377
+ !showSidebar && "lg:hidden",
378
+ ]}
375
379
  >
376
380
  {
377
381
  // Pinned links (blog, changelog, contact…) sit above every section on
@@ -419,9 +423,17 @@ const bannerScript = banner?.dismissible
419
423
  // The header's tab bar is hidden below `md`, so the drawer is the only
420
424
  // way to move between sections on mobile: list the tabs above the
421
425
  // page tree, separated by a divider. Hidden from `md` up, where the
422
- // header tabs take over.
426
+ // header tabs take over — unless the page tree is empty (a tab-scoped
427
+ // standalone page like the changelog), where they keep the drawer
428
+ // from opening blank.
423
429
  navigation.tabs.length > 0 && (
424
- <nav aria-label="Sections" class="mb-4 border-border border-b pb-4 md:hidden">
430
+ <nav
431
+ aria-label="Sections"
432
+ class:list={[
433
+ "mb-4 border-border border-b pb-4",
434
+ sidebar.length > 0 && "md:hidden",
435
+ ]}
436
+ >
425
437
  <ul class="m-0 list-none p-0">
426
438
  {navigation.tabs.map((tab) => (
427
439
  <li>
@@ -460,8 +472,6 @@ const bannerScript = banner?.dismissible
460
472
  }
461
473
  </nav>
462
474
  </aside>
463
- )
464
- }
465
475
  <main class="px-6 pt-6 pb-10 lg:px-8 xl:px-10" id="blume-content">
466
476
  <BreadcrumbsSlot crumbs={crumbs} wide={isApiOperation} />
467
477
  <TableOfContentsSlot
package/src/og/card.ts CHANGED
@@ -80,27 +80,34 @@ export const truncate = (value: string, max: number): string => {
80
80
  : value;
81
81
  };
82
82
 
83
- // Brand mark sizing: target this height, but scale down so a wide wordmark logo
84
- // stays within the lockup.
83
+ // Brand mark sizing: target this height, but scale down so an extremely wide
84
+ // logo stays within the lockup. The cap leaves room for a wordmark to render
85
+ // at full height — it stands alone as the brand (no text label beside it).
85
86
  const MARK_HEIGHT = 32;
86
- const MARK_MAX_WIDTH = 100;
87
+ const MARK_MAX_WIDTH = 240;
87
88
  // Accept either quote style and a non-zero min-x/min-y; only width/height
88
89
  // matter for the aspect ratio. A miss falls back to a square mark.
89
90
  const VIEW_BOX =
90
91
  /viewBox=(?<q>["'])[\d.-]+[\s,]+[\d.-]+[\s,]+(?<w>[\d.]+)[\s,]+(?<h>[\d.]+)\k<q>/u;
91
92
 
93
+ /** The SVG's viewBox aspect ratio (w/h), or null without a usable viewBox. */
94
+ const logoAspect = (svg: string): number | null => {
95
+ const box = svg.match(VIEW_BOX);
96
+ const w = Number(box?.groups?.w);
97
+ const h = Number(box?.groups?.h);
98
+ return w && h ? w / h : null;
99
+ };
100
+
92
101
  // Render the configured logo as the brand mark. A `currentColor` logo carries
93
102
  // no intrinsic color, so it is painted in the foreground to read on the light
94
103
  // card, then handed to Takumi as a data URI sized from the SVG's aspect ratio.
95
104
  const logoMark = (svg: string): Node => {
96
105
  const painted = svg.replaceAll("currentColor", FOREGROUND);
97
- const box = painted.match(VIEW_BOX);
98
- const w = Number(box?.groups?.w);
99
- const h = Number(box?.groups?.h);
106
+ const aspect = logoAspect(painted);
100
107
  let height = MARK_HEIGHT;
101
- let width = w && h ? (MARK_HEIGHT * w) / h : MARK_HEIGHT;
108
+ let width = aspect ? MARK_HEIGHT * aspect : MARK_HEIGHT;
102
109
  if (width > MARK_MAX_WIDTH) {
103
- height = w && h ? (MARK_MAX_WIDTH * h) / w : MARK_HEIGHT;
110
+ height = aspect ? MARK_MAX_WIDTH / aspect : MARK_HEIGHT;
104
111
  width = MARK_MAX_WIDTH;
105
112
  }
106
113
  return image({
@@ -152,19 +159,13 @@ export const renderOgImage = (options: OgCardOptions): Promise<Buffer> => {
152
159
  const repo = options.repo?.trim();
153
160
  const site = options.site?.trim();
154
161
 
162
+ // Logo only — no brand-name label beside it. A wordmark logo already spells
163
+ // the name, and rendering the site title next to it duplicated the brand
164
+ // ("Ultracite Ultracite"). Without a logo, the accent tile with the brand
165
+ // initial stands in.
155
166
  const header = container({
156
- children: [
157
- logo ? logoMark(logo) : initialMark(accent, initial),
158
- brand
159
- ? text(brand, {
160
- color: FOREGROUND,
161
- fontSize: 30,
162
- fontWeight: 500,
163
- letterSpacing: "-0.01em",
164
- })
165
- : container({}),
166
- ],
167
- style: { alignItems: "center", display: "flex", gap: 18 },
167
+ children: [logo ? logoMark(logo) : initialMark(accent, initial)],
168
+ style: { alignItems: "center", display: "flex" },
168
169
  });
169
170
 
170
171
  const body = container({