style(roadmap): align first milestone with content column left edge
The first route item now starts where the dispatch banner's left edge sits (the page's content-max column), instead of 60px from the viewport edge. Looks intentional now — the route and the dispatch banner share a vertical anchor. - computeRouteLayout now accepts optional paddingLeft / paddingRight that override the symmetric paddingX. Existing call sites and tests are unchanged. - RoadmapRoute SSR + client recompute set paddingLeft = max(60, (vw - 1152) / 2), so on viewports ≤ 1152px nothing moves (degrades gracefully) and on wider screens the start migrates inward. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8bbf8568f4
commit
220f8e0290
2 changed files with 27 additions and 9 deletions
|
|
@ -9,7 +9,17 @@ interface Props {
|
||||||
|
|
||||||
const { items, viewportWidth = 1100 } = Astro.props;
|
const { items, viewportWidth = 1100 } = Astro.props;
|
||||||
|
|
||||||
const layout = computeRouteLayout({ itemCount: items.length, viewportWidth });
|
// Align the first milestone with the left edge of the page's content column
|
||||||
|
// (matches the LatestDispatchBanner below). --content-max is 72rem = 1152px.
|
||||||
|
const CONTENT_MAX = 1152;
|
||||||
|
const DEFAULT_PADDING = 60;
|
||||||
|
const paddingLeft = Math.max(DEFAULT_PADDING, (viewportWidth - CONTENT_MAX) / 2);
|
||||||
|
|
||||||
|
const layout = computeRouteLayout({
|
||||||
|
itemCount: items.length,
|
||||||
|
viewportWidth,
|
||||||
|
paddingLeft,
|
||||||
|
});
|
||||||
const travelledStop = travelledStopFor(items.map(i => i.status));
|
const travelledStop = travelledStopFor(items.map(i => i.status));
|
||||||
|
|
||||||
const STATUS_LABEL: Record<RoadmapStatus, string> = {
|
const STATUS_LABEL: Record<RoadmapStatus, string> = {
|
||||||
|
|
@ -158,6 +168,7 @@ const initialShippingX = lastShippingIndex >= 0 ? layout.itemX[lastShippingIndex
|
||||||
// amplitude doesn't change with viewport, only the horizontal spread).
|
// amplitude doesn't change with viewport, only the horizontal spread).
|
||||||
const MIN_SPACING = 320;
|
const MIN_SPACING = 320;
|
||||||
const PADDING_X = 60;
|
const PADDING_X = 60;
|
||||||
|
const CONTENT_MAX = 1152; // matches --content-max (72rem)
|
||||||
|
|
||||||
document.querySelectorAll<HTMLElement>('.route').forEach((section) => {
|
document.querySelectorAll<HTMLElement>('.route').forEach((section) => {
|
||||||
const scroll = section.querySelector<HTMLElement>('#rr-scroll');
|
const scroll = section.querySelector<HTMLElement>('#rr-scroll');
|
||||||
|
|
@ -180,14 +191,17 @@ const initialShippingX = lastShippingIndex >= 0 ? layout.itemX[lastShippingIndex
|
||||||
const targetUsableWidth = vw * 0.80;
|
const targetUsableWidth = vw * 0.80;
|
||||||
const dataDrivenWidth = (itemCount - 1) * MIN_SPACING;
|
const dataDrivenWidth = (itemCount - 1) * MIN_SPACING;
|
||||||
const usableWidth = Math.max(targetUsableWidth, dataDrivenWidth);
|
const usableWidth = Math.max(targetUsableWidth, dataDrivenWidth);
|
||||||
const trackWidth = usableWidth + PADDING_X * 2;
|
// Match the SSR offset — first item aligns with the content-column
|
||||||
|
// left edge so the route lines up with the dispatch banner below.
|
||||||
|
const paddingLeft = Math.max(PADDING_X, (vw - CONTENT_MAX) / 2);
|
||||||
|
const trackWidth = paddingLeft + usableWidth + PADDING_X;
|
||||||
|
|
||||||
const itemX: number[] = [];
|
const itemX: number[] = [];
|
||||||
for (let i = 0; i < itemCount; i += 1) {
|
for (let i = 0; i < itemCount; i += 1) {
|
||||||
itemX.push(
|
itemX.push(
|
||||||
itemCount === 1
|
itemCount === 1
|
||||||
? PADDING_X + usableWidth / 2
|
? paddingLeft + usableWidth / 2
|
||||||
: PADDING_X + (i / (itemCount - 1)) * usableWidth,
|
: paddingLeft + (i / (itemCount - 1)) * usableWidth,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ export interface LayoutOpts {
|
||||||
minSpacingX?: number; // default 320
|
minSpacingX?: number; // default 320
|
||||||
trackHeight?: number; // default 460
|
trackHeight?: number; // default 460
|
||||||
amplitude?: number; // default 120
|
amplitude?: number; // default 120
|
||||||
paddingX?: number; // default 60
|
paddingX?: number; // default 60 — symmetric leading + trailing padding
|
||||||
|
paddingLeft?: number; // overrides paddingX on the leading edge only
|
||||||
|
paddingRight?: number; // overrides paddingX on the trailing edge only
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LayoutResult {
|
export interface LayoutResult {
|
||||||
|
|
@ -35,7 +37,9 @@ export function computeRouteLayout(opts: LayoutOpts): LayoutResult {
|
||||||
const minSpacing = opts.minSpacingX ?? 320;
|
const minSpacing = opts.minSpacingX ?? 320;
|
||||||
const trackHeight = opts.trackHeight ?? 420;
|
const trackHeight = opts.trackHeight ?? 420;
|
||||||
const amplitude = opts.amplitude ?? 120;
|
const amplitude = opts.amplitude ?? 120;
|
||||||
const padding = opts.paddingX ?? 60;
|
const paddingDef = opts.paddingX ?? 60;
|
||||||
|
const paddingL = opts.paddingLeft ?? paddingDef;
|
||||||
|
const paddingR = opts.paddingRight ?? paddingDef;
|
||||||
const midY = trackHeight / 2;
|
const midY = trackHeight / 2;
|
||||||
|
|
||||||
const itemCount = Math.max(0, opts.itemCount);
|
const itemCount = Math.max(0, opts.itemCount);
|
||||||
|
|
@ -57,12 +61,12 @@ export function computeRouteLayout(opts: LayoutOpts): LayoutResult {
|
||||||
const targetUsableWidth = opts.viewportWidth * 0.80;
|
const targetUsableWidth = opts.viewportWidth * 0.80;
|
||||||
const dataDrivenWidth = (itemCount - 1) * minSpacing;
|
const dataDrivenWidth = (itemCount - 1) * minSpacing;
|
||||||
const usableWidth = Math.max(targetUsableWidth, dataDrivenWidth);
|
const usableWidth = Math.max(targetUsableWidth, dataDrivenWidth);
|
||||||
const trackWidth = usableWidth + padding * 2;
|
const trackWidth = paddingL + usableWidth + paddingR;
|
||||||
|
|
||||||
const itemX: number[] = Array.from({ length: itemCount }, (_, i) =>
|
const itemX: number[] = Array.from({ length: itemCount }, (_, i) =>
|
||||||
itemCount === 1
|
itemCount === 1
|
||||||
? padding + usableWidth / 2
|
? paddingL + usableWidth / 2
|
||||||
: padding + (i / (itemCount - 1)) * usableWidth,
|
: paddingL + (i / (itemCount - 1)) * usableWidth,
|
||||||
);
|
);
|
||||||
|
|
||||||
// First item on the centreline; subsequent items alternate up/down with
|
// First item on the centreline; subsequent items alternate up/down with
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue