From 3644dbda6e13b5692616ff4d27e01969dc3abed9 Mon Sep 17 00:00:00 2001
From: Robin Appelman <robin@icewind.nl>
Date: Sun, 22 Dec 2024 15:54:14 +0100
Subject: [PATCH] highlight player dot/spec on hover

---
 script/viewer/Analyse/Analyser.tsx          |  10 +-
 script/viewer/Analyse/MapRender.tsx         |   7 +-
 script/viewer/Analyse/Render/KillFeed.tsx   |   6 +-
 script/viewer/Analyse/Render/Player.tsx     |   9 +-
 script/viewer/Analyse/Render/PlayerSpec.tsx | 228 ++++++------
 script/viewer/Analyse/Render/SpecHUD.tsx    |   6 +-
 style/pages/viewer/PlayerSpec.css           | 387 +++++++++++---------
 7 files changed, 355 insertions(+), 298 deletions(-)

diff --git a/script/viewer/Analyse/Analyser.tsx b/script/viewer/Analyse/Analyser.tsx
index 9802757..cce9bf9 100644
--- a/script/viewer/Analyse/Analyser.tsx
+++ b/script/viewer/Analyse/Analyser.tsx
@@ -51,6 +51,7 @@ export const Analyser = (props: AnalyseProps) => {
     const closeDialogs = () => {
         setModalState(ModalState.Closed);
     };
+    const [highlighted, setHighlighted] = createSignal<number>(0);
 
     createEffect(() => {
         const e = event();
@@ -243,7 +244,10 @@ export const Analyser = (props: AnalyseProps) => {
                                projectiles={projectiles()}
                                header={props.header}
                                world={backgroundBoundaries}
-                               scale={scale()}/>
+                               scale={scale()}
+                               onHover={setHighlighted}
+                               highlighted={highlighted()}
+                    />
                 </MapContainer>
                 <AnalyseMenu sessionName={sessionName()}
                              onShare={() => {
@@ -261,7 +265,9 @@ export const Analyser = (props: AnalyseProps) => {
                              inShared={inShared}
                 />
                 <SpecHUD parser={parser} tick={tick()}
-                         players={players()} events={events}/>
+                         players={players()} events={events}
+                         highlighted={highlighted()}
+                         onHover={setHighlighted}/>
             </div>
             <div class="time-control"
                  title={timeTitle()}>
diff --git a/script/viewer/Analyse/MapRender.tsx b/script/viewer/Analyse/MapRender.tsx
index 5058e1f..fafc8f8 100644
--- a/script/viewer/Analyse/MapRender.tsx
+++ b/script/viewer/Analyse/MapRender.tsx
@@ -16,6 +16,8 @@ export interface MapRenderProps {
     },
     world: WorldBoundaries;
     scale: number;
+    onHover: (userId: number) => void;
+    highlighted: number;
 }
 
 const map_root = document.querySelector('[data-maps]').getAttribute('data-maps');
@@ -30,7 +32,10 @@ export function MapRender(props: MapRenderProps) {
              style={{"background-image": background}}>
             <For each={props.players}>{(player) =>
                 <Show when={player.health}>
-                    <PlayerDot player={player} mapBoundary={props.world} targetSize={props.size} scale={props.scale}/>
+                    <PlayerDot player={player} mapBoundary={props.world} targetSize={props.size} scale={props.scale}
+                               onHover={props.onHover}
+                               highlighted={props.highlighted === player.info.userId}
+                    />
                 </Show>
             }</For>
             <For each={props.buildings}>{(building) =>
diff --git a/script/viewer/Analyse/Render/KillFeed.tsx b/script/viewer/Analyse/Render/KillFeed.tsx
index dc41855..617c8c6 100644
--- a/script/viewer/Analyse/Render/KillFeed.tsx
+++ b/script/viewer/Analyse/Render/KillFeed.tsx
@@ -71,7 +71,7 @@ export function KillFeedDestroyedItem(props: KillFeedDestroyedItemProps) {
     return <li class="kill">
         <PlayerNames players={[attacker, assister]}/>
         <KillIcon kill={props.event}/>
-        <PlayerName player={victim}/><span className={teamMap[victim.team]}>({props.event.building_type})</span>
+        <PlayerName player={victim}/><span class={teamMap[victim.team]}>({props.event.building_type})</span>
     </li>
 }
 
@@ -98,7 +98,7 @@ interface PlayerNameProps {
 
 export function PlayerName(props: PlayerNameProps) {
     return <Show when={props.player}>
-        <span className={"player " + teamMap[props.player.team]}>
+        <span class={"player " + teamMap[props.player.team]}>
             {props.player.info.name}
         </span>
     </Show>
@@ -111,7 +111,7 @@ interface PlayerNamesProps {
 export function PlayerNames(props: PlayerNamesProps) {
     return <For each={props.players}>{(player, i) => <>
         <Show when={i() > 0 && player}>
-            <span className={teamMap[player.team]}>+</span>
+            <span class={teamMap[player.team]}>+</span>
         </Show>
         <PlayerName player={player}/>
     </>}</For>
diff --git a/script/viewer/Analyse/Render/Player.tsx b/script/viewer/Analyse/Render/Player.tsx
index 672b594..4f39d3c 100644
--- a/script/viewer/Analyse/Render/Player.tsx
+++ b/script/viewer/Analyse/Render/Player.tsx
@@ -8,6 +8,8 @@ export interface PlayerProp {
         height: number;
     };
     scale: number;
+    onHover: (userId: number) => void;
+    highlighted: boolean;
 }
 
 const healthMap = {
@@ -51,12 +53,15 @@ export function Player(props: PlayerProp) {
     const rotate = () => `rotate(${270 - props.player.angle})`;
 
     return <g
+        onmouseover={() => props.onHover(props.player.info.userId)}
+        onmouseout={() => props.onHover(0)}
         transform={transform()}>
         <polygon points="-6,14 0, 16 6,14 0,24" fill="white"
                  opacity={imageOpacity()}
                  transform={rotate()}/>
-        <circle r={16} stroke-width={1.5} stroke="white" fill={teamColor()}
-                opacity={alpha()}/>
+        <circle r={16} stroke-width={props.highlighted ? 5 : 1.5} stroke="white" fill={teamColor()}
+                opacity={alpha()}
+        />
         {getClassImage(props.player, imageOpacity())}
     </g>
 }
diff --git a/script/viewer/Analyse/Render/PlayerSpec.tsx b/script/viewer/Analyse/Render/PlayerSpec.tsx
index 7434154..029d2e2 100644
--- a/script/viewer/Analyse/Render/PlayerSpec.tsx
+++ b/script/viewer/Analyse/Render/PlayerSpec.tsx
@@ -2,146 +2,162 @@ import {PlayerState} from "../Data/Parser";
 import {KillFeedItem} from "./KillFeed";
 
 export interface PlayerSpecProps {
-	player: PlayerState;
+    player: PlayerState;
+    onHover: (userId: number) => void;
+    highlighted: boolean;
 }
 
 const healthMap = {
-	0: 100, //fallback
-	1: 125, //scout
-	2: 150, //sniper
-	3: 200, //soldier,
-	4: 175, //demoman,
-	5: 150, //medic,
-	6: 300, //heavy,
-	7: 175, //pyro
-	8: 125, //spy
-	9: 125, //engineer
+    0: 100, //fallback
+    1: 125, //scout
+    2: 150, //sniper
+    3: 200, //soldier,
+    4: 175, //demoman,
+    5: 150, //medic,
+    6: 300, //heavy,
+    7: 175, //pyro
+    8: 125, //spy
+    9: 125, //engineer
 };
 
 const classMap = {
-	1: "scout",
-	2: "sniper",
-	3: "soldier",
-	4: "demoman",
-	5: "medic",
-	6: "heavy",
-	7: "pyro",
-	8: "spy",
-	9: "engineer"
+    1: "scout",
+    2: "sniper",
+    3: "soldier",
+    4: "demoman",
+    5: "medic",
+    6: "heavy",
+    7: "pyro",
+    8: "spy",
+    9: "engineer"
 };
 
 const classSort = {
-	1: 1, //scout
-	3: 2, //soldier
-	7: 3, //pyro
-	4: 4, //demoman
-	6: 5, //heavy
-	9: 6, //engineer
-	5: 7, //medic
-	2: 8, //sniper
-	8: 9, //spy
+    1: 1, //scout
+    3: 2, //soldier
+    7: 3, //pyro
+    4: 4, //demoman
+    6: 5, //heavy
+    9: 6, //engineer
+    5: 7, //medic
+    2: 8, //sniper
+    8: 9, //spy
 };
 
 const teamMap = {
-	0: "other",
-	1: "spectator",
-	2: "red",
-	3: "blue",
+    0: "other",
+    1: "spectator",
+    2: "red",
+    3: "blue",
 }
 
 export interface PlayersSpecProps {
-	players: PlayerState[];
+    players: PlayerState[];
+    onHover: (userId: number) => void;
+    highlighted: number;
 }
 
 function sortPlayer(a, b) {
-	return classSort[a.playerClass] - classSort[b.playerClass];
+    return classSort[a.playerClass] - classSort[b.playerClass];
 }
+
 function filterPlayers(players: PlayerState[], team: number): PlayerState[] {
-	const filtered = players.filter((player) => player.team === team);
-	filtered.sort(sortPlayer);
-	return filtered;
+    const filtered = players.filter((player) => player.team === team);
+    filtered.sort(sortPlayer);
+    return filtered;
 }
+
 function medics(players: PlayerState[]): PlayerState[] {
-	return players.filter(player => player.playerClass === 5);
+    return players.filter(player => player.playerClass === 5);
 }
 
 export function PlayersSpec(props: PlayersSpecProps) {
-	const redPlayers = () => filterPlayers(props.players, 2);
-	const bluePlayers = () => filterPlayers(props.players, 3);
-	const redMedics = () => medics(redPlayers());
-	const blueMedics = () => medics(bluePlayers());
-
-	return (<div>
-		<div class="redSpecHolder">
-			<For each={redPlayers()}>{(player) =>
-				<PlayerSpec player={player}/>
-			}</For>
-			<For each={redMedics()}>{(player) =>
-				<UberSpec
-					team={teamMap[player.team]}
-					chargeLevel={player.charge}
-					isDeath={player.health < 1}
-				/>
-			}</For>
-		</div>
-		<div class="blueSpecHolder">
-			<For each={bluePlayers()}>{(player) =>
-				<PlayerSpec player={player}/>
-			}</For>
-			<For each={blueMedics()}>{(player) =>
-				<UberSpec
-					team={teamMap[player.team]}
-					chargeLevel={player.charge}
-					isDeath={player.health < 1}
-				/>
-			}</For>
-		</div>
-	</div>);
+    const redPlayers = () => filterPlayers(props.players, 2);
+    const bluePlayers = () => filterPlayers(props.players, 3);
+    const redMedics = () => medics(redPlayers());
+    const blueMedics = () => medics(bluePlayers());
+
+    return (<div>
+        <div class="redSpecHolder">
+            <For each={redPlayers()}>{(player: PlayerState) =>
+                <PlayerSpec player={player} highlighted={player.info.userId == props.highlighted}
+                            onHover={props.onHover}/>
+            }</For>
+            <For each={redMedics()}>{(player) =>
+                <UberSpec
+                    team={teamMap[player.team]}
+                    chargeLevel={player.charge}
+                    isDeath={player.health < 1}
+                />
+            }</For>
+        </div>
+        <div class="blueSpecHolder">
+            <For each={bluePlayers()}>{(player) =>
+                <PlayerSpec player={player} highlighted={player.info.userId == props.highlighted}
+                            onHover={props.onHover}/>
+            }</For>
+            <For each={blueMedics()}>{(player) =>
+                <UberSpec
+                    team={teamMap[player.team]}
+                    chargeLevel={player.charge}
+                    isDeath={player.health < 1}
+                />
+            }</For>
+        </div>
+    </div>);
 }
 
-export function PlayerSpec({player}: PlayerSpecProps) {
-	const healthPercent = Math.min(100, player.health / healthMap[player.playerClass] * 100);
-	const healthStatusClass = (player.health > healthMap[player.playerClass]) ? 'overhealed' : (player.health <= 0 ? 'dead' : '');
-
-	return (
-		<div
-			class={"playerspec " + teamMap[player.team] + " webp " + healthStatusClass}>
-			{getPlayerIcon(player)}
-			<div class="health-container">
-				<div class="healthbar"
-					 style={{width: healthPercent + '%'}}/>
-				<span class="player-name">{player.info.name}</span>
-				<span class="health">{player.health}</span>
-			</div>
-		</div>
-	);
+export function PlayerSpec(props: PlayerSpecProps) {
+    const healthPercent = () => Math.min(100, props.player.health / healthMap[props.player.playerClass] * 100);
+    const healthStatusClass = () => (props.player.health > healthMap[props.player.playerClass]) ? 'overhealed' : (props.player.health <= 0 ? 'dead' : '');
+
+    return (
+        <div
+            onmouseover={() => props.onHover(props.player.info.userId)}
+            onmouseout={() => props.onHover(0)}
+            classList={{
+                "playerspec": true,
+                [teamMap[props.player.team]]: true,
+                "webp": true,
+                [healthStatusClass()]: true,
+                highlighted: props.highlighted,
+            }}>
+            {getPlayerIcon(props.player)}
+            <div class="health-container">
+                <div class="healthbar"
+                     style={{width: healthPercent() + '%'}}/>
+                <span class="player-name">{props.player.info.name}</span>
+                <span class="health">{props.player.health}</span>
+            </div>
+        </div>
+    );
 }
 
 function getPlayerIcon(player: PlayerState) {
-	if (classMap[player.playerClass]) {
-		return <div class={classMap[player.playerClass] + " class-icon"}/>
-	} else {
-		return <div class={"class-icon"}/>
-	}
+    if (classMap[player.playerClass]) {
+        return <div class={classMap[player.playerClass] + " class-icon"}/>
+    } else {
+        return <div class={"class-icon"}/>
+    }
 }
 
 export interface UberSpecProps {
-	chargeLevel: number;
-	team: string;
-	isDeath: boolean;
+    chargeLevel: number;
+    team: string;
+    isDeath: boolean;
 }
 
 export function UberSpec({chargeLevel, team, isDeath}: UberSpecProps) {
-	const healthStatusClass = (isDeath) ? 'dead' : '';
-	return (
-		<div class={`playerspec uber ${team} ${healthStatusClass}`}>
-			<div class={"uber class-icon"}/>
-			<div class="health-container">
-				<div class="healthbar"
-					 style={{width: chargeLevel + '%'}}/>
-				<span class="player-name">Charge</span>
-				<span class="health">{Math.round(chargeLevel)}</span>
-			</div>
-		</div>
-	);
+    const healthStatusClass = (isDeath) ? 'dead' : '';
+    return (
+        <div class={`playerspec uber ${team} ${healthStatusClass}`}>
+            <div class={"uber class-icon"}/>
+            <div class="health-container">
+                <div class="healthbar"
+                     style={{width: chargeLevel + '%'}}/>
+                <span class="player-name">Charge</span>
+                <span class="health">{Math.round(chargeLevel)}</span>
+            </div>
+        </div>
+    );
 }
diff --git a/script/viewer/Analyse/Render/SpecHUD.tsx b/script/viewer/Analyse/Render/SpecHUD.tsx
index b0d3f15..f951f48 100644
--- a/script/viewer/Analyse/Render/SpecHUD.tsx
+++ b/script/viewer/Analyse/Render/SpecHUD.tsx
@@ -7,13 +7,15 @@ export interface SpecHUDProps {
     tick: number;
     parser: AsyncParser;
     players: PlayerState[];
-    events: Event[]
+    events: Event[];
+    onHover: (userId: number) => void;
+    highlighted: number | null;
 }
 
 export function SpecHUD(props: SpecHUDProps) {
     return (<div class="spechud">
         <KillFeed tick={props.tick} events={props.events} players={props.players}/>
-        <PlayersSpec players={props.players}/>
+        <PlayersSpec players={props.players} onHover={props.onHover} highlighted={props.highlighted}/>
     </div>)
 }
 
diff --git a/style/pages/viewer/PlayerSpec.css b/style/pages/viewer/PlayerSpec.css
index 4048a0c..6926e78 100644
--- a/style/pages/viewer/PlayerSpec.css
+++ b/style/pages/viewer/PlayerSpec.css
@@ -1,218 +1,241 @@
 .blueSpecHolder {
-  position: absolute;
-  left: 0;
-  top: 50%;
-  transform: translate(0, -50%);
+    position: absolute;
+    left: 0;
+    top: 50%;
+    transform: translate(0, -50%);
 }
 
 .redSpecHolder {
-  position: absolute;
-  right: 0;
-  top: 50%;
-  transform: translate(0, -50%);
+    position: absolute;
+    right: 0;
+    top: 50%;
+    transform: translate(0, -50%);
 }
 
 .playerspec {
-  background-color: black;
-  color: white;
-  height: 42px;
-  width: 200px;
-  position: relative;
-  font-family: sans-serif;
-  margin-bottom: 2px;
-  user-select: none;
-
-  &.uber {
-    height: 28px;
-  }
-
-  & .class-icon, .steam-avatar {
-    width: 42px;
+    background-color: black;
+    color: white;
     height: 42px;
-    display: inline-block;
-    position: absolute;
-    top: 0;
-    left: 0;
-    background-position: top left;
-    background-size: 100% 100%;
+    width: 200px;
+    position: relative;
+    font-family: sans-serif;
+    margin-bottom: 2px;
+    user-select: none;
 
     &.uber {
-      height: 28px;
-      background-size: 28px 28px;
-      background-repeat: no-repeat;
-      background-position: 50% 50%;
+        height: 28px;
     }
-  }
 
-  & .player-name {
-    display: inline-block;
-    position: relative;
-    padding: 0 5px;
-    white-space: nowrap;
-    width: 120px;
-    overflow: hidden;
-    text-overflow: ellipsis;
-  }
-
-  & .health-container {
-    display: inline-block;
-    position: absolute;
-    left: 42px;
-    top: 0;
-    height: 28px;
-    width: calc(100% - 42px);
-    line-height: 28px;
-    font-weight: bold;
-    & .health {
-      position: relative;
-      float: right;
-      padding: 0 5px;
+    & .class-icon, .steam-avatar {
+        width: 42px;
+        height: 42px;
+        display: inline-block;
+        position: absolute;
+        top: 0;
+        left: 0;
+        background-position: top left;
+        background-size: 100% 100%;
+
+        &.uber {
+            height: 28px;
+            background-size: 28px 28px;
+            background-repeat: no-repeat;
+            background-position: 50% 50%;
+        }
     }
 
-    & .healthbar {
-      position: absolute;
-      top: 0;
-      left: 0;
-      height: 28px;
+    & .player-name {
+        display: inline-block;
+        position: relative;
+        padding: 0 5px;
+        white-space: nowrap;
+        width: 120px;
+        overflow: hidden;
+        text-overflow: ellipsis;
     }
-  }
 
-  &.red {
     & .health-container {
-      background-color: #a75d50aa;
-    }
+        display: inline-block;
+        position: absolute;
+        left: 42px;
+        top: 0;
+        height: 28px;
+        width: calc(100% - 42px);
+        line-height: 28px;
+        font-weight: bold;
+
+        & .health {
+            position: relative;
+            float: right;
+            padding: 0 5px;
+        }
+
+        & .healthbar {
+            position: absolute;
+            top: 0;
+            left: 0;
+            height: 28px;
+        }
+    }
+
+    &.red {
+        & .health-container {
+            background-color: #a75d50aa;
+        }
+
+        & .healthbar {
+            background-color: #a75d50;
+        }
+
+        & .class-icon.scout {
+            background-image: url('inline://images/class_portraits/Icon_scout.webp');
+        }
+
+        & .class-icon.soldier {
+            background-image: url('inline://images/class_portraits/Icon_soldier.webp');
+        }
+
+        & .class-icon.pyro {
+            background-image: url('inline://images/class_portraits/Icon_pyro.webp');
+        }
+
+        & .class-icon.demoman {
+            background-image: url('inline://images/class_portraits/Icon_demoman.webp');
+        }
+
+        & .class-icon.engineer {
+            background-image: url('inline://images/class_portraits/Icon_engineer.webp');
+        }
+
+        & .class-icon.heavy {
+            background-image: url('inline://images/class_portraits/Icon_heavy.webp');
+        }
+
+        & .class-icon.medic {
+            background-image: url('inline://images/class_portraits/Icon_medic.webp');
+        }
+
+        & .class-icon.sniper {
+            background-image: url('inline://images/class_portraits/Icon_sniper.webp');
+        }
+
+        & .class-icon.spy {
+            background-image: url('inline://images/class_portraits/Icon_spy.webp');
+        }
+
+        & .class-icon.uber {
+            background-image: url('inline://images/charge_red.svg');
+        }
+
+        & .class-icon, & .steam-avatar {
+            right: 0;
+            left: auto;
+        }
+
+        & .health-container {
+            right: 42px;
+            left: auto;
+        }
+
+        & .health {
+            float: left;
+        }
+
+        & .player-name {
+            float: right;
+            direction: ltr;
+            text-align: right;
+        }
+    }
+
+    &.blue {
+        & .health-container {
+            background-color: #5b818faa;
+        }
+
+        & .healthbar {
+            background-color: #5b818f;
+        }
+
+        & .class-icon.scout {
+            background-image: url('inline://images/class_portraits/Icon_scout_blue.webp');
+        }
+
+        & .class-icon.soldier {
+            background-image: url('inline://images/class_portraits/Icon_soldier_blue.webp');
+        }
+
+        & .class-icon.pyro {
+            background-image: url('inline://images/class_portraits/Icon_pyro_blue.webp');
+        }
 
-    & .healthbar {
-      background-color: #a75d50;
-    }
+        & .class-icon.demoman {
+            background-image: url('inline://images/class_portraits/Icon_demoman_blue.webp');
+        }
 
-    & .class-icon.scout {
-      background-image: url('inline://images/class_portraits/Icon_scout.webp');
-    }
-    & .class-icon.soldier {
-      background-image: url('inline://images/class_portraits/Icon_soldier.webp');
-    }
-    & .class-icon.pyro {
-      background-image: url('inline://images/class_portraits/Icon_pyro.webp');
-    }
-    & .class-icon.demoman {
-      background-image: url('inline://images/class_portraits/Icon_demoman.webp');
-    }
-    & .class-icon.engineer {
-      background-image: url('inline://images/class_portraits/Icon_engineer.webp');
-    }
-    & .class-icon.heavy {
-      background-image: url('inline://images/class_portraits/Icon_heavy.webp');
-    }
-    & .class-icon.medic {
-      background-image: url('inline://images/class_portraits/Icon_medic.webp');
-    }
-    & .class-icon.sniper {
-      background-image: url('inline://images/class_portraits/Icon_sniper.webp');
-    }
-    & .class-icon.spy{
-      background-image: url('inline://images/class_portraits/Icon_spy.webp');
-    }
-    & .class-icon.uber {
-      background-image: url('inline://images/charge_red.svg');
-    }
+        & .class-icon.engineer {
+            background-image: url('inline://images/class_portraits/Icon_engineer_blue.webp');
+        }
 
-    & .class-icon, & .steam-avatar {
-      right: 0;
-      left: auto;
-    }
+        & .class-icon.heavy {
+            background-image: url('inline://images/class_portraits/Icon_heavy_blue.webp');
+        }
 
-    & .health-container {
-      right: 42px;
-      left: auto;
-    }
+        & .class-icon.medic {
+            background-image: url('inline://images/class_portraits/Icon_medic_blue.webp');
+        }
 
-    & .health {
-      float: left;
-    }
+        & .class-icon.sniper {
+            background-image: url('inline://images/class_portraits/Icon_sniper_blue.webp');
+        }
 
-    & .player-name {
-      float: right;
-      direction: ltr;
-      text-align: right;
-    }
-  }
+        & .class-icon.spy {
+            background-image: url('inline://images/class_portraits/Icon_spy_blue.webp');
+        }
 
-  &.blue {
-    & .health-container {
-      background-color: #5b818faa;
+        & .class-icon.uber {
+            background-image: url('inline://images/charge_blue.svg');
+        }
     }
 
-    & .healthbar {
-      background-color: #5b818f;
-    }
-
-    & .class-icon.scout {
-      background-image: url('inline://images/class_portraits/Icon_scout_blue.webp');
-    }
-    & .class-icon.soldier {
-      background-image: url('inline://images/class_portraits/Icon_soldier_blue.webp');
-    }
-    & .class-icon.pyro {
-      background-image: url('inline://images/class_portraits/Icon_pyro_blue.webp');
-    }
-    & .class-icon.demoman {
-      background-image: url('inline://images/class_portraits/Icon_demoman_blue.webp');
+    &.highlighted:not(.dead) {
+        outline: white 2px solid;
     }
-    & .class-icon.engineer {
-      background-image: url('inline://images/class_portraits/Icon_engineer_blue.webp');
-    }
-    & .class-icon.heavy {
-      background-image: url('inline://images/class_portraits/Icon_heavy_blue.webp');
-    }
-    & .class-icon.medic {
-      background-image: url('inline://images/class_portraits/Icon_medic_blue.webp');
-    }
-    & .class-icon.sniper {
-      background-image: url('inline://images/class_portraits/Icon_sniper_blue.webp');
-    }
-    & .class-icon.spy {
-      background-image: url('inline://images/class_portraits/Icon_spy_blue.webp');
-    }
-    & .class-icon.uber {
-      background-image: url('inline://images/charge_blue.svg');
-    }
-  }
 
-  &.overhealed {
-    & .health {
-      color: #79d297;
-    }
+    &.overhealed {
+        & .health {
+            color: #79d297;
+        }
 
-    & .health:after {
-      position: absolute;
-      top: 21px;
-      right: 0;
-      padding: 0 5px;
-      font-size: 10px;
-      font-weight: bold;
-      content: 'OVERHEALED'
-    }
+        & .health:after {
+            position: absolute;
+            top: 21px;
+            right: 0;
+            padding: 0 5px;
+            font-size: 10px;
+            font-weight: bold;
+            content: 'OVERHEALED'
+        }
 
-    &.red .health:after {
-      position: absolute;
-      top: 21px;
-      left: 0;
-      right: auto;
+        &.red .health:after {
+            position: absolute;
+            top: 21px;
+            left: 0;
+            right: auto;
+        }
     }
-  }
 
-  &.dead {
-    & .healthbar, & .health {
-      display: none;
-    }
+    &.dead {
+        & .healthbar, & .health {
+            display: none;
+        }
 
-    & .health-container {
-      background-color: transparent;
-    }
+        & .health-container {
+            background-color: transparent;
+        }
 
-    & .class-icon {
-      opacity: 0.5;
+        & .class-icon {
+            opacity: 0.5;
+        }
     }
-  }
 }