Commit Graph

136 Commits

Author SHA1 Message Date
Tommy D. Rossi 0ad5219fd8 Add 'vs Built-in Chrome CDP' comparison to README and website
Chrome's --remote-debugging-port shows a persistent automation infobar
that agents cannot dismiss, interrupting users mid-workflow. Playwriter
runs silently with no blocking dialogs, enabling fully autonomous agent
operation without user intervention.

Added to both README.md (markdown table + blockquote) and
website/src/content/index.mdx (ComparisonTable component).
2026-04-03 12:32:57 +02:00
Tommy D. Rossi 1a8daa3b2f simpler max width. support for full page width 2026-04-01 12:21:41 +02:00
Tommy D. Rossi 612473b578 add justify-between to editorial grid layout
Distribute the three grid columns (TOC, content, aside) with
justify-content: space-between on the hero and content grids.
The gap-x is preserved as minimum spacing. When max-width is
increased later, sidebars will push toward screen edges while
content stays centered.
2026-04-01 11:47:51 +02:00
Tommy D. Rossi 9864c1487b fix: resolve publicDir via spiceflow virtual module for Vercel compat
The website was using `import.meta.dirname` with relative paths to
resolve `publicDir` and `cacheDir`, which broke on Vercel because the
deployment directory structure differs from source layout:

- Source: `src/pages/index.tsx` → `../../public` → `website/public/`
- Vercel: `rsc/index.js` → `../../public` → `/var/public` (ENOENT)

Now uses spiceflow's `publicDir` export (from `virtual:spiceflow-dirs`)
which resolves correctly in both dev and Vercel deployments.

Also makes cache writes optional with try/catch since Vercel's
filesystem is read-only — images regenerate in-memory on each request.

- **spiceflow** bumped to `1.18.0-rsc.22` (adds `publicDir`/`distDir`)
- **image-cache.ts** wraps `mkdirSync`/`writeFileSync` in try/catch
- **index.tsx** imports `publicDir` from spiceflow instead of computing it
2026-03-31 02:43:00 +02:00
Tommy D. Rossi d82f82f978 add server-side automatic image placeholder generation with JSON cache
Previously, placeholder images for PixelatedImage had to be manually generated
by running a separate script (generate-placeholders.ts) before each build, and
passed explicitly as static Vite imports. The placeholder prop was never actually
wired up in the MDX content, so the fade-in effect wasn't working.

This replaces the entire approach with on-demand server-side processing:

**Architecture**

index.tsx is now a server component (no more 'use client'). During each render,
it calls buildImageManifest() which walks the mdast AST, finds all image srcs
(both <PixelatedImage src=...> JSX nodes and markdown ![alt](url) nodes), and
for each local public/ image:

1. Checks .cache/images/<name>.json — if present and mtime matches, returns cached data
2. On cache miss: reads dimensions with image-size, generates 64px-wide placeholder
   PNG with sharp, encodes as base64 data URI, writes JSON cache

Placeholder injection uses two mechanisms:
- mdxComponents mapping: wraps PixelatedImage with PixelatedImageWithPlaceholder
  that injects placeholder + dimensions from the manifest via closure. This handles
  JSX elements like <PixelatedImage src=...> in MDX, including when nested inside
  other components like <Bleed>. renderNode is NOT called for nested JSX children
  in safe-mdx, so the component mapping is the correct injection point.
- renderNode: converts markdown images ![alt](url) to PixelatedImage with placeholder

**Cache format** (.cache/images/, gitignored)

  { mtime: 1234567890, width: 1280, height: 800, placeholder: 'data:image/png;base64,...' }

Invalidation is mtime-based — any change to the source image regenerates the cache.
On warm runs (no source change), the manifest is built entirely from fast JSON reads.

**Files**

- website/src/lib/image-cache.ts (new): buildImageManifest(), collectImageSrcs(),
  getJsxAttr(), getAttrStringValue() — pure server utilities, no React
- website/src/pages/index.tsx: remove 'use client', make IndexPage async, move
  rendering logic inside component body, add PixelatedImageWithPlaceholder wrapper
- website/src/components/markdown.tsx: update PixelatedImage JSDoc to reflect
  the new auto-injection pattern
- website/package.json: add image-size dep, simplify build script (no more
  generate-placeholders.ts pre-step)
- deleted: website/scripts/generate-placeholders.ts
- deleted: website/src/assets/placeholders/placeholder-*.png (6 files)
2026-03-29 16:44:27 +02:00
Tommy D. Rossi 962e4f83b5 move flattenTocTree and TOC types from markdown.tsx to toc-tree.ts
markdown.tsx is a 'use client' module. Any function exported from it cannot
be called in a server component (RSC boundary). flattenTocTree and the TOC
type definitions (TocNodeType, VisualLevel, TocTreeNode, FlatTocItem) are
pure functions with no hooks or browser APIs, so they belong in toc-tree.ts
which has no 'use client' directive.

Changes:
- toc-tree.ts: add all TOC types and flattenTocTree (self-contained, no imports
  from markdown.tsx to break the circular dep)
- markdown.tsx: re-export the types from toc-tree.ts for backward compatibility,
  remove the now-duplicated definitions
- toc-tree.test.ts: update import to come from toc-tree.ts directly; also update
  6 stale inline snapshots (prefix spacing normalization from the prefix fix in
  addPrefixes — '└ ' → '└─ ')
2026-03-29 16:39:07 +02:00
Tommy D. Rossi 22ee8b3cf4 rename CSS variable --code-bg to --border-subtle
--code-bg was being used for both code block backgrounds and border colors,
which created semantic confusion. The usages in TocLink hover background,
SectionRow aside border, and SidebarBanner border are really border/surface
accents, not code backgrounds.

Rename to --border-subtle with adjusted values:
- light: rgba(0,0,0,0.02) → rgba(0,0,0,0.08) (more visible border)
- dark:  rgba(255,255,255,0.05) → rgba(255,255,255,0.12) (more visible border)

Note: --code-bg as a name still exists as a concept in the codebase where it
refers to actual code block backgrounds, those usages are separate.
2026-03-29 16:38:20 +02:00
Tommy D. Rossi 7a11505cbf self-host Inter and JetBrainsMono fonts instead of fetching from GitHub
Inter variable font v4.1 (roman, 344KB woff2) and JetBrainsMono Nerd Font
Mono v3.3.0 (regular, 2.3MB ttf) are now served from /fonts/ in public.

Removes runtime dependency on raw.githubusercontent.com for font loading.
2026-03-29 14:05:11 +02:00
Tommy D. Rossi 23bca69782 style(website): increase vertical padding for non-root TOC tree nodes
Non-root tree items (visualLevel > 0) now use 4px vertical padding
instead of 2px, giving nested headings more breathing room while
keeping root-level items compact.
2026-03-29 11:12:22 +02:00
Tommy D. Rossi ad134d3d71 feat(website): add FullWidth MDX component + TOC expand fix + styling tweaks
- Add FullWidth marker component that spans both content and aside grid
  columns (lg:col-[2/-1]). Extracted at parse time like Aside/Hero.
  FullWidth sections get my-5 vertical margin to match content rhythm.

- Fix auto-expand: clicking an expandable TOC item now expands it.
  Previously only ancestors were expanded, not the item itself.

- TOC tree prefix characters use 1.2em font size with negative margin
  so they don't inflate line height. Prefix indentation reduced to 1
  char per level.

