diff --git a/packages/website/public/assets/images/tankopedia/visualizers/reload/caret.png b/packages/website/public/assets/images/tankopedia/visualizers/reload/caret.png
new file mode 100644
index 000000000..660853971
Binary files /dev/null and b/packages/website/public/assets/images/tankopedia/visualizers/reload/caret.png differ
diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx
index fe806b2b7..c2eb22cb2 100644
--- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx
+++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx
@@ -24,6 +24,7 @@ import { Duel } from '../../../../../../stores/duel';
import type { StatsAcceptorProps } from './HullTraverseVisualizer';
import { Info } from './Info';
import { InfoWithDelta } from './InfoWithDelta';
+import { ReloadVisualizer } from './ReloadVisualizer';
import { RicochetVisualizer } from './RicochetVisualizer';
import { StatsTableWrapper } from './StatsTableWrapper';
@@ -165,6 +166,8 @@ export function Firepower({ stats }: StatsAcceptorProps) {
value="intraClip"
/>
)}
+
+
{typeof shell.penetration !== 'number' && (
<>
diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/Shell.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/Shell.tsx
new file mode 100644
index 000000000..e345c3f89
--- /dev/null
+++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/Shell.tsx
@@ -0,0 +1,61 @@
+import { Box, Flex } from '@radix-ui/themes';
+import type { QuicklimeCallback } from 'quicklime';
+import { useEffect, useRef } from 'react';
+import { reloadUpdate, type ReloadUpdateData } from '..';
+import { Var } from '../../../../../../../../core/radix/var';
+import type { StatsAcceptorProps } from '../../HullTraverseVisualizer';
+
+interface ShellProps {
+ index: number;
+}
+
+export function Shell({ index, stats }: ShellProps & StatsAcceptorProps) {
+ const bar = useRef(null!);
+
+ useEffect(() => {
+ const handleReloadUpdate: QuicklimeCallback = ({
+ data,
+ }) => {
+ bar.current.style.width = `${data.shells[index] * 100}%`;
+ };
+
+ reloadUpdate.on(handleReloadUpdate);
+
+ return () => {
+ reloadUpdate.off(handleReloadUpdate);
+ };
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/index.tsx
new file mode 100644
index 000000000..001cb7ba3
--- /dev/null
+++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/index.tsx
@@ -0,0 +1,135 @@
+import { Box, Card } from '@radix-ui/themes';
+import { times } from 'lodash-es';
+import { Quicklime, type QuicklimeCallback } from 'quicklime';
+import { useEffect, useRef } from 'react';
+import { Var } from '../../../../../../../core/radix/var';
+import type { StatsAcceptorProps } from '../HullTraverseVisualizer';
+import { Shell } from './components/Shell';
+
+export interface ReloadUpdateData {
+ reload: number;
+ shells: number[];
+}
+
+export const reloadUpdate = new Quicklime();
+
+export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
+ const reloadCircle = useRef(null!);
+ const reloadCore = useRef(null!);
+ const reloadGlow = useRef(null!);
+
+ const size = `${stats.shells === 1 ? 66 : 58}%`;
+
+ useEffect(() => {
+ let cancel = false;
+
+ function frame() {
+ const now = Date.now() / 1000;
+
+ const t = now % stats.shellReload!;
+ const x = t / stats.shellReload!;
+
+ reloadUpdate.dispatch({
+ reload: x,
+ shells: Array(stats.shells).fill(x),
+ });
+
+ if (!cancel) requestAnimationFrame(frame);
+ }
+
+ frame();
+
+ return () => {
+ cancel = true;
+ };
+ }, [stats]);
+
+ useEffect(() => {
+ const handleReloadUpdate: QuicklimeCallback = ({
+ data,
+ }) => {
+ const reloadAngle = data.reload * 2 * Math.PI;
+
+ reloadCircle.current.style.background = `conic-gradient(${Var('amber-9')} 0 ${reloadAngle}rad, ${Var('red-7')} ${reloadAngle}rad)`;
+ reloadCore.current.style.background = `conic-gradient(${Var('amber-3')} 0 ${reloadAngle}rad, ${Var('red-2')} ${reloadAngle}rad)`;
+ reloadGlow.current.style.background = `conic-gradient(${Var('amber-8')} 0 ${reloadAngle}rad, transparent ${reloadAngle}rad)`;
+ };
+
+ reloadUpdate.on(handleReloadUpdate);
+
+ return () => {
+ reloadUpdate.off(handleReloadUpdate);
+ };
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {times(stats.shells, (index) => (
+
+ ))}
+
+ );
+}