diff --git a/src/components/RoadmapRoute.astro b/src/components/RoadmapRoute.astro new file mode 100644 index 0000000..5a8ecb8 --- /dev/null +++ b/src/components/RoadmapRoute.astro @@ -0,0 +1,411 @@ +--- +import type { RoadmapItemWithAttribution, RoadmapStatus } from '../lib/db'; +import { computeRouteLayout, travelledStopFor } from '../lib/roadmap-layout'; + +interface Props { + items: RoadmapItemWithAttribution[]; + viewportWidth?: number; // SSR fallback for the layout math +} + +const { items, viewportWidth = 1100 } = Astro.props; + +const layout = computeRouteLayout({ itemCount: items.length, viewportWidth }); +const travelledStop = travelledStopFor(items.map(i => i.status)); + +const STATUS_LABEL: Record = { + shipping: 'SHIPPING', + in_beta: 'IN BETA', + exploring: 'EXPLORING', + considering: 'CONSIDERING', +}; +const STATUS_LABEL_COLOR: Record = { + shipping: '#6d8c7c', + in_beta: '#b96b58', + exploring: '#b4b2a9', + considering: '#b4b2a9', +}; +const STATUS_DOT_COLOR: Record = { + shipping: '#6d8c7c', + in_beta: '#b96b58', + exploring: '#b4b2a9', + considering: '#d4d2c8', +}; + +// "You are here" — the most recent shipping item. -1 if nothing has shipped yet. +let lastShippingIndex = -1; +items.forEach((it, i) => { if (it.status === 'shipping') lastShippingIndex = i; }); + +function trailingLine(item: RoadmapItemWithAttribution): string | null { + if (item.metadata_text && item.metadata_text.trim().length > 0) return item.metadata_text; + if (item.attributed.length > 0) { + const names = item.attributed.map(a => a.name.split(' ')[0]); + if (names.length === 1) return `Shaped by ${names[0]}`; + if (names.length === 2) return `Shaped by ${names[0]} and ${names[1]}`; + return `Shaped by ${names.slice(0, -1).join(', ')} and ${names.at(-1)}`; + } + return null; +} + +// Progress dots — between 2 and 6, scaling with item count. +const progressDots = Math.max(2, Math.min(6, Math.ceil(items.length / 2))); + +// JSON-stringified ids for the nav script's initial-scroll logic. +const itemXByIndex = layout.itemX; +const initialShippingX = lastShippingIndex >= 0 ? itemXByIndex[lastShippingIndex] : 0; +--- +
+ + +
+
+

The route

+
    +
  • Shipping
  • +
  • In beta
  • +
  • Exploring
  • +
  • Considering
  • +
+
+
+ + +
+
+ + + + + + + + +
    + {items.map((item, i) => ( +
  1. + +
    +

    + {item.target ? `${item.target.toUpperCase()} · ` : ''}{STATUS_LABEL[item.status]} +

    +

    {item.title}

    + {item.description &&

    {item.description}

    } + {trailingLine(item) &&

    {trailingLine(item)}

    } +
    +
  2. + ))} +
+
+ +