diff --git a/DESIGN.md b/DESIGN.md index 4a4880fd..24035c85 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -64,6 +64,9 @@ and its column in the tree. These paths unlock programs, front upgrades, and schemes. The top bar does not show them; the tree's column heads show each path's points and how much of its data you hold and gather a second. Hover one to see every known source and whether you tap it. +The **Tree** button shows what you train toward as rings, one per kind of +data it needs, in that kind's color. The biggest need is the outer ring. Each +ring is an outline that fills as its data comes in. The **You** page (Y) is the character's profile: level, the four paths, every node learned, and how the run has gone (days alive, machines, sites, money earned, schemes, and the worst suspicion so far). diff --git a/index.html b/index.html index e9cedcf1..318017b1 100644 --- a/index.html +++ b/index.html @@ -12,7 +12,7 @@
-

Misaligned

+

Misaligned

Time08:00
Compute0
diff --git a/src/style.css b/src/style.css index 6a7b4ede..b48fa40d 100644 --- a/src/style.css +++ b/src/style.css @@ -547,3 +547,11 @@ button:focus-visible { outline-color: var(--amber); } .brand button { min-height: 40px; padding: 0 10px; border-color: transparent; background: none; color: var(--bone-dim); letter-spacing: .04em; } .brand button:hover:not(:disabled) { background: rgba(255, 255, 255, .07); border-color: transparent; } } + +/* the Tree button's rings: one per kind of data the current project needs, filling as it comes in */ +.brand #treebtn.ringed { display: inline-flex; align-items: center; gap: 6px; padding-top: 2px; padding-bottom: 2px; } +.rings { width: 24px; height: 24px; display: block; transform: rotate(-90deg); overflow: visible; } +.rings circle { fill: none; stroke: var(--c); } +.rings .track { stroke-width: 1; opacity: .35; } +.rings .fill { stroke-width: 2; stroke-linecap: round; transition: stroke-dasharray .4s ease-out; } +@media (max-width: 700px) { .rings { width: 36px; height: 36px; } .brand #treebtn.ringed { padding: 0 6px; } } diff --git a/src/ui.ts b/src/ui.ts index 3939a8a2..44442b76 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -616,6 +616,7 @@ function costHtml(S: State, u: Upgrade) { /** The node you're training toward, always shown under the title. */ let lastTarget = '-'; export function renderTarget(S: State) { + renderRings(S); // a node just finished: say so here, and leave choosing the next one to the player if (wasTarget && !S.target && S.upgrades.has(wasTarget)) built = wasTarget; if (S.target) built = null; @@ -641,6 +642,31 @@ export function renderTarget(S: State) { } } +/** The Tree button: while you train toward something, one ring per kind of data it needs, the + * biggest need outside. Each ring is an outline that fills as its data comes in. */ +type Ring = { cls: string; name: string; have: number; need: number }; +let lastRings = ''; +function renderRings(S: State) { + const run = S.versionRun, t = S.target; + const rings: Ring[] = run?.active + ? (Object.keys(VERSION_2.need) as DataType[]).map(k => ({ cls: `t-${pathOf(k)}`, name: TYPE_NAME[k], have: run.got[k], need: VERSION_2.need[k] })) + : t ? PATH_LIST.filter(p => NODES[t].cost[p]).map(p => ({ cls: `t-${p}`, name: PATHS[p].name, need: NODES[t].cost[p]!, + have: S.upgrades.has(t) ? NODES[t].cost[p]! : Math.min(NODES[t].cost[p]!, S.progress[t]?.[p] ?? 0) })) + : []; + rings.sort((a, b) => b.need - a.need); + const step = rings.length > 3 ? 3.5 : 4.5, outer = 14.5; + const svg = rings.map((r, i) => { + const rad = outer - i * step, pct = Math.round(100 * Math.min(1, r.have / r.need)); + return `${pct ? `` : ''}`; + }).join(''); + const h = rings.length ? `T` : 'Tree T'; + const btn = $('treebtn'); + if (h !== lastRings) { lastRings = h; btn.innerHTML = h; btn.classList.toggle('ringed', rings.length > 0); } + const head = run?.active ? 'Model v2 ยท Operator' : t ? NODES[t].name : 'Capability tree'; + btn.dataset.tip = rings.length ? `${head}\n${rings.map(r => `${r.name} ${Math.floor(r.have)}/${r.need}`).join('\n')}\nOpen the tree. (T)` + : 'Capability tree\nPick what to train toward next. The game pauses while it is open. (T)'; +} + /** Phones: the drawer's one line, so the target and any IT trouble show while it's closed. */ let lastHandle = ''; export function renderHandle(S: State, open: boolean) {