diff options
| author | Adam Malczewski <[email protected]> | 2026-09-07 16:12:58 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-09-07 16:12:58 +0900 |
| commit | 83fb6e3ce7f80a9c287764df26b6245e0945a941 (patch) | |
| tree | afe8a0046b40a45a85f3bc56a4bcd4c93a6f36c7 /src/features/workspaces/logic/tile-layout.ts | |
| parent | 754450b539818f9ad94c9586888973b0edd74b68 (diff) | |
| download | dispatch-web-83fb6e3ce7f80a9c287764df26b6245e0945a941.tar.gz dispatch-web-83fb6e3ce7f80a9c287764df26b6245e0945a941.zip | |
feat(workspaces): rework tile-grid visuals — Start-screen polish
The home canvas now reads as a real Windows 8 Start screen instead of a
cramped, edge-hugging list:
- Geometry: the wide unit is DERIVED from the canvas height (4 rows fill
it: unit=height/5 clamped to 112-184px, gap=unit/9, margin=unit/3 clamped
24-64px) — tiles are no longer tiny, and the tile field always carries a
proportional outer margin (never compressed against the left edge). Tall
mode keeps fluid columns with a taller 148px row + 12px gap.
- Tiles take a per-workspace SATURATED tone (new pure tileTone(id): FNV-1a
hash → one of the six theme-semantic colors) with tone-content text —
the Win8 colored-tile identity. Starred is now a ★ badge (a tile keeps
its own color), not a gray-tint override.
- The tile face is the Win8 layout: small uppercase slug caption at the top,
a bold span-aware title (lg → 3xl across 1×1 → 4×2), and a single-line
stat row pinned to the bottom (count compacts to 'conv.' on 1×1 tiles,
truncation-safe so narrow tiles never clip).
- Selection is a high-contrast offset ring (visible on any tile color);
tiles lift on hover; the pan arrow is a solid elevated circle.
- Header gains a 'WORKSPACES' kicker; empty/loading states are centered
and styled for the Start-screen canvas.
Persisted data (order + spans only) and all interaction semantics
(drag/FLIP, long-press, action bar, resize picker, create popover) are
untouched. Tests: geometry derivation/clamping, tileTone determinism,
wide-vs-compact count label — 1664 green.
Diffstat (limited to 'src/features/workspaces/logic/tile-layout.ts')
| -rw-r--r-- | src/features/workspaces/logic/tile-layout.ts | 55 |
1 files changed, 49 insertions, 6 deletions
diff --git a/src/features/workspaces/logic/tile-layout.ts b/src/features/workspaces/logic/tile-layout.ts index 17923c0..4d81a0c 100644 --- a/src/features/workspaces/logic/tile-layout.ts +++ b/src/features/workspaces/logic/tile-layout.ts @@ -46,6 +46,13 @@ export interface GridGeometry { /** Maximum vertical span a tile may occupy in this geometry. */ readonly maxRowSpan: number; readonly unit: TileUnitSize; + /** + * The breathing room around the whole tile field — the grid never renders + * flush against the canvas edge. In wide mode the scroller pads all four + * sides by this amount (the pan arrow/scroll indicator offset inward by it + * too); in tall mode it insets the two fluid columns horizontally. + */ + readonly margin: number; } /** One persisted tile: which workspace it pins, how big it wants to be, and its manual position in the order. */ @@ -82,14 +89,41 @@ export interface Span { /** The viewport assumed before the canvas has been measured (a landscape desktop). */ export const DEFAULT_VIEWPORT = { width: 1280, height: 800 } as const; -const WIDE_UNIT: TileUnitSize = { width: 128, height: 120, gap: 8 }; -const TALL_UNIT: TileUnitSize = { width: 0, height: 120, gap: 8 }; +/** + * The wide unit's clamp band (px). Tiles FILL the canvas' height across the 4 + * fixed rows (the Start-screen feel), but never shrink below a legible small + * tile nor grow past a comfortable large one. + */ +const WIDE_UNIT_MIN = 112; +const WIDE_UNIT_MAX = 184; + +/** The wide canvas' outer margin band (px) — proportional to the unit size. */ +const WIDE_MARGIN_MIN = 24; +const WIDE_MARGIN_MAX = 64; + +/** The tall unit: the two fluid columns size in `1fr` (width `0` = fluid). */ +const TALL_UNIT: TileUnitSize = { width: 0, height: 148, gap: 12 }; +const TALL_MARGIN = 16; + +/** Round to whole pixels (fractional track sizes blur tile edges). */ +function px(value: number): number { + return Math.round(value); +} + +function clampNumber(value: number, min: number, max: number): number { + return Math.max(min, Math.min(max, value)); +} /** * Resolve the grid geometry from the tile canvas' size. Landscape/desktop → - * WIDE (4 fixed ~120px rows, column-wise flow, 4×2 span cap); portrait/mobile - * → TALL (2 fixed columns, row-wise flow, 2×2 span cap). Degenerate sizes - * (not yet measured) resolve to wide. Pure: numbers in, geometry out. + * WIDE (4 fixed rows, column-wise flow, 4×2 span cap); portrait/mobile → TALL + * (2 fixed columns, row-wise flow, 2×2 span cap). Degenerate sizes (not yet + * measured) resolve to wide. + * + * The wide unit is DERIVED from the canvas height so the 4 rows exactly fill + * it (unit + margin + gap all proportional, clamped to their bands); the tall + * unit is fixed (its columns are fluid instead). Pure: numbers in, geometry + * out. */ export function geometryFor(width: number, height: number): GridGeometry { const tall = width > 0 && height > 0 && height > width; @@ -101,15 +135,24 @@ export function geometryFor(width: number, height: number): GridGeometry { maxColSpan: 2, maxRowSpan: 2, unit: TALL_UNIT, + margin: TALL_MARGIN, }; } + // Solve 4·unit + 3·gap + 2·margin = height with gap = unit/9 and margin = + // unit/3: 4u + u/3 + 2u/3 = 5u → u = height/5. Unmeasured (height ≤ 0) → + // the band's middle, not a tiny tile. + const raw = height > 0 ? height / 5 : (WIDE_UNIT_MIN + WIDE_UNIT_MAX) / 2; + const unit = px(clampNumber(raw, WIDE_UNIT_MIN, WIDE_UNIT_MAX)); + const gap = Math.max(10, px(unit / 9)); + const margin = px(clampNumber(unit / 3, WIDE_MARGIN_MIN, WIDE_MARGIN_MAX)); return { mode: "wide", flowRows: 4, flowCols: 0, maxColSpan: 4, maxRowSpan: 2, - unit: WIDE_UNIT, + unit: { width: unit, height: unit, gap }, + margin, }; } |