- Use FullWidth for the hero screenshot in index.mdx as demo.
2026-03-28 19:19:30 +01:00
Tommy D. Rossi a896fd69cb nicer unicode tree chars 2026-03-28 18:48:57 +01:00
Tommy D. Rossi 41908cd6ad style(website): restyle TOC sidebar — chevron icons, spacing, font weight
- Add SVG ChevronIcon component for expandable items, shown on the right
- Toggle expand only on chevron click, not the whole row
- Remove width: fit-content from aside so it fills its column
- Add 6px margin between root-level TOC groups
- Use regular font weight (400) for all tree items
- Remove lowercase text-transform from tree items
- Remove line-height override from TocLink (inherits from parent)
2026-03-28 18:28:46 +01:00
Tommy D. Rossi d0802128f3 style(website): move font-size from proseStyle to slot-main so asides use toc size
Removed fontSize from the shared proseStyle object and added it to all
slot-main containers instead. Prose elements (P, OL, List) now inherit
font-size from their parent container rather than setting it explicitly,
so inside the aside they respect --type-toc-size (13px) instead of
overriding it with --type-body-size (14px).
2026-03-28 18:11:43 +01:00
Tommy D. Rossi b2adc1caaa style(website): replace aside background with 1px border and match body font size
- SectionRow aside and SidebarBanner now use a border instead of bg fill
- font size changed from --type-toc-size (13px) to --type-body-size (14px)
- line height aligned with prose style (1.6)
2026-03-28 18:07:53 +01:00
Tommy D. Rossi c183e1ba99 style: increase hero top/bottom margin on landing page
Changes Hero className from my-6 to my-16 for more breathing room around
the hero section on the website homepage.
2026-03-28 16:39:07 +01:00
Tommy D. Rossi 65922f1bbf chore: upgrade vite to v8.0.3 across all packages
Bumps vite from various versions (5.x, 6.x, 7.x) to v8.0.3 uniformly:

- root package.json: ^7.2.2 → ^8.0.3
- extension/package.json: ^5.4.21 → ^8.0.3
- website/package.json: ^7.3.0 → ^8.0.3
- playwright submodule package.json: ^6.4.1 → ^8.0.3
2026-03-28 16:39:04 +01:00
Tommy D. Rossi 86773f4cfd Replace TOC search debounce with useTransition for non-blocking input
The 80ms setTimeout debounce blocked the input from reflecting keystrokes
while the TOC re-render was in progress. useTransition splits the work:
setQuery is urgent (input stays responsive), while setSearchState is
deferred and interruptible — new keystrokes cancel in-progress renders.

Input opacity drops to 0.5 during isPending as a subtle loading signal.
2026-03-28 15:09:39 +01:00
Tommy D. Rossi 21cfadce7d feat(website): refactor TOC to recursive tree with multi-page support
Replace flat TocItem/PreparedTocItem/TocGroup types with a recursive
TocTreeNode -> FlatTocItem pipeline that supports arbitrary page nesting.

**Types:**
- TocNodeType = 'page' | 'h1'-'h6' — heading level encoded in type field,
  no separate headingDepth. Smaller objects, self-descriptive.
- TocTreeNode = { label, href, type, children } — recursive tree, the wire format.
- FlatTocItem = { label, href, type, visualLevel, prefix, parentHref, pageHref }
  — flat rendering item with depth clamped to 0-3.

