From 65d06f1754e831aa71648e45d8f7613040bc055d Mon Sep 17 00:00:00 2001 From: Vantz Stockwell Date: Sat, 23 May 2026 07:58:27 -0400 Subject: [PATCH] Two-column layout with sticky character summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Build Ledger now uses a two-column grid: the builder UI stays on the left, and a new sticky CharacterSummary panel on the right aggregates the whole character at a glance — house, level, total XP, skill/intel point pools (with POI bonus broken out), per-spec levels with cumulative XP and perks unlocked, current faction tier and progress to next, skill allocations grouped by class, and the six most recent perk unlocks across character + specs. The previous separate "Skill Summary" panel is subsumed by the sidebar. Wrap width grows from 1240px to 1560px. Below 1180px the layout collapses to one column and the summary becomes non-sticky. Co-Authored-By: Claude Opus 4.7 (1M context) --- character-builder/frontend/src/App.vue | 119 ++--- .../src/components/CharacterSummary.vue | 465 ++++++++++++++++++ character-builder/frontend/src/styles.css | 24 +- 3 files changed, 517 insertions(+), 91 deletions(-) create mode 100644 character-builder/frontend/src/components/CharacterSummary.vue diff --git a/character-builder/frontend/src/App.vue b/character-builder/frontend/src/App.vue index d867c10..ef79cb3 100644 --- a/character-builder/frontend/src/App.vue +++ b/character-builder/frontend/src/App.vue @@ -3,6 +3,7 @@ import { computed, onMounted, ref, shallowRef, watch } from 'vue'; import XpProgressCard from './components/XpProgressCard.vue'; import FactionTrack from './components/FactionTrack.vue'; import SkillTree from './components/SkillTree.vue'; +import CharacterSummary from './components/CharacterSummary.vue'; import { applyBuild, build, @@ -130,33 +131,20 @@ const totalSpentAcrossClasses = computed(() => Object.values(build.skills).reduce((a, b) => a + (b || 0), 0), ); -// Per-class summary: { classId: { spent, allocations: [{node, points}] } } -const summaryByClass = computed(() => { - const out: Record< - ClassId, - { spent: number; allocations: { tag: string; name: string; kind: string; points: number; max: number }[] } - > = { - benegesserit: { spent: 0, allocations: [] }, - mentat: { spent: 0, allocations: [] }, - planetologist: { spent: 0, allocations: [] }, - swordmaster: { spent: 0, allocations: [] }, - trooper: { spent: 0, allocations: [] }, +// Per-class spent counts — used by the skill-tree tab chips. +const spentByClass = computed>(() => { + const out: Record = { + benegesserit: 0, + mentat: 0, + planetologist: 0, + swordmaster: 0, + trooper: 0, }; for (const c of CLASSES) { const tree = skillTrees.value[c.id]; if (!tree) continue; for (const node of tree.nodes) { - const pts = build.skills[node.tag] || 0; - if (pts > 0) { - out[c.id].spent += pts; - out[c.id].allocations.push({ - tag: node.tag, - name: node.name, - kind: node.kind, - points: pts, - max: node.maxPoints, - }); - } + out[c.id] += build.skills[node.tag] || 0; } } return out; @@ -265,6 +253,9 @@ const specMeta: Record = {

+
+
+
@@ -422,7 +413,7 @@ const specMeta: Record = { > {{ c.name }} - {{ summaryByClass[c.id].spent }} pt{{ - summaryByClass[c.id].spent === 1 ? '' : 's' - }} + {{ spentByClass[c.id] }} pt{{ spentByClass[c.id] === 1 ? '' : 's' }}
@@ -446,70 +435,22 @@ const specMeta: Record = { /> - -
-
-

Skill Summary

-
All allocations across classes
-
-
-
{{ c.name.toUpperCase().slice(0, 3) }}
-

- {{ c.name }} - {{ summaryByClass[c.id].spent }} pts -

-
- no points allocated -
-
    -
  • - - {{ - a.kind.slice(0, 3).toUpperCase() - }} - {{ a.name }} - - {{ a.points }}/{{ a.max }} -
  • -
-
-
-
+ + +