From 78ecb6c164e4b1c1c8fc0e99a944608367863d0a Mon Sep 17 00:00:00 2001
From: emilis
Date: Wed, 4 Feb 2026 00:46:31 +0000
Subject: [PATCH] host gets dead chat too
---
werewolves-proto/src/game/mod.rs | 34 ++-
werewolves-proto/src/game/night.rs | 10 +-
werewolves-proto/src/game/village.rs | 13 +
werewolves-proto/src/message/dead.rs | 50 +++-
werewolves-proto/src/message/host.rs | 6 +
werewolves-server/src/game.rs | 37 ++-
werewolves/Cargo.toml | 1 +
werewolves/index.scss | 27 +-
werewolves/src/clients/host/host.rs | 275 +++++++++++++++------
werewolves/src/components/chat/deadchat.rs | 15 +-
werewolves/src/main.rs | 1 +
werewolves/src/scroll.rs | 21 ++
12 files changed, 389 insertions(+), 101 deletions(-)
create mode 100644 werewolves/src/scroll.rs
diff --git a/werewolves-proto/src/game/mod.rs b/werewolves-proto/src/game/mod.rs
index 543cd1d..bf41b1d 100644
--- a/werewolves-proto/src/game/mod.rs
+++ b/werewolves-proto/src/game/mod.rs
@@ -83,6 +83,13 @@ impl Game {
}
}
+ pub fn dead_chats_since(&mut self, since: DateTime) -> Vec {
+ match &mut self.state {
+ GameState::Day { village, .. } => village.dead_chat_mut().host_get_since(since),
+ GameState::Night { night } => night.dead_chat_mut().host_get_since(since),
+ }
+ }
+
pub fn process_dead_chat_request(
&mut self,
player_id: PlayerId,
@@ -106,7 +113,10 @@ impl Game {
GameState::Day { village, .. } => {
village.send_dead_chat_message(msg.clone())?
}
- GameState::Night { night } => night.send_dead_chat_message(msg.clone())?,
+ GameState::Night { night } => {
+ let time = night.village().time();
+ night.dead_chat_mut().add(time, msg.clone())?
+ }
}
Ok(ServerToClientMessage::DeadChatMessage(msg))
}
@@ -138,6 +148,28 @@ impl Game {
pub fn process(&mut self, message: HostGameMessage) -> Result {
match (&mut self.state, message) {
+ (_, HostGameMessage::SendChatMessage(msg)) => {
+ let msg = match &mut self.state {
+ GameState::Day { village, .. } => {
+ let time = village.time();
+ village.dead_chat_mut().add_host_message(time, msg)
+ }
+ GameState::Night { night } => {
+ let time = night.village().time();
+ night.dead_chat_mut().add_host_message(time, msg)
+ }
+ };
+ log::info!("host sent dead chat message: {msg:?}");
+
+ Ok(ServerToHostMessage::DeadChatMessage(msg))
+ }
+ (_, HostGameMessage::GetDeadChatSince(since)) => Ok(ServerToHostMessage::DeadChat(
+ match &mut self.state {
+ GameState::Day { village, .. } => village.dead_chat(),
+ GameState::Night { night } => night.dead_chat(),
+ }
+ .host_get_since(since),
+ )),
(GameState::Night { night }, HostGameMessage::SeePlayersWithRoles) => {
Ok(ServerToHostMessage::PlayerStates(
night
diff --git a/werewolves-proto/src/game/night.rs b/werewolves-proto/src/game/night.rs
index e0cf0d0..fe5cc18 100644
--- a/werewolves-proto/src/game/night.rs
+++ b/werewolves-proto/src/game/night.rs
@@ -33,7 +33,7 @@ use crate::{
night::changes::{ChangesLookup, NightChange},
},
message::{
- dead::DeadChatMessage,
+ dead::{DeadChat, DeadChatMessage},
night::{
ActionPrompt, ActionPromptTitle, ActionResponse, ActionResult, ActionType, Visits,
},
@@ -1224,8 +1224,12 @@ impl Night {
&self.village
}
- pub fn send_dead_chat_message(&mut self, msg: DeadChatMessage) -> Result<()> {
- self.village.send_dead_chat_message(msg)
+ pub fn dead_chat(&self) -> &DeadChat {
+ self.village.dead_chat()
+ }
+
+ pub fn dead_chat_mut(&mut self) -> &mut DeadChat {
+ self.village.dead_chat_mut()
}
#[cfg(test)]
diff --git a/werewolves-proto/src/game/village.rs b/werewolves-proto/src/game/village.rs
index 1446117..aa95f15 100644
--- a/werewolves-proto/src/game/village.rs
+++ b/werewolves-proto/src/game/village.rs
@@ -15,6 +15,7 @@
mod apply;
use core::num::NonZeroU8;
+use chrono::{DateTime, Utc};
use rand::Rng;
use serde::{Deserialize, Serialize};
@@ -65,10 +66,22 @@ impl Village {
&self.dead_chat
}
+ pub const fn dead_chat_mut(&mut self) -> &mut DeadChat {
+ &mut self.dead_chat
+ }
+
pub fn send_dead_chat_message(&mut self, msg: DeadChatMessage) -> Result<()> {
self.dead_chat.add(self.time, msg)
}
+ pub fn host_send_dead_chat_message(&mut self, msg: String) -> DeadChatMessage {
+ self.dead_chat.add_host_message(self.time, msg)
+ }
+
+ pub fn host_dead_chat_since(&mut self, since: DateTime) -> Vec {
+ self.dead_chat.host_get_since(since)
+ }
+
pub fn settings(&self) -> GameSettings {
self.settings.clone()
}
diff --git a/werewolves-proto/src/message/dead.rs b/werewolves-proto/src/message/dead.rs
index 0a76eaf..073279c 100644
--- a/werewolves-proto/src/message/dead.rs
+++ b/werewolves-proto/src/message/dead.rs
@@ -15,7 +15,7 @@
use std::collections::HashMap;
-use chrono::{DateTime, Utc};
+use chrono::{DateTime, TimeDelta, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@@ -33,6 +33,7 @@ type Result = core::result::Result;
pub struct DeadChat {
deaths: Vec<(GameTime, CharacterId)>,
messages: HashMap>,
+ new_messages_from: Option>,
}
impl DeadChat {
@@ -49,6 +50,7 @@ impl DeadChat {
Self {
messages,
deaths: Vec::new(),
+ new_messages_from: None,
}
}
@@ -63,6 +65,10 @@ impl DeadChat {
}
pub fn add(&mut self, time: GameTime, msg: DeadChatMessage) -> Result<()> {
+ let start = msg
+ .timestamp
+ .checked_sub_signed(TimeDelta::nanoseconds(1))
+ .unwrap_or_default();
if !self
.deaths
.iter()
@@ -75,9 +81,28 @@ impl DeadChat {
} else {
self.messages.insert(time, vec![msg]);
}
+ self.new_messages_from.get_or_insert(start);
Ok(())
}
+ pub fn add_host_message(&mut self, time: GameTime, msg: String) -> DeadChatMessage {
+ let start = Utc::now();
+ let msg = DeadChatMessage {
+ id: Uuid::new_v4(),
+ message: DeadChatContent::HostMessage(msg),
+ timestamp: start
+ .checked_add_signed(TimeDelta::nanoseconds(1))
+ .unwrap_or_else(Utc::now),
+ };
+ if let Some(msgs) = self.messages.get_mut(&time) {
+ msgs.push(msg.clone());
+ } else {
+ self.messages.insert(time, vec![msg.clone()]);
+ }
+ self.new_messages_from.get_or_insert(start);
+ msg
+ }
+
pub fn set_dead(
&mut self,
dead: impl Iterator- + Clone,
@@ -129,6 +154,23 @@ impl DeadChat {
) {
log::warn!("replaced: {existing:?}");
}
+ self.new_messages_from.get_or_insert(Utc::now());
+ }
+
+ pub fn new_messages_since(&mut self) -> Option> {
+ self.new_messages_from.take()
+ }
+
+ pub fn host_get_since(&self, t: DateTime) -> Vec {
+ let mut messages = self
+ .messages
+ .clone()
+ .into_iter()
+ .flat_map(|(_, msgs)| msgs.into_iter())
+ .filter(|m| m.timestamp >= t)
+ .collect::>();
+ messages.sort_by_key(|m| m.timestamp);
+ messages
}
pub fn get_since(&self, t: DateTime, character: CharacterId) -> Box<[DeadChatMessage]> {
@@ -173,6 +215,7 @@ pub enum DeadChatContent {
cause: DiedTo,
},
TimeChange(GameTime),
+ HostMessage(String),
}
impl DeadChatContent {
@@ -180,13 +223,14 @@ impl DeadChatContent {
match self {
DeadChatContent::PlayerMessage { from, .. } => from.character_id == character_id,
DeadChatContent::Death { character, .. } => character.character_id == character_id,
- DeadChatContent::TimeChange(_) => false,
+ DeadChatContent::TimeChange(_) | DeadChatContent::HostMessage(_) => false,
}
}
pub const fn message(&self) -> Option<&str> {
match self {
- DeadChatContent::PlayerMessage { message, .. } => Some(message.as_str()),
+ DeadChatContent::HostMessage(message)
+ | DeadChatContent::PlayerMessage { message, .. } => Some(message.as_str()),
_ => None,
}
}
diff --git a/werewolves-proto/src/message/host.rs b/werewolves-proto/src/message/host.rs
index 3e7eff0..62a6ad8 100644
--- a/werewolves-proto/src/message/host.rs
+++ b/werewolves-proto/src/message/host.rs
@@ -14,6 +14,7 @@
// along with this program. If not, see .
use core::num::NonZeroU8;
+use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::{
@@ -22,6 +23,7 @@ use crate::{
game::{GameOver, GameSettings, story::GameStory},
message::{
CharacterIdentity,
+ dead::DeadChatMessage,
night::{ActionPrompt, ActionResponse, ActionResult},
},
player::PlayerId,
@@ -48,6 +50,8 @@ pub enum PostGameMessage {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum HostGameMessage {
+ SendChatMessage(String),
+ GetDeadChatSince(DateTime),
Day(HostDayMessage),
Night(HostNightMessage),
PreviousState,
@@ -113,4 +117,6 @@ pub enum ServerToHostMessage {
story: GameStory,
page: usize,
},
+ DeadChat(Vec),
+ DeadChatMessage(DeadChatMessage),
}
diff --git a/werewolves-server/src/game.rs b/werewolves-server/src/game.rs
index b1d1dc9..8a2d11f 100644
--- a/werewolves-server/src/game.rs
+++ b/werewolves-server/src/game.rs
@@ -21,6 +21,7 @@ use crate::{
lobby::{Lobby, LobbyPlayers},
runner::{ClientUpdate, IdentifiedClientMessage, Message},
};
+use chrono::Utc;
use tokio::time::Instant;
use werewolves_proto::{
character::Character,
@@ -259,6 +260,7 @@ impl GameRunner {
}
pub async fn next(&mut self) -> Option {
+ let start = Utc::now();
let msg = match self.comms.message().await {
Ok(Message::Client(IdentifiedClientMessage {
update: ClientUpdate::ConnectStateUpdate,
@@ -343,30 +345,48 @@ impl GameRunner {
};
let pre_time = self.game.village().time();
+ let mut is_host_message = false;
match self.host_message(msg) {
+ Ok(ServerToHostMessage::DeadChatMessage(msg)) => {
+ self.comms
+ .host()
+ .send(ServerToHostMessage::DeadChatMessage(msg))
+ .log_err();
+ is_host_message = true;
+ }
Ok(resp) => {
- self.comms.host().send(resp).log_warn();
+ self.comms.host().send(resp).log_err();
}
Err(err) => {
self.comms
.host()
.send(ServerToHostMessage::Error(err))
- .log_warn();
+ .log_err();
}
}
+ let messages_for_host = self.game.dead_chats_since(start);
+ let msg_count = messages_for_host.len();
+ if !messages_for_host.is_empty()
+ && let Err(err) = self
+ .comms
+ .host()
+ .send(ServerToHostMessage::DeadChat(messages_for_host))
+ {
+ log::error!("sending {msg_count} dead chat messages to host channel: {err}");
+ }
let post_time = self.game.village().time();
if let Some(game_over) = self.game.game_over() {
return Some(game_over);
}
- if pre_time != post_time {
- let newly_dead = self
+ if pre_time != post_time || is_host_message {
+ let dead = self
.game
.village()
.dead_characters()
.into_iter()
.filter_map(|c| c.died_to().map(|_| (c.character_id(), c.player_id())));
- for (char, player) in newly_dead {
+ for (char, player) in dead {
let msgs = self
.game
.village()
@@ -381,6 +401,13 @@ impl GameRunner {
}
pub async fn send_dead_message(&mut self, msg: &DeadChatMessage) -> Result<()> {
+ if let Err(err) = self
+ .comms
+ .host()
+ .send(ServerToHostMessage::DeadChatMessage(msg.clone()))
+ {
+ log::error!("sending message {} to host channel: {err}", msg.id);
+ }
let player_ids = self
.game
.village()
diff --git a/werewolves/Cargo.toml b/werewolves/Cargo.toml
index 73ce2f8..2f88098 100644
--- a/werewolves/Cargo.toml
+++ b/werewolves/Cargo.toml
@@ -15,6 +15,7 @@ web-sys = { version = "0.3", features = [
"HtmlSelectElement",
"HtmlDialogElement",
"DomRect",
+ "WheelEvent",
] }
wasm-bindgen = { version = "=0.2.100" }
log = "0.4"
diff --git a/werewolves/index.scss b/werewolves/index.scss
index cd333af..cb78ec0 100644
--- a/werewolves/index.scss
+++ b/werewolves/index.scss
@@ -89,6 +89,9 @@ body {
user-select: none;
color: rgba(255, 255, 255, 1);
background: black;
+
+ scrollbar-width: thin;
+ scrollbar-color: rgba(255, 255, 255, 0.7) black;
}
app {
@@ -128,6 +131,10 @@ $error_shadow_color: hsla(340, 95%, 61%, 0.7);
$error_shadow_color_2: hsla(0, 95%, 61%, 0.7);
$error_filter: drop-shadow(5px 5px 0 $error_shadow_color) drop-shadow(5px 5px 0 $error_shadow_color_2);
+$host_nav_height: 36px;
+$host_nav_top_pad: 10px;
+$host_nav_bottom_pad: 10px;
+$host_nav_total_height: $host_nav_height + $host_nav_top_pad + $host_nav_bottom_pad;
nav.host-nav {
position: sticky;
@@ -140,7 +147,10 @@ nav.host-nav {
padding-left: 5vw;
padding-right: 5vw;
gap: 10px;
-
+ height: $host_nav_height;
+ overflow-x: scroll;
+ scrollbar-width: none;
+ white-space: nowrap;
}
@@ -2093,7 +2103,6 @@ li.choice {
justify-content: center;
align-items: center;
gap: 30px;
- max-height: 40%;
@media only screen and (min-width : 1600px) {
width: 100%;
@@ -2956,6 +2965,10 @@ dialog {
gap: 3px;
.chat-messages {
+ &:first-child {
+ margin-top: auto;
+ }
+
user-select: text;
padding-inline-start: 0px;
overflow-y: scroll;
@@ -2965,10 +2978,7 @@ dialog {
flex-grow: 1;
max-width: 100%;
max-height: 95vh;
- justify-content: flex-end;
- // scrollbar-width: thin;
- scrollbar-width: none;
- scrollbar-color: rgba(255, 255, 255, 0.7) black;
+
}
.message {
@@ -3127,3 +3137,8 @@ dialog {
flex-wrap: wrap;
gap: 1ch;
}
+
+.host-dead-chat {
+ height: calc(100vh - $host_nav_total_height);
+ width: 100%;
+}
diff --git a/werewolves/src/clients/host/host.rs b/werewolves/src/clients/host/host.rs
index 6160696..d68cc58 100644
--- a/werewolves/src/clients/host/host.rs
+++ b/werewolves/src/clients/host/host.rs
@@ -29,6 +29,7 @@ use werewolves_proto::{
game::{GameOver, GameSettings, story::GameStory},
message::{
CharacterIdentity, CharacterState, PlayerState, PublicIdentity,
+ dead::DeadChatMessage,
host::{
HostDayMessage, HostGameMessage, HostLobbyMessage, HostMessage, HostNightMessage,
PostGameMessage, ServerToHostMessage,
@@ -44,11 +45,13 @@ use crate::{
components::{
Button, Footer, Lobby, LobbyPlayerAction, RoleReveal, Victory,
action::{ActionResultView, Prompt},
+ chat::DeadChat,
host::{CharacterStatesReadOnly, DaytimePlayerList, Setup, VotingMode},
settings::Settings,
story::Story,
},
pages::RolePage,
+ scroll::vertical_scroll_to_horizontal,
storage::StorageKey,
test_util::TestScreens,
};
@@ -204,6 +207,8 @@ pub enum HostEvent {
ToOverrideView,
ReturnFromOverride,
ExpectEcho(Box),
+ DeadChat(Vec),
+ DeadChatMessage(DeadChatMessage),
}
#[derive(Debug, Clone, PartialEq, Titles)]
pub enum HostState {
@@ -240,11 +245,16 @@ pub enum HostState {
page: usize,
},
CharacterStates(Box<[CharacterState]>),
+ InChat {
+ previously: Box,
+ },
}
impl From for HostEvent {
fn from(msg: ServerToHostMessage) -> Self {
match msg {
+ ServerToHostMessage::DeadChatMessage(msg) => HostEvent::DeadChatMessage(msg),
+ ServerToHostMessage::DeadChat(msgs) => HostEvent::DeadChat(msgs),
ServerToHostMessage::PlayerStates(states) => HostEvent::CharacterList(states),
ServerToHostMessage::QrMode(mode) => HostEvent::QrMode(mode),
ServerToHostMessage::Disconnect => HostEvent::SetState(HostState::Disconnected),
@@ -292,6 +302,7 @@ pub struct Host {
qr_mode: bool,
debug: bool,
expecting_echo: Option,
+ dead_chat: Option>,
}
impl Component for Host {
@@ -323,11 +334,67 @@ impl Component for Host {
}
}),
expecting_echo: None,
+ dead_chat: None,
}
}
fn view(&self, _ctx: &Context) -> Html {
+ if self.dead_chat.is_none()
+ && matches!(&self.state, HostState::Day { .. } | HostState::Prompt(_, _))
+ {
+ let mut send = self.send.clone();
+ let on_err = self.error_callback.clone();
+ yew::platform::spawn_local(async move {
+ if let Err(err) = send
+ .send(HostMessage::InGame(HostGameMessage::GetDeadChatSince(
+ Default::default(),
+ )))
+ .await
+ {
+ on_err.emit(Some(WerewolfError::Send(err)));
+ }
+ });
+ }
+
let content = match self.state.clone() {
+ HostState::InChat { .. } => {
+ let on_send = {
+ let send = self.send.clone();
+ let on_err = self.error_callback.clone();
+ move |msg: String| {
+ let mut send = send.clone();
+ let on_err = on_err.clone();
+ yew::platform::spawn_local(async move {
+ if let Err(err) = send
+ .send(HostMessage::InGame(HostGameMessage::SendChatMessage(msg)))
+ .await
+ {
+ on_err.emit(Some(WerewolfError::Send(err)))
+ }
+ });
+ }
+ };
+ let messages = self.dead_chat.clone().unwrap_or_default();
+ if messages.is_empty() {
+ let mut send = self.send.clone();
+ let on_err = self.error_callback.clone();
+ yew::platform::spawn_local(async move {
+ if let Err(err) = send
+ .send(HostMessage::InGame(HostGameMessage::GetDeadChatSince(
+ Default::default(),
+ )))
+ .await
+ {
+ on_err.emit(Some(WerewolfError::Send(err)));
+ }
+ });
+ }
+ html! {
+
+
+
+ }
+ }
HostState::VotingMode { characters, .. } => {
html! {
@@ -508,56 +575,66 @@ impl Component for Host {
}
}
};
- let voting_mode_btn = {
- match &self.state {
- HostState::Day { characters, .. } => {
- let on_vote_mode = {
- let scope = _ctx.link().clone();
- let state = self.state.clone();
- let characters = characters.clone();
- move |_| {
- scope.send_message(HostEvent::SetState(HostState::VotingMode {
- return_to: Box::new(state.clone()),
- characters: characters.clone(),
- }))
- }
- };
-
- Some(html! {
-
- })
+ let mut nav_buttons = Vec::::new();
+ let dead_chat_btn = {
+ let on_dead_chat = {
+ let scope = _ctx.link().clone();
+ let state = self.state.clone();
+ move |_| {
+ scope.send_message(HostEvent::SetState(HostState::InChat {
+ previously: Box::new(state.clone()),
+ }))
}
- HostState::VotingMode { .. } => {
- let back =
- crate::callback::send_message(HostMessage::GetState, self.send.clone());
- Some(html! {
-
- })
- }
- _ => None,
+ };
+ html! {
+
}
};
- let view_roles_btn = match &self.state {
+ match &self.state {
+ HostState::Day { characters, .. } => {
+ let on_vote_mode = {
+ let scope = _ctx.link().clone();
+ let state = self.state.clone();
+ let characters = characters.clone();
+ move |_| {
+ scope.send_message(HostEvent::SetState(HostState::VotingMode {
+ return_to: Box::new(state.clone()),
+ characters: characters.clone(),
+ }))
+ }
+ };
+
+ nav_buttons.push(html! {
+
+ });
+
+ nav_buttons.push(dead_chat_btn);
+ }
+ HostState::VotingMode { .. } => {
+ let back = crate::callback::send_message(HostMessage::GetState, self.send.clone());
+ nav_buttons.push(html! {
+
+ });
+ }
HostState::Prompt(_, _) | HostState::Result(_, _) => {
+ let on_prev_click = callback::send_message(
+ HostMessage::InGame(HostGameMessage::PreviousState),
+ self.send.clone(),
+ );
+
+ nav_buttons.push(html! {
+
+ });
+
let on_view_click = crate::callback::send_message(
HostMessage::InGame(HostGameMessage::SeePlayersWithRoles),
self.send.clone(),
);
- Some(html! {
+ nav_buttons.push(html! {
- })
- }
- HostState::CharacterStates(_) => {
- let back = crate::callback::send_message(HostMessage::GetState, self.send.clone());
- Some(html! {
-
- })
- }
- _ => None,
- };
- let override_screens_btn = match &self.state {
- HostState::Prompt(_, _) | HostState::Result(_, _) => {
+ });
+
let overrides_click = {
let scope = _ctx.link().clone();
Callback::from(move |_| {
@@ -565,39 +642,12 @@ impl Component for Host {
})
};
- Some(html! {
+ nav_buttons.push(html! {
- })
- }
- HostState::ScreenOverrides { .. } => {
- let return_click = {
- let scope = _ctx.link().clone();
- Callback::from(move |_| {
- scope.send_message(HostEvent::ReturnFromOverride);
- })
- };
+ });
- Some(html! {
-
- })
- }
- _ => None,
- };
- let previous_btn = match &self.state {
- HostState::Prompt(_, _) | HostState::Result(_, _) => {
- let on_prev_click = callback::send_message(
- HostMessage::InGame(HostGameMessage::PreviousState),
- self.send.clone(),
- );
+ nav_buttons.push(dead_chat_btn);
- Some(html! {
-
- })
- }
- _ => None,
- };
- let skip_btn = match &self.state {
- HostState::Prompt(_, _) | HostState::Result(_, _) => {
let on_skip_click = callback::send_message(
HostMessage::InGame(HostGameMessage::Night(HostNightMessage::SkipAction)),
self.send.clone(),
@@ -609,7 +659,7 @@ impl Component for Host {
})
};
- Some(html! {
+ nav_buttons.push(html! {
{"if this is the final prompt of the night, you may not be able to go back"}
- })
+ });
}
- _ => None,
- };
+ HostState::CharacterStates(_) => {
+ let back = crate::callback::send_message(HostMessage::GetState, self.send.clone());
+ nav_buttons.push(html! {
+
+ });
+ }
+ HostState::ScreenOverrides { .. } => {
+ let return_click = {
+ let scope = _ctx.link().clone();
+ Callback::from(move |_| {
+ scope.send_message(HostEvent::ReturnFromOverride);
+ })
+ };
+
+ nav_buttons.push(html! {
+
+ });
+ }
+ HostState::InChat { previously } => {
+ let return_click = {
+ let scope = _ctx.link().clone();
+ let previously = (**previously).clone();
+ Callback::from(move |_| {
+ scope.send_message(HostEvent::SetState(previously.clone()));
+ })
+ };
+
+ nav_buttons.push(html! {
+
+ });
+ }
+ _ => {}
+ }
+ let on_wheel = vertical_scroll_to_horizontal(".host-nav");
+
let nav = self.big_screen.not().then(|| {
html! {
-