diff --git a/CLAUDE.md b/CLAUDE.md index bb465f3..45fb74b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,7 @@ Svelte 5 + TypeScript + Vite. Public repository. | Game data with sources | `src/data/` (decision 004) | | Owned properties and upgrades | `data/properties.ts`, `lib/properties.ts` (011) | | Business production (stock, supplies, value) | `data/production.ts`, `lib/production.ts` (012) | +| Business card order (drag, keys) | `lib/order.ts`, `ui/Panel` `move` (decision 014) | | Theme tokens | `src/app.css` (decision 009) | | CI, templates | `.github/` | | Skills: `handoff`, `finish-pr`; agent: `scout` | `.claude/` | diff --git a/docs/decisions/010-resync.md b/docs/decisions/010-resync.md index 4da5249..8192fa4 100644 --- a/docs/decisions/010-resync.md +++ b/docs/decisions/010-resync.md @@ -29,10 +29,21 @@ delivery that arrives while nobody looks still counts. The wait is 10 minutes for every business and runs on real time for now; which clock the game uses is not confirmed. A raid also cancels a delivery on its way. +- **Segmented bars, drag to resync** (issue #32): stock and supplies bars are split into five + segments like in game (`BAR_SEGMENTS` in `src/data/business-events.ts`), in the resync + controls only; collapsed cards keep a plain bar to stay clean. Each segment is its own box + with an edge (`--track-edge`, 3.4:1 on the panel), so empty segments are easy to see. + In `BusinessBars` each bar is also a slider (`Meter` with `onset`): drag its handle with mouse, + pen or touch, or use the keys (arrows step 1% to the next whole percent, Page Up/Down jump to + the next segment edge, Home/End to 0/100%). A drag rounds to whole percent and snaps to a + segment edge from up to 1.5% away (`dragValue`/`keyValue` in `src/lib/ui.ts`), so 19% and 21% + are reached by key or typing. It commits once, on release, and shows the level under the + pointer as the value while dragging. Typing stays for exact values. - What each event does, and the delivery, are game data in `src/data/business-events.ts` (decision 004). - **Undo takes back the last edit or preset** of that value; in `BusinessBars` one Undo covers - the last change to either bar, events included. It subtracts what the edit changed + the last change to either bar, events included. Dragging and key steps on one bar in a row + are one change (`recordEdit`), so Undo takes back the whole adjustment, not the last 1%. It subtracts what the edit changed rather than restoring the old number, so progress counted since is kept: a cooldown mistyped as 1:30 instead of 2:00 and undone ten minutes later shows 1:50. The undo lives in the component for the session and is not saved. diff --git a/docs/decisions/011-owned-properties.md b/docs/decisions/011-owned-properties.md index 6395eba..be44f3f 100644 --- a/docs/decisions/011-owned-properties.md +++ b/docs/decisions/011-owned-properties.md @@ -15,8 +15,10 @@ no upgrades. Unchecking "owned" keeps the upgrades, so owning it again restores them. Entries for ids the catalog does not list are kept; a saved value that no longer fits its upgrade reads as the default. Logic: `src/lib/properties.ts`. -- **UI:** Settings has a Properties panel with every catalog entry (owned checkbox; upgrades - appear once owned). The Businesses tab shows only owned properties, each panel with a gear +- **UI:** Settings has a Properties panel with every catalog entry: the name as the row label and + a toggle button next to it, "Owned" (accent, filled) or "Not owned" (muted, outlined), with + `aria-pressed` and the name as its accessible label, so the state reads at a glance (issue + #31; it replaced an "Own " checkbox). Upgrades appear once owned. The Businesses tab shows only owned properties, each panel with a gear button that opens the same settings in place; closed, it summarises the bought upgrades. Both components take the catalog as a prop, so `#/kit` previews them with sample entries. diff --git a/docs/decisions/014-card-order.md b/docs/decisions/014-card-order.md new file mode 100644 index 0000000..f23d878 --- /dev/null +++ b/docs/decisions/014-card-order.md @@ -0,0 +1,31 @@ +# 014: Business cards in the user's order + +2026-10-14, issue #30. Builds on decisions 006 and 009. + +## Decision + +- **The title bar is the handle.** `Panel` takes an optional `move` prop: its title bar (with the + dot grips of the in-game cards) becomes a focusable handle with a grab cursor. The Businesses + tab passes it for every card; other panels stay fixed. +- **Pointer drag** (mouse, pen and touch alike, through pointer events), like moving apps on a + phone: pressing the handle lifts the card, which then sticks to the pointer while its slot + stays outlined. Over another card, the two swap: that card slides into the empty slot and + nothing else moves (`swapAt`; issue #30 review: shifting every card in between made them race + along the rows like one long list). A swap needs the pointer in the middle of the other + card (20% in from each side, `cardAt`), measured where cards end up rather than mid-slide, so + a pointer resting on a border does not flick the cards back and forth. Releasing saves the + order and the card glides into its slot; a cancelled pointer leaves the order as it was. The handle has `touch-action: none`, so + a touch that starts on a title bar drags instead of scrolling. +- **Keyboard:** with the handle focused, arrow keys move the card one place back or forward + (reading order, since the grid wraps), Home/End to the first or last place. A polite live + region says where it went, and focus stays on the handle. +- **Saved state** (version 5): `businessOrder`, a list of property ids. Shown cards follow it; + owned businesses it does not list (e.g. bought later) come after in catalog order. Ids of + businesses not owned now are kept at the end, so owning one again does not lose it. + Logic: `src/lib/order.ts`. + +## Why + +Not everyone cares about every business equally, and the in-game card titles already look like +drag handles. Pointer events cover touch without a second code path, unlike HTML drag and drop. +Saving ids rather than positions keeps the order stable when businesses are bought or sold. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 0cdd9bb..64b2b0c 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -20,7 +20,8 @@ free number, add one line below. **Replacing one:** write a new decision, then m - [007](007-state-store.md) State store: unreadable data blocks saving like a newer version; import overrides - [008](008-clock-details.md) Clock details: session play time, opens paused (setting), throttled ticks counted, long gaps asked about - [009](009-terminal-design.md) Terminal look: tokens in `app.css`, self-hosted Oswald (OFL), browser-style tabs, base components in `src/components/ui/` -- [010](010-resync.md) Resync: `EditableValue` for every tracked value, `BusinessBars` with Sold/Raided/Full and supply deliveries, undo keeps progress since +- [010](010-resync.md) Resync: `EditableValue` for every tracked value, `BusinessBars` with Sold/Raided/Full and supply deliveries, undo keeps progress since, five-segment bars you can drag - [011](011-owned-properties.md) Owned properties and upgrades: catalog in `src/data/properties.ts`, toggle/choice upgrades, Settings panel and Businesses gear - [012](012-business-production.md) Business production: rate per exact upgrade combination in `src/data/production.ts`, continuous on play time, unknown rates do not run - [013](013-marked-estimates.md) Missing game values may be estimated by a stated rule from sourced ones, marked "(est.)" wherever shown +- [014](014-card-order.md) Business cards: drag a title bar to lift a card and swap it with another, or move it with arrow keys; order saved as property ids diff --git a/src/App.svelte b/src/App.svelte index 00ad08b..42c244d 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -53,6 +53,8 @@ now={clocks.now} onresync={(id, bars) => (store.state.businesses = { ...store.state.businesses, [id]: bars })} + order={store.state.businessOrder} + onorder={(order) => (store.state.businessOrder = order)} /> {:else if active === 'settings'} diff --git a/src/app.css b/src/app.css index c5bc64d..6c85e95 100644 --- a/src/app.css +++ b/src/app.css @@ -25,6 +25,8 @@ --supplies: #3f6db8; --supplies-text: #8fb2ee; --track: #2b343e; + /* Edge of an empty bar segment: 3.4:1 on --panel, so the segments read before they fill. */ + --track-edge: #6b7a88; --warn: #f0b450; --danger: #ef7a6e; --focus: #ffd75e; diff --git a/src/components/Businesses.svelte b/src/components/Businesses.svelte index 237db7c..4a8b0b5 100644 --- a/src/components/Businesses.svelte +++ b/src/components/Businesses.svelte @@ -1,8 +1,14 @@ + + {#if shown.length === 0}

{#if catalog.length === 0} @@ -64,47 +203,57 @@

{#each shown as property (property.id)} {@const production = productions.find((p) => p.propertyId === property.id)} - - {#if open === property.id} - - {:else} -

{summary(property)}

- {/if} - {#if production} - onresync(property.id, bars)} - /> - {/if} - {#snippet footer()} - {#if production} - - {/if} - - {/snippet} -
+ {/if} + {#snippet footer()} + {#if production} + + {/if} + + {/snippet} + +
+ {/each} +

{announcement}

{/if} diff --git a/src/components/ui/Panel.svelte b/src/components/ui/Panel.svelte index 4f3e61a..9d16fab 100644 --- a/src/components/ui/Panel.svelte +++ b/src/components/ui/Panel.svelte @@ -1,20 +1,42 @@ -
+

- {title} + {#if move} +
+ {@render heading()} +
+ + {:else} +
{@render heading()}
+ {/if}

{@render children()}
{#if footer} @@ -22,6 +44,10 @@ {/if}
+{#snippet heading()} + {title} +{/snippet} +