**Depth clamping:**
flattenTocTree() walks the recursive tree and clamps visual depth to max 4
levels (0-3). When pages nest deeply, headings flatten to siblings at level 3.
Formula: visualLevel = min(pageDepth + headingDepth, 3). Preserves correct
visual gap for skipped markdown levels (## -> ####) via the h-type encoding.

**Component changes:**
- TableOfContents accepts FlatTocItem[], renders with visibility filtering
  instead of two-tier TocGroup. Expand/collapse works at any depth by
  tracking expanded hrefs and computing visibility from parentHref chain.
- Search expand override merged into visibility computation so matched items
  inside collapsed branches become visible during search.
- search.ts operates directly on FlatTocItem[] — removed SearchEntry and
  buildSearchEntries(). Walks parentHref chain for ancestor expansion.

**Extraction:**
slugify, extractText, generateTocTree moved to toc-tree.ts (side-effect-free)
so they can be tested without importing the page module.

**Tests:**
21 vitest tests covering slugify, extractText, tree building (nested, skipped
levels, pop-back), flattening (prefixes, depth clamping, page wrapping,
parentHref/pageHref tracking), and full mdast -> tree -> flat pipeline.

**Remaining for multi-page:**
- Multi-page data source (multiple MDX files / manifest)
- Route-aware active tracking (pathname for page, IntersectionObserver for
  headings on current page only)
- Cross-page navigation (SPA or full page load)
2026-03-28 15:03:05 +01:00
Tommy D. Rossi 234b36dae3 feat(website): add Hero MDX component with unified layout gap
Add <Hero> component that gets auto-extracted from MDX at parse time
(same pattern as <Aside>) and rendered above the 3-column grid, aligned
with the center content column.

Layout changes:
- slot-page uses flex column with gap-[--layout-gap] for uniform spacing
  between navbar, hero, and content grid
- --layout-gap (21px) is the single source of truth for both the flex gap
  (flow spacing) and --sticky-top offset (viewport-relative sticky offset)
- Remove --content-top-gap variable and all its usage (spacer divs,
  sidebar padding-top) — the flex gap handles all vertical spacing now
- Hero accepts arbitrary div props for extensibility

Hero extraction pipeline:
- isHeroNode() detects <Hero> mdxJsxFlowElement nodes in the mdast
- Hero nodes are filtered out before TOC generation and section grouping
- Rendered via RenderNodes through SafeMdxRenderer with full component map
- EditorialPage receives hero as a prop, renders it in a matching grid
  (same column widths) so content aligns with the center column
2026-03-28 13:45:40 +01:00
Tommy D. Rossi 64bcd428ed replace index mdx. add hero 2026-03-28 13:43:08 +01:00
Tommy D. Rossi 00242bb71f style: make TOC sidebar scrollable, adjust search input spacing
- Sticky sidebar container gets maxHeight + flex column so TOC scrolls
  when it exceeds viewport height
- Search input stays pinned at top (flexShrink: 0), nav scrolls below
- Nav gets paddingRight 4px for scrollbar gap
- Search input padding-y reduced from 4px to 2px
- Search-to-tree gap set to 12px
2026-03-28 12:55:24 +01:00
Tommy D. Rossi 1ed2077d13 feat: add TOC search with Orama full-text search
Adds a search input above the left sidebar table of contents powered by
Orama (in-memory, typo-tolerant, ~20μs per query).

**search.ts** — flat SearchEntry[] data model with parent/group references,
Orama DB creation, searchToc() that returns matched/dimmed/focusable hrefs
and group expand overrides. Reference: mintlify.com/oramasearch/orama/quickstart.md

**markdown.tsx** changes:
- TocLink: new props dimmed (opacity 0.3 + tabindex=-1), isHighlighted
  (code-bg background), linkRef (scroll-into-view)
- TableOfContents: search input with F hotkey badge, 80ms debounced Orama
  search, arrow key navigation through matched results, Enter navigates
  to section anchor, Escape clears and blurs
- Groups containing matches auto-expand, non-matching items dimmed at 0.3
- IntersectionObserver scroll lock via ref to prevent activeId bouncing
  during programmatic smooth scroll (scrollend + safety timeout)
- Global F key listener focuses search (skips input/textarea/contenteditable)
2026-03-28 12:44:37 +01:00
Tommy D. Rossi 9d03a2d4dd fix: add raw import plugin workaround for Vite 7 + @vitejs/plugin-rsc
The built-in vite:asset load hook uses a regex that doesn't match when
query params get normalized to ?raw= (URLSearchParams.toString() adds
the =). This pre-load plugin handles ?raw imports for non-JS files
explicitly.
2026-03-28 12:44:24 +01:00
Tommy D. Rossi 0c98e16867 Update pnpm-lock.yaml 2026-03-28 12:16:18 +01:00
Tommy D. Rossi 967ddce431 Update .gitignore 2026-03-28 12:16:13 +01:00
Tommy D. Rossi d33fbf37f9 stabilize useSyncExternalStore callbacks for TOC active heading
- Wrap subscribe, getSnapshot, getServerSnapshot in useCallback to prevent
  React from tearing down and recreating the IntersectionObserver on every render
- Move window.location.hash read from render into subscribe, keeping render pure
- Add emit() helper that deduplicates ref writes and only notifies React on change
- Initialize activeRef with fallbackId; hash is read on first subscribe
2026-03-28 12:15:45 +01:00
Tommy D. Rossi 9869975290 use useSyncExternalStore for TOC active heading to avoid hydration mismatch
Replaces useState + useEffect with a single useSyncExternalStore that:
- Initializes activeRef from window.location.hash to fix flash on first paint
- Uses IntersectionObserver in subscribe to track scroll position
- Returns fallbackId as server snapshot to prevent hydration errors

One less state hook (no useState, no useEffect for the observer).
2026-03-28 12:07:37 +01:00
Tommy D. Rossi 08cdea91d4 simplify TOC initial hash: read in useState initializer instead of useSyncExternalStore
No-op subscribe meant useSyncExternalStore was effectively a constant.
Replaced with a direct window.location.hash read in the useState
initializer, guarded by typeof window for SSR. One less hook.
2026-03-28 11:57:39 +01:00
Tommy D. Rossi a095b52186 fix TOC flash on initial paint by reading URL hash with useSyncExternalStore
Previously the TOC always initialized activeId to the first item's href,
causing a visible flash when the page loaded with a hash (e.g. #getting-started)
until the IntersectionObserver corrected it.

Now useLocationHash() reads location.hash synchronously during hydration via
useSyncExternalStore, so the first render already highlights the correct section.
The hook also subscribes to hashchange events for anchor link clicks.
2026-03-28 11:53:48 +01:00
Tommy D. Rossi 047b47976a update spiceflow to 1.18.0-rsc.18 in website 2026-03-28 10:11:51 +01:00
Tommy D. Rossi 1dd91074d8 update spiceflow to 1.18.0 in website
Moves from prerelease 1.18.0-rsc.16 to stable 1.18.0.
2026-03-28 10:00:02 +01:00
Tommy D. Rossi aceada75e5 feat(website): add /api/health route 2026-03-27 16:02:17 +01:00
Tommy D. Rossi 464c25109e refactor(website): migrate from React Router to Spiceflow
Replace React Router 7 + Vercel adapter with Spiceflow RSC framework.
The landing page is now a statically prerendered page via .staticPage('/').

Deleted 10 boilerplate files (~450 lines):
- entry.server.tsx (stream handling, bot detection)
- entry.client.tsx (hydration)
- root.tsx (HTML shell, error boundary)
- routes.ts, react-router.config.ts
- routes/_index.tsx, routes/github.tsx, routes/defer-example.tsx
- lib/errors.ts (Sentry), lib/utils.ts (cn, sleep)
- components.json (shadcn config)

Removed 18 dependencies: @react-router/* (4), @vercel/react-router,
react-router, isbot, @vitejs/plugin-react, vite-plugin-environment,
@xmorse/deployment-utils, sentries, @sentry/node, clsx, tailwind-merge,
lucide-react, @radix-ui/react-slot, class-variance-authority, tw-animate-css

Created 2 files:
- src/main.tsx: Spiceflow entry with layout, static page, redirect
- src/pages/index.tsx: editorial page (moved from routes/_index.tsx)

markdown.tsx and pages/index.tsx marked 'use client' since they use
React hooks and DOM event handlers (required by RSC boundary).
2026-03-27 15:55:42 +01:00
Tommy D. Rossi fc3bc77334 refactor(website): replace 5-column gap-slot grid with 3-column + CSS gap-x
The page layout grid previously used 5 explicit columns to embed spacing:

  grid-cols: [210px] [50px] [520px] [50px] [210px]
               TOC    gap   content  gap   sidebar
               col1   col2   col3   col4    col5

The two gap columns (2 and 4) were real grid tracks, not CSS gap. This meant:
- Column references were off-by-two (sidebar = col 5, main = col 3)
- SectionRow subgrid spans referenced col-[3/-1] to skip the gap column
- slot-main had overflow:hidden to prevent content from bleeding into the
  adjacent gap column — this later caused visible clipping of content

New layout uses 3 columns + CSS gap-x:

  grid-cols: [210px] [520px] [210px]   gap-x: 50px (--grid-gap)
               TOC   content  sidebar
               col1    col2    col3

Changes:
- markdown.tsx: grid template from 5 cols to 3 cols + lg:gap-x-(--grid-gap)
  - SectionRow: col-[3/-1] → col-[2/-1], aside col-[3] → col-[2]
  - inline subgrid (section-based layout): col-[3/-1] → col-[2/-1]
  - flat layout slot-main: col-[3] → col-[2]
- globals.css: slot-sidebar-right grid-column 5 → 3
- globals.css: remove overflow:hidden from slot-main — no longer needed
  since there is no adjacent gap column for content to bleed into
- AGENTS.md: update layout diagram to reflect the 3-column structure
  and note that spacing comes from CSS gap-x, not a slot

--grid-max-width calc and --grid-gap variable are unchanged; the total
computed width (1040px) is identical.
2026-03-27 15:30:58 +01:00
Tommy D. Rossi 3c3fed300a refactor(website): switch editorial layout to slot classes 2026-03-27 13:14:14 +01:00
Tommy D. Rossi 98ca9f4a5f Update index.md 2026-03-27 13:13:09 +01:00
Tommy D. Rossi b20e3de0fb refactor(website): switch editorial responsive CSS to lg variants
Replace hardcoded standalone media queries with co-located @variant lg blocks so
base and responsive overrides live together in each selector.

Also make bleed tokens mobile-first (0 by default, enabled at lg) and remove the
now-redundant 650px bleed override in editorial.css.

Set Tailwind breakpoint token --breakpoint-lg to 1080px to match the editorial
grid geometry and keep utility and CSS responsive behavior aligned.
2026-03-26 21:22:26 +01:00
Tommy D. Rossi b23ca29176 refactor(website): extract CSS variable system for header, grid, and typography tokens
Replace ~50 hardcoded magic numbers with a dependency-chain CSS variable system.
Changing one base value (e.g. --logo-height) now automatically updates all
derived values (sticky offsets, fade position, grid max-width, etc.).

Header anatomy chain:
  --header-padding-y → --header-row-height → --header-height → --sticky-top
  --fade-top and --fade-height derive from header-row-height

Grid geometry chain:
  --grid-toc-width + --grid-gap + --grid-content-width → --grid-max-width
  Replaces 3× hardcoded 1040px, 3× 210px/50px/520px grid columns

Typography primitives (markdown.tsx):
  WEIGHT: 8 distinct values → 4 (regular/prose/heading/bold)
  LINE_HEIGHT: 5 values → 2 (heading 1.4 / prose 1.6)
  LETTER_SPACING: 6 values → 3 (prose/code/normal)
  Shared proseStyle object eliminates 5× duplicate 7-line style blocks

Sub-pixel cleanup:
  12.6px → 13px, 12.5px → 13px, 4.8px → 4px, 1.26px → 1px, 1.6px → 2px, 3.2px → 4px

New utility tokens: --type-code-size, --type-table-size, --border-radius-md,
--transition-hover, --mobile-padding, --content-top-gap
2026-03-26 20:53:33 +01:00
Tommy D. Rossi cc346187f0 fix(website): reduce editorial top spacing 80→60px and switch logo to foreground color
Two visual tweaks:

- Editorial layout top spacer: reduced from 80px to 60px in three places
  (TOC paddingTop, grid spacer row, and flat-layout spacer div) so the
  article content starts slightly higher on the page.

- Logo color: was hardcoded orange (#e8602c, matching the hero screenshot
  gradient). Switched to var(--foreground) so the logo adapts to dark/light
  theme instead of always rendering in orange. The old value is kept as a
  comment for easy rollback if the orange look is preferred.
2026-03-26 19:01:13 +01:00
Tommy D. Rossi 885e47cdeb fix: mobile overflow + desktop bleed clipping for section grid
- Add min-width: 0, max-width: 100%, overflow: hidden on
  .editorial-section-content to prevent content blowing out the grid
  on mobile viewports
- Override to overflow: visible on desktop (1080px+) so that bleed
  elements (negative-margin code blocks and images) render beyond
  the 520px content column
- Adjust TOC paddingTop to 80px
2026-03-26 18:47:31 +01:00
Tommy D. Rossi 644055856f website updates 2026-03-26 18:37:37 +01:00
Tommy D. Rossi 5f93f9909c lower bleeding and nicer aside in website 2026-03-26 18:12:00 +01:00
Tommy D. Rossi f64f949123 website: add <Aside> MDX component with section-based sticky sidebar
Split the mdast tree at ## headings into sections. Each section becomes a
CSS subgrid row spanning columns 3-5. <Aside> JSX elements are extracted
from each section and rendered in the right sidebar column (col 5) with
position: sticky, anchored to the section's vertical extent.

Layout:
- TOC (col 1) spans all rows via grid-row: 1/span 100
- Section content (col 3) renders headings, paragraphs, code blocks
- Section aside (col 5) sticks at --sticky-top (81px) while scrolling
  through its section, then scrolls away when the next section begins
- Mobile (<1080px): asides render inline as styled callout boxes

Other changes:
- Add --sticky-top CSS variable, used by both TOC and aside
- Add symmetric padding (16px top+bottom) to header navbar
- Add two example <Aside> blocks in index.md (Getting started, Accessibility snapshots)
2026-03-26 18:06:12 +01:00
Tommy D. Rossi 0f73c1f6e2 fix: reduce shade height to 48px, keep original gradient curve 2026-03-26 18:04:23 +01:00
Tommy D. Rossi ded7141cc6 fix: shade gradient starts below header, only fades content area
Moved ::before shade from top:0 to top:81px (below the 44px logo row +
36px tab row + 1px border). Reduced height from 8rem to 6rem since it
no longer needs to cover the header area.
2026-03-26 17:43:55 +01:00
Tommy D. Rossi cd51f51ae2 fix: move overflow:hidden from grid to content column to restore sticky TOC
overflow:hidden on .editorial-grid broke position:sticky on the TOC sidebar.
Moved it to .editorial-grid-content which only clips the content column,
leaving the TOC free to stick.
2026-03-26 17:38:44 +01:00
Tommy D. Rossi 8c8b0de25d website: collapsible TOC, tab navigation, CSS grid responsive layout
- Collapsible TOC sidebar: sections start collapsed, auto-expand on scroll,
  click to toggle. First section with children expanded by default.
- Mintlify-style tab bar: intro/examples/changelog tabs with 1.5px active
  indicator and faux bold via text-shadow.
- Header row with logo above tabs, both sticky at top.
- CSS grid layout with named classes: 210px TOC | 50px gap | 520px content
  | 50px gap | 210px mirror, centered via max-width + margin auto.
- Responsive: below 1080px collapses to single column, hides TOC/sidebar,
  zeroes bleed variables, adds horizontal padding.
- Images capped at max-width: 100% on all breakpoints. PixelatedImage
  container uses min(width, 100%). Bleed component gets overflow: hidden.
- SidebarBanner component for right gutter CTA cards.
- TabLink, TocLink extracted as reusable components.
2026-03-26 17:36:08 +01:00
Tommy D. Rossi a1fe5c3002 website: switch to safe-mdx for index page rendering
Migrate from hand-written JSX components to MDX content rendered via safe-mdx.
Content now lives in src/content/index.md as standard markdown + JSX components.

- Parse MDX with mdxParse(), render with SafeMdxRenderer and component overrides
- Map markdown elements to editorial components: p→P, a→A, code→Code, ul→List, ol→OL, li→Li
- Headings via renderNode → SectionHeading with auto-slugified IDs
- Code blocks via renderNode → CodeBlock with Prism.highlight() (SSR-safe, no useEffect)
- TOC auto-generated from mdast headings instead of manual array
- Add Bleed component for image bleeds (--bleed-image: 48px)
- Fix code block alignment: --bleed: 44px so code text aligns with prose left edge
- PixelatedImage: make placeholder optional, skip fade-in when absent
- Reduce article gap 32px→20px, body font 15px→14px
- Add 12px padding-top above header logo bar
- Remove string-dedent dependency, add safe-mdx + @types/mdast
2026-03-26 16:44:40 +01:00
Tommy D. Rossi 7112585f02 website: add safe-mdx dependency for MDX rendering without eval 2026-03-26 16:19:46 +01:00
Tommy D. Rossi d69be484d2 website: redesign header, TOC, and layout
- Add 5-column CSS grid layout (TOC | gap | content | gap | sidebar)
- Add sticky header with logo, tab bar (Intro/GitHub/Changelog), and icon links
- Replace text logo with SVG logo using CSS mask-image + --logo-color variable (#e8602c)
- TOC: support multiple expanded sections, remove View Transition API, add toggle
- TOC: remove tree prefixes from root-level items, keep for nested children only
- TOC: sticky positioning with proper alignment to content column
- Increase TOC line height from 1.32 to 1.6
- Remove search bar from header
- Add SidebarBanner and TabLink components
- Add --logo-color CSS variable, remove --logo-filter dark mode hack
- Reduce --bleed from 44px to 16px
2026-03-26 16:14:23 +01:00