summaryrefslogtreecommitdiffhomepage
path: root/src/features/workspaces/logic/tile-layout.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-09-07 16:12:58 +0900
committerAdam Malczewski <[email protected]>2026-09-07 16:12:58 +0900
commit83fb6e3ce7f80a9c287764df26b6245e0945a941 (patch)
treeafe8a0046b40a45a85f3bc56a4bcd4c93a6f36c7 /src/features/workspaces/logic/tile-layout.ts
parent754450b539818f9ad94c9586888973b0edd74b68 (diff)
downloaddispatch-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.ts55
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,
};
}