csr version imported
This commit is contained in:
parent
2a1fb81567
commit
b09dcb5fc1
15
Cargo.toml
15
Cargo.toml
@ -15,7 +15,20 @@ leptos = { version = "0.8.2", features = ["nightly"] }
|
|||||||
leptos_meta = { version = "0.8.2" }
|
leptos_meta = { version = "0.8.2" }
|
||||||
leptos_actix = { version = "0.8.2", optional = true }
|
leptos_actix = { version = "0.8.2", optional = true }
|
||||||
leptos_router = { version = "0.8.2", features = ["nightly"] }
|
leptos_router = { version = "0.8.2", features = ["nightly"] }
|
||||||
wasm-bindgen = "=0.2.100"
|
wasm-bindgen = "=0.2.101"
|
||||||
|
anyhow = "1.0.99"
|
||||||
|
thiserror = "2.0.16"
|
||||||
|
itertools = "0.14.0"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
pretty_env_logger = "0.5.0"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
leptos-use = "0.16.2"
|
||||||
|
log = "0.4.28"
|
||||||
|
nucleo-matcher = "0.3.1"
|
||||||
|
reactive_stores = "0.2.5"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0.143"
|
||||||
|
chrono = { version = "0.4.42", features = ["serde"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
csr = ["leptos/csr"]
|
csr = ["leptos/csr"]
|
||||||
|
|||||||
4265
data/base.json
Normal file
4265
data/base.json
Normal file
File diff suppressed because it is too large
Load Diff
11871
data/fluff.json
Normal file
11871
data/fluff.json
Normal file
File diff suppressed because it is too large
Load Diff
23593
data/foundry.json
Normal file
23593
data/foundry.json
Normal file
File diff suppressed because it is too large
Load Diff
74409
data/items.json
Normal file
74409
data/items.json
Normal file
File diff suppressed because it is too large
Load Diff
7088
data/magic.json
Normal file
7088
data/magic.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,8 @@ use leptos_router::{
|
|||||||
StaticSegment, WildcardSegment,
|
StaticSegment, WildcardSegment,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::Dash;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn App() -> impl IntoView {
|
pub fn App() -> impl IntoView {
|
||||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||||
@ -22,7 +24,7 @@ pub fn App() -> impl IntoView {
|
|||||||
<Router>
|
<Router>
|
||||||
<main>
|
<main>
|
||||||
<Routes fallback=move || "Not found.">
|
<Routes fallback=move || "Not found.">
|
||||||
<Route path=StaticSegment("") view=HomePage/>
|
<Route path=StaticSegment("") view=Dash/>
|
||||||
<Route path=WildcardSegment("any") view=NotFound/>
|
<Route path=WildcardSegment("any") view=NotFound/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
17
src/components/header.rs
Normal file
17
src/components/header.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use crate::{ entities::DashboardStoreFields, prelude::* };
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Header () -> impl IntoView {
|
||||||
|
let state = expect_context::<Context>();
|
||||||
|
let campaign = state.campaign();
|
||||||
|
let date = state.date();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<header>
|
||||||
|
<h1>Aboba v{env!("CARGO_PKG_VERSION")} by mk</h1>
|
||||||
|
<h2>{campaign.get_untracked()}</h2>
|
||||||
|
<h3>{move || date.get().to_string()}</h3>
|
||||||
|
</header>
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/components/mod.rs
Normal file
73
src/components/mod.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
use nucleo_matcher::{pattern::{AtomKind, CaseMatching, Pattern, Normalization}, Config, Matcher};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
mod header;
|
||||||
|
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Dash () -> impl IntoView {
|
||||||
|
provide_context(Store::new(Dashboard::mock()));
|
||||||
|
|
||||||
|
use header::Header;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="wrapper">
|
||||||
|
<Header />
|
||||||
|
<main></main>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ItemSearchField () -> impl IntoView {
|
||||||
|
let (query, set_query) = signal("".to_owned());
|
||||||
|
|
||||||
|
let (match_list, set_match_list) = signal(vec![]);
|
||||||
|
|
||||||
|
Effect::new(move |_| {
|
||||||
|
let q = query.get();
|
||||||
|
if !q.trim().is_empty() {
|
||||||
|
let mut matcher = Matcher::new(Config::DEFAULT.match_paths());
|
||||||
|
set_match_list.set(Pattern::parse(&q, CaseMatching::Ignore, Normalization::Smart).match_list(ITEMS_NAMES.iter(), &mut matcher))
|
||||||
|
} else {
|
||||||
|
set_match_list.set(vec![]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
on:input:target=move |ev| {
|
||||||
|
set_query.set(ev.target().value());
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Show when=move || {!query.get().trim().is_empty()}>
|
||||||
|
<p>{query.get()}: {match_list.get().len()}" results found"</p>
|
||||||
|
</Show>
|
||||||
|
<ul class="results">
|
||||||
|
<For
|
||||||
|
each=move || match_list.get().into_iter().map(|(name, _score)| ITEMS_REFS.0.get(*name)).flatten()
|
||||||
|
key=|entry| entry.name.clone()
|
||||||
|
let(item)
|
||||||
|
>
|
||||||
|
<ItemSearchCard item=item />
|
||||||
|
</For>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ItemSearchCard <'a> (item: &'a Item) -> impl IntoView {
|
||||||
|
let card_class = format!("item-search-card {}", item.rarity.as_ref().unwrap_or(&String::new()));
|
||||||
|
view! {
|
||||||
|
<div class=card_class>
|
||||||
|
<div>{item.name.clone()}</div>
|
||||||
|
<div>{item.weight.clone()}</div>
|
||||||
|
<div>{item.rarity.clone()}</div>
|
||||||
|
<button>+</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
368
src/entities/mock.rs
Normal file
368
src/entities/mock.rs
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl Dashboard {
|
||||||
|
pub fn mock () -> Self {
|
||||||
|
Dashboard {
|
||||||
|
campaign: "В Поисках Дмитрия Шардалина".to_owned(),
|
||||||
|
date: NaiveDate::from_ymd(1488, 3, 19),
|
||||||
|
player: PlayerData {
|
||||||
|
name: "Ней Гигга Визард".to_owned(),
|
||||||
|
image: String::new(),
|
||||||
|
level: 8,
|
||||||
|
xp: 3231,
|
||||||
|
temp_hp: 12,
|
||||||
|
hp: 1,
|
||||||
|
max_hp: 61,
|
||||||
|
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]),
|
||||||
|
hit_dice: HitDice { used: 1, total: 7 },
|
||||||
|
death_save_throws: DeathSaveThrows { failed: 1, succeeded: 3 },
|
||||||
|
prepared: PreparedSpells(vec![Spell { name: "Fire Bolt".to_owned() }]),
|
||||||
|
inventory: generate_mock_inventory(),
|
||||||
|
},
|
||||||
|
common: CommonData {
|
||||||
|
balance: Balance { platinum: 1312, gold: 3211200, electrum: 231, silver: 1, copper: 21021 },
|
||||||
|
inventory: generate_mock_inventory(),
|
||||||
|
},
|
||||||
|
quest_book: QuestBook(
|
||||||
|
vec![
|
||||||
|
Quest { title: "Slay the Bandit Chief".to_string(), location_taken: Some("Waterdeep".to_string()), location_task: Some("Phandalin".to_string()), date_taken: Some(NaiveDate::from_ymd_opt(1481, 4, 15).unwrap()), date_by: Some(NaiveDate::from_ymd_opt(1481, 5, 15).unwrap()), description: Some("Kill the bandit leader.".to_string()), from: Some("Lord Neverember".to_string()), reward: Some(Balance { platinum: 0, gold: 100, electrum: 0, silver: 0, copper: 0 }) },
|
||||||
|
Quest { title: "Find the Lost Artifact".to_string(), location_taken: Some("Baldur's Gate".to_string()), location_task: None, date_taken: Some(NaiveDate::from_ymd_opt(1481, 6, 1).unwrap()), date_by: None, description: Some("Seek a relic in ruins.".to_string()), from: Some("Elminster".to_string()), reward: Some(Balance { platinum: 10, gold: 50, electrum: 0, silver: 0, copper: 0 }) },
|
||||||
|
Quest { title: "Escort the Caravan".to_string(), location_taken: Some("Neverwinter".to_string()), location_task: Some("Luskan".to_string()), date_taken: None, date_by: Some(NaiveDate::from_ymd_opt(1481, 7, 30).unwrap()), description: Some("Protect traders.".to_string()), from: None, reward: Some(Balance { platinum: 0, gold: 20, electrum: 0, silver: 50, copper: 0 }) },
|
||||||
|
Quest { title: "Clear the Goblin Den".to_string(), location_taken: Some("Daggerford".to_string()), location_task: Some("Cragmaw Hideout".to_string()), date_taken: Some(NaiveDate::from_ymd_opt(1481, 3, 10).unwrap()), date_by: Some(NaiveDate::from_ymd_opt(1481, 4, 10).unwrap()), description: None, from: Some("Local Militia".to_string()), reward: Some(Balance { platinum: 0, gold: 30, electrum: 0, silver: 0, copper: 100 }) },
|
||||||
|
Quest { title: "Rescue the Noble".to_string(), location_taken: None, location_task: Some("Underdark".to_string()), date_taken: Some(NaiveDate::from_ymd_opt(1481, 5, 20).unwrap()), date_by: None, description: Some("Save a kidnapped noble.".to_string()), from: Some("Silverhand Family".to_string()), reward: Some(Balance { platinum: 5, gold: 200, electrum: 0, silver: 0, copper: 0 }) },
|
||||||
|
Quest { title: "Steal the Zhentarim Plans".to_string(), location_taken: Some("Zhentil Keep".to_string()), location_task: Some("Zhentil Keep".to_string()), date_taken: None, date_by: Some(NaiveDate::from_ymd_opt(1481, 8, 15).unwrap()), description: None, from: Some("Harpers".to_string()), reward: Some(Balance { platinum: 0, gold: 75, electrum: 0, silver: 0, copper: 0 }) },
|
||||||
|
Quest { title: "Hunt the Dire Wolf".to_string(), location_taken: Some("Silverymoon".to_string()), location_task: Some("Evermoors".to_string()), date_taken: Some(NaiveDate::from_ymd_opt(1481, 2, 5).unwrap()), date_by: None, description: Some("Track and kill a beast.".to_string()), from: None, reward: Some(Balance { platinum: 0, gold: 15, electrum: 0, silver: 20, copper: 50 }) },
|
||||||
|
Quest { title: "Recover the Spellbook".to_string(), location_taken: Some("Candlekeep".to_string()), location_task: None, date_taken: Some(NaiveDate::from_ymd_opt(1481, 1, 10).unwrap()), date_by: Some(NaiveDate::from_ymd_opt(1481, 2, 10).unwrap()), description: Some("Find a stolen tome.".to_string()), from: Some("Candlekeep Scholars".to_string()), reward: None },
|
||||||
|
Quest { title: "Guard the Temple".to_string(), location_taken: Some("Suzail".to_string()), location_task: Some("Temple of Lathander".to_string()), date_taken: None, date_by: None, description: Some("Protect the shrine.".to_string()), from: Some("Clerics of Lathander".to_string()), reward: Some(Balance { platinum: 0, gold: 50, electrum: 0, silver: 0, copper: 0 }) },
|
||||||
|
Quest { title: "Explore the Ruins".to_string(), location_taken: Some("Cormyr".to_string()), location_task: Some("Netherese Ruins".to_string()), date_taken: Some(NaiveDate::from_ymd_opt(1481, 9, 1).unwrap()), date_by: Some(NaiveDate::from_ymd_opt(1481, 10, 1).unwrap()), description: None, from: None, reward: None },
|
||||||
|
]),
|
||||||
|
notes: NoteBook(
|
||||||
|
vec![
|
||||||
|
Note("Met Elminster in Waterdeep; he hinted at a Netherese ruin nearby.".to_string()),
|
||||||
|
Note("Avoid Zhentarim agents in Baldur's Gate; they're watching the tavern.".to_string()),
|
||||||
|
Note("Phandalin: Redbrand thugs extorting locals, hideout in manor basement.".to_string()),
|
||||||
|
Note("Neverwinter's Lord Neverember offering 100gp for bandit leader's head.".to_string()),
|
||||||
|
Note("Found a strange rune in Underdark; ask Candlekeep scholars about it.".to_string()),
|
||||||
|
Note("Luskan pirates smuggling in the docks; check crates for clues.".to_string()),
|
||||||
|
Note("Silverymoon's wards flicker at midnight; possible arcane sabotage?".to_string()),
|
||||||
|
Note("Daggerford militia needs help with goblin raids from Cragmaw.".to_string()),
|
||||||
|
Note("Temple of Lathander in Suzail requests guards for festival.".to_string()),
|
||||||
|
Note("Heard rumors of a dragon in Evermoors; prepare fire resistance.".to_string()),
|
||||||
|
Note("Harpers want Zhentarim plans stolen from Zhentil Keep by next tenday.".to_string()),
|
||||||
|
Note("Keep an eye on that shady merchant in Cormyr; might be a doppelganger.".to_string()),
|
||||||
|
]),
|
||||||
|
people: generate_mock_contact_book(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_mock_inventory() -> Inventory {
|
||||||
|
let items = vec![
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Vorpal Sword".to_string(),
|
||||||
|
weight: Some(4.0),
|
||||||
|
description: Some("A blade that severs heads with a single swing.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "weapon".to_string()),
|
||||||
|
("rarity".to_string(), "legendary".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 3,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Potion of Healing".to_string(),
|
||||||
|
weight: Some(0.5),
|
||||||
|
description: Some("A vial of red liquid that restores vitality.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "potion".to_string()),
|
||||||
|
("rarity".to_string(), "common".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Cloak of Elvenkind".to_string(),
|
||||||
|
weight: Some(1.0),
|
||||||
|
description: Some("A cloak that blends into shadows.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "uncommon".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 2,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Bag of Holding".to_string(),
|
||||||
|
weight: Some(15.0),
|
||||||
|
description: Some("A small bag with vast internal space.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "uncommon".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Mithril Chainmail".to_string(),
|
||||||
|
weight: Some(20.0),
|
||||||
|
description: Some("Lightweight armor that gleams like silver.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "armor".to_string()),
|
||||||
|
("rarity".to_string(), "rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Wand of Fireballs".to_string(),
|
||||||
|
weight: Some(0.2),
|
||||||
|
description: Some("A wand that unleashes fiery explosions.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wand".to_string()),
|
||||||
|
("rarity".to_string(), "rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Ring of Invisibility".to_string(),
|
||||||
|
weight: Some(0.1),
|
||||||
|
description: Some("Grants the wearer invisibility.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "ring".to_string()),
|
||||||
|
("rarity".to_string(), "legendary".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 2,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Boots of Speed".to_string(),
|
||||||
|
weight: Some(1.5),
|
||||||
|
description: Some("Boots that enhance the wearer's speed.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Dagger of Venom".to_string(),
|
||||||
|
weight: Some(1.0),
|
||||||
|
description: Some("A dagger coated with deadly poison.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "weapon".to_string()),
|
||||||
|
("rarity".to_string(), "rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Staff of Power".to_string(),
|
||||||
|
weight: Some(5.0),
|
||||||
|
description: Some("A staff pulsing with arcane might.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "staff".to_string()),
|
||||||
|
("rarity".to_string(), "very rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 4,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Torch".to_string(),
|
||||||
|
weight: Some(1.0),
|
||||||
|
description: Some("A simple torch for illumination.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "tool".to_string()),
|
||||||
|
("rarity".to_string(), "common".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Amulet of Health".to_string(),
|
||||||
|
weight: Some(0.3),
|
||||||
|
description: Some("Boosts the wearer's vitality.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Flame Tongue".to_string(),
|
||||||
|
weight: Some(3.0),
|
||||||
|
description: Some("A sword that ignites on command.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "weapon".to_string()),
|
||||||
|
("rarity".to_string(), "rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 5,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Arrow of Slaying".to_string(),
|
||||||
|
weight: Some(0.1),
|
||||||
|
description: Some("An arrow designed to kill a specific foe.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "ammunition".to_string()),
|
||||||
|
("rarity".to_string(), "very rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Cloak of Displacement".to_string(),
|
||||||
|
weight: Some(1.0),
|
||||||
|
description: Some("Makes the wearer hard to hit.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Helm of Teleportation".to_string(),
|
||||||
|
weight: Some(3.0),
|
||||||
|
description: Some("Allows teleportation to known locations.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Crystal Ball".to_string(),
|
||||||
|
weight: Some(7.0),
|
||||||
|
description: Some("A magical orb for scrying.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "very rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Gauntlets of Ogre Power".to_string(),
|
||||||
|
weight: Some(2.0),
|
||||||
|
description: Some("Grants the strength of an ogre.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "uncommon".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Robe of the Archmagi".to_string(),
|
||||||
|
weight: Some(1.0),
|
||||||
|
description: Some("Enhances a wizard's spellcasting.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "legendary".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
InventoryEntry {
|
||||||
|
quantity: 1,
|
||||||
|
item: InventoryItem::Custom(CustomInventoryItem {
|
||||||
|
name: "Horn of Blasting".to_string(),
|
||||||
|
weight: Some(2.0),
|
||||||
|
description: Some("Emits a destructive sonic blast.".to_string()),
|
||||||
|
tags: HashMap::from([
|
||||||
|
("type".to_string(), "wondrous".to_string()),
|
||||||
|
("rarity".to_string(), "rare".to_string()),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
Inventory(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn generate_mock_contact_book() -> ContactBook {
|
||||||
|
let mut contacts = HashMap::new();
|
||||||
|
// 40 Characters Met by Party (80%)
|
||||||
|
contacts.insert("Elminster".to_string(), Contact { name: "Elminster".to_string(), image: None, alias: Some("The Sage of Shadowdale".to_string()), status: Some(ContactStatus::Alive), location: Some("Shadowdale".to_string()), notes: Some("Wise but cryptic wizard.".to_string()) });
|
||||||
|
contacts.insert("Drizzt Do'Urden".to_string(), Contact { name: "Drizzt Do'Urden".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Icewind Dale".to_string()), notes: Some("Renegade drow ranger.".to_string()) });
|
||||||
|
contacts.insert("Lord Neverember".to_string(), Contact { name: "Lord Neverember".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Neverwinter".to_string()), notes: Some("Ruler seeking bandit leader.".to_string()) });
|
||||||
|
contacts.insert("Minsc".to_string(), Contact { name: "Minsc".to_string(), image: None, alias: Some("Rascal of Rashemen".to_string()), status: Some(ContactStatus::Alive), location: Some("Baldur's Gate".to_string()), notes: Some("Loud warrior with hamster.".to_string()) });
|
||||||
|
contacts.insert("Jarlaxle".to_string(), Contact { name: "Jarlaxle".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Luskan".to_string()), notes: Some("Sly drow mercenary leader.".to_string()) });
|
||||||
|
contacts.insert("Volo".to_string(), Contact { name: "Volo".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Waterdeep".to_string()), notes: Some("Exaggerates his travel tales.".to_string()) });
|
||||||
|
contacts.insert("Laeral Silverhand".to_string(), Contact { name: "Laeral Silverhand".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Waterdeep".to_string()), notes: Some("Open Lord, powerful mage.".to_string()) });
|
||||||
|
contacts.insert("Hammond Ardeep".to_string(), Contact { name: "Hammond Ardeep".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Daggerford".to_string()), notes: Some("Militia leader vs. goblins.".to_string()) });
|
||||||
|
contacts.insert("Sister Garaele".to_string(), Contact { name: "Sister Garaele".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Phandalin".to_string()), notes: Some("Harper agent in temple.".to_string()) });
|
||||||
|
contacts.insert("Gundren Rockseeker".to_string(), Contact { name: "Gundren Rockseeker".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Phandalin".to_string()), notes: Some("Dwarf seeking lost mine.".to_string()) });
|
||||||
|
contacts.insert("Sildar Hallwinter".to_string(), Contact { name: "Sildar Hallwinter".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Phandalin".to_string()), notes: Some("Lord's Alliance knight.".to_string()) });
|
||||||
|
contacts.insert("Qelline Alderleaf".to_string(), Contact { name: "Qelline Alderleaf".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Phandalin".to_string()), notes: Some("Halfling farmer, kind.".to_string()) });
|
||||||
|
contacts.insert("Toblen Stonehill".to_string(), Contact { name: "Toblen Stonehill".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Phandalin".to_string()), notes: Some("Innkeeper, knows rumors.".to_string()) });
|
||||||
|
contacts.insert("Darathra Shendrel".to_string(), Contact { name: "Darathra Shendrel".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Triboar".to_string()), notes: Some("Lord Protector, fair.".to_string()) });
|
||||||
|
contacts.insert("Ziraj the Hunter".to_string(), Contact { name: "Ziraj the Hunter".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Neverwinter Wood".to_string()), notes: Some("Tracks orcs.".to_string()) });
|
||||||
|
contacts.insert("Eldrin Thalindra".to_string(), Contact { name: "Eldrin Thalindra".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Silverymoon".to_string()), notes: Some("Arcane ward keeper.".to_string()) });
|
||||||
|
contacts.insert("Randal Morn".to_string(), Contact { name: "Randal Morn".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Daggerdale".to_string()), notes: Some("Fights Zhentarim.".to_string()) });
|
||||||
|
contacts.insert("Khelben Blackstaff".to_string(), Contact { name: "Khelben Blackstaff".to_string(), image: None, alias: Some("The Blackstaff".to_string()), status: Some(ContactStatus::Dead), location: Some("Waterdeep".to_string()), notes: Some("Former archmage, deceased.".to_string()) });
|
||||||
|
contacts.insert("Mirt the Moneylender".to_string(), Contact { name: "Mirt the Moneylender".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Waterdeep".to_string()), notes: Some("Harper ally, wealthy.".to_string()) });
|
||||||
|
contacts.insert("Durnan".to_string(), Contact { name: "Durnan".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Waterdeep".to_string()), notes: Some("Yawning Portal owner.".to_string()) });
|
||||||
|
contacts.insert("Fzoul Chembryl".to_string(), Contact { name: "Fzoul Chembryl".to_string(), image: None, alias: None, status: Some(ContactStatus::Dead), location: Some("Zhentil Keep".to_string()), notes: Some("Former Zhentarim leader.".to_string()) });
|
||||||
|
contacts.insert("Bruenor Battlehammer".to_string(), Contact { name: "Bruenor Battlehammer".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Mithral Hall".to_string()), notes: Some("Dwarf king, ally.".to_string()) });
|
||||||
|
contacts.insert("Catti-brie".to_string(), Contact { name: "Catti-brie".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Mithral Hall".to_string()), notes: Some("Skilled archer.".to_string()) });
|
||||||
|
contacts.insert("Regis".to_string(), Contact { name: "Regis".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Icewind Dale".to_string()), notes: Some("Halfling, sneaky.".to_string()) });
|
||||||
|
contacts.insert("Artemis Entreri".to_string(), Contact { name: "Artemis Entreri".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Calimport".to_string()), notes: Some("Dangerous assassin.".to_string()) });
|
||||||
|
contacts.insert("Storm Silverhand".to_string(), Contact { name: "Storm Silverhand".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Shadowdale".to_string()), notes: Some("Bard and Chosen.".to_string()) });
|
||||||
|
contacts.insert("Halia Thornton".to_string(), Contact { name: "Halia Thornton".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Phandalin".to_string()), notes: Some("Zhentarim agent, subtle.".to_string()) });
|
||||||
|
contacts.insert("Darlia Greystone".to_string(), Contact { name: "Darlia Greystone".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Triboar".to_string()), notes: Some("Blacksmith, reliable.".to_string()) });
|
||||||
|
contacts.insert("Eldric Varn".to_string(), Contact { name: "Eldric Varn".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Luskan".to_string()), notes: Some("Ship captain, shady.".to_string()) });
|
||||||
|
contacts.insert("Tymora’s Priest".to_string(), Contact { name: "Tymora’s Priest".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Suzail".to_string()), notes: Some("Blesses adventurers.".to_string()) });
|
||||||
|
contacts.insert("Redbrand Leader".to_string(), Contact { name: "Redbrand Leader".to_string(), image: None, alias: Some("Glasstaff".to_string()), status: Some(ContactStatus::Alive), location: Some("Phandalin".to_string()), notes: Some("Runs thug gang.".to_string()) });
|
||||||
|
contacts.insert("Brynna of Lathander".to_string(), Contact { name: "Brynna of Lathander".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Suzail".to_string()), notes: Some("Temple cleric.".to_string()) });
|
||||||
|
contacts.insert("Korgul the Tumbler".to_string(), Contact { name: "Korgul the Tumbler".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Baldur's Gate".to_string()), notes: Some("Thieves’ guild contact.".to_string()) });
|
||||||
|
contacts.insert("Tharivol the Mage".to_string(), Contact { name: "Tharivol the Mage".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Silverymoon".to_string()), notes: Some("Studies arcane wards.".to_string()) });
|
||||||
|
contacts.insert("Gorlag the Orc".to_string(), Contact { name: "Gorlag the Orc".to_string(), image: None, alias: None, status: Some(ContactStatus::Dead), location: Some("Neverwinter Wood".to_string()), notes: Some("Slain orc chieftain.".to_string()) });
|
||||||
|
contacts.insert("Lhara the Merchant".to_string(), Contact { name: "Lhara the Merchant".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Cormyr".to_string()), notes: Some("Sells rare goods.".to_string()) });
|
||||||
|
contacts.insert("Ebon the Rogue".to_string(), Contact { name: "Ebon the Rogue".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Zhentil Keep".to_string()), notes: Some("Sneaky informant.".to_string()) });
|
||||||
|
contacts.insert("Father Llymic".to_string(), Contact { name: "Father Llymic".to_string(), image: None, alias: None, status: Some(ContactStatus::Dead), location: Some("Icewind Dale".to_string()), notes: Some("Evil priest, defeated.".to_string()) });
|
||||||
|
contacts.insert("Sylvara the Druid".to_string(), Contact { name: "Sylvara the Druid".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("High Forest".to_string()), notes: Some("Protects the woods.".to_string()) });
|
||||||
|
contacts.insert("Raul the Barkeep".to_string(), Contact { name: "Raul the Barkeep".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Daggerford".to_string()), notes: Some("Hears local gossip.".to_string()) });
|
||||||
|
// 5 Contacts (10%)
|
||||||
|
contacts.insert("Valthor the Fence".to_string(), Contact { name: "Valthor the Fence".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Baldur's Gate".to_string()), notes: Some("Buys stolen goods.".to_string()) });
|
||||||
|
contacts.insert("Zyra the Alchemist".to_string(), Contact { name: "Zyra the Alchemist".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Waterdeep".to_string()), notes: Some("Sells potions.".to_string()) });
|
||||||
|
contacts.insert("Kren the Smuggler".to_string(), Contact { name: "Kren the Smuggler".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Luskan".to_string()), notes: Some("Moves contraband.".to_string()) });
|
||||||
|
contacts.insert("Mira the Scribe".to_string(), Contact { name: "Mira the Scribe".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Candlekeep".to_string()), notes: Some("Deciphers texts.".to_string()) });
|
||||||
|
contacts.insert("Thok the Armorer".to_string(), Contact { name: "Thok the Armorer".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: Some("Neverwinter".to_string()), notes: Some("Crafts fine armor.".to_string()) });
|
||||||
|
// 3 Present-Day Legends (6%)
|
||||||
|
contacts.insert("Tiamat".to_string(), Contact { name: "Tiamat".to_string(), image: None, alias: Some("The Dragon Queen".to_string()), status: Some(ContactStatus::Alive), location: None, notes: Some("Evil dragon goddess.".to_string()) });
|
||||||
|
contacts.insert("Vecna".to_string(), Contact { name: "Vecna".to_string(), image: None, alias: None, status: Some(ContactStatus::Alive), location: None, notes: Some("Lich god of secrets.".to_string()) });
|
||||||
|
contacts.insert("Claugiyliamatar".to_string(), Contact { name: "Claugiyliamatar".to_string(), image: None, alias: Some("Old Gnawbone".to_string()), status: Some(ContactStatus::Alive), location: Some("Kryptgarden Forest".to_string()), notes: Some("Ancient green dragon.".to_string()) });
|
||||||
|
// 2 Past Legends (4%, DeadBygone)
|
||||||
|
contacts.insert("Manshoon".to_string(), Contact { name: "Manshoon".to_string(), image: None, alias: None, status: Some(ContactStatus::DeadBygone), location: Some("Zhentil Keep".to_string()), notes: Some("Zhentarim founder, long dead.".to_string()) });
|
||||||
|
contacts.insert("Netherese Archwizard".to_string(), Contact { name: "Netherese Archwizard".to_string(), image: None, alias: None, status: Some(ContactStatus::DeadBygone), location: None, notes: Some("Ancient mage of lost empire.".to_string()) });
|
||||||
|
|
||||||
|
ContactBook(contacts)
|
||||||
|
}
|
||||||
184
src/entities/mod.rs
Normal file
184
src/entities/mod.rs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
use chrono::NaiveDate;
|
||||||
|
use reactive_stores::Store;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
mod mock;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct Item {
|
||||||
|
pub name: String,
|
||||||
|
pub weight: Option<f64>,
|
||||||
|
pub rarity: Option<String>,
|
||||||
|
pub rest: Map<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Item {
|
||||||
|
pub fn from_json (t: Value) -> Option<Self> {
|
||||||
|
let Value::String(name) = t["name"].clone() else { return None };
|
||||||
|
let weight = match t["weight"].clone() {
|
||||||
|
Value::Number(t) => t.as_f64(),
|
||||||
|
Value::String(t) => t.parse().ok(),
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let rarity = if let Value::String(ref rarity) = t["rarity"] {
|
||||||
|
Some(rarity.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let Value::Object(rest) = t else { return None };
|
||||||
|
|
||||||
|
Some(Self { name, weight, rarity, rest })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct ItemReferenceMap (pub HashMap<String, Item>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct Dashboard {
|
||||||
|
pub campaign: String,
|
||||||
|
pub date: NaiveDate,
|
||||||
|
pub player: PlayerData,
|
||||||
|
pub common: CommonData,
|
||||||
|
pub quest_book: QuestBook,
|
||||||
|
pub notes: NoteBook,
|
||||||
|
pub people: ContactBook,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct PlayerData {
|
||||||
|
pub name: String,
|
||||||
|
pub image: String,
|
||||||
|
pub level: u8,
|
||||||
|
pub xp: u32,
|
||||||
|
pub temp_hp: u16,
|
||||||
|
pub hp: u16,
|
||||||
|
pub max_hp: u16,
|
||||||
|
pub balance: Balance,
|
||||||
|
pub spell_slots: SpellSlots,
|
||||||
|
pub inventory: Inventory,
|
||||||
|
// pub consumables: Consumables,
|
||||||
|
pub attunements: Attunements,
|
||||||
|
pub prepared: PreparedSpells,
|
||||||
|
pub hit_dice: HitDice,
|
||||||
|
pub death_save_throws: DeathSaveThrows,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct CommonData {
|
||||||
|
pub balance: Balance,
|
||||||
|
pub inventory: Inventory,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct Balance {
|
||||||
|
pub platinum: u32,
|
||||||
|
pub gold: u32,
|
||||||
|
pub electrum: u32,
|
||||||
|
pub silver: u32,
|
||||||
|
pub copper: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct Inventory (pub Vec<InventoryEntry>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct InventoryEntry {
|
||||||
|
pub quantity: u32,
|
||||||
|
pub item: InventoryItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub enum InventoryItem {
|
||||||
|
Custom (CustomInventoryItem),
|
||||||
|
Book (Item),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct CustomInventoryItem {
|
||||||
|
pub name: String,
|
||||||
|
pub weight: Option<f32>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub tags: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct QuestBook (pub Vec<Quest>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct Quest {
|
||||||
|
pub title: String,
|
||||||
|
pub location_taken: Option<String>,
|
||||||
|
pub location_task: Option<String>,
|
||||||
|
pub date_taken: Option<NaiveDate>,
|
||||||
|
pub date_by: Option<NaiveDate>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub from: Option<String>,
|
||||||
|
pub reward: Option<Balance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct NoteBook (pub Vec<Note>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct Note (pub String);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct SpellSlots (pub [SpellSlotLevel; 9]);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct SpellSlotLevel {
|
||||||
|
pub used: u8,
|
||||||
|
pub total: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct Attunements (pub [Option<String>; 3]);
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct PreparedSpells (pub Vec<Spell>);
|
||||||
|
|
||||||
|
// @TODO
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct Spell {
|
||||||
|
pub name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct HitDice {
|
||||||
|
pub used: u8,
|
||||||
|
pub total: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct DeathSaveThrows {
|
||||||
|
pub failed: u8,
|
||||||
|
pub succeeded: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct ContactBook (pub HashMap<String, Contact>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub struct Contact {
|
||||||
|
pub name: String,
|
||||||
|
pub image: Option<String>,
|
||||||
|
pub alias: Option<String>,
|
||||||
|
pub status: Option<ContactStatus>,
|
||||||
|
pub location: Option<String>,
|
||||||
|
pub notes: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Store)]
|
||||||
|
pub enum ContactStatus {
|
||||||
|
Unknown,
|
||||||
|
DeadBygone,
|
||||||
|
Dead,
|
||||||
|
Alive,
|
||||||
|
}
|
||||||
18
src/lib.rs
18
src/lib.rs
@ -1,5 +1,23 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
|
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate anyhow;
|
||||||
|
|
||||||
|
pub (crate) mod prelude;
|
||||||
|
pub mod entities;
|
||||||
|
pub mod state;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod statics;
|
||||||
|
mod components;
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||||
pub fn hydrate() {
|
pub fn hydrate() {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
|
|||||||
19
src/prelude.rs
Normal file
19
src/prelude.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#![allow(unused_imports)]
|
||||||
|
pub use std::collections::{ HashMap, HashSet };
|
||||||
|
|
||||||
|
pub use serde_json::{ Value, Map };
|
||||||
|
|
||||||
|
pub use log::{ warn as w, log as l, info as i, error as e, trace as t, debug as d };
|
||||||
|
pub use anyhow::{ anyhow, bail };
|
||||||
|
|
||||||
|
pub use reactive_stores::Store;
|
||||||
|
|
||||||
|
pub use crate::{
|
||||||
|
utils,
|
||||||
|
entities::{ ItemReferenceMap, Item, Dashboard },
|
||||||
|
statics::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type Error = anyhow::Error;
|
||||||
|
pub type Result <T> = anyhow::Result<T>;
|
||||||
|
pub type Context = Store<Dashboard>;
|
||||||
1
src/state/mod.rs
Normal file
1
src/state/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
23
src/statics/mod.rs
Normal file
23
src/statics/mod.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref ITEMS_REFS: ItemReferenceMap = {
|
||||||
|
let mut map = HashMap::with_capacity(5_000);
|
||||||
|
|
||||||
|
utils::read_items(include_str!("../../data/items.json"), &mut map);
|
||||||
|
utils::read_items(include_str!("../../data/base.json"), &mut map);
|
||||||
|
utils::read_items(include_str!("../../data/foundry.json"), &mut map);
|
||||||
|
utils::read_items(include_str!("../../data/magic.json"), &mut map);
|
||||||
|
utils::read_items(include_str!("../../data/foundry.json"), &mut map);
|
||||||
|
|
||||||
|
info!("Loaded {} items", map.len());
|
||||||
|
|
||||||
|
ItemReferenceMap(HashMap::from_iter(map.into_iter()))
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ref ITEMS_NAMES: Vec<&'static String> = {
|
||||||
|
ITEMS_REFS.0.keys().collect()
|
||||||
|
};
|
||||||
|
}
|
||||||
21
src/utils/mod.rs
Normal file
21
src/utils/mod.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn read_items (contents: &str, map: &mut HashMap<String, Item>) -> usize {
|
||||||
|
let Ok(json): std::result::Result<Value, _> = serde_json::from_str(&contents) else { error!("Can't read Value from contents"); return 0 };
|
||||||
|
let Value::Array(list) = json else { error!("Value isn't Array, skipping"); return 0 };
|
||||||
|
|
||||||
|
let start = map.len();
|
||||||
|
|
||||||
|
for t in list.into_iter().map(Item::from_json).flatten() {
|
||||||
|
if map.contains_key(&t.name) {
|
||||||
|
let entry = map.get_mut(&t.name).unwrap();
|
||||||
|
entry.weight = entry.weight.or(t.weight);
|
||||||
|
entry.rest.extend(t.rest);
|
||||||
|
} else {
|
||||||
|
map.insert(t.name.clone(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.len() - start
|
||||||
|
}
|
||||||
@ -1,4 +1,21 @@
|
|||||||
|
@use 'reset';
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-size: 1.22em;
|
||||||
text-align: center;
|
}
|
||||||
}
|
|
||||||
|
header {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: 1px solid black;
|
||||||
|
height: 36px;
|
||||||
|
font-size: 28px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-search-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|||||||
940
style/reset.scss
Normal file
940
style/reset.scss
Normal file
@ -0,0 +1,940 @@
|
|||||||
|
@namespace svg "http://www.w3.org/2000/svg";
|
||||||
|
/**
|
||||||
|
* Total Reset
|
||||||
|
* ==================================================
|
||||||
|
* Universal reset of styles for all elements and pseudo-elements for customizing Web Components and browser extensions.
|
||||||
|
*/
|
||||||
|
/* Selector targets all elements except tables and svg elements */
|
||||||
|
*:where(:not(table, thead, tbody, tr, th, td, svg|*)) {
|
||||||
|
/* Resets all styles for the selected elements */
|
||||||
|
all: unset;
|
||||||
|
/* Set box-sizing to border-box so padding and borders do not affect the total width and height of elements */
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* Resets styles for ::before and ::after pseudo-elements */
|
||||||
|
}
|
||||||
|
*:where(:not(table, thead, tbody, tr, th, td, svg|*))::before, *:where(:not(table, thead, tbody, tr, th, td, svg|*))::after {
|
||||||
|
/* Unsets all styles for pseudo-elements */
|
||||||
|
all: unset;
|
||||||
|
/* Set box-sizing to border-box for pseudo-elements */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
abbr,
|
||||||
|
acronym,
|
||||||
|
address,
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
audio,
|
||||||
|
b,
|
||||||
|
big,
|
||||||
|
blockquote,
|
||||||
|
button,
|
||||||
|
canvas,
|
||||||
|
caption,
|
||||||
|
center,
|
||||||
|
cite,
|
||||||
|
code,
|
||||||
|
dd,
|
||||||
|
del,
|
||||||
|
details,
|
||||||
|
dfn,
|
||||||
|
div,
|
||||||
|
dl,
|
||||||
|
dt,
|
||||||
|
em,
|
||||||
|
embed,
|
||||||
|
fieldset,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
form,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
i,
|
||||||
|
iframe,
|
||||||
|
img,
|
||||||
|
ins,
|
||||||
|
kbd,
|
||||||
|
label,
|
||||||
|
legend,
|
||||||
|
li,
|
||||||
|
main,
|
||||||
|
mark,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
ol,
|
||||||
|
output,
|
||||||
|
p,
|
||||||
|
pre,
|
||||||
|
q,
|
||||||
|
ruby,
|
||||||
|
s,
|
||||||
|
samp,
|
||||||
|
section,
|
||||||
|
small,
|
||||||
|
span,
|
||||||
|
strike,
|
||||||
|
strong,
|
||||||
|
sub,
|
||||||
|
summary,
|
||||||
|
sup,
|
||||||
|
table,
|
||||||
|
tbody,
|
||||||
|
td,
|
||||||
|
tfoot,
|
||||||
|
th,
|
||||||
|
thead,
|
||||||
|
time,
|
||||||
|
tr,
|
||||||
|
tt,
|
||||||
|
u,
|
||||||
|
ul,
|
||||||
|
var,
|
||||||
|
video {
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
blockquote,
|
||||||
|
details,
|
||||||
|
div,
|
||||||
|
fieldset,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
form,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
main,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
p,
|
||||||
|
pre,
|
||||||
|
section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio,
|
||||||
|
canvas,
|
||||||
|
video,
|
||||||
|
img,
|
||||||
|
picture,
|
||||||
|
svg {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas,
|
||||||
|
iframe {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
head,
|
||||||
|
link,
|
||||||
|
meta,
|
||||||
|
script,
|
||||||
|
title,
|
||||||
|
template,
|
||||||
|
style {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[href],
|
||||||
|
label[for],
|
||||||
|
select,
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table
|
||||||
|
*/
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
text-indent: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table,
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tr,
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
font-size: 100%;
|
||||||
|
font: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forms
|
||||||
|
*/
|
||||||
|
input {
|
||||||
|
appearance: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=color] {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=color]::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=color]::-webkit-color-swatch {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:required,
|
||||||
|
input {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
input:-webkit-autofill:active {
|
||||||
|
-webkit-box-shadow: 0 0 0 30px white inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=search]::-webkit-search-cancel-button,
|
||||||
|
input[type=search]::-webkit-search-decoration,
|
||||||
|
input[type=search]::-webkit-search-results-button,
|
||||||
|
input[type=search]::-webkit-search-results-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=search] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-box-sizing: content-box;
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
vertical-align: top;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only apply smooth scrolling when the user hasn't set their motion preference to "reduce"
|
||||||
|
*/
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=total-reset.css.map */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modern CSS Reset Tweaks
|
||||||
|
* ==================================================
|
||||||
|
* A collection of modern CSS reset and normalization styles
|
||||||
|
* to ensure consistent behavior across browsers, OS and devices.
|
||||||
|
*/
|
||||||
|
/* Ensure consistent font resizing on mobile devices */
|
||||||
|
html {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
html:focus-within {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Basic body setup for layout and text rendering optimization */
|
||||||
|
body {
|
||||||
|
text-size-adjust: 100%;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply box-sizing globally for consistent element sizing */
|
||||||
|
*,
|
||||||
|
::after,
|
||||||
|
::before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style unclassed links for better accessibility */
|
||||||
|
a:not([class]) {
|
||||||
|
text-decoration-skip-ink: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS Reset Tweaks
|
||||||
|
* Based on Eric Meyer's CSS Reset v2.0-modified (public domain)
|
||||||
|
* URL: http://meyerweb.com/eric/tools/css/reset/
|
||||||
|
*/
|
||||||
|
a,
|
||||||
|
abbr,
|
||||||
|
acronym,
|
||||||
|
address,
|
||||||
|
applet,
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
audio,
|
||||||
|
b,
|
||||||
|
big,
|
||||||
|
blockquote,
|
||||||
|
body,
|
||||||
|
br,
|
||||||
|
button,
|
||||||
|
canvas,
|
||||||
|
caption,
|
||||||
|
center,
|
||||||
|
cite,
|
||||||
|
code,
|
||||||
|
col,
|
||||||
|
colgroup,
|
||||||
|
data,
|
||||||
|
datalist,
|
||||||
|
dd,
|
||||||
|
del,
|
||||||
|
details,
|
||||||
|
dfn,
|
||||||
|
div,
|
||||||
|
dl,
|
||||||
|
dt,
|
||||||
|
em,
|
||||||
|
embed,
|
||||||
|
fieldset,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
form,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
head,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
hr,
|
||||||
|
html,
|
||||||
|
i,
|
||||||
|
iframe,
|
||||||
|
img,
|
||||||
|
input,
|
||||||
|
ins,
|
||||||
|
kbd,
|
||||||
|
label,
|
||||||
|
legend,
|
||||||
|
li,
|
||||||
|
link,
|
||||||
|
main,
|
||||||
|
map,
|
||||||
|
mark,
|
||||||
|
menu,
|
||||||
|
meta,
|
||||||
|
meter,
|
||||||
|
nav,
|
||||||
|
noscript,
|
||||||
|
object,
|
||||||
|
ol,
|
||||||
|
optgroup,
|
||||||
|
option,
|
||||||
|
output,
|
||||||
|
p,
|
||||||
|
param,
|
||||||
|
picture,
|
||||||
|
pre,
|
||||||
|
progress,
|
||||||
|
q,
|
||||||
|
rb,
|
||||||
|
rp,
|
||||||
|
rt,
|
||||||
|
rtc,
|
||||||
|
ruby,
|
||||||
|
s,
|
||||||
|
samp,
|
||||||
|
script,
|
||||||
|
section,
|
||||||
|
select,
|
||||||
|
small,
|
||||||
|
source,
|
||||||
|
span,
|
||||||
|
strong,
|
||||||
|
style,
|
||||||
|
svg,
|
||||||
|
sub,
|
||||||
|
summary,
|
||||||
|
sup,
|
||||||
|
table,
|
||||||
|
tbody,
|
||||||
|
td,
|
||||||
|
template,
|
||||||
|
textarea,
|
||||||
|
tfoot,
|
||||||
|
th,
|
||||||
|
thead,
|
||||||
|
time,
|
||||||
|
title,
|
||||||
|
tr,
|
||||||
|
track,
|
||||||
|
tt,
|
||||||
|
u,
|
||||||
|
ul,
|
||||||
|
var,
|
||||||
|
video,
|
||||||
|
wbr {
|
||||||
|
font-size: 100%;
|
||||||
|
font: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add focus styles to improve accessibility */
|
||||||
|
:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normalize HTML5 elements for older browsers */
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
details,
|
||||||
|
embed,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
main,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
object,
|
||||||
|
section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas,
|
||||||
|
iframe {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove default list styling */
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normalize quote styling */
|
||||||
|
blockquote,
|
||||||
|
q {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
blockquote:before, blockquote:after,
|
||||||
|
q:before,
|
||||||
|
q:after {
|
||||||
|
content: "";
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset and normalize form inputs */
|
||||||
|
input:required,
|
||||||
|
input {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Autofill styling for better compatibility */
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
input:-webkit-autofill:active {
|
||||||
|
-webkit-box-shadow: 0 0 0 30px white inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Improve appearance of search inputs */
|
||||||
|
input[type=search]::-webkit-search-cancel-button,
|
||||||
|
input[type=search]::-webkit-search-decoration,
|
||||||
|
input[type=search]::-webkit-search-results-button,
|
||||||
|
input[type=search]::-webkit-search-results-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=search] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-box-sizing: content-box;
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
vertical-align: top;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent modern browsers from displaying `audio` without controls.
|
||||||
|
* Remove excess height in iOS 5 devices.
|
||||||
|
*/
|
||||||
|
audio:not([controls]) {
|
||||||
|
display: none;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
|
||||||
|
*/
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Improve readability when focused and also mouse hovered in all browsers.
|
||||||
|
*/
|
||||||
|
a:active,
|
||||||
|
a:hover {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make media easier to work with
|
||||||
|
*/
|
||||||
|
audio,
|
||||||
|
img,
|
||||||
|
picture,
|
||||||
|
svg,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address Firefox 3+ setting `line-height` on `input` using `!important` in
|
||||||
|
* the UA stylesheet.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||||
|
* All other form control elements do not inherit `text-transform` values.
|
||||||
|
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
|
||||||
|
* Correct `select` style inheritance in Firefox 4+ and Opera.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
html input[type=button],
|
||||||
|
input[type=reset],
|
||||||
|
input[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-set default cursor for disabled elements.
|
||||||
|
*/
|
||||||
|
button[disabled],
|
||||||
|
html input[disabled] {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Additional attribute handling for accessibility */
|
||||||
|
[disabled],
|
||||||
|
[disabled=true],
|
||||||
|
[aria-disabled=true] {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address box sizing set to content-box in IE 8/9.
|
||||||
|
*/
|
||||||
|
input[type=checkbox],
|
||||||
|
input[type=radio] {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
|
||||||
|
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
|
||||||
|
* (include `-moz` to future-proof).
|
||||||
|
*/
|
||||||
|
input[type=search] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
-webkit-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove inner padding and search cancel button in Safari 5 and Chrome
|
||||||
|
* on OS X.
|
||||||
|
*/
|
||||||
|
input[type=search]::-webkit-search-cancel-button,
|
||||||
|
input[type=search]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove inner padding and border in Firefox 3+.
|
||||||
|
*/
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
input::-moz-focus-inner {
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
vertical-align: top;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove most spacing between table cells.
|
||||||
|
*/
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
text-indent: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on normalize.css v8.0.1
|
||||||
|
* github.com/necolas/normalize.css
|
||||||
|
*/
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
overflow: visible;
|
||||||
|
background: #000;
|
||||||
|
border: 0;
|
||||||
|
height: 1px;
|
||||||
|
line-height: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
page-break-after: always;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
*/
|
||||||
|
pre {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the gray background on active links in IE 10.
|
||||||
|
*/
|
||||||
|
a {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Remove the bottom border in Chrome 57-
|
||||||
|
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||||
|
*/
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
pre,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
small {
|
||||||
|
font-size: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||||
|
* all browsers.
|
||||||
|
*/
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Change the font styles in all browsers.
|
||||||
|
* 2. Remove the margin in Firefox and Safari.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 100%;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the overflow in IE and Edge.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
/* 1 */
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
/* 1 */
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
[type=button],
|
||||||
|
[type=reset],
|
||||||
|
[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner border and padding in Firefox.
|
||||||
|
*/
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type=button]::-moz-focus-inner,
|
||||||
|
[type=reset]::-moz-focus-inner,
|
||||||
|
[type=submit]::-moz-focus-inner {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
color: inherit;
|
||||||
|
white-space: normal;
|
||||||
|
display: block;
|
||||||
|
border: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:not(:-moz-handler-blocked) fieldset {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||||
|
*/
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||||
|
*/
|
||||||
|
[type=number]::-webkit-inner-spin-button,
|
||||||
|
[type=number]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
* 2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
[type=search] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
/* 1 */
|
||||||
|
outline-offset: -2px;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
[type=search]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
* 2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
font: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive
|
||||||
|
========================================================================== */
|
||||||
|
/*
|
||||||
|
* Add the correct display in all browsers.
|
||||||
|
*/
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Misc
|
||||||
|
========================================================================== */
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variables
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Fonts
|
||||||
|
* ========================================================================== */
|
||||||
|
/*
|
||||||
|
* Palette
|
||||||
|
* ========================================================================== */
|
||||||
|
/*
|
||||||
|
* Basic Colors
|
||||||
|
* ========================================================================== */
|
||||||
|
/*
|
||||||
|
* Mixing Colors
|
||||||
|
* ========================================================================== */
|
||||||
|
/**
|
||||||
|
* Typography
|
||||||
|
*/
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: #000;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #000;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
a:focus, a:active, a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
i,
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.br {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=main.css.map */
|
||||||
Loading…
Reference in New Issue
Block a user