From 294bf430fde7de331a5801e1d5f3fbcdd2d323ab Mon Sep 17 00:00:00 2001 From: yaroslav k Date: Sun, 16 Aug 2026 13:07:07 +0300 Subject: [PATCH] character: drop player card; RU headings; growable attunements; AC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PlayerCard removed — it duplicated the sidebar editors; the character page now shows only prepared spells and attunements - headings are plain RU: Заготовленные заклинания / Атюнменты (no EN/RU double labels; h6 no longer lowercased) - Attunements is Vec> instead of a fixed 3-slot array; '+ слот' appends a slot, so custom campaigns can track more items (old saved JSON parses: arrays map to Vec) - PlayerData.ac (u8, serde default 0): manual armor-class input in the sidebar HP block (hp/оз, ac/КД), editable like the HP numbers --- src/components/character/attunements.rs | 9 ++++- src/components/character/mod.rs | 9 ++--- src/components/character/player_card.rs | 45 --------------------- src/components/character/prepared_spells.rs | 2 +- src/components/mod.rs | 6 +-- src/components/sidebar/hit_points.rs | 15 +++++-- src/entities/mock.rs | 3 +- src/entities/mod.rs | 14 +++++-- style/_character.scss | 33 --------------- style/_sidebar.scss | 8 ++++ 10 files changed, 47 insertions(+), 97 deletions(-) delete mode 100644 src/components/character/player_card.rs diff --git a/src/components/character/attunements.rs b/src/components/character/attunements.rs index 65379df..8336671 100644 --- a/src/components/character/attunements.rs +++ b/src/components/character/attunements.rs @@ -12,7 +12,7 @@ pub fn Attunements () -> impl IntoView { view! {
-
"attunements/слоты привязки:"
+
"Атюнменты:"
impl IntoView { > {slot_row(attunements, idx, slot)} +
} } diff --git a/src/components/character/mod.rs b/src/components/character/mod.rs index b1b834a..d85fa08 100644 --- a/src/components/character/mod.rs +++ b/src/components/character/mod.rs @@ -1,8 +1,7 @@ -//! Character page blocks (route `/`): player card, prepared spells and -//! attunements. The sidebar already carries the detailed editors (names, -//! class/level/XP, HP, hit dice, death saves, spell slots); this module -//! renders the page-level view of that data. +//! Character page blocks (route `/`): prepared spells and attunements. +//! The sidebar carries the character editors (names, class/level/XP, +//! HP, hit dice, death saves, spell slots); this module renders the +//! page-level view. pub mod attunements; -pub mod player_card; pub mod prepared_spells; diff --git a/src/components/character/player_card.rs b/src/components/character/player_card.rs deleted file mode 100644 index 29129d0..0000000 --- a/src/components/character/player_card.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::prelude::*; -use leptos::prelude::*; - -/// Read-only player summary: portrait, name, class, level and XP. -/// -/// The sidebar holds the editors for this data (names, about); this card -/// presents it at the top of the character page. -#[component] -pub fn PlayerCard () -> impl IntoView { - let state = expect_context::(); - let player = state.player(); - - let image = player.image(); - let name = player.name(); - let class = player.class(); - let level = player.level(); - let xp = player.xp(); - - // "First «alias» Last" with the optional parts dropped when empty. - let display_name = move || { - let n = name.get(); - let mut s = n.first.clone(); - if n.display_alias() { - s.push_str(&format!(" «{}»", n.alias)); - } - if n.display_last() { - s.push_str(&format!(" {}", n.last)); - } - s - }; - - view! { -
- - portrait - -
-
{display_name}
-
{move || class.get()}
-
{move || format!("уровень {}", level.get())}
-
{move || format!("{} XP", xp.get())}
-
-
- } -} diff --git a/src/components/character/prepared_spells.rs b/src/components/character/prepared_spells.rs index 6a26100..26e1f20 100644 --- a/src/components/character/prepared_spells.rs +++ b/src/components/character/prepared_spells.rs @@ -16,7 +16,7 @@ pub fn PreparedSpells () -> impl IntoView { view! {
-
"prepared spells/подготовленные заклинания:"
+
"Заготовленные заклинания:"
impl IntoView { view! {
-
diff --git a/src/components/sidebar/hit_points.rs b/src/components/sidebar/hit_points.rs index 1d3df35..0e4d041 100644 --- a/src/components/sidebar/hit_points.rs +++ b/src/components/sidebar/hit_points.rs @@ -2,7 +2,8 @@ use crate::prelude::*; use leptos::prelude::*; /// Current/max HP with a fill bar, plus temp HP with its own bar. -/// All three numbers are editable `type="number"` inputs. +/// All three numbers are editable `type="number"` inputs; AC sits +/// next to them and is set manually. #[component] pub fn HitPoints () -> impl IntoView { let state = expect_context::(); @@ -11,6 +12,7 @@ pub fn HitPoints () -> impl IntoView { let hp = player.hp(); let max_hp = player.max_hp(); let temp_hp = player.temp_hp(); + let ac = player.ac(); // All three inputs parse via `utils::adjust_checked`; invalid text // restores the previous value. @@ -26,13 +28,16 @@ pub fn HitPoints () -> impl IntoView { utils::adjust_checked(ev, temp_hp); }; + let adjust_ac = move |ev: Targeted| { + utils::adjust_checked(ev, ac); + }; + let pc_hp = move |a: u16, b: u16| -> String { format!("width: {}%", utils::filled_pc(a, b).min(100.)) }; - view! { -
hp/оз:
+
"hp/оз, ac/КД:"
@@ -48,6 +53,10 @@ pub fn HitPoints () -> impl IntoView {
+
+ "КД:" + +
} } diff --git a/src/entities/mock.rs b/src/entities/mock.rs index 1f3a2c2..2f9da6e 100644 --- a/src/entities/mock.rs +++ b/src/entities/mock.rs @@ -30,9 +30,10 @@ impl Dashboard { temp_hp: 12, hp: 1, max_hp: 61, + ac: 16, balance: Balance { platinum: 12, gold: 200, electrum: 1, silver: 101, copper: 129021021 }, spell_slots: SpellSlots(core::array::from_fn(|i| SpellSlotLevel { used: 1, total: 9 - i as u8 })), - attunements: Attunements([const { None }; 3]), + attunements: Attunements(vec![None; 3]), hit_dice: HitDice { used: 1, kind: 20 }, death_save_throws: DeathSaveThrows { failed: 1, succeeded: 3 }, prepared: generate_prepared_spells_list(), diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 3575b8a..36e1ec5 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -101,10 +101,11 @@ impl Dashboard { temp_hp: 0, hp: 0, max_hp: 0, + ac: 0, balance: Balance::default(), spell_slots: SpellSlots (core::array::from_fn(|_| SpellSlotLevel { used: 0, total: 0 })), inventory: Inventory::default(), - attunements: Attunements (core::array::from_fn(|_| None)), + attunements: Attunements (vec![None; 3]), prepared: PreparedSpells (Vec::new()), hit_dice: HitDice { used: 0, kind: 8 }, death_save_throws: DeathSaveThrows { failed: 0, succeeded: 0 }, @@ -134,6 +135,10 @@ pub struct PlayerData { pub temp_hp: u16, pub hp: u16, pub max_hp: u16, + /// Armor class; set manually by the player (no derived calculation + /// yet). Missing in old saved JSON, so default to 0. + #[serde(default)] + pub ac: u8, pub balance: Balance, pub spell_slots: SpellSlots, pub inventory: Inventory, @@ -248,9 +253,10 @@ pub struct SpellSlotLevel { pub total: u8, } -/// Three attunement slots (D&D rules allow three attuned items). -#[derive(Clone, Debug, Store, Serialize, Deserialize)] -pub struct Attunements (pub [Option; 3]); +/// Attunement slots (D&D rules allow three attuned items; the UI can +/// add more slots for custom campaigns). +#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)] +pub struct Attunements (pub Vec>); /// The spell list the character has prepared (or is learning). diff --git a/style/_character.scss b/style/_character.scss index 3f65f4c..0b6b638 100644 --- a/style/_character.scss +++ b/style/_character.scss @@ -8,39 +8,8 @@ overflow-y: auto; } -.player-card { - display: flex; - align-items: center; - gap: 18px; - - img { - width: 92px; - height: 92px; - object-fit: cover; - border-radius: 6px; - box-shadow: 0 0 0 2px crimson; - } - - .player-meta { - display: flex; - flex-direction: column; - gap: 2px; - } - - .player-name { - font-size: 1.5em; - font-weight: 900; - } - - .player-class, .player-level, .player-xp { - font-weight: 300; - opacity: 0.8; - } -} - .prepared-spells, .attunements { h6 { - text-transform: lowercase; font-size: 1.1em; margin: 0 0 10px; font-weight: 900; @@ -100,9 +69,7 @@ font-weight: 900; min-width: 200px; } -} -.prepared-spells { .add-slot { @include mixins.button; margin-top: 10px; diff --git a/style/_sidebar.scss b/style/_sidebar.scss index fc1be2f..4debfaa 100644 --- a/style/_sidebar.scss +++ b/style/_sidebar.scss @@ -134,6 +134,14 @@ aside { .val { z-index: 2; } + + .ac { + display: flex; + align-items: center; + gap: 6px; + padding-left: 10px; + border-left: 1px solid rgba(white, 0.15); + } } .hit-dice {