<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://deadlock.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=~2026-GlassGreenKinetic677</id>
	<title>The Deadlock Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://deadlock.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=~2026-GlassGreenKinetic677"/>
	<link rel="alternate" type="text/html" href="https://deadlock.wiki/Special:Contributions/~2026-GlassGreenKinetic677"/>
	<updated>2026-09-06T07:15:59Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.46.0</generator>
	<entry>
		<id>https://deadlock.wiki/Template:Infobox_hero/styles.css?diff=93701</id>
		<title>Template:Infobox hero/styles.css</title>
		<link rel="alternate" type="text/html" href="https://deadlock.wiki/Template:Infobox_hero/styles.css?diff=93701"/>
		<updated>2026-07-16T20:02:16Z</updated>

		<summary type="html">&lt;p&gt;~2026-GlassGreenKinetic677: clarify link-underline class&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ========== @MEDIA/MINERVA FIXES/OVERRIDES ========== */&lt;br /&gt;
body.skin-minerva .infobox-h table{&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.skin-minerva .infobox-h table caption{&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    display: table-caption;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 639px){&lt;br /&gt;
	.infobox-h table,&lt;br /&gt;
	body.skin-minerva .infobox-h table{&lt;br /&gt;
		display: table;&lt;br /&gt;
		overflow: visible;&lt;br /&gt;
	}&lt;br /&gt;
	&lt;br /&gt;
	.infobox-h table.infobox-h-content{&lt;br /&gt;
		display: table;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 539px){&lt;br /&gt;
	body.skin-minerva .infobox-h{&lt;br /&gt;
		max-width: 350px;&lt;br /&gt;
		margin: 0em auto 1em auto;&lt;br /&gt;
		float: none;&lt;br /&gt;
	}&lt;br /&gt;
	body.skin-minerva .infobox-h-subsection table td{&lt;br /&gt;
		width: 50%;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ========== INFOBOX_HERO CSS ========== */&lt;br /&gt;
.infobox-h{&lt;br /&gt;
	/* -- variables -- */&lt;br /&gt;
	--th-heroinfobox-text-normal: white;&lt;br /&gt;
	&lt;br /&gt;
	--th-heroinfobox-bg-image: url(&#039;/images/3/3e/Infobox_Item_BG.png&#039;);&lt;br /&gt;
	--th-heroinfobox-bg-height: 700px;&lt;br /&gt;
	--th-heroinfobox-bg-margin: 0px;&lt;br /&gt;
	--th-heroinfobox-bg-grad-offset: 0px;&lt;br /&gt;
	--th-heroinfobox-bg-grad-height: calc(var(--th-heroinfobox-bg-grad-offset) + var(--th-heroinfobox-bg-height));&lt;br /&gt;
	--th-heroinfobox-bg-grad-point1: calc(var(--th-heroinfobox-bg-grad-height) * calc(3 / 7));&lt;br /&gt;
	--th-heroinfobox-logo-bleed: 0px;&lt;br /&gt;
	--th-heroinfobox-logo-bottom: 0px;&lt;br /&gt;
	--th-heroinfobox-tags-text: #FFEFD7;&lt;br /&gt;
	--th-heroinfobox-tags-back: #000000;&lt;br /&gt;
	--th-heroinfobox-color1: #2F2F2F;&lt;br /&gt;
	--th-heroinfobox-color1-transparent: color-mix(in srgb, var(--th-heroinfobox-color1) 70%, transparent 30%);&lt;br /&gt;
	--th-heroinfobox-color1-grad: #2F2F2F;&lt;br /&gt;
	--th-heroinfobox-color2: #5b5b5b;&lt;br /&gt;
	--th-heroinfobox-color2-shadow: color-mix(in srgb, var(--th-heroinfobox-color2) 60%, black 20%, transparent 20%);&lt;br /&gt;
	&lt;br /&gt;
	--th-heroinfobox-text-title: #FFEFD7;&lt;br /&gt;
	--th-heroinfobox-stats-text: #F2EDE5;&lt;br /&gt;
	--th-heroinfobox-stats-back: #11111180;&lt;br /&gt;
	&lt;br /&gt;
	--th-heroinfobox-tornbox: url(&#039;/images/a/a1/Infobox_UI_Torn-Box.png&#039;);&lt;br /&gt;
&lt;br /&gt;
	/* -- base styles -- */&lt;br /&gt;
	float: right;&lt;br /&gt;
    position: relative;&lt;br /&gt;
	width: 100%;&lt;br /&gt;
    max-width: 275px;&lt;br /&gt;
	min-width: 240px;&lt;br /&gt;
	border-collapse: collapse;&lt;br /&gt;
	&lt;br /&gt;
	font-family: &#039;Retail Demo&#039;, &#039;Open Sans&#039;, serif;&lt;br /&gt;
	font-size: 15px;&lt;br /&gt;
    background-color: var(--th-heroinfobox-color1);&lt;br /&gt;
    margin: 0 0 1em 1em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ---------- Background Styling ---------- */&lt;br /&gt;
.infobox-h-background {&lt;br /&gt;
	position: absolute;&lt;br /&gt;
	width: 100%;&lt;br /&gt;
	height: 100%;&lt;br /&gt;
	overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* --- Background Horizontal Positioning --- */&lt;br /&gt;
.infobox-h-background &amp;gt; figure{&lt;br /&gt;
	margin-right: var(--th-heroinfobox-bg-margin) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---------- Content Styling ---------- */&lt;br /&gt;
.infobox-h-content {&lt;br /&gt;
	position: relative;&lt;br /&gt;
	width: 100%;&lt;br /&gt;
	top: 0;&lt;br /&gt;
	right: 0;&lt;br /&gt;
	background-position: center top;&lt;br /&gt;
	background-repeat: repeat;&lt;br /&gt;
	background-size: cover;&lt;br /&gt;
	&lt;br /&gt;
	color: var(--th-heroinfobox-text-normal);&lt;br /&gt;
	background-image: linear-gradient(180deg, var(--th-heroinfobox-color1-transparent) var(--th-heroinfobox-bg-grad-point1), var(--th-heroinfobox-color1-grad) var(--th-heroinfobox-bg-grad-height));&lt;br /&gt;
	background-color: transparent;&lt;br /&gt;
	font-size: 15px;&lt;br /&gt;
	text-align: center;&lt;br /&gt;
	text-shadow: 1.5px 1.5px rgba(0, 0, 0, 0.7);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h, .infobox-h-background, .infobox-h-content{&lt;br /&gt;
	border-radius: 8px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h .infobox-h-image {&lt;br /&gt;
	margin: auto;&lt;br /&gt;
	border: none;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---------- Top Section (Above Portrait) ---------- */&lt;br /&gt;
.infobox-h-title{&lt;br /&gt;
	color: var(--th-heroinfobox-text-title);&lt;br /&gt;
	font-size: 22px;&lt;br /&gt;
	font-weight: bold;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
	text-shadow: 1.2px 2px 3px black, 1.2px 1.2px 0.1px #A5A5A5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-title-regular{&lt;br /&gt;
	color: var(--th-heroinfobox-text-title);&lt;br /&gt;
	font-size: 22px;&lt;br /&gt;
	font-weight: normal;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
	text-shadow: 1.2px 2px 3px black, 1.2px 1.2px 0.1px #A5A5A5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-title-hero{&lt;br /&gt;
	display: flex;&lt;br /&gt;
	position: relative;&lt;br /&gt;
	justify-content: space-between;&lt;br /&gt;
	align-items: center;&lt;br /&gt;
	min-height: 34px;&lt;br /&gt;
	margin-top: 2px;&lt;br /&gt;
	margin-bottom: var(--th-heroinfobox-logo-bottom);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-title-hero-left{&lt;br /&gt;
	position: absolute;&lt;br /&gt;
	height: 100%;&lt;br /&gt;
	top: 0;&lt;br /&gt;
	left: 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* --- Logo/Name --- */&lt;br /&gt;
.infobox-h-title-hero-middle{&lt;br /&gt;
	position: relative;&lt;br /&gt;
	width: 100%;&lt;br /&gt;
	font-family: &#039;Valve Occult&#039;, &#039;Retail Demo&#039;, &#039;Open Sans&#039;, serif;&lt;br /&gt;
	line-height: 30px; &lt;br /&gt;
	filter: drop-shadow(1px 1px 2px black);&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-title-hero-middle-text{&lt;br /&gt;
	width: calc(100% - 120px);&lt;br /&gt;
	margin: 0px 60px;&lt;br /&gt;
	line-height: 1em;&lt;br /&gt;
	padding-bottom: 4px;&lt;br /&gt;
	word-break: break-word;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-title-hero-middle &amp;gt; span{&lt;br /&gt;
	bottom: 0 !important;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-title-hero-middle img{&lt;br /&gt;
	margin: var(--th-heroinfobox-logo-bleed);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* --- Map Icon --- */&lt;br /&gt;
.infobox-h-title-hero-right{&lt;br /&gt;
	position: absolute;&lt;br /&gt;
	height: 45px;&lt;br /&gt;
	width: auto;&lt;br /&gt;
	aspect-ratio: 1;&lt;br /&gt;
	top: 0;&lt;br /&gt;
	right: 0;&lt;br /&gt;
	margin-right: 5px;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-title-hero-right &amp;gt; div{&lt;br /&gt;
	position: relative;&lt;br /&gt;
	width: 100%;&lt;br /&gt;
	height: 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* --- Torn Box Behind Map Icon --- */&lt;br /&gt;
.infobox-h-title-hero-tornbox{&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    width: 105%;&lt;br /&gt;
    height: 105%;&lt;br /&gt;
    top: -2.5%;&lt;br /&gt;
    right: -2.5%;&lt;br /&gt;
	transform: rotate(-72deg);&lt;br /&gt;
    background-color: var(--th-heroinfobox-color2);&lt;br /&gt;
    mask-image: var(--th-heroinfobox-tornbox);&lt;br /&gt;
    mask-mode: luminance;&lt;br /&gt;
    mask-position: 0 0;&lt;br /&gt;
    mask-size: 100% 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-title-hero-icon{&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    width: calc(100% - 10px);&lt;br /&gt;
    height: calc(100% - 10px);&lt;br /&gt;
    top: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
	padding: 5px;&lt;br /&gt;
	font-size: 16px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* --- Text Before Stats --- */&lt;br /&gt;
.infobox-h-text-weapon{&lt;br /&gt;
	padding: 2px 0;&lt;br /&gt;
	line-height: 1.2em;&lt;br /&gt;
	position: relative;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-text-stat-subtitle &amp;gt; *:first-child,&lt;br /&gt;
.infobox-h-text-stat-subtitle-border &amp;gt; *:first-child{&lt;br /&gt;
	margin-top: 4px;&lt;br /&gt;
	margin-bottom: 0;&lt;br /&gt;
	text-transform: uppercase;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-text-stat-subtitle &amp;gt; *:first-child{&lt;br /&gt;
	margin-top: 4px;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-text-stat-subtitle-border{&lt;br /&gt;
	margin-top: 2px;&lt;br /&gt;
	border-top: solid 2px var(--th-heroinfobox-color2);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---------- Hero Portrait Image ---------- */&lt;br /&gt;
.infobox-h-image &amp;gt; div{&lt;br /&gt;
	max-width: 150px;&lt;br /&gt;
	height: auto;&lt;br /&gt;
	margin: auto;&lt;br /&gt;
	padding: 0;&lt;br /&gt;
	overflow: hidden;&lt;br /&gt;
	border-radius: 8px;&lt;br /&gt;
	background-color: var(--th-heroinfobox-color2);&lt;br /&gt;
	box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.7);&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-image &amp;gt; div img{&lt;br /&gt;
	filter: drop-shadow(2px 3px 2px var(--th-heroinfobox-color2-shadow));&lt;br /&gt;
	clip-path: rect(3px calc(100% - 3px) calc(100% - 3px) 3px round 8px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---------- Hero Tags (Inside Infobox) ---------- */&lt;br /&gt;
.infobox-h-herotags-row {&lt;br /&gt;
	padding: 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-herotags-inner {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	justify-content: center;&lt;br /&gt;
	flex-wrap: wrap;&lt;br /&gt;
	gap: 6px;&lt;br /&gt;
	padding: 4px 0 6px 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-herotag-pill {&lt;br /&gt;
	display: inline-block;&lt;br /&gt;
	padding: 2px 8px;&lt;br /&gt;
	border-radius: 4px;&lt;br /&gt;
	background: var(--th-heroinfobox-stats-back);&lt;br /&gt;
	font-size: 13px;&lt;br /&gt;
	font-weight: 600;&lt;br /&gt;
	color: var(--th-heroinfobox-text-title);&lt;br /&gt;
	text-transform: uppercase;&lt;br /&gt;
	letter-spacing: 0.03em;&lt;br /&gt;
	text-shadow: 1.5px 1.5px 0.8px rgba(0, 0, 0, 0.5);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ------------ Statistic Subsections -------------------*/&lt;br /&gt;
/* note: dropdown is a child of subsection            */&lt;br /&gt;
&lt;br /&gt;
.infobox-h-subsection {&lt;br /&gt;
	padding: 0 2px 2px 2px;&lt;br /&gt;
	vertical-align: middle;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-subsection table{&lt;br /&gt;
	table-layout: auto;&lt;br /&gt;
	padding: 0 6px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-subsection tr{&lt;br /&gt;
	font-size: 14px;&lt;br /&gt;
	text-align: left;&lt;br /&gt;
	line-height: 1.1em;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-subsection tr &amp;gt; td{&lt;br /&gt;
	padding-bottom: 2px;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-subsection tr:first-child &amp;gt; td{&lt;br /&gt;
	padding-top: 2px;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-dropdown .infobox-h-subsection-child tr:first-child &amp;gt; td {&lt;br /&gt;
    padding-top: 5px;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-subsection tr:last-child &amp;gt; td{&lt;br /&gt;
	padding-bottom: 4px;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-subsection tr &amp;gt; td:first-child{&lt;br /&gt;
	width: auto;&lt;br /&gt;
	font-family: &amp;quot;Retail Demo Medium&amp;quot;, &amp;quot;Retail Demo&amp;quot;, &#039;Open Sans&#039;, serif;&lt;br /&gt;
	text-shadow: 1.5px 1.5px 0.8px rgba(0,0,0,0.5);&lt;br /&gt;
	color: var(--th-heroinfobox-stats-text);&lt;br /&gt;
	text-align: right;&lt;br /&gt;
	padding-right: 4px;&lt;br /&gt;
	vertical-align: top;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-subsection tr &amp;gt; td:first-child .infobox-h-stat-label{&lt;br /&gt;
	color: var(--th-heroinfobox-stats-text);&lt;br /&gt;
	text-decoration: underline;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-subsection tr &amp;gt; td:last-child{&lt;br /&gt;
	width: max-content;&lt;br /&gt;
	padding-left: 4px;&lt;br /&gt;
	vertical-align: middle;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-spiritboon{&lt;br /&gt;
	display: inline-block;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-spiritboon:not(:empty){&lt;br /&gt;
	padding-right: 0;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-dropdown tr:first-child .infobox-h-spiritboon:not(:empty){&lt;br /&gt;
	padding-top: 3px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-subsection-child{&lt;br /&gt;
	width: 100%;&lt;br /&gt;
	font-weight: normal;&lt;br /&gt;
	text-shadow: 1.5px 1.5px 0.8px rgba(0, 0, 0, 0.8);&lt;br /&gt;
	background-color: var(--th-heroinfobox-stats-back);&lt;br /&gt;
	border-radius: 4px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-dropdown-header &amp;gt; button{&lt;br /&gt;
	position: absolute;&lt;br /&gt;
	top: 0;&lt;br /&gt;
	left: 0;&lt;br /&gt;
	right: 0;&lt;br /&gt;
	bottom: 0;&lt;br /&gt;
	width: min-content;&lt;br /&gt;
	height: min-content;&lt;br /&gt;
	margin: auto 0 auto auto;&lt;br /&gt;
	padding-right: 5px;&lt;br /&gt;
	text-shadow: inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-dropdown-title{&lt;br /&gt;
	text-shadow: 0 2px 4px black, 1.2px 1.2px rgba(0, 0, 0, 0.7);&lt;br /&gt;
	color: var(--th-heroinfobox-text-normal);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-dropdown-header button,&lt;br /&gt;
.infobox-h-dropdown-header button .mw-collapsible-text{&lt;br /&gt;
	color: var(--th-heroinfobox-header-link);&lt;br /&gt;
	font-size: 13px;&lt;br /&gt;
	font-weight: bold;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-dropdown-header button.mw-collapsible-text:hover,&lt;br /&gt;
.infobox-h-dropdown-header button .mw-collapsible-text:hover{&lt;br /&gt;
	color: white;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-dropdown-header-overflow a{&lt;br /&gt;
	display: block;&lt;br /&gt;
	margin: 2px 65px 2px 5px;&lt;br /&gt;
	width: fit-content;&lt;br /&gt;
	line-height: 1.25em;&lt;br /&gt;
	word-break: break-word;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ------------ Dropdown Subsection Only -------------------*/&lt;br /&gt;
.infobox-h-dropdown{&lt;br /&gt;
	--th-heroinfobox-header-back: #d5903f;&lt;br /&gt;
	--th-heroinfobox-header-link: #F4E3CF;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-dropdown .infobox-h-subsection-child{&lt;br /&gt;
	border-top-right-radius: 0;&lt;br /&gt;
	border-top-left-radius: 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-dropdown .infobox-h-subsection-child:not(.mw-collapsed) .infobox-h-dropdown-header{&lt;br /&gt;
	border-bottom-right-radius: 0;&lt;br /&gt;
	border-bottom-left-radius: 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.infobox-h-dropdown tr &amp;gt; td:last-child{&lt;br /&gt;
	white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*--- Statistic Dropdown Themes --- */&lt;br /&gt;
.infobox-h-dropdown-weapon{&lt;br /&gt;
	--th-heroinfobox-header-back: #CD8837;&lt;br /&gt;
	--th-heroinfobox-header-link: #FFF0DF;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-dropdown-vitality{&lt;br /&gt;
	--th-heroinfobox-header-back: #74b01c;&lt;br /&gt;
	--th-heroinfobox-header-link: #DAFFD2;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-dropdown-spirit{&lt;br /&gt;
	--th-heroinfobox-header-back: #A657BC;&lt;br /&gt;
	--th-heroinfobox-header-link: #ffdff8;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*--- Statistic Dropdown Header --- */&lt;br /&gt;
.infobox-h-dropdown-header{&lt;br /&gt;
	position: relative;&lt;br /&gt;
	width: calc(100% - 4px);&lt;br /&gt;
	padding: 1px;&lt;br /&gt;
	&lt;br /&gt;
	background-color: var(--th-heroinfobox-header-back);&lt;br /&gt;
	border-radius: 4px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ------------ Subsection Text Themes -------------------*/&lt;br /&gt;
.infobox-h-content .infotext-h-bold{&lt;br /&gt;
	font-weight: bold;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-content .infotext-h-title{&lt;br /&gt;
	color: var(--th-heroinfobox-text-title);&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-content .infotext-h-big{&lt;br /&gt;
	font-size: 16px;&lt;br /&gt;
}&lt;br /&gt;
.infobox-h-content .infotext-h-bigger{&lt;br /&gt;
	text-shadow: 1.1px 2px 2px black, 1.2px 1.2px 0.2px #7a7369;&lt;br /&gt;
	font-size: 18px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ------------ Stat Cell Modifiers -------------------*/&lt;br /&gt;
&lt;br /&gt;
/* Headroom for a section&#039;s first row when it carries a scaling icon. */&lt;br /&gt;
.infobox-h .infobox-h-dropdown .infobox-h-subsection-child tr &amp;gt; td.infobox-h-stat-label-scaled{&lt;br /&gt;
	padding-top: 8px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Breathing room for value cells whose scaling icon sits near the edge. */&lt;br /&gt;
.infobox-h .infobox-h-dropdown .infobox-h-subsection-child tr &amp;gt; td.infobox-h-stat-value-padded{&lt;br /&gt;
	padding-right: 5px;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>~2026-GlassGreenKinetic677</name></author>
	</entry>
	<entry>
		<id>https://deadlock.wiki/Module:InfoboxHero?diff=93700</id>
		<title>Module:InfoboxHero</title>
		<link rel="alternate" type="text/html" href="https://deadlock.wiki/Module:InfoboxHero?diff=93700"/>
		<updated>2026-07-16T20:01:44Z</updated>

		<summary type="html">&lt;p&gt;~2026-GlassGreenKinetic677: move back to fancy tooltips, also add classes to label spans&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;--------------------------------------------------------------------------------&lt;br /&gt;
-- Module:InfoboxHero&lt;br /&gt;
--&lt;br /&gt;
-- Renders the hero infobox. Called from [[Template:Infobox hero]], which passes&lt;br /&gt;
-- the editor&#039;s parameters through as the parent frame:&lt;br /&gt;
--&lt;br /&gt;
--     {{#invoke:InfoboxHero|render}}&lt;br /&gt;
--&lt;br /&gt;
-- May also be invoked directly for testing, in which case parameters are read&lt;br /&gt;
-- from the invoke itself:&lt;br /&gt;
--&lt;br /&gt;
--     {{#invoke:InfoboxHero|render|key=hero_atlas}}&lt;br /&gt;
--&lt;br /&gt;
-- Stat data comes from [[Data:HeroData.json]]; per-hero colors, backgrounds and&lt;br /&gt;
-- logo sizes from [[Data:Infobox hero.json]]; labels from [[Module:Lang]] and&lt;br /&gt;
-- [[Module:Dictionary]]. Styling lives in [[Template:Infobox hero/styles.css]]&lt;br /&gt;
-- and [[Template:Infobox hero/themes.css]].&lt;br /&gt;
--&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local hero_data_all = mw.loadJsonData(&#039;Data:HeroData.json&#039;)&lt;br /&gt;
local theme_data    = mw.loadJsonData(&#039;Data:Infobox_hero.json&#039;)&lt;br /&gt;
&lt;br /&gt;
local util        = require(&#039;Module:Utilities&#039;)&lt;br /&gt;
local lang_module = require(&#039;Module:Lang&#039;)&lt;br /&gt;
local dict_module = require(&#039;Module:Dictionary&#039;)&lt;br /&gt;
local hero_module = require(&#039;Module:HeroData&#039;)&lt;br /&gt;
&lt;br /&gt;
local get_stat, has_stat, has_override_or_stat, stat_number&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- CONFIG&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local DEFAULT_HERO_KEY = &#039;hero_targetdummy&#039;  -- used when |key= is missing/unknown&lt;br /&gt;
local DEFAULT_SIG_FIGS = 3&lt;br /&gt;
local FALLBACK_CARD    = &#039;Dummy card.png&#039;&lt;br /&gt;
&lt;br /&gt;
-- Row fields (all optional except a label):&lt;br /&gt;
--   stat        Key in Data:HeroData.json. Resolved top-level, then .Weapon,&lt;br /&gt;
--               then .Weapon.AltFire. Omit for manual-input-only rows.&lt;br /&gt;
--   param       |param= that overrides the value.&lt;br /&gt;
--   label       Key in Data:Lang_&amp;lt;code&amp;gt;.json.&lt;br /&gt;
--   label_dict  Key in Data:Dictionary. Used instead of `label`.&lt;br /&gt;
--   label_text  Literal, untranslated label. Used instead of `label`.&lt;br /&gt;
--   link        Target for the label, as &#039;Page&#039; or &#039;Page#Anchor&#039;. The language&lt;br /&gt;
--               suffix is inserted for us, so write the plain English title.&lt;br /&gt;
--   icon_link   Target for the icon. Defaults to `link`.&lt;br /&gt;
--   icon        File: name of the value icon. Omit for no icon.&lt;br /&gt;
--   sig_figs    Significant figures. Defaults to 3.&lt;br /&gt;
--   suffix      Appended directly after the value, e.g. &#039;%&#039;, &#039;s&#039;, &#039;m/s&#039;.&lt;br /&gt;
--   scaling     Set false to suppress the Boon/Spirit scaling span. Scaling is&lt;br /&gt;
--               shown by default wherever the data provides it.&lt;br /&gt;
--   optional    Hide the row unless the hero has a non-zero value or scaling.&lt;br /&gt;
--   cell_class  Extra class on the value cell.&lt;br /&gt;
--   show        function(ctx) -&amp;gt; boolean. Row omitted when false.&lt;br /&gt;
&lt;br /&gt;
local SECTIONS = {&lt;br /&gt;
	{&lt;br /&gt;
		class  = &#039;infobox-h-dropdown-weapon&#039;,&lt;br /&gt;
		header = &#039;CitadelHeroStats_Weapon&#039;,&lt;br /&gt;
		link   = &#039;Stats#Weapon&#039;,&lt;br /&gt;
		color  = &#039;Brown&#039;,&lt;br /&gt;
		rows = {&lt;br /&gt;
			{&lt;br /&gt;
				stat    = &#039;DPS&#039;,&lt;br /&gt;
				param   = &#039;dps&#039;,&lt;br /&gt;
				label   = &#039;DPS_label&#039;,&lt;br /&gt;
				link    = &#039;Damage per second&#039;,&lt;br /&gt;
				icon    = &#039;Damage.png&#039;,&lt;br /&gt;
				-- DPS is derived from bullet damage and fire rate, both of which&lt;br /&gt;
				-- show their own scaling; repeating it here would double-count.&lt;br /&gt;
				scaling = false,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;BulletDamage&#039;,&lt;br /&gt;
				param      = &#039;bullet_damage&#039;,&lt;br /&gt;
				label      = &#039;StatDesc_BulletDamage&#039;,&lt;br /&gt;
				link       = &#039;Bullet Damage&#039;,&lt;br /&gt;
				icon       = &#039;Bullet damage.png&#039;,&lt;br /&gt;
				cell_class = &#039;infobox-h-stat-value-padded&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat      = &#039;BulletsPerShot&#039;,&lt;br /&gt;
				param     = &#039;pellets_per_shot&#039;,&lt;br /&gt;
				label     = &#039;BonusPelletsPerShot_label&#039;,&lt;br /&gt;
				link      = &#039;Damage per second&#039;,&lt;br /&gt;
				icon      = &#039;Bullets per sec icon.png&#039;,&lt;br /&gt;
				icon_link = &#039;Fire Rate&#039;,&lt;br /&gt;
				show      = function(ctx) return ctx.pellets &amp;gt; 1 end,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;BulletsPerBurst&#039;,&lt;br /&gt;
				param      = &#039;bullets_per_burst&#039;,&lt;br /&gt;
				label_dict = &#039;BulletsPerBurst&#039;,&lt;br /&gt;
				link       = &#039;Damage per second&#039;,&lt;br /&gt;
				icon       = &#039;Bullets per sec icon.png&#039;,&lt;br /&gt;
				icon_link  = &#039;Fire Rate&#039;,&lt;br /&gt;
				show       = function(ctx) return ctx.burst &amp;gt; 1 end,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat  = &#039;ClipSize&#039;,&lt;br /&gt;
				param = &#039;ammo&#039;,&lt;br /&gt;
				label = &#039;StatDesc_ClipSizeBonus&#039;,&lt;br /&gt;
				link  = &#039;Ammo&#039;,&lt;br /&gt;
				icon  = &#039;Clip size icon.png&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat  = &#039;RoundsPerSecond&#039;,&lt;br /&gt;
				param = &#039;bullets_per_sec&#039;,&lt;br /&gt;
				label = &#039;StatDesc_RoundsPerSecond&#039;,&lt;br /&gt;
				link  = &#039;Fire Rate&#039;,&lt;br /&gt;
				icon  = &#039;Bullets per sec icon.png&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;ReloadTime&#039;,&lt;br /&gt;
				param  = &#039;reload_time&#039;,&lt;br /&gt;
				label  = &#039;StatDesc_ReloadTime&#039;,&lt;br /&gt;
				link   = &#039;Reload Time&#039;,&lt;br /&gt;
				icon   = &#039;AttributeIconReloadTime.png&#039;,&lt;br /&gt;
				suffix = &#039;s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;BulletSpeed&#039;,&lt;br /&gt;
				param  = &#039;bullet_velocity&#039;,&lt;br /&gt;
				label  = &#039;StatDesc_BulletSpeed&#039;,&lt;br /&gt;
				link   = &#039;Bullet Velocity&#039;,&lt;br /&gt;
				icon   = &#039;AttributeIconBulletSpeed.png&#039;,&lt;br /&gt;
				suffix = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;LightMeleeDamage&#039;,&lt;br /&gt;
				param      = &#039;light_melee&#039;,&lt;br /&gt;
				label      = &#039;StatDesc_LightMeleeDamage&#039;,&lt;br /&gt;
				link       = &#039;Melee Damage&#039;,&lt;br /&gt;
				icon       = &#039;Melee damage.png&#039;,&lt;br /&gt;
				cell_class = &#039;infobox-h-stat-value-padded&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;HeavyMeleeDamage&#039;,&lt;br /&gt;
				param      = &#039;heavy_melee&#039;,&lt;br /&gt;
				label      = &#039;StatDesc_HeavyMeleeDamage&#039;,&lt;br /&gt;
				link       = &#039;Melee Damage&#039;,&lt;br /&gt;
				icon       = &#039;Melee damage.png&#039;,&lt;br /&gt;
				cell_class = &#039;infobox-h-stat-value-padded&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				param      = &#039;max_range&#039;,&lt;br /&gt;
				label_text = &#039;Max Range&#039;,&lt;br /&gt;
				link       = &#039;Falloff Range&#039;,&lt;br /&gt;
				show       = function(ctx) return ctx.args.max_range ~= &#039;&#039; end,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;CritDamageBonusPercent&#039;,&lt;br /&gt;
				label  = &#039;StatDesc_CritDamageBonusScale&#039;,&lt;br /&gt;
				link   = &#039;Weapon Damage#Crit_Multiplier&#039;,&lt;br /&gt;
				icon   = &#039;Crit reduction.png&#039;,&lt;br /&gt;
				suffix = &#039;%&#039;,&lt;br /&gt;
				show   = function(ctx)&lt;br /&gt;
					return stat_number(ctx, &#039;CritDamageBonusPercent&#039;) ~= 0&lt;br /&gt;
				end,&lt;br /&gt;
			},&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		class  = &#039;infobox-h-dropdown-vitality&#039;,&lt;br /&gt;
		header = &#039;CitadelHeroStats_Vitality&#039;,&lt;br /&gt;
		link   = &#039;Stats#Vitality&#039;,&lt;br /&gt;
		color  = &#039;Green&#039;,&lt;br /&gt;
		rows = {&lt;br /&gt;
			{&lt;br /&gt;
				stat  = &#039;MaxHealth&#039;,&lt;br /&gt;
				param = &#039;max_health&#039;,&lt;br /&gt;
				label = &#039;StatDesc_HealthAttribute&#039;,&lt;br /&gt;
				link  = &#039;Health&#039;,&lt;br /&gt;
				icon  = &#039;Health.png&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;BaseHealthRegen&#039;,&lt;br /&gt;
				param    = &#039;health_regen&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_HealthRegen&#039;,&lt;br /&gt;
				link     = &#039;Health Regen&#039;,&lt;br /&gt;
				icon     = &#039;Health regen.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;CritDamageReceivedPercent&#039;,&lt;br /&gt;
				param    = &#039;crit_reduction&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_CritDamageReceivedScale&#039;,&lt;br /&gt;
				link     = &#039;Damage Resistance#Crit_Reduction&#039;,&lt;br /&gt;
				icon     = &#039;Crit reduction.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat      = &#039;BulletResist&#039;,&lt;br /&gt;
				param     = &#039;bullet_resist&#039;,&lt;br /&gt;
				label     = &#039;StatDesc_BulletArmorDamageReduction&#039;,&lt;br /&gt;
				link      = &#039;Damage Resistance&#039;,&lt;br /&gt;
				icon      = &#039;Bullet Armor.png&#039;,&lt;br /&gt;
				icon_link = &#039;Bullet Resist&#039;,&lt;br /&gt;
				suffix    = &#039;%&#039;,&lt;br /&gt;
				optional  = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;TechResist&#039;,&lt;br /&gt;
				param    = &#039;spirit_resist&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_TechArmorDamageReduction&#039;,&lt;br /&gt;
				link     = &#039;Damage Resistance#Spirit_Resist&#039;,&lt;br /&gt;
				icon     = &#039;Spirit Armor.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;MeleeResist&#039;,&lt;br /&gt;
				param    = &#039;melee_resist&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_MeleeResist&#039;,&lt;br /&gt;
				link     = &#039;Damage Resistance#Melee Resist&#039;,&lt;br /&gt;
				icon     = &#039;Melee damage.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;DebuffResist&#039;,&lt;br /&gt;
				param    = &#039;debuff_resist&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_DebuffResist&#039;,&lt;br /&gt;
				link     = &#039;Debuff Resist&#039;,&lt;br /&gt;
				icon     = &#039;Spirit Armor.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;BulletLifesteal&#039;,&lt;br /&gt;
				param    = &#039;bullet_lifesteal&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_BulletLifesteal&#039;,&lt;br /&gt;
				link     = &#039;Lifesteal#Bullet Lifesteal&#039;,&lt;br /&gt;
				icon     = &#039;AttributeIconBulletLifesteal.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;SpiritLifesteal&#039;,&lt;br /&gt;
				param    = &#039;spirit_lifesteal&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_SpiritLifesteal&#039;,&lt;br /&gt;
				link     = &#039;Lifesteal#Spirit Lifesteal&#039;,&lt;br /&gt;
				icon     = &#039;AttributeIconBulletLifesteal.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;MaxMoveSpeed&#039;,&lt;br /&gt;
				param    = &#039;move_speed&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_RunSpeed&#039;,&lt;br /&gt;
				link     = &#039;Move Speed&#039;,&lt;br /&gt;
				icon     = &#039;Move speed.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
				suffix   = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;SprintSpeed&#039;,&lt;br /&gt;
				param    = &#039;sprint_speed&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_SprintSpeed&#039;,&lt;br /&gt;
				link     = &#039;Move Speed#Sprint&#039;,&lt;br /&gt;
				icon     = &#039;Move speed sprint.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
				suffix   = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;GroundDashSpeed&#039;,&lt;br /&gt;
				label  = &#039;DashSpeed_label&#039;,&lt;br /&gt;
				link   = &#039;Stamina#Dash_Speed&#039;,&lt;br /&gt;
				icon   = &#039;Springy boots icon.png&#039;,&lt;br /&gt;
				suffix = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;Stamina&#039;,&lt;br /&gt;
				param    = &#039;stamina&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_Stamina&#039;,&lt;br /&gt;
				link     = &#039;Stamina&#039;,&lt;br /&gt;
				icon     = &#039;Springy boots icon.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;StaminaCooldown&#039;,&lt;br /&gt;
				param    = &#039;stamina_cooldown&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_StaminaCooldown&#039;,&lt;br /&gt;
				link     = &#039;Stamina#Cooldown&#039;,&lt;br /&gt;
				icon     = &#039;AttributeIconTechDuration.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
				suffix   = &#039;s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		class  = &#039;infobox-h-dropdown-spirit&#039;,&lt;br /&gt;
		header = &#039;CitadelHeroStats_Spirit&#039;,&lt;br /&gt;
		link   = &#039;Stats#Spirit&#039;,&lt;br /&gt;
		color  = &#039;Purple&#039;,&lt;br /&gt;
		rows = {&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;TechPower&#039;,&lt;br /&gt;
				param    = &#039;spirit_power&#039;,&lt;br /&gt;
				label    = &#039;SpiritPower_label&#039;,&lt;br /&gt;
				link     = &#039;Spirit Power&#039;,&lt;br /&gt;
				icon     = &#039;Spirit icon noborder.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
			},&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- The &amp;quot;Information&amp;quot; block. Manual input only; hidden entirely when all are empty.&lt;br /&gt;
local INFO_ROWS = {&lt;br /&gt;
	{ param = &#039;release_date&#039;, label_dict = &#039;ReleaseDate&#039;, link = &#039;Heroes by release&#039; },&lt;br /&gt;
	{ param = &#039;pronouns&#039;,     label_dict = &#039;Pronouns&#039; },&lt;br /&gt;
	{ param = &#039;voice_actor&#039;,  label_dict = &#039;VoiceActor&#039; },&lt;br /&gt;
	{ param = &#039;codenames&#039;,    label_dict = &#039;Codenames&#039;, tooltip = &#039;Codenames used for this hero in the game files&#039; },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- name: literal, or function(ctx) -&amp;gt; string.  when: function(ctx) -&amp;gt; boolean.&lt;br /&gt;
local CATEGORIES = {&lt;br /&gt;
	{ name = &#039;Heroes&#039; },&lt;br /&gt;
	{&lt;br /&gt;
		name = function(ctx) return ctx.hero_en end,&lt;br /&gt;
		-- Silver (Transformed) is documented on Silver&#039;s page and shares her category.&lt;br /&gt;
		when = function(ctx) return ctx.hero_en ~= &#039;Silver (Transformed)&#039; end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with spreadshot weapons&#039;,&lt;br /&gt;
		when = function(ctx) return ctx.pellets &amp;gt; 1 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with single damage spreadshot weapons&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return ctx.pellets &amp;gt; 1 and get_stat(ctx.hero, &#039;HitOnceAcrossAllBullets&#039;) == true&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with burst weapons&#039;,&lt;br /&gt;
		when = function(ctx) return ctx.burst &amp;gt; 1 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with automatic weapons&#039;,&lt;br /&gt;
		when = function(ctx) return ctx.pellets &amp;lt;= 1 and ctx.burst &amp;lt;= 1 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with alternate fire weapons&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return ctx.hero.Weapon ~= nil and ctx.hero.Weapon.AltFire ~= nil&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with single reload weapons&#039;,&lt;br /&gt;
		when = function(ctx) return get_stat(ctx.hero, &#039;ReloadSingle&#039;) == true end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with spin up weapons&#039;,&lt;br /&gt;
		when = function(ctx) return has_stat(ctx.hero, &#039;SpinAcceleration&#039;) end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with no weapon damage falloff&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return (get_stat(ctx.hero, &#039;FalloffStartRange&#039;) or 0)&lt;br /&gt;
			    == (get_stat(ctx.hero, &#039;FalloffEndRange&#039;) or 0)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &amp;quot;Heroes that don&#039;t deal critical damage&amp;quot;,&lt;br /&gt;
		when = function(ctx) return get_stat(ctx.hero, &#039;CanCrit&#039;) == false end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified crit scale&#039;,&lt;br /&gt;
		when = function(ctx) return stat_number(ctx, &#039;CritDamageBonusPercent&#039;) ~= 0 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified crit resistance&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;crit_reduction&#039;, &#039;CritDamageReceivedPercent&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified bullet resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;bullet_resist&#039;, &#039;BulletResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified spirit resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;spirit_resist&#039;, &#039;TechResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified melee resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;melee_resist&#039;, &#039;MeleeResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified debuff resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;debuff_resist&#039;, &#039;DebuffResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified bullet lifesteal&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;bullet_lifesteal&#039;, &#039;BulletLifesteal&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified spirit lifesteal&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;spirit_lifesteal&#039;, &#039;SpiritLifesteal&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified gravity scale&#039;,&lt;br /&gt;
		when = function(ctx) return has_stat(ctx.hero, &#039;GravityChange&#039;) end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = function(ctx) return &#039;Heroes with &#039; .. ctx.stamina .. &#039; stamina&#039; end,&lt;br /&gt;
		when = function(ctx) return ctx.stamina ~= &#039;&#039; end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = function(ctx)&lt;br /&gt;
			if ctx.dash &amp;lt; 14.7 then return &#039;Heroes with slow dash speed&#039; end&lt;br /&gt;
			if ctx.dash &amp;gt; 14.7 then return &#039;Heroes with fast dash speed&#039; end&lt;br /&gt;
			return &#039;Heroes with medium dash speed&#039;&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Data access&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- Stats live at the top level for vitality, and under .Weapon for weapon stats.&lt;br /&gt;
get_stat = function(hero, stat)&lt;br /&gt;
	if hero == nil or stat == nil then return nil end&lt;br /&gt;
	local value = hero[stat]&lt;br /&gt;
	if value == nil and hero.Weapon then&lt;br /&gt;
		value = hero.Weapon[stat]&lt;br /&gt;
		if value == nil and hero.Weapon.AltFire then&lt;br /&gt;
			value = hero.Weapon.AltFire[stat]&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return value&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- The raw Boon (Level) and Spirit scaling values for a stat, if any.&lt;br /&gt;
local function get_scaling(hero, stat)&lt;br /&gt;
	local out = {}&lt;br /&gt;
	for _, scaling_type in ipairs({ &#039;Level&#039;, &#039;Spirit&#039; }) do&lt;br /&gt;
		local scaling = hero[scaling_type .. &#039;Scaling&#039;]&lt;br /&gt;
		local value = scaling and stat and scaling[stat]&lt;br /&gt;
		if value ~= nil and value ~= 0 then&lt;br /&gt;
			out[#out + 1] = { type = scaling_type, value = value }&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return out&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- True when the hero has a non-zero base value, or any scaling, for a stat.&lt;br /&gt;
has_stat = function(hero, stat)&lt;br /&gt;
	local value = get_stat(hero, stat)&lt;br /&gt;
	if value ~= nil and value ~= 0 then return true end&lt;br /&gt;
	return #get_scaling(hero, stat) &amp;gt; 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
has_override_or_stat = function(ctx, param, stat)&lt;br /&gt;
	if param and ctx.args[param] ~= &#039;&#039; then return true end&lt;br /&gt;
	return has_stat(ctx.hero, stat)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Rounded numeric value of a stat, honouring a |param= override.&lt;br /&gt;
stat_number = function(ctx, stat, param)&lt;br /&gt;
	if param and ctx.args[param] ~= &#039;&#039; then return tonumber(ctx.args[param]) or 0 end&lt;br /&gt;
	return tonumber(util.round_to_sig_fig(get_stat(ctx.hero, stat) or 0, DEFAULT_SIG_FIGS)) or 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function format_value(raw, sig_figs)&lt;br /&gt;
	if raw == nil then return &#039;0&#039; end&lt;br /&gt;
	local rounded = util.round_to_sig_fig(raw, sig_figs or DEFAULT_SIG_FIGS)&lt;br /&gt;
	return tostring(rounded ~= nil and rounded or raw)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Rendering helpers&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function theme_var(hero_key, name)&lt;br /&gt;
	local hero_theme = theme_data[hero_key]&lt;br /&gt;
	if hero_theme and hero_theme[name] ~= nil then return hero_theme[name] end&lt;br /&gt;
	return theme_data[&#039;hero_default&#039;][name]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function label_of(row)&lt;br /&gt;
	if row.label_text then return row.label_text end&lt;br /&gt;
	if row.label_dict then return dict_module.translate(row.label_dict) end&lt;br /&gt;
	return lang_module.get_string(row.label)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- &#039;Damage Resistance#Spirit_Resist&#039; -&amp;gt; &#039;Damage Resistance/ru#Spirit_Resist&#039;&lt;br /&gt;
local function page_link(ctx, target)&lt;br /&gt;
	if not target then return &#039;&#039; end&lt;br /&gt;
	local page, anchor = target:match(&#039;^([^#]*)(#?.*)$&#039;)&lt;br /&gt;
	return page .. ctx.if_lang .. anchor&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function icon_cell(ctx, row, color, value_html)&lt;br /&gt;
	local icon = &#039;&#039;&lt;br /&gt;
	if row.icon then&lt;br /&gt;
		icon = string.format(&#039;[[File:%s|20px|link=%s]]&#039;, row.icon,&lt;br /&gt;
			page_link(ctx, row.icon_link or row.link))&lt;br /&gt;
	end&lt;br /&gt;
	return ctx.frame:expandTemplate{ title = &#039;Icon/&#039; .. color, args = { icon, value_html } }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Ammo and reload time are meaningless for heroes whose weapon never reloads.&lt;br /&gt;
local NO_AMMO_STATS = { ClipSize = true, ReloadTime = true }&lt;br /&gt;
&lt;br /&gt;
-- Returns true when the row rendered.&lt;br /&gt;
local function render_stat_row(ctx, tbl, row, color, is_first)&lt;br /&gt;
	if row.show and not row.show(ctx) then return false end&lt;br /&gt;
	if row.optional and not has_override_or_stat(ctx, row.param, row.stat) then return false end&lt;br /&gt;
&lt;br /&gt;
	local value, scaling_inner&lt;br /&gt;
	if NO_AMMO_STATS[row.stat] and ctx.args.no_ammo == &#039;true&#039; then&lt;br /&gt;
		value, scaling_inner = &#039;N/A&#039;, &#039;&#039;&lt;br /&gt;
	else&lt;br /&gt;
		local override = row.param and ctx.args[row.param] or &#039;&#039;&lt;br /&gt;
		if override ~= &#039;&#039; then&lt;br /&gt;
			value = override&lt;br /&gt;
		else&lt;br /&gt;
			value = format_value(get_stat(ctx.hero, row.stat), row.sig_figs)&lt;br /&gt;
		end&lt;br /&gt;
		value = value .. (row.suffix or &#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
		if row.stat and row.scaling ~= false then&lt;br /&gt;
			local parts = {}&lt;br /&gt;
			for _, scaling in ipairs(get_scaling(ctx.hero, row.stat)) do&lt;br /&gt;
				parts[#parts + 1] = hero_module.write_scalar_str(scaling.value, scaling.type)&lt;br /&gt;
			end&lt;br /&gt;
			scaling_inner = table.concat(parts)&lt;br /&gt;
			value = value .. &#039;&amp;lt;span class=&amp;quot;infobox-h-spiritboon&amp;quot;&amp;gt;&#039; .. scaling_inner .. &#039;&amp;lt;/span&amp;gt;&#039;&lt;br /&gt;
		else&lt;br /&gt;
			scaling_inner = &#039;&#039;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local tr = tbl:tag(&#039;tr&#039;)&lt;br /&gt;
&lt;br /&gt;
	local label_cell = tr:tag(&#039;td&#039;)&lt;br /&gt;
	-- The section&#039;s first row needs extra headroom when it carries a scaling icon.&lt;br /&gt;
	if is_first and scaling_inner ~= &#039;&#039; then&lt;br /&gt;
		label_cell:addClass(&#039;infobox-h-stat-label-scaled&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	if row.link then&lt;br /&gt;
		label_cell:wikitext(string.format(&#039;[[%s|&amp;lt;span class=&amp;quot;infobox-h-stat-label&amp;quot;&amp;gt;%s&amp;lt;/span&amp;gt;]]:&#039;,&lt;br /&gt;
			page_link(ctx, row.link), label_of(row)))&lt;br /&gt;
	else&lt;br /&gt;
		label_cell:wikitext(label_of(row) .. &#039;:&#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local value_cell = tr:tag(&#039;td&#039;)&lt;br /&gt;
	if row.cell_class then value_cell:addClass(row.cell_class) end&lt;br /&gt;
	value_cell:wikitext(icon_cell(ctx, row, color, value))&lt;br /&gt;
&lt;br /&gt;
	return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_section(ctx, parent, section)&lt;br /&gt;
	local tbl = parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-subsection infobox-h-dropdown &#039; .. section.class)&lt;br /&gt;
		:tag(&#039;table&#039;):addClass(&#039;mw-collapsible infobox-h-subsection-child&#039;)&lt;br /&gt;
&lt;br /&gt;
	local caption = tbl:tag(&#039;caption&#039;):addClass(&#039;infobox-h-dropdown-header&#039;)&lt;br /&gt;
	if ctx.if_lang == &#039;/ru&#039; then&lt;br /&gt;
		caption:addClass(&#039;infobox-h-dropdown-header-overflow&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	caption:wikitext(string.format(&lt;br /&gt;
		&#039;[[%s|&amp;lt;span class=&amp;quot;infotext-h-bold infobox-h-dropdown-title&amp;quot;&amp;gt;%s&amp;lt;/span&amp;gt;]]&#039;,&lt;br /&gt;
		page_link(ctx, section.link), lang_module.get_string(section.header)))&lt;br /&gt;
&lt;br /&gt;
	local is_first = true&lt;br /&gt;
	for _, row in ipairs(section.rows) do&lt;br /&gt;
		if render_stat_row(ctx, tbl, row, section.color, is_first) then&lt;br /&gt;
			is_first = false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_information(ctx, parent)&lt;br /&gt;
	local present = {}&lt;br /&gt;
	for _, row in ipairs(INFO_ROWS) do&lt;br /&gt;
		if ctx.args[row.param] ~= &#039;&#039; then present[#present + 1] = row end&lt;br /&gt;
	end&lt;br /&gt;
	if #present == 0 then return end&lt;br /&gt;
&lt;br /&gt;
	parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-text-stat-subtitle&#039;)&lt;br /&gt;
		:wikitext(&#039;&amp;lt;span class=&amp;quot;infotext-h-bold infotext-h-title infotext-h-big&amp;quot;&amp;gt;&#039;&lt;br /&gt;
			.. dict_module.translate(&#039;Information&#039;) .. &#039;&amp;lt;/span&amp;gt;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local tbl = parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-subsection&#039;)&lt;br /&gt;
		:tag(&#039;table&#039;):addClass(&#039;infobox-h-subsection-child&#039;)&lt;br /&gt;
&lt;br /&gt;
	for _, row in ipairs(present) do&lt;br /&gt;
		local tr = tbl:tag(&#039;tr&#039;)&lt;br /&gt;
		local label = dict_module.translate(row.label_dict)&lt;br /&gt;
		if row.tooltip then&lt;br /&gt;
			label = ctx.frame:expandTemplate{ title = &#039;Tooltip&#039;, args = { label, row.tooltip } }&lt;br /&gt;
		end&lt;br /&gt;
		if row.link then&lt;br /&gt;
			tr:tag(&#039;td&#039;):wikitext(string.format(&#039;[[%s|&amp;lt;span class=&amp;quot;infobox-h-stat-label&amp;quot;&amp;gt;%s&amp;lt;/span&amp;gt;]]:&#039;,&lt;br /&gt;
				page_link(ctx, row.link), label))&lt;br /&gt;
		else&lt;br /&gt;
			tr:tag(&#039;td&#039;):wikitext(label .. &#039;:&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		tr:tag(&#039;td&#039;):wikitext(ctx.args[row.param])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_title(ctx, parent)&lt;br /&gt;
	local th = parent:tag(&#039;tr&#039;):tag(&#039;th&#039;):attr(&#039;colspan&#039;, &#039;2&#039;):addClass(&#039;infobox-h-title&#039;)&lt;br /&gt;
	if ctx.args.name_font_size ~= &#039;&#039; then&lt;br /&gt;
		th:cssText(&#039;font-size: &#039; .. ctx.args.name_font_size .. &#039;;&#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local wrap = th:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero&#039;)&lt;br /&gt;
	wrap:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-left&#039;)&lt;br /&gt;
&lt;br /&gt;
	-- Name logo: &amp;lt;Name&amp;gt;_name.svg, or &amp;lt;Name&amp;gt;_name_&amp;lt;lang&amp;gt;.svg on translated pages.&lt;br /&gt;
	-- Heroes without a logo fall back to their name as plain text.&lt;br /&gt;
	local svg = ((ctx.args.name ~= &#039;&#039; and ctx.args.name) or ctx.hero_en) .. &#039;_name&#039;&lt;br /&gt;
	if ctx.if_lang ~= &#039;&#039; then svg = svg .. &#039;_&#039; .. ctx.lang_code end&lt;br /&gt;
&lt;br /&gt;
	local middle = wrap:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-middle&#039;)&lt;br /&gt;
	if util.page_exists(&#039;File:&#039; .. svg .. &#039;.svg&#039;) then&lt;br /&gt;
		middle:wikitext(ctx.frame:expandTemplate{ title = &#039;Icon/White&#039;, args = {&lt;br /&gt;
			string.format(&#039;[[File:%s.svg|%s|class=notpageimage]]&#039;,&lt;br /&gt;
				svg, theme_var(ctx.hero_key, &#039;logo-height&#039;)) } })&lt;br /&gt;
	else&lt;br /&gt;
		middle:wikitext(ctx.display_name)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local right = wrap:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-right&#039;):tag(&#039;div&#039;)&lt;br /&gt;
	if ctx.show_icon then&lt;br /&gt;
		local icon = ((ctx.args.icon ~= &#039;&#039; and ctx.args.icon) or ctx.hero_en) .. &#039;.png&#039;&lt;br /&gt;
		if util.page_exists(&#039;File:&#039; .. icon) then&lt;br /&gt;
			right:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-tornbox&#039;)&lt;br /&gt;
			right:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-icon&#039;)&lt;br /&gt;
				:wikitext(string.format(&#039;[[File:%s|center|35px|class=notpageimage]]&#039;, icon))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_card(ctx, parent)&lt;br /&gt;
	local card = (ctx.args.card ~= &#039;&#039; and ctx.args.card) or (ctx.hero_en .. &#039; card.png&#039;)&lt;br /&gt;
	if not util.page_exists(&#039;File:&#039; .. card) then card = FALLBACK_CARD end&lt;br /&gt;
	parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-image&#039;)&lt;br /&gt;
		:tag(&#039;div&#039;)&lt;br /&gt;
		:wikitext(string.format(&#039;[[File:%s|150px|alt=%s]]&#039;,&lt;br /&gt;
			card, mw.title.getCurrentTitle().baseText))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_tags(ctx, parent)&lt;br /&gt;
	if not ctx.show_hero_tags then return end&lt;br /&gt;
&lt;br /&gt;
	local tags = {}&lt;br /&gt;
	for i = 1, 3 do&lt;br /&gt;
		local tag = hero_module.get_hero_tag({ args = { ctx.hero_en, tostring(i) } })&lt;br /&gt;
		if tag and tag ~= &#039;&#039; and not tag:find(&#039;Error:&#039;, 1, true) then&lt;br /&gt;
			tags[#tags + 1] = tag&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if #tags == 0 then return end&lt;br /&gt;
&lt;br /&gt;
	local inner = parent:tag(&#039;tr&#039;):tag(&#039;td&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-herotags-row&#039;)&lt;br /&gt;
		:tag(&#039;div&#039;):addClass(&#039;infobox-h-herotags-inner &#039; .. ctx.hero_key)&lt;br /&gt;
	for _, tag in ipairs(tags) do&lt;br /&gt;
		inner:tag(&#039;div&#039;):addClass(&#039;infobox-h-herotag-pill&#039;):wikitext(tag)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_categories(ctx)&lt;br /&gt;
	if mw.ustring.lower(ctx.args.nocat ~= &#039;&#039; and ctx.args.nocat or &#039;false&#039;) ~= &#039;false&#039; then&lt;br /&gt;
		return &#039;&#039;&lt;br /&gt;
	end&lt;br /&gt;
	if get_stat(ctx.hero, &#039;IsDisabled&#039;) ~= false then return &#039;&#039; end&lt;br /&gt;
&lt;br /&gt;
	local out = {}&lt;br /&gt;
	for _, category in ipairs(CATEGORIES) do&lt;br /&gt;
		if category.when == nil or category.when(ctx) then&lt;br /&gt;
			local name = category.name&lt;br /&gt;
			if type(name) == &#039;function&#039; then name = name(ctx) end&lt;br /&gt;
			out[#out + 1] = ctx.frame:expandTemplate{ title = &#039;lang cat&#039;, args = { name } }&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(out)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Entry point&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- |key= accepts a hero_* key or the hero&#039;s English name; unknown values fall back&lt;br /&gt;
-- to the target dummy, the default character in Sandbox mode.&lt;br /&gt;
local function resolve_hero_key(provided)&lt;br /&gt;
	if provided == nil or provided == &#039;&#039; then return DEFAULT_HERO_KEY end&lt;br /&gt;
	if hero_data_all[provided] ~= nil then return provided end&lt;br /&gt;
	local wanted = mw.ustring.lower(provided)&lt;br /&gt;
	for key, hero in pairs(hero_data_all) do&lt;br /&gt;
		if hero.Name and mw.ustring.lower(hero.Name) == wanted then return key end&lt;br /&gt;
	end&lt;br /&gt;
	return DEFAULT_HERO_KEY&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render(frame)&lt;br /&gt;
	local parent = frame:getParent()&lt;br /&gt;
	local invoke_args = frame.args&lt;br /&gt;
	local template_args = parent and parent.args or {}&lt;br /&gt;
&lt;br /&gt;
	-- Missing parameters read as &#039;&#039; so callers never have to nil-check.&lt;br /&gt;
	local args = setmetatable({}, { __index = function(_, k)&lt;br /&gt;
		local value = invoke_args[k]&lt;br /&gt;
		if value == nil then value = template_args[k] end&lt;br /&gt;
		return value or &#039;&#039;&lt;br /&gt;
	end })&lt;br /&gt;
&lt;br /&gt;
	local hero_key = resolve_hero_key(args.key)&lt;br /&gt;
	local hero = hero_data_all[hero_key]&lt;br /&gt;
&lt;br /&gt;
	local ctx = {&lt;br /&gt;
		frame     = frame,&lt;br /&gt;
		args      = args,&lt;br /&gt;
		hero      = hero,&lt;br /&gt;
		hero_key  = hero_key,&lt;br /&gt;
		hero_en   = hero.Name,&lt;br /&gt;
		lang_code = lang_module.get_lang_code(),&lt;br /&gt;
		if_lang   = frame:expandTemplate{ title = &#039;If lang&#039; },&lt;br /&gt;
	}&lt;br /&gt;
	ctx.display_name = (args.name ~= &#039;&#039; and args.name) or lang_module.get_string(hero_key)&lt;br /&gt;
&lt;br /&gt;
	ctx.show_hero_tags = args.show_hero_tags ~= &#039;false&#039;&lt;br /&gt;
	ctx.show_icon      = args.show_icon ~= &#039;false&#039;&lt;br /&gt;
&lt;br /&gt;
	-- Needed by both row visibility and the category rules.&lt;br /&gt;
	ctx.pellets = stat_number(ctx, &#039;BulletsPerShot&#039;, &#039;pellets_per_shot&#039;)&lt;br /&gt;
	ctx.burst   = stat_number(ctx, &#039;BulletsPerBurst&#039;, &#039;bullets_per_burst&#039;)&lt;br /&gt;
	ctx.dash    = stat_number(ctx, &#039;GroundDashSpeed&#039;)&lt;br /&gt;
	ctx.stamina = (args.stamina ~= &#039;&#039; and args.stamina)&lt;br /&gt;
		or format_value(get_stat(hero, &#039;Stamina&#039;), 2)&lt;br /&gt;
&lt;br /&gt;
	local root = mw.html.create(&#039;div&#039;):addClass(&#039;infobox-h &#039; .. hero_key)&lt;br /&gt;
	root:tag(&#039;div&#039;):addClass(&#039;infobox-h-background&#039;)&lt;br /&gt;
		:wikitext(string.format(&#039;[[File:%s|%s|right|link=|class=notpageimage]]&#039;,&lt;br /&gt;
			theme_var(hero_key, &#039;bg-image&#039;), theme_var(hero_key, &#039;bg-height&#039;)))&lt;br /&gt;
&lt;br /&gt;
	local tbl = root:tag(&#039;table&#039;):addClass(&#039;infobox-h-content&#039;)&lt;br /&gt;
	render_title(ctx, tbl)&lt;br /&gt;
	render_card(ctx, tbl)&lt;br /&gt;
	render_tags(ctx, tbl)&lt;br /&gt;
	for _, section in ipairs(SECTIONS) do&lt;br /&gt;
		render_section(ctx, tbl, section)&lt;br /&gt;
	end&lt;br /&gt;
	render_information(ctx, tbl)&lt;br /&gt;
&lt;br /&gt;
	if mw.title.getCurrentTitle().nsText ~= &#039;Template&#039; then&lt;br /&gt;
		frame:callParserFunction(&#039;DISPLAYTITLE&#039;, ctx.display_name)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root) .. render_categories(ctx)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>~2026-GlassGreenKinetic677</name></author>
	</entry>
	<entry>
		<id>https://deadlock.wiki/Module:InfoboxHero?diff=93698</id>
		<title>Module:InfoboxHero</title>
		<link rel="alternate" type="text/html" href="https://deadlock.wiki/Module:InfoboxHero?diff=93698"/>
		<updated>2026-07-16T19:46:30Z</updated>

		<summary type="html">&lt;p&gt;~2026-GlassGreenKinetic677: change tooltip type (the boring html title style)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;--------------------------------------------------------------------------------&lt;br /&gt;
-- Module:InfoboxHero&lt;br /&gt;
--&lt;br /&gt;
-- Renders the hero infobox. Called from [[Template:Infobox hero]], which passes&lt;br /&gt;
-- the editor&#039;s parameters through as the parent frame:&lt;br /&gt;
--&lt;br /&gt;
--     {{#invoke:InfoboxHero|render}}&lt;br /&gt;
--&lt;br /&gt;
-- May also be invoked directly for testing, in which case parameters are read&lt;br /&gt;
-- from the invoke itself:&lt;br /&gt;
--&lt;br /&gt;
--     {{#invoke:InfoboxHero|render|key=hero_atlas}}&lt;br /&gt;
--&lt;br /&gt;
-- Stat data comes from [[Data:HeroData.json]]; per-hero colors, backgrounds and&lt;br /&gt;
-- logo sizes from [[Data:Infobox hero.json]]; labels from [[Module:Lang]] and&lt;br /&gt;
-- [[Module:Dictionary]]. Styling lives in [[Template:Infobox hero/styles.css]]&lt;br /&gt;
-- and [[Template:Infobox hero/themes.css]].&lt;br /&gt;
--&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local hero_data_all = mw.loadJsonData(&#039;Data:HeroData.json&#039;)&lt;br /&gt;
local theme_data    = mw.loadJsonData(&#039;Data:Infobox_hero.json&#039;)&lt;br /&gt;
&lt;br /&gt;
local util        = require(&#039;Module:Utilities&#039;)&lt;br /&gt;
local lang_module = require(&#039;Module:Lang&#039;)&lt;br /&gt;
local dict_module = require(&#039;Module:Dictionary&#039;)&lt;br /&gt;
local hero_module = require(&#039;Module:HeroData&#039;)&lt;br /&gt;
&lt;br /&gt;
local get_stat, has_stat, has_override_or_stat, stat_number&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- CONFIG&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local DEFAULT_HERO_KEY = &#039;hero_targetdummy&#039;  -- used when |key= is missing/unknown&lt;br /&gt;
local DEFAULT_SIG_FIGS = 3&lt;br /&gt;
local FALLBACK_CARD    = &#039;Dummy card.png&#039;&lt;br /&gt;
&lt;br /&gt;
-- Row fields (all optional except a label):&lt;br /&gt;
--   stat        Key in Data:HeroData.json. Resolved top-level, then .Weapon,&lt;br /&gt;
--               then .Weapon.AltFire. Omit for manual-input-only rows.&lt;br /&gt;
--   param       |param= that overrides the value.&lt;br /&gt;
--   label       Key in Data:Lang_&amp;lt;code&amp;gt;.json.&lt;br /&gt;
--   label_dict  Key in Data:Dictionary. Used instead of `label`.&lt;br /&gt;
--   label_text  Literal, untranslated label. Used instead of `label`.&lt;br /&gt;
--   link        Target for the label, as &#039;Page&#039; or &#039;Page#Anchor&#039;. The language&lt;br /&gt;
--               suffix is inserted for us, so write the plain English title.&lt;br /&gt;
--   icon_link   Target for the icon. Defaults to `link`.&lt;br /&gt;
--   icon        File: name of the value icon. Omit for no icon.&lt;br /&gt;
--   sig_figs    Significant figures. Defaults to 3.&lt;br /&gt;
--   suffix      Appended directly after the value, e.g. &#039;%&#039;, &#039;s&#039;, &#039;m/s&#039;.&lt;br /&gt;
--   scaling     Set false to suppress the Boon/Spirit scaling span. Scaling is&lt;br /&gt;
--               shown by default wherever the data provides it.&lt;br /&gt;
--   optional    Hide the row unless the hero has a non-zero value or scaling.&lt;br /&gt;
--   cell_class  Extra class on the value cell.&lt;br /&gt;
--   show        function(ctx) -&amp;gt; boolean. Row omitted when false.&lt;br /&gt;
&lt;br /&gt;
local SECTIONS = {&lt;br /&gt;
	{&lt;br /&gt;
		class  = &#039;infobox-h-dropdown-weapon&#039;,&lt;br /&gt;
		header = &#039;CitadelHeroStats_Weapon&#039;,&lt;br /&gt;
		link   = &#039;Stats#Weapon&#039;,&lt;br /&gt;
		color  = &#039;Brown&#039;,&lt;br /&gt;
		rows = {&lt;br /&gt;
			{&lt;br /&gt;
				stat    = &#039;DPS&#039;,&lt;br /&gt;
				param   = &#039;dps&#039;,&lt;br /&gt;
				label   = &#039;DPS_label&#039;,&lt;br /&gt;
				link    = &#039;Damage per second&#039;,&lt;br /&gt;
				icon    = &#039;Damage.png&#039;,&lt;br /&gt;
				-- DPS is derived from bullet damage and fire rate, both of which&lt;br /&gt;
				-- show their own scaling; repeating it here would double-count.&lt;br /&gt;
				scaling = false,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;BulletDamage&#039;,&lt;br /&gt;
				param      = &#039;bullet_damage&#039;,&lt;br /&gt;
				label      = &#039;StatDesc_BulletDamage&#039;,&lt;br /&gt;
				link       = &#039;Bullet Damage&#039;,&lt;br /&gt;
				icon       = &#039;Bullet damage.png&#039;,&lt;br /&gt;
				cell_class = &#039;infobox-h-stat-value-padded&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat      = &#039;BulletsPerShot&#039;,&lt;br /&gt;
				param     = &#039;pellets_per_shot&#039;,&lt;br /&gt;
				label     = &#039;BonusPelletsPerShot_label&#039;,&lt;br /&gt;
				link      = &#039;Damage per second&#039;,&lt;br /&gt;
				icon      = &#039;Bullets per sec icon.png&#039;,&lt;br /&gt;
				icon_link = &#039;Fire Rate&#039;,&lt;br /&gt;
				show      = function(ctx) return ctx.pellets &amp;gt; 1 end,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;BulletsPerBurst&#039;,&lt;br /&gt;
				param      = &#039;bullets_per_burst&#039;,&lt;br /&gt;
				label_dict = &#039;BulletsPerBurst&#039;,&lt;br /&gt;
				link       = &#039;Damage per second&#039;,&lt;br /&gt;
				icon       = &#039;Bullets per sec icon.png&#039;,&lt;br /&gt;
				icon_link  = &#039;Fire Rate&#039;,&lt;br /&gt;
				show       = function(ctx) return ctx.burst &amp;gt; 1 end,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat  = &#039;ClipSize&#039;,&lt;br /&gt;
				param = &#039;ammo&#039;,&lt;br /&gt;
				label = &#039;StatDesc_ClipSizeBonus&#039;,&lt;br /&gt;
				link  = &#039;Ammo&#039;,&lt;br /&gt;
				icon  = &#039;Clip size icon.png&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat  = &#039;RoundsPerSecond&#039;,&lt;br /&gt;
				param = &#039;bullets_per_sec&#039;,&lt;br /&gt;
				label = &#039;StatDesc_RoundsPerSecond&#039;,&lt;br /&gt;
				link  = &#039;Fire Rate&#039;,&lt;br /&gt;
				icon  = &#039;Bullets per sec icon.png&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;ReloadTime&#039;,&lt;br /&gt;
				param  = &#039;reload_time&#039;,&lt;br /&gt;
				label  = &#039;StatDesc_ReloadTime&#039;,&lt;br /&gt;
				link   = &#039;Reload Time&#039;,&lt;br /&gt;
				icon   = &#039;AttributeIconReloadTime.png&#039;,&lt;br /&gt;
				suffix = &#039;s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;BulletSpeed&#039;,&lt;br /&gt;
				param  = &#039;bullet_velocity&#039;,&lt;br /&gt;
				label  = &#039;StatDesc_BulletSpeed&#039;,&lt;br /&gt;
				link   = &#039;Bullet Velocity&#039;,&lt;br /&gt;
				icon   = &#039;AttributeIconBulletSpeed.png&#039;,&lt;br /&gt;
				suffix = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;LightMeleeDamage&#039;,&lt;br /&gt;
				param      = &#039;light_melee&#039;,&lt;br /&gt;
				label      = &#039;StatDesc_LightMeleeDamage&#039;,&lt;br /&gt;
				link       = &#039;Melee Damage&#039;,&lt;br /&gt;
				icon       = &#039;Melee damage.png&#039;,&lt;br /&gt;
				cell_class = &#039;infobox-h-stat-value-padded&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;HeavyMeleeDamage&#039;,&lt;br /&gt;
				param      = &#039;heavy_melee&#039;,&lt;br /&gt;
				label      = &#039;StatDesc_HeavyMeleeDamage&#039;,&lt;br /&gt;
				link       = &#039;Melee Damage&#039;,&lt;br /&gt;
				icon       = &#039;Melee damage.png&#039;,&lt;br /&gt;
				cell_class = &#039;infobox-h-stat-value-padded&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				param      = &#039;max_range&#039;,&lt;br /&gt;
				label_text = &#039;Max Range&#039;,&lt;br /&gt;
				link       = &#039;Falloff Range&#039;,&lt;br /&gt;
				show       = function(ctx) return ctx.args.max_range ~= &#039;&#039; end,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;CritDamageBonusPercent&#039;,&lt;br /&gt;
				label  = &#039;StatDesc_CritDamageBonusScale&#039;,&lt;br /&gt;
				link   = &#039;Weapon Damage#Crit_Multiplier&#039;,&lt;br /&gt;
				icon   = &#039;Crit reduction.png&#039;,&lt;br /&gt;
				suffix = &#039;%&#039;,&lt;br /&gt;
				show   = function(ctx)&lt;br /&gt;
					return stat_number(ctx, &#039;CritDamageBonusPercent&#039;) ~= 0&lt;br /&gt;
				end,&lt;br /&gt;
			},&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		class  = &#039;infobox-h-dropdown-vitality&#039;,&lt;br /&gt;
		header = &#039;CitadelHeroStats_Vitality&#039;,&lt;br /&gt;
		link   = &#039;Stats#Vitality&#039;,&lt;br /&gt;
		color  = &#039;Green&#039;,&lt;br /&gt;
		rows = {&lt;br /&gt;
			{&lt;br /&gt;
				stat  = &#039;MaxHealth&#039;,&lt;br /&gt;
				param = &#039;max_health&#039;,&lt;br /&gt;
				label = &#039;StatDesc_HealthAttribute&#039;,&lt;br /&gt;
				link  = &#039;Health&#039;,&lt;br /&gt;
				icon  = &#039;Health.png&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;BaseHealthRegen&#039;,&lt;br /&gt;
				param    = &#039;health_regen&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_HealthRegen&#039;,&lt;br /&gt;
				link     = &#039;Health Regen&#039;,&lt;br /&gt;
				icon     = &#039;Health regen.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;CritDamageReceivedPercent&#039;,&lt;br /&gt;
				param    = &#039;crit_reduction&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_CritDamageReceivedScale&#039;,&lt;br /&gt;
				link     = &#039;Damage Resistance#Crit_Reduction&#039;,&lt;br /&gt;
				icon     = &#039;Crit reduction.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat      = &#039;BulletResist&#039;,&lt;br /&gt;
				param     = &#039;bullet_resist&#039;,&lt;br /&gt;
				label     = &#039;StatDesc_BulletArmorDamageReduction&#039;,&lt;br /&gt;
				link      = &#039;Damage Resistance&#039;,&lt;br /&gt;
				icon      = &#039;Bullet Armor.png&#039;,&lt;br /&gt;
				icon_link = &#039;Bullet Resist&#039;,&lt;br /&gt;
				suffix    = &#039;%&#039;,&lt;br /&gt;
				optional  = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;TechResist&#039;,&lt;br /&gt;
				param    = &#039;spirit_resist&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_TechArmorDamageReduction&#039;,&lt;br /&gt;
				link     = &#039;Damage Resistance#Spirit_Resist&#039;,&lt;br /&gt;
				icon     = &#039;Spirit Armor.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;MeleeResist&#039;,&lt;br /&gt;
				param    = &#039;melee_resist&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_MeleeResist&#039;,&lt;br /&gt;
				link     = &#039;Damage Resistance#Melee Resist&#039;,&lt;br /&gt;
				icon     = &#039;Melee damage.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;DebuffResist&#039;,&lt;br /&gt;
				param    = &#039;debuff_resist&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_DebuffResist&#039;,&lt;br /&gt;
				link     = &#039;Debuff Resist&#039;,&lt;br /&gt;
				icon     = &#039;Spirit Armor.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;BulletLifesteal&#039;,&lt;br /&gt;
				param    = &#039;bullet_lifesteal&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_BulletLifesteal&#039;,&lt;br /&gt;
				link     = &#039;Lifesteal#Bullet Lifesteal&#039;,&lt;br /&gt;
				icon     = &#039;AttributeIconBulletLifesteal.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;SpiritLifesteal&#039;,&lt;br /&gt;
				param    = &#039;spirit_lifesteal&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_SpiritLifesteal&#039;,&lt;br /&gt;
				link     = &#039;Lifesteal#Spirit Lifesteal&#039;,&lt;br /&gt;
				icon     = &#039;AttributeIconBulletLifesteal.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;MaxMoveSpeed&#039;,&lt;br /&gt;
				param    = &#039;move_speed&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_RunSpeed&#039;,&lt;br /&gt;
				link     = &#039;Move Speed&#039;,&lt;br /&gt;
				icon     = &#039;Move speed.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
				suffix   = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;SprintSpeed&#039;,&lt;br /&gt;
				param    = &#039;sprint_speed&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_SprintSpeed&#039;,&lt;br /&gt;
				link     = &#039;Move Speed#Sprint&#039;,&lt;br /&gt;
				icon     = &#039;Move speed sprint.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
				suffix   = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;GroundDashSpeed&#039;,&lt;br /&gt;
				label  = &#039;DashSpeed_label&#039;,&lt;br /&gt;
				link   = &#039;Stamina#Dash_Speed&#039;,&lt;br /&gt;
				icon   = &#039;Springy boots icon.png&#039;,&lt;br /&gt;
				suffix = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;Stamina&#039;,&lt;br /&gt;
				param    = &#039;stamina&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_Stamina&#039;,&lt;br /&gt;
				link     = &#039;Stamina&#039;,&lt;br /&gt;
				icon     = &#039;Springy boots icon.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;StaminaCooldown&#039;,&lt;br /&gt;
				param    = &#039;stamina_cooldown&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_StaminaCooldown&#039;,&lt;br /&gt;
				link     = &#039;Stamina#Cooldown&#039;,&lt;br /&gt;
				icon     = &#039;AttributeIconTechDuration.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
				suffix   = &#039;s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		class  = &#039;infobox-h-dropdown-spirit&#039;,&lt;br /&gt;
		header = &#039;CitadelHeroStats_Spirit&#039;,&lt;br /&gt;
		link   = &#039;Stats#Spirit&#039;,&lt;br /&gt;
		color  = &#039;Purple&#039;,&lt;br /&gt;
		rows = {&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;TechPower&#039;,&lt;br /&gt;
				param    = &#039;spirit_power&#039;,&lt;br /&gt;
				label    = &#039;SpiritPower_label&#039;,&lt;br /&gt;
				link     = &#039;Spirit Power&#039;,&lt;br /&gt;
				icon     = &#039;Spirit icon noborder.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
			},&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- The &amp;quot;Information&amp;quot; block. Manual input only; hidden entirely when all are empty.&lt;br /&gt;
local INFO_ROWS = {&lt;br /&gt;
	{ param = &#039;release_date&#039;, label_dict = &#039;ReleaseDate&#039;, link = &#039;Heroes by release&#039; },&lt;br /&gt;
	{ param = &#039;pronouns&#039;,     label_dict = &#039;Pronouns&#039; },&lt;br /&gt;
	{ param = &#039;voice_actor&#039;,  label_dict = &#039;VoiceActor&#039; },&lt;br /&gt;
	{ param = &#039;codenames&#039;,    label_dict = &#039;Codenames&#039;, tooltip = &#039;Codenames used for this hero in the game files&#039; },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- name: literal, or function(ctx) -&amp;gt; string.  when: function(ctx) -&amp;gt; boolean.&lt;br /&gt;
local CATEGORIES = {&lt;br /&gt;
	{ name = &#039;Heroes&#039; },&lt;br /&gt;
	{&lt;br /&gt;
		name = function(ctx) return ctx.hero_en end,&lt;br /&gt;
		-- Silver (Transformed) is documented on Silver&#039;s page and shares her category.&lt;br /&gt;
		when = function(ctx) return ctx.hero_en ~= &#039;Silver (Transformed)&#039; end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with spreadshot weapons&#039;,&lt;br /&gt;
		when = function(ctx) return ctx.pellets &amp;gt; 1 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with single damage spreadshot weapons&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return ctx.pellets &amp;gt; 1 and get_stat(ctx.hero, &#039;HitOnceAcrossAllBullets&#039;) == true&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with burst weapons&#039;,&lt;br /&gt;
		when = function(ctx) return ctx.burst &amp;gt; 1 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with automatic weapons&#039;,&lt;br /&gt;
		when = function(ctx) return ctx.pellets &amp;lt;= 1 and ctx.burst &amp;lt;= 1 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with alternate fire weapons&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return ctx.hero.Weapon ~= nil and ctx.hero.Weapon.AltFire ~= nil&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with single reload weapons&#039;,&lt;br /&gt;
		when = function(ctx) return get_stat(ctx.hero, &#039;ReloadSingle&#039;) == true end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with spin up weapons&#039;,&lt;br /&gt;
		when = function(ctx) return has_stat(ctx.hero, &#039;SpinAcceleration&#039;) end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with no weapon damage falloff&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return (get_stat(ctx.hero, &#039;FalloffStartRange&#039;) or 0)&lt;br /&gt;
			    == (get_stat(ctx.hero, &#039;FalloffEndRange&#039;) or 0)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &amp;quot;Heroes that don&#039;t deal critical damage&amp;quot;,&lt;br /&gt;
		when = function(ctx) return get_stat(ctx.hero, &#039;CanCrit&#039;) == false end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified crit scale&#039;,&lt;br /&gt;
		when = function(ctx) return stat_number(ctx, &#039;CritDamageBonusPercent&#039;) ~= 0 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified crit resistance&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;crit_reduction&#039;, &#039;CritDamageReceivedPercent&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified bullet resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;bullet_resist&#039;, &#039;BulletResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified spirit resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;spirit_resist&#039;, &#039;TechResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified melee resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;melee_resist&#039;, &#039;MeleeResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified debuff resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;debuff_resist&#039;, &#039;DebuffResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified bullet lifesteal&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;bullet_lifesteal&#039;, &#039;BulletLifesteal&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified spirit lifesteal&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;spirit_lifesteal&#039;, &#039;SpiritLifesteal&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified gravity scale&#039;,&lt;br /&gt;
		when = function(ctx) return has_stat(ctx.hero, &#039;GravityChange&#039;) end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = function(ctx) return &#039;Heroes with &#039; .. ctx.stamina .. &#039; stamina&#039; end,&lt;br /&gt;
		when = function(ctx) return ctx.stamina ~= &#039;&#039; end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = function(ctx)&lt;br /&gt;
			if ctx.dash &amp;lt; 14.7 then return &#039;Heroes with slow dash speed&#039; end&lt;br /&gt;
			if ctx.dash &amp;gt; 14.7 then return &#039;Heroes with fast dash speed&#039; end&lt;br /&gt;
			return &#039;Heroes with medium dash speed&#039;&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Data access&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- Stats live at the top level for vitality, and under .Weapon for weapon stats.&lt;br /&gt;
get_stat = function(hero, stat)&lt;br /&gt;
	if hero == nil or stat == nil then return nil end&lt;br /&gt;
	local value = hero[stat]&lt;br /&gt;
	if value == nil and hero.Weapon then&lt;br /&gt;
		value = hero.Weapon[stat]&lt;br /&gt;
		if value == nil and hero.Weapon.AltFire then&lt;br /&gt;
			value = hero.Weapon.AltFire[stat]&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return value&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- The raw Boon (Level) and Spirit scaling values for a stat, if any.&lt;br /&gt;
local function get_scaling(hero, stat)&lt;br /&gt;
	local out = {}&lt;br /&gt;
	for _, scaling_type in ipairs({ &#039;Level&#039;, &#039;Spirit&#039; }) do&lt;br /&gt;
		local scaling = hero[scaling_type .. &#039;Scaling&#039;]&lt;br /&gt;
		local value = scaling and stat and scaling[stat]&lt;br /&gt;
		if value ~= nil and value ~= 0 then&lt;br /&gt;
			out[#out + 1] = { type = scaling_type, value = value }&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return out&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- True when the hero has a non-zero base value, or any scaling, for a stat.&lt;br /&gt;
has_stat = function(hero, stat)&lt;br /&gt;
	local value = get_stat(hero, stat)&lt;br /&gt;
	if value ~= nil and value ~= 0 then return true end&lt;br /&gt;
	return #get_scaling(hero, stat) &amp;gt; 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
has_override_or_stat = function(ctx, param, stat)&lt;br /&gt;
	if param and ctx.args[param] ~= &#039;&#039; then return true end&lt;br /&gt;
	return has_stat(ctx.hero, stat)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Rounded numeric value of a stat, honouring a |param= override.&lt;br /&gt;
stat_number = function(ctx, stat, param)&lt;br /&gt;
	if param and ctx.args[param] ~= &#039;&#039; then return tonumber(ctx.args[param]) or 0 end&lt;br /&gt;
	return tonumber(util.round_to_sig_fig(get_stat(ctx.hero, stat) or 0, DEFAULT_SIG_FIGS)) or 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function format_value(raw, sig_figs)&lt;br /&gt;
	if raw == nil then return &#039;0&#039; end&lt;br /&gt;
	local rounded = util.round_to_sig_fig(raw, sig_figs or DEFAULT_SIG_FIGS)&lt;br /&gt;
	return tostring(rounded ~= nil and rounded or raw)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Rendering helpers&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function theme_var(hero_key, name)&lt;br /&gt;
	local hero_theme = theme_data[hero_key]&lt;br /&gt;
	if hero_theme and hero_theme[name] ~= nil then return hero_theme[name] end&lt;br /&gt;
	return theme_data[&#039;hero_default&#039;][name]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function label_of(row)&lt;br /&gt;
	if row.label_text then return row.label_text end&lt;br /&gt;
	if row.label_dict then return dict_module.translate(row.label_dict) end&lt;br /&gt;
	return lang_module.get_string(row.label)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- &#039;Damage Resistance#Spirit_Resist&#039; -&amp;gt; &#039;Damage Resistance/ru#Spirit_Resist&#039;&lt;br /&gt;
local function page_link(ctx, target)&lt;br /&gt;
	if not target then return &#039;&#039; end&lt;br /&gt;
	local page, anchor = target:match(&#039;^([^#]*)(#?.*)$&#039;)&lt;br /&gt;
	return page .. ctx.if_lang .. anchor&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function icon_cell(ctx, row, color, value_html)&lt;br /&gt;
	local icon = &#039;&#039;&lt;br /&gt;
	if row.icon then&lt;br /&gt;
		icon = string.format(&#039;[[File:%s|20px|link=%s]]&#039;, row.icon,&lt;br /&gt;
			page_link(ctx, row.icon_link or row.link))&lt;br /&gt;
	end&lt;br /&gt;
	return ctx.frame:expandTemplate{ title = &#039;Icon/&#039; .. color, args = { icon, value_html } }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Ammo and reload time are meaningless for heroes whose weapon never reloads.&lt;br /&gt;
local NO_AMMO_STATS = { ClipSize = true, ReloadTime = true }&lt;br /&gt;
&lt;br /&gt;
-- Returns true when the row rendered.&lt;br /&gt;
local function render_stat_row(ctx, tbl, row, color, is_first)&lt;br /&gt;
	if row.show and not row.show(ctx) then return false end&lt;br /&gt;
	if row.optional and not has_override_or_stat(ctx, row.param, row.stat) then return false end&lt;br /&gt;
&lt;br /&gt;
	local value, scaling_inner&lt;br /&gt;
	if NO_AMMO_STATS[row.stat] and ctx.args.no_ammo == &#039;true&#039; then&lt;br /&gt;
		value, scaling_inner = &#039;N/A&#039;, &#039;&#039;&lt;br /&gt;
	else&lt;br /&gt;
		local override = row.param and ctx.args[row.param] or &#039;&#039;&lt;br /&gt;
		if override ~= &#039;&#039; then&lt;br /&gt;
			value = override&lt;br /&gt;
		else&lt;br /&gt;
			value = format_value(get_stat(ctx.hero, row.stat), row.sig_figs)&lt;br /&gt;
		end&lt;br /&gt;
		value = value .. (row.suffix or &#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
		if row.stat and row.scaling ~= false then&lt;br /&gt;
			local parts = {}&lt;br /&gt;
			for _, scaling in ipairs(get_scaling(ctx.hero, row.stat)) do&lt;br /&gt;
				parts[#parts + 1] = hero_module.write_scalar_str(scaling.value, scaling.type)&lt;br /&gt;
			end&lt;br /&gt;
			scaling_inner = table.concat(parts)&lt;br /&gt;
			value = value .. &#039;&amp;lt;span class=&amp;quot;infobox-h-spiritboon&amp;quot;&amp;gt;&#039; .. scaling_inner .. &#039;&amp;lt;/span&amp;gt;&#039;&lt;br /&gt;
		else&lt;br /&gt;
			scaling_inner = &#039;&#039;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local tr = tbl:tag(&#039;tr&#039;)&lt;br /&gt;
&lt;br /&gt;
	local label_cell = tr:tag(&#039;td&#039;)&lt;br /&gt;
	-- The section&#039;s first row needs extra headroom when it carries a scaling icon.&lt;br /&gt;
	if is_first and scaling_inner ~= &#039;&#039; then&lt;br /&gt;
		label_cell:addClass(&#039;infobox-h-stat-label-scaled&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	if row.link then&lt;br /&gt;
		label_cell:wikitext(string.format(&#039;[[%s|&amp;lt;span&amp;gt;%s&amp;lt;/span&amp;gt;]]:&#039;,&lt;br /&gt;
			page_link(ctx, row.link), label_of(row)))&lt;br /&gt;
	else&lt;br /&gt;
		label_cell:wikitext(label_of(row) .. &#039;:&#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local value_cell = tr:tag(&#039;td&#039;)&lt;br /&gt;
	if row.cell_class then value_cell:addClass(row.cell_class) end&lt;br /&gt;
	value_cell:wikitext(icon_cell(ctx, row, color, value))&lt;br /&gt;
&lt;br /&gt;
	return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_section(ctx, parent, section)&lt;br /&gt;
	local tbl = parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-subsection infobox-h-dropdown &#039; .. section.class)&lt;br /&gt;
		:tag(&#039;table&#039;):addClass(&#039;mw-collapsible infobox-h-subsection-child&#039;)&lt;br /&gt;
&lt;br /&gt;
	local caption = tbl:tag(&#039;caption&#039;):addClass(&#039;infobox-h-dropdown-header&#039;)&lt;br /&gt;
	if ctx.if_lang == &#039;/ru&#039; then&lt;br /&gt;
		caption:addClass(&#039;infobox-h-dropdown-header-overflow&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	caption:wikitext(string.format(&lt;br /&gt;
		&#039;[[%s|&amp;lt;span class=&amp;quot;infotext-h-bold infobox-h-dropdown-title&amp;quot;&amp;gt;%s&amp;lt;/span&amp;gt;]]&#039;,&lt;br /&gt;
		page_link(ctx, section.link), lang_module.get_string(section.header)))&lt;br /&gt;
&lt;br /&gt;
	local is_first = true&lt;br /&gt;
	for _, row in ipairs(section.rows) do&lt;br /&gt;
		if render_stat_row(ctx, tbl, row, section.color, is_first) then&lt;br /&gt;
			is_first = false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_information(ctx, parent)&lt;br /&gt;
	local present = {}&lt;br /&gt;
	for _, row in ipairs(INFO_ROWS) do&lt;br /&gt;
		if ctx.args[row.param] ~= &#039;&#039; then present[#present + 1] = row end&lt;br /&gt;
	end&lt;br /&gt;
	if #present == 0 then return end&lt;br /&gt;
&lt;br /&gt;
	parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-text-stat-subtitle&#039;)&lt;br /&gt;
		:wikitext(&#039;&amp;lt;span class=&amp;quot;infotext-h-bold infotext-h-title infotext-h-big&amp;quot;&amp;gt;&#039;&lt;br /&gt;
			.. dict_module.translate(&#039;Information&#039;) .. &#039;&amp;lt;/span&amp;gt;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local tbl = parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-subsection&#039;)&lt;br /&gt;
		:tag(&#039;table&#039;):addClass(&#039;infobox-h-subsection-child&#039;)&lt;br /&gt;
&lt;br /&gt;
	for _, row in ipairs(present) do&lt;br /&gt;
		local tr = tbl:tag(&#039;tr&#039;)&lt;br /&gt;
		local label = dict_module.translate(row.label_dict)&lt;br /&gt;
		local label_cell = tr:tag(&#039;td&#039;)&lt;br /&gt;
		if row.tooltip then&lt;br /&gt;
			label_cell:attr(&#039;title&#039;, row.tooltip)&lt;br /&gt;
		end&lt;br /&gt;
		if row.link then&lt;br /&gt;
			label_cell:wikitext(string.format(&#039;[[%s|&amp;lt;span&amp;gt;%s&amp;lt;/span&amp;gt;]]:&#039;,&lt;br /&gt;
				page_link(ctx, row.link), label))&lt;br /&gt;
		else&lt;br /&gt;
			label_cell:wikitext(label .. &#039;:&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		tr:tag(&#039;td&#039;):wikitext(ctx.args[row.param])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_title(ctx, parent)&lt;br /&gt;
	local th = parent:tag(&#039;tr&#039;):tag(&#039;th&#039;):attr(&#039;colspan&#039;, &#039;2&#039;):addClass(&#039;infobox-h-title&#039;)&lt;br /&gt;
	if ctx.args.name_font_size ~= &#039;&#039; then&lt;br /&gt;
		th:cssText(&#039;font-size: &#039; .. ctx.args.name_font_size .. &#039;;&#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local wrap = th:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero&#039;)&lt;br /&gt;
	wrap:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-left&#039;)&lt;br /&gt;
&lt;br /&gt;
	-- Name logo: &amp;lt;Name&amp;gt;_name.svg, or &amp;lt;Name&amp;gt;_name_&amp;lt;lang&amp;gt;.svg on translated pages.&lt;br /&gt;
	-- Heroes without a logo fall back to their name as plain text.&lt;br /&gt;
	local svg = ((ctx.args.name ~= &#039;&#039; and ctx.args.name) or ctx.hero_en) .. &#039;_name&#039;&lt;br /&gt;
	if ctx.if_lang ~= &#039;&#039; then svg = svg .. &#039;_&#039; .. ctx.lang_code end&lt;br /&gt;
&lt;br /&gt;
	local middle = wrap:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-middle&#039;)&lt;br /&gt;
	if util.page_exists(&#039;File:&#039; .. svg .. &#039;.svg&#039;) then&lt;br /&gt;
		middle:wikitext(ctx.frame:expandTemplate{ title = &#039;Icon/White&#039;, args = {&lt;br /&gt;
			string.format(&#039;[[File:%s.svg|%s|class=notpageimage]]&#039;,&lt;br /&gt;
				svg, theme_var(ctx.hero_key, &#039;logo-height&#039;)) } })&lt;br /&gt;
	else&lt;br /&gt;
		middle:wikitext(ctx.display_name)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local right = wrap:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-right&#039;):tag(&#039;div&#039;)&lt;br /&gt;
	if ctx.show_icon then&lt;br /&gt;
		local icon = ((ctx.args.icon ~= &#039;&#039; and ctx.args.icon) or ctx.hero_en) .. &#039;.png&#039;&lt;br /&gt;
		if util.page_exists(&#039;File:&#039; .. icon) then&lt;br /&gt;
			right:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-tornbox&#039;)&lt;br /&gt;
			right:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-icon&#039;)&lt;br /&gt;
				:wikitext(string.format(&#039;[[File:%s|center|35px|class=notpageimage]]&#039;, icon))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_card(ctx, parent)&lt;br /&gt;
	local card = (ctx.args.card ~= &#039;&#039; and ctx.args.card) or (ctx.hero_en .. &#039; card.png&#039;)&lt;br /&gt;
	if not util.page_exists(&#039;File:&#039; .. card) then card = FALLBACK_CARD end&lt;br /&gt;
	parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-image&#039;)&lt;br /&gt;
		:tag(&#039;div&#039;)&lt;br /&gt;
		:wikitext(string.format(&#039;[[File:%s|150px|alt=%s]]&#039;,&lt;br /&gt;
			card, mw.title.getCurrentTitle().baseText))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_tags(ctx, parent)&lt;br /&gt;
	if not ctx.show_hero_tags then return end&lt;br /&gt;
&lt;br /&gt;
	local tags = {}&lt;br /&gt;
	for i = 1, 3 do&lt;br /&gt;
		local tag = hero_module.get_hero_tag({ args = { ctx.hero_en, tostring(i) } })&lt;br /&gt;
		if tag and tag ~= &#039;&#039; and not tag:find(&#039;Error:&#039;, 1, true) then&lt;br /&gt;
			tags[#tags + 1] = tag&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if #tags == 0 then return end&lt;br /&gt;
&lt;br /&gt;
	local inner = parent:tag(&#039;tr&#039;):tag(&#039;td&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-herotags-row&#039;)&lt;br /&gt;
		:tag(&#039;div&#039;):addClass(&#039;infobox-h-herotags-inner &#039; .. ctx.hero_key)&lt;br /&gt;
	for _, tag in ipairs(tags) do&lt;br /&gt;
		inner:tag(&#039;div&#039;):addClass(&#039;infobox-h-herotag-pill&#039;):wikitext(tag)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_categories(ctx)&lt;br /&gt;
	if mw.ustring.lower(ctx.args.nocat ~= &#039;&#039; and ctx.args.nocat or &#039;false&#039;) ~= &#039;false&#039; then&lt;br /&gt;
		return &#039;&#039;&lt;br /&gt;
	end&lt;br /&gt;
	if get_stat(ctx.hero, &#039;IsDisabled&#039;) ~= false then return &#039;&#039; end&lt;br /&gt;
&lt;br /&gt;
	local out = {}&lt;br /&gt;
	for _, category in ipairs(CATEGORIES) do&lt;br /&gt;
		if category.when == nil or category.when(ctx) then&lt;br /&gt;
			local name = category.name&lt;br /&gt;
			if type(name) == &#039;function&#039; then name = name(ctx) end&lt;br /&gt;
			out[#out + 1] = ctx.frame:expandTemplate{ title = &#039;lang cat&#039;, args = { name } }&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(out)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Entry point&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- |key= accepts a hero_* key or the hero&#039;s English name; unknown values fall back&lt;br /&gt;
-- to the target dummy, the default character in Sandbox mode.&lt;br /&gt;
local function resolve_hero_key(provided)&lt;br /&gt;
	if provided == nil or provided == &#039;&#039; then return DEFAULT_HERO_KEY end&lt;br /&gt;
	if hero_data_all[provided] ~= nil then return provided end&lt;br /&gt;
	local wanted = mw.ustring.lower(provided)&lt;br /&gt;
	for key, hero in pairs(hero_data_all) do&lt;br /&gt;
		if hero.Name and mw.ustring.lower(hero.Name) == wanted then return key end&lt;br /&gt;
	end&lt;br /&gt;
	return DEFAULT_HERO_KEY&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render(frame)&lt;br /&gt;
	local parent = frame:getParent()&lt;br /&gt;
	local invoke_args = frame.args&lt;br /&gt;
	local template_args = parent and parent.args or {}&lt;br /&gt;
&lt;br /&gt;
	-- Missing parameters read as &#039;&#039; so callers never have to nil-check.&lt;br /&gt;
	local args = setmetatable({}, { __index = function(_, k)&lt;br /&gt;
		local value = invoke_args[k]&lt;br /&gt;
		if value == nil then value = template_args[k] end&lt;br /&gt;
		return value or &#039;&#039;&lt;br /&gt;
	end })&lt;br /&gt;
&lt;br /&gt;
	local hero_key = resolve_hero_key(args.key)&lt;br /&gt;
	local hero = hero_data_all[hero_key]&lt;br /&gt;
&lt;br /&gt;
	local ctx = {&lt;br /&gt;
		frame     = frame,&lt;br /&gt;
		args      = args,&lt;br /&gt;
		hero      = hero,&lt;br /&gt;
		hero_key  = hero_key,&lt;br /&gt;
		hero_en   = hero.Name,&lt;br /&gt;
		lang_code = lang_module.get_lang_code(),&lt;br /&gt;
		if_lang   = frame:expandTemplate{ title = &#039;If lang&#039; },&lt;br /&gt;
	}&lt;br /&gt;
	ctx.display_name = (args.name ~= &#039;&#039; and args.name) or lang_module.get_string(hero_key)&lt;br /&gt;
&lt;br /&gt;
	ctx.show_hero_tags = args.show_hero_tags ~= &#039;false&#039;&lt;br /&gt;
	ctx.show_icon      = args.show_icon ~= &#039;false&#039;&lt;br /&gt;
&lt;br /&gt;
	-- Needed by both row visibility and the category rules.&lt;br /&gt;
	ctx.pellets = stat_number(ctx, &#039;BulletsPerShot&#039;, &#039;pellets_per_shot&#039;)&lt;br /&gt;
	ctx.burst   = stat_number(ctx, &#039;BulletsPerBurst&#039;, &#039;bullets_per_burst&#039;)&lt;br /&gt;
	ctx.dash    = stat_number(ctx, &#039;GroundDashSpeed&#039;)&lt;br /&gt;
	ctx.stamina = (args.stamina ~= &#039;&#039; and args.stamina)&lt;br /&gt;
		or format_value(get_stat(hero, &#039;Stamina&#039;), 2)&lt;br /&gt;
&lt;br /&gt;
	local root = mw.html.create(&#039;div&#039;):addClass(&#039;infobox-h &#039; .. hero_key)&lt;br /&gt;
	root:tag(&#039;div&#039;):addClass(&#039;infobox-h-background&#039;)&lt;br /&gt;
		:wikitext(string.format(&#039;[[File:%s|%s|right|link=|class=notpageimage]]&#039;,&lt;br /&gt;
			theme_var(hero_key, &#039;bg-image&#039;), theme_var(hero_key, &#039;bg-height&#039;)))&lt;br /&gt;
&lt;br /&gt;
	local tbl = root:tag(&#039;table&#039;):addClass(&#039;infobox-h-content&#039;)&lt;br /&gt;
	render_title(ctx, tbl)&lt;br /&gt;
	render_card(ctx, tbl)&lt;br /&gt;
	render_tags(ctx, tbl)&lt;br /&gt;
	for _, section in ipairs(SECTIONS) do&lt;br /&gt;
		render_section(ctx, tbl, section)&lt;br /&gt;
	end&lt;br /&gt;
	render_information(ctx, tbl)&lt;br /&gt;
&lt;br /&gt;
	if mw.title.getCurrentTitle().nsText ~= &#039;Template&#039; then&lt;br /&gt;
		frame:callParserFunction(&#039;DISPLAYTITLE&#039;, ctx.display_name)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root) .. render_categories(ctx)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>~2026-GlassGreenKinetic677</name></author>
	</entry>
	<entry>
		<id>https://deadlock.wiki/Module:InfoboxHero?diff=93697</id>
		<title>Module:InfoboxHero</title>
		<link rel="alternate" type="text/html" href="https://deadlock.wiki/Module:InfoboxHero?diff=93697"/>
		<updated>2026-07-16T19:45:55Z</updated>

		<summary type="html">&lt;p&gt;~2026-GlassGreenKinetic677: add tooltips (the fancy template style)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;--------------------------------------------------------------------------------&lt;br /&gt;
-- Module:InfoboxHero&lt;br /&gt;
--&lt;br /&gt;
-- Renders the hero infobox. Called from [[Template:Infobox hero]], which passes&lt;br /&gt;
-- the editor&#039;s parameters through as the parent frame:&lt;br /&gt;
--&lt;br /&gt;
--     {{#invoke:InfoboxHero|render}}&lt;br /&gt;
--&lt;br /&gt;
-- May also be invoked directly for testing, in which case parameters are read&lt;br /&gt;
-- from the invoke itself:&lt;br /&gt;
--&lt;br /&gt;
--     {{#invoke:InfoboxHero|render|key=hero_atlas}}&lt;br /&gt;
--&lt;br /&gt;
-- Stat data comes from [[Data:HeroData.json]]; per-hero colors, backgrounds and&lt;br /&gt;
-- logo sizes from [[Data:Infobox hero.json]]; labels from [[Module:Lang]] and&lt;br /&gt;
-- [[Module:Dictionary]]. Styling lives in [[Template:Infobox hero/styles.css]]&lt;br /&gt;
-- and [[Template:Infobox hero/themes.css]].&lt;br /&gt;
--&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local hero_data_all = mw.loadJsonData(&#039;Data:HeroData.json&#039;)&lt;br /&gt;
local theme_data    = mw.loadJsonData(&#039;Data:Infobox_hero.json&#039;)&lt;br /&gt;
&lt;br /&gt;
local util        = require(&#039;Module:Utilities&#039;)&lt;br /&gt;
local lang_module = require(&#039;Module:Lang&#039;)&lt;br /&gt;
local dict_module = require(&#039;Module:Dictionary&#039;)&lt;br /&gt;
local hero_module = require(&#039;Module:HeroData&#039;)&lt;br /&gt;
&lt;br /&gt;
local get_stat, has_stat, has_override_or_stat, stat_number&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- CONFIG&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local DEFAULT_HERO_KEY = &#039;hero_targetdummy&#039;  -- used when |key= is missing/unknown&lt;br /&gt;
local DEFAULT_SIG_FIGS = 3&lt;br /&gt;
local FALLBACK_CARD    = &#039;Dummy card.png&#039;&lt;br /&gt;
&lt;br /&gt;
-- Row fields (all optional except a label):&lt;br /&gt;
--   stat        Key in Data:HeroData.json. Resolved top-level, then .Weapon,&lt;br /&gt;
--               then .Weapon.AltFire. Omit for manual-input-only rows.&lt;br /&gt;
--   param       |param= that overrides the value.&lt;br /&gt;
--   label       Key in Data:Lang_&amp;lt;code&amp;gt;.json.&lt;br /&gt;
--   label_dict  Key in Data:Dictionary. Used instead of `label`.&lt;br /&gt;
--   label_text  Literal, untranslated label. Used instead of `label`.&lt;br /&gt;
--   link        Target for the label, as &#039;Page&#039; or &#039;Page#Anchor&#039;. The language&lt;br /&gt;
--               suffix is inserted for us, so write the plain English title.&lt;br /&gt;
--   icon_link   Target for the icon. Defaults to `link`.&lt;br /&gt;
--   icon        File: name of the value icon. Omit for no icon.&lt;br /&gt;
--   sig_figs    Significant figures. Defaults to 3.&lt;br /&gt;
--   suffix      Appended directly after the value, e.g. &#039;%&#039;, &#039;s&#039;, &#039;m/s&#039;.&lt;br /&gt;
--   scaling     Set false to suppress the Boon/Spirit scaling span. Scaling is&lt;br /&gt;
--               shown by default wherever the data provides it.&lt;br /&gt;
--   optional    Hide the row unless the hero has a non-zero value or scaling.&lt;br /&gt;
--   cell_class  Extra class on the value cell.&lt;br /&gt;
--   show        function(ctx) -&amp;gt; boolean. Row omitted when false.&lt;br /&gt;
&lt;br /&gt;
local SECTIONS = {&lt;br /&gt;
	{&lt;br /&gt;
		class  = &#039;infobox-h-dropdown-weapon&#039;,&lt;br /&gt;
		header = &#039;CitadelHeroStats_Weapon&#039;,&lt;br /&gt;
		link   = &#039;Stats#Weapon&#039;,&lt;br /&gt;
		color  = &#039;Brown&#039;,&lt;br /&gt;
		rows = {&lt;br /&gt;
			{&lt;br /&gt;
				stat    = &#039;DPS&#039;,&lt;br /&gt;
				param   = &#039;dps&#039;,&lt;br /&gt;
				label   = &#039;DPS_label&#039;,&lt;br /&gt;
				link    = &#039;Damage per second&#039;,&lt;br /&gt;
				icon    = &#039;Damage.png&#039;,&lt;br /&gt;
				-- DPS is derived from bullet damage and fire rate, both of which&lt;br /&gt;
				-- show their own scaling; repeating it here would double-count.&lt;br /&gt;
				scaling = false,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;BulletDamage&#039;,&lt;br /&gt;
				param      = &#039;bullet_damage&#039;,&lt;br /&gt;
				label      = &#039;StatDesc_BulletDamage&#039;,&lt;br /&gt;
				link       = &#039;Bullet Damage&#039;,&lt;br /&gt;
				icon       = &#039;Bullet damage.png&#039;,&lt;br /&gt;
				cell_class = &#039;infobox-h-stat-value-padded&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat      = &#039;BulletsPerShot&#039;,&lt;br /&gt;
				param     = &#039;pellets_per_shot&#039;,&lt;br /&gt;
				label     = &#039;BonusPelletsPerShot_label&#039;,&lt;br /&gt;
				link      = &#039;Damage per second&#039;,&lt;br /&gt;
				icon      = &#039;Bullets per sec icon.png&#039;,&lt;br /&gt;
				icon_link = &#039;Fire Rate&#039;,&lt;br /&gt;
				show      = function(ctx) return ctx.pellets &amp;gt; 1 end,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;BulletsPerBurst&#039;,&lt;br /&gt;
				param      = &#039;bullets_per_burst&#039;,&lt;br /&gt;
				label_dict = &#039;BulletsPerBurst&#039;,&lt;br /&gt;
				link       = &#039;Damage per second&#039;,&lt;br /&gt;
				icon       = &#039;Bullets per sec icon.png&#039;,&lt;br /&gt;
				icon_link  = &#039;Fire Rate&#039;,&lt;br /&gt;
				show       = function(ctx) return ctx.burst &amp;gt; 1 end,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat  = &#039;ClipSize&#039;,&lt;br /&gt;
				param = &#039;ammo&#039;,&lt;br /&gt;
				label = &#039;StatDesc_ClipSizeBonus&#039;,&lt;br /&gt;
				link  = &#039;Ammo&#039;,&lt;br /&gt;
				icon  = &#039;Clip size icon.png&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat  = &#039;RoundsPerSecond&#039;,&lt;br /&gt;
				param = &#039;bullets_per_sec&#039;,&lt;br /&gt;
				label = &#039;StatDesc_RoundsPerSecond&#039;,&lt;br /&gt;
				link  = &#039;Fire Rate&#039;,&lt;br /&gt;
				icon  = &#039;Bullets per sec icon.png&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;ReloadTime&#039;,&lt;br /&gt;
				param  = &#039;reload_time&#039;,&lt;br /&gt;
				label  = &#039;StatDesc_ReloadTime&#039;,&lt;br /&gt;
				link   = &#039;Reload Time&#039;,&lt;br /&gt;
				icon   = &#039;AttributeIconReloadTime.png&#039;,&lt;br /&gt;
				suffix = &#039;s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;BulletSpeed&#039;,&lt;br /&gt;
				param  = &#039;bullet_velocity&#039;,&lt;br /&gt;
				label  = &#039;StatDesc_BulletSpeed&#039;,&lt;br /&gt;
				link   = &#039;Bullet Velocity&#039;,&lt;br /&gt;
				icon   = &#039;AttributeIconBulletSpeed.png&#039;,&lt;br /&gt;
				suffix = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;LightMeleeDamage&#039;,&lt;br /&gt;
				param      = &#039;light_melee&#039;,&lt;br /&gt;
				label      = &#039;StatDesc_LightMeleeDamage&#039;,&lt;br /&gt;
				link       = &#039;Melee Damage&#039;,&lt;br /&gt;
				icon       = &#039;Melee damage.png&#039;,&lt;br /&gt;
				cell_class = &#039;infobox-h-stat-value-padded&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat       = &#039;HeavyMeleeDamage&#039;,&lt;br /&gt;
				param      = &#039;heavy_melee&#039;,&lt;br /&gt;
				label      = &#039;StatDesc_HeavyMeleeDamage&#039;,&lt;br /&gt;
				link       = &#039;Melee Damage&#039;,&lt;br /&gt;
				icon       = &#039;Melee damage.png&#039;,&lt;br /&gt;
				cell_class = &#039;infobox-h-stat-value-padded&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				param      = &#039;max_range&#039;,&lt;br /&gt;
				label_text = &#039;Max Range&#039;,&lt;br /&gt;
				link       = &#039;Falloff Range&#039;,&lt;br /&gt;
				show       = function(ctx) return ctx.args.max_range ~= &#039;&#039; end,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;CritDamageBonusPercent&#039;,&lt;br /&gt;
				label  = &#039;StatDesc_CritDamageBonusScale&#039;,&lt;br /&gt;
				link   = &#039;Weapon Damage#Crit_Multiplier&#039;,&lt;br /&gt;
				icon   = &#039;Crit reduction.png&#039;,&lt;br /&gt;
				suffix = &#039;%&#039;,&lt;br /&gt;
				show   = function(ctx)&lt;br /&gt;
					return stat_number(ctx, &#039;CritDamageBonusPercent&#039;) ~= 0&lt;br /&gt;
				end,&lt;br /&gt;
			},&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		class  = &#039;infobox-h-dropdown-vitality&#039;,&lt;br /&gt;
		header = &#039;CitadelHeroStats_Vitality&#039;,&lt;br /&gt;
		link   = &#039;Stats#Vitality&#039;,&lt;br /&gt;
		color  = &#039;Green&#039;,&lt;br /&gt;
		rows = {&lt;br /&gt;
			{&lt;br /&gt;
				stat  = &#039;MaxHealth&#039;,&lt;br /&gt;
				param = &#039;max_health&#039;,&lt;br /&gt;
				label = &#039;StatDesc_HealthAttribute&#039;,&lt;br /&gt;
				link  = &#039;Health&#039;,&lt;br /&gt;
				icon  = &#039;Health.png&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;BaseHealthRegen&#039;,&lt;br /&gt;
				param    = &#039;health_regen&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_HealthRegen&#039;,&lt;br /&gt;
				link     = &#039;Health Regen&#039;,&lt;br /&gt;
				icon     = &#039;Health regen.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;CritDamageReceivedPercent&#039;,&lt;br /&gt;
				param    = &#039;crit_reduction&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_CritDamageReceivedScale&#039;,&lt;br /&gt;
				link     = &#039;Damage Resistance#Crit_Reduction&#039;,&lt;br /&gt;
				icon     = &#039;Crit reduction.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat      = &#039;BulletResist&#039;,&lt;br /&gt;
				param     = &#039;bullet_resist&#039;,&lt;br /&gt;
				label     = &#039;StatDesc_BulletArmorDamageReduction&#039;,&lt;br /&gt;
				link      = &#039;Damage Resistance&#039;,&lt;br /&gt;
				icon      = &#039;Bullet Armor.png&#039;,&lt;br /&gt;
				icon_link = &#039;Bullet Resist&#039;,&lt;br /&gt;
				suffix    = &#039;%&#039;,&lt;br /&gt;
				optional  = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;TechResist&#039;,&lt;br /&gt;
				param    = &#039;spirit_resist&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_TechArmorDamageReduction&#039;,&lt;br /&gt;
				link     = &#039;Damage Resistance#Spirit_Resist&#039;,&lt;br /&gt;
				icon     = &#039;Spirit Armor.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;MeleeResist&#039;,&lt;br /&gt;
				param    = &#039;melee_resist&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_MeleeResist&#039;,&lt;br /&gt;
				link     = &#039;Damage Resistance#Melee Resist&#039;,&lt;br /&gt;
				icon     = &#039;Melee damage.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;DebuffResist&#039;,&lt;br /&gt;
				param    = &#039;debuff_resist&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_DebuffResist&#039;,&lt;br /&gt;
				link     = &#039;Debuff Resist&#039;,&lt;br /&gt;
				icon     = &#039;Spirit Armor.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;BulletLifesteal&#039;,&lt;br /&gt;
				param    = &#039;bullet_lifesteal&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_BulletLifesteal&#039;,&lt;br /&gt;
				link     = &#039;Lifesteal#Bullet Lifesteal&#039;,&lt;br /&gt;
				icon     = &#039;AttributeIconBulletLifesteal.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;SpiritLifesteal&#039;,&lt;br /&gt;
				param    = &#039;spirit_lifesteal&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_SpiritLifesteal&#039;,&lt;br /&gt;
				link     = &#039;Lifesteal#Spirit Lifesteal&#039;,&lt;br /&gt;
				icon     = &#039;AttributeIconBulletLifesteal.png&#039;,&lt;br /&gt;
				suffix   = &#039;%&#039;,&lt;br /&gt;
				optional = true,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;MaxMoveSpeed&#039;,&lt;br /&gt;
				param    = &#039;move_speed&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_RunSpeed&#039;,&lt;br /&gt;
				link     = &#039;Move Speed&#039;,&lt;br /&gt;
				icon     = &#039;Move speed.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
				suffix   = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;SprintSpeed&#039;,&lt;br /&gt;
				param    = &#039;sprint_speed&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_SprintSpeed&#039;,&lt;br /&gt;
				link     = &#039;Move Speed#Sprint&#039;,&lt;br /&gt;
				icon     = &#039;Move speed sprint.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
				suffix   = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat   = &#039;GroundDashSpeed&#039;,&lt;br /&gt;
				label  = &#039;DashSpeed_label&#039;,&lt;br /&gt;
				link   = &#039;Stamina#Dash_Speed&#039;,&lt;br /&gt;
				icon   = &#039;Springy boots icon.png&#039;,&lt;br /&gt;
				suffix = &#039;m/s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;Stamina&#039;,&lt;br /&gt;
				param    = &#039;stamina&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_Stamina&#039;,&lt;br /&gt;
				link     = &#039;Stamina&#039;,&lt;br /&gt;
				icon     = &#039;Springy boots icon.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;StaminaCooldown&#039;,&lt;br /&gt;
				param    = &#039;stamina_cooldown&#039;,&lt;br /&gt;
				label    = &#039;StatDesc_StaminaCooldown&#039;,&lt;br /&gt;
				link     = &#039;Stamina#Cooldown&#039;,&lt;br /&gt;
				icon     = &#039;AttributeIconTechDuration.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
				suffix   = &#039;s&#039;,&lt;br /&gt;
			},&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		class  = &#039;infobox-h-dropdown-spirit&#039;,&lt;br /&gt;
		header = &#039;CitadelHeroStats_Spirit&#039;,&lt;br /&gt;
		link   = &#039;Stats#Spirit&#039;,&lt;br /&gt;
		color  = &#039;Purple&#039;,&lt;br /&gt;
		rows = {&lt;br /&gt;
			{&lt;br /&gt;
				stat     = &#039;TechPower&#039;,&lt;br /&gt;
				param    = &#039;spirit_power&#039;,&lt;br /&gt;
				label    = &#039;SpiritPower_label&#039;,&lt;br /&gt;
				link     = &#039;Spirit Power&#039;,&lt;br /&gt;
				icon     = &#039;Spirit icon noborder.png&#039;,&lt;br /&gt;
				sig_figs = 2,&lt;br /&gt;
			},&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- The &amp;quot;Information&amp;quot; block. Manual input only; hidden entirely when all are empty.&lt;br /&gt;
local INFO_ROWS = {&lt;br /&gt;
	{ param = &#039;release_date&#039;, label_dict = &#039;ReleaseDate&#039;, link = &#039;Heroes by release&#039; },&lt;br /&gt;
	{ param = &#039;pronouns&#039;,     label_dict = &#039;Pronouns&#039; },&lt;br /&gt;
	{ param = &#039;voice_actor&#039;,  label_dict = &#039;VoiceActor&#039; },&lt;br /&gt;
	{ param = &#039;codenames&#039;,    label_dict = &#039;Codenames&#039;, tooltip = &#039;Codenames used for this hero in the game files&#039; },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- name: literal, or function(ctx) -&amp;gt; string.  when: function(ctx) -&amp;gt; boolean.&lt;br /&gt;
local CATEGORIES = {&lt;br /&gt;
	{ name = &#039;Heroes&#039; },&lt;br /&gt;
	{&lt;br /&gt;
		name = function(ctx) return ctx.hero_en end,&lt;br /&gt;
		-- Silver (Transformed) is documented on Silver&#039;s page and shares her category.&lt;br /&gt;
		when = function(ctx) return ctx.hero_en ~= &#039;Silver (Transformed)&#039; end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with spreadshot weapons&#039;,&lt;br /&gt;
		when = function(ctx) return ctx.pellets &amp;gt; 1 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with single damage spreadshot weapons&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return ctx.pellets &amp;gt; 1 and get_stat(ctx.hero, &#039;HitOnceAcrossAllBullets&#039;) == true&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with burst weapons&#039;,&lt;br /&gt;
		when = function(ctx) return ctx.burst &amp;gt; 1 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with automatic weapons&#039;,&lt;br /&gt;
		when = function(ctx) return ctx.pellets &amp;lt;= 1 and ctx.burst &amp;lt;= 1 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with alternate fire weapons&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return ctx.hero.Weapon ~= nil and ctx.hero.Weapon.AltFire ~= nil&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with single reload weapons&#039;,&lt;br /&gt;
		when = function(ctx) return get_stat(ctx.hero, &#039;ReloadSingle&#039;) == true end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with spin up weapons&#039;,&lt;br /&gt;
		when = function(ctx) return has_stat(ctx.hero, &#039;SpinAcceleration&#039;) end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with no weapon damage falloff&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return (get_stat(ctx.hero, &#039;FalloffStartRange&#039;) or 0)&lt;br /&gt;
			    == (get_stat(ctx.hero, &#039;FalloffEndRange&#039;) or 0)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &amp;quot;Heroes that don&#039;t deal critical damage&amp;quot;,&lt;br /&gt;
		when = function(ctx) return get_stat(ctx.hero, &#039;CanCrit&#039;) == false end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified crit scale&#039;,&lt;br /&gt;
		when = function(ctx) return stat_number(ctx, &#039;CritDamageBonusPercent&#039;) ~= 0 end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified crit resistance&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;crit_reduction&#039;, &#039;CritDamageReceivedPercent&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified bullet resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;bullet_resist&#039;, &#039;BulletResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified spirit resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;spirit_resist&#039;, &#039;TechResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified melee resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;melee_resist&#039;, &#039;MeleeResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified debuff resist&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;debuff_resist&#039;, &#039;DebuffResist&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified bullet lifesteal&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;bullet_lifesteal&#039;, &#039;BulletLifesteal&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified spirit lifesteal&#039;,&lt;br /&gt;
		when = function(ctx)&lt;br /&gt;
			return has_override_or_stat(ctx, &#039;spirit_lifesteal&#039;, &#039;SpiritLifesteal&#039;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = &#039;Heroes with modified gravity scale&#039;,&lt;br /&gt;
		when = function(ctx) return has_stat(ctx.hero, &#039;GravityChange&#039;) end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = function(ctx) return &#039;Heroes with &#039; .. ctx.stamina .. &#039; stamina&#039; end,&lt;br /&gt;
		when = function(ctx) return ctx.stamina ~= &#039;&#039; end,&lt;br /&gt;
	},&lt;br /&gt;
	{&lt;br /&gt;
		name = function(ctx)&lt;br /&gt;
			if ctx.dash &amp;lt; 14.7 then return &#039;Heroes with slow dash speed&#039; end&lt;br /&gt;
			if ctx.dash &amp;gt; 14.7 then return &#039;Heroes with fast dash speed&#039; end&lt;br /&gt;
			return &#039;Heroes with medium dash speed&#039;&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Data access&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- Stats live at the top level for vitality, and under .Weapon for weapon stats.&lt;br /&gt;
get_stat = function(hero, stat)&lt;br /&gt;
	if hero == nil or stat == nil then return nil end&lt;br /&gt;
	local value = hero[stat]&lt;br /&gt;
	if value == nil and hero.Weapon then&lt;br /&gt;
		value = hero.Weapon[stat]&lt;br /&gt;
		if value == nil and hero.Weapon.AltFire then&lt;br /&gt;
			value = hero.Weapon.AltFire[stat]&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return value&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- The raw Boon (Level) and Spirit scaling values for a stat, if any.&lt;br /&gt;
local function get_scaling(hero, stat)&lt;br /&gt;
	local out = {}&lt;br /&gt;
	for _, scaling_type in ipairs({ &#039;Level&#039;, &#039;Spirit&#039; }) do&lt;br /&gt;
		local scaling = hero[scaling_type .. &#039;Scaling&#039;]&lt;br /&gt;
		local value = scaling and stat and scaling[stat]&lt;br /&gt;
		if value ~= nil and value ~= 0 then&lt;br /&gt;
			out[#out + 1] = { type = scaling_type, value = value }&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return out&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- True when the hero has a non-zero base value, or any scaling, for a stat.&lt;br /&gt;
has_stat = function(hero, stat)&lt;br /&gt;
	local value = get_stat(hero, stat)&lt;br /&gt;
	if value ~= nil and value ~= 0 then return true end&lt;br /&gt;
	return #get_scaling(hero, stat) &amp;gt; 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
has_override_or_stat = function(ctx, param, stat)&lt;br /&gt;
	if param and ctx.args[param] ~= &#039;&#039; then return true end&lt;br /&gt;
	return has_stat(ctx.hero, stat)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Rounded numeric value of a stat, honouring a |param= override.&lt;br /&gt;
stat_number = function(ctx, stat, param)&lt;br /&gt;
	if param and ctx.args[param] ~= &#039;&#039; then return tonumber(ctx.args[param]) or 0 end&lt;br /&gt;
	return tonumber(util.round_to_sig_fig(get_stat(ctx.hero, stat) or 0, DEFAULT_SIG_FIGS)) or 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function format_value(raw, sig_figs)&lt;br /&gt;
	if raw == nil then return &#039;0&#039; end&lt;br /&gt;
	local rounded = util.round_to_sig_fig(raw, sig_figs or DEFAULT_SIG_FIGS)&lt;br /&gt;
	return tostring(rounded ~= nil and rounded or raw)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Rendering helpers&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function theme_var(hero_key, name)&lt;br /&gt;
	local hero_theme = theme_data[hero_key]&lt;br /&gt;
	if hero_theme and hero_theme[name] ~= nil then return hero_theme[name] end&lt;br /&gt;
	return theme_data[&#039;hero_default&#039;][name]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function label_of(row)&lt;br /&gt;
	if row.label_text then return row.label_text end&lt;br /&gt;
	if row.label_dict then return dict_module.translate(row.label_dict) end&lt;br /&gt;
	return lang_module.get_string(row.label)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- &#039;Damage Resistance#Spirit_Resist&#039; -&amp;gt; &#039;Damage Resistance/ru#Spirit_Resist&#039;&lt;br /&gt;
local function page_link(ctx, target)&lt;br /&gt;
	if not target then return &#039;&#039; end&lt;br /&gt;
	local page, anchor = target:match(&#039;^([^#]*)(#?.*)$&#039;)&lt;br /&gt;
	return page .. ctx.if_lang .. anchor&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function icon_cell(ctx, row, color, value_html)&lt;br /&gt;
	local icon = &#039;&#039;&lt;br /&gt;
	if row.icon then&lt;br /&gt;
		icon = string.format(&#039;[[File:%s|20px|link=%s]]&#039;, row.icon,&lt;br /&gt;
			page_link(ctx, row.icon_link or row.link))&lt;br /&gt;
	end&lt;br /&gt;
	return ctx.frame:expandTemplate{ title = &#039;Icon/&#039; .. color, args = { icon, value_html } }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Ammo and reload time are meaningless for heroes whose weapon never reloads.&lt;br /&gt;
local NO_AMMO_STATS = { ClipSize = true, ReloadTime = true }&lt;br /&gt;
&lt;br /&gt;
-- Returns true when the row rendered.&lt;br /&gt;
local function render_stat_row(ctx, tbl, row, color, is_first)&lt;br /&gt;
	if row.show and not row.show(ctx) then return false end&lt;br /&gt;
	if row.optional and not has_override_or_stat(ctx, row.param, row.stat) then return false end&lt;br /&gt;
&lt;br /&gt;
	local value, scaling_inner&lt;br /&gt;
	if NO_AMMO_STATS[row.stat] and ctx.args.no_ammo == &#039;true&#039; then&lt;br /&gt;
		value, scaling_inner = &#039;N/A&#039;, &#039;&#039;&lt;br /&gt;
	else&lt;br /&gt;
		local override = row.param and ctx.args[row.param] or &#039;&#039;&lt;br /&gt;
		if override ~= &#039;&#039; then&lt;br /&gt;
			value = override&lt;br /&gt;
		else&lt;br /&gt;
			value = format_value(get_stat(ctx.hero, row.stat), row.sig_figs)&lt;br /&gt;
		end&lt;br /&gt;
		value = value .. (row.suffix or &#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
		if row.stat and row.scaling ~= false then&lt;br /&gt;
			local parts = {}&lt;br /&gt;
			for _, scaling in ipairs(get_scaling(ctx.hero, row.stat)) do&lt;br /&gt;
				parts[#parts + 1] = hero_module.write_scalar_str(scaling.value, scaling.type)&lt;br /&gt;
			end&lt;br /&gt;
			scaling_inner = table.concat(parts)&lt;br /&gt;
			value = value .. &#039;&amp;lt;span class=&amp;quot;infobox-h-spiritboon&amp;quot;&amp;gt;&#039; .. scaling_inner .. &#039;&amp;lt;/span&amp;gt;&#039;&lt;br /&gt;
		else&lt;br /&gt;
			scaling_inner = &#039;&#039;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local tr = tbl:tag(&#039;tr&#039;)&lt;br /&gt;
&lt;br /&gt;
	local label_cell = tr:tag(&#039;td&#039;)&lt;br /&gt;
	-- The section&#039;s first row needs extra headroom when it carries a scaling icon.&lt;br /&gt;
	if is_first and scaling_inner ~= &#039;&#039; then&lt;br /&gt;
		label_cell:addClass(&#039;infobox-h-stat-label-scaled&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	if row.link then&lt;br /&gt;
		label_cell:wikitext(string.format(&#039;[[%s|&amp;lt;span&amp;gt;%s&amp;lt;/span&amp;gt;]]:&#039;,&lt;br /&gt;
			page_link(ctx, row.link), label_of(row)))&lt;br /&gt;
	else&lt;br /&gt;
		label_cell:wikitext(label_of(row) .. &#039;:&#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local value_cell = tr:tag(&#039;td&#039;)&lt;br /&gt;
	if row.cell_class then value_cell:addClass(row.cell_class) end&lt;br /&gt;
	value_cell:wikitext(icon_cell(ctx, row, color, value))&lt;br /&gt;
&lt;br /&gt;
	return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_section(ctx, parent, section)&lt;br /&gt;
	local tbl = parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-subsection infobox-h-dropdown &#039; .. section.class)&lt;br /&gt;
		:tag(&#039;table&#039;):addClass(&#039;mw-collapsible infobox-h-subsection-child&#039;)&lt;br /&gt;
&lt;br /&gt;
	local caption = tbl:tag(&#039;caption&#039;):addClass(&#039;infobox-h-dropdown-header&#039;)&lt;br /&gt;
	if ctx.if_lang == &#039;/ru&#039; then&lt;br /&gt;
		caption:addClass(&#039;infobox-h-dropdown-header-overflow&#039;)&lt;br /&gt;
	end&lt;br /&gt;
	caption:wikitext(string.format(&lt;br /&gt;
		&#039;[[%s|&amp;lt;span class=&amp;quot;infotext-h-bold infobox-h-dropdown-title&amp;quot;&amp;gt;%s&amp;lt;/span&amp;gt;]]&#039;,&lt;br /&gt;
		page_link(ctx, section.link), lang_module.get_string(section.header)))&lt;br /&gt;
&lt;br /&gt;
	local is_first = true&lt;br /&gt;
	for _, row in ipairs(section.rows) do&lt;br /&gt;
		if render_stat_row(ctx, tbl, row, section.color, is_first) then&lt;br /&gt;
			is_first = false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_information(ctx, parent)&lt;br /&gt;
	local present = {}&lt;br /&gt;
	for _, row in ipairs(INFO_ROWS) do&lt;br /&gt;
		if ctx.args[row.param] ~= &#039;&#039; then present[#present + 1] = row end&lt;br /&gt;
	end&lt;br /&gt;
	if #present == 0 then return end&lt;br /&gt;
&lt;br /&gt;
	parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-text-stat-subtitle&#039;)&lt;br /&gt;
		:wikitext(&#039;&amp;lt;span class=&amp;quot;infotext-h-bold infotext-h-title infotext-h-big&amp;quot;&amp;gt;&#039;&lt;br /&gt;
			.. dict_module.translate(&#039;Information&#039;) .. &#039;&amp;lt;/span&amp;gt;&#039;)&lt;br /&gt;
&lt;br /&gt;
	local tbl = parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-subsection&#039;)&lt;br /&gt;
		:tag(&#039;table&#039;):addClass(&#039;infobox-h-subsection-child&#039;)&lt;br /&gt;
&lt;br /&gt;
	for _, row in ipairs(present) do&lt;br /&gt;
		local tr = tbl:tag(&#039;tr&#039;)&lt;br /&gt;
		local label = dict_module.translate(row.label_dict)&lt;br /&gt;
		if row.tooltip then&lt;br /&gt;
			label = ctx.frame:expandTemplate{ title = &#039;Tooltip&#039;, args = { label, row.tooltip } }&lt;br /&gt;
		end&lt;br /&gt;
		if row.link then&lt;br /&gt;
			tr:tag(&#039;td&#039;):wikitext(string.format(&#039;[[%s|&amp;lt;span&amp;gt;%s&amp;lt;/span&amp;gt;]]:&#039;,&lt;br /&gt;
				page_link(ctx, row.link), label))&lt;br /&gt;
		else&lt;br /&gt;
			tr:tag(&#039;td&#039;):wikitext(label .. &#039;:&#039;)&lt;br /&gt;
		end&lt;br /&gt;
		tr:tag(&#039;td&#039;):wikitext(ctx.args[row.param])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_title(ctx, parent)&lt;br /&gt;
	local th = parent:tag(&#039;tr&#039;):tag(&#039;th&#039;):attr(&#039;colspan&#039;, &#039;2&#039;):addClass(&#039;infobox-h-title&#039;)&lt;br /&gt;
	if ctx.args.name_font_size ~= &#039;&#039; then&lt;br /&gt;
		th:cssText(&#039;font-size: &#039; .. ctx.args.name_font_size .. &#039;;&#039;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local wrap = th:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero&#039;)&lt;br /&gt;
	wrap:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-left&#039;)&lt;br /&gt;
&lt;br /&gt;
	-- Name logo: &amp;lt;Name&amp;gt;_name.svg, or &amp;lt;Name&amp;gt;_name_&amp;lt;lang&amp;gt;.svg on translated pages.&lt;br /&gt;
	-- Heroes without a logo fall back to their name as plain text.&lt;br /&gt;
	local svg = ((ctx.args.name ~= &#039;&#039; and ctx.args.name) or ctx.hero_en) .. &#039;_name&#039;&lt;br /&gt;
	if ctx.if_lang ~= &#039;&#039; then svg = svg .. &#039;_&#039; .. ctx.lang_code end&lt;br /&gt;
&lt;br /&gt;
	local middle = wrap:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-middle&#039;)&lt;br /&gt;
	if util.page_exists(&#039;File:&#039; .. svg .. &#039;.svg&#039;) then&lt;br /&gt;
		middle:wikitext(ctx.frame:expandTemplate{ title = &#039;Icon/White&#039;, args = {&lt;br /&gt;
			string.format(&#039;[[File:%s.svg|%s|class=notpageimage]]&#039;,&lt;br /&gt;
				svg, theme_var(ctx.hero_key, &#039;logo-height&#039;)) } })&lt;br /&gt;
	else&lt;br /&gt;
		middle:wikitext(ctx.display_name)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local right = wrap:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-right&#039;):tag(&#039;div&#039;)&lt;br /&gt;
	if ctx.show_icon then&lt;br /&gt;
		local icon = ((ctx.args.icon ~= &#039;&#039; and ctx.args.icon) or ctx.hero_en) .. &#039;.png&#039;&lt;br /&gt;
		if util.page_exists(&#039;File:&#039; .. icon) then&lt;br /&gt;
			right:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-tornbox&#039;)&lt;br /&gt;
			right:tag(&#039;div&#039;):addClass(&#039;infobox-h-title-hero-icon&#039;)&lt;br /&gt;
				:wikitext(string.format(&#039;[[File:%s|center|35px|class=notpageimage]]&#039;, icon))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_card(ctx, parent)&lt;br /&gt;
	local card = (ctx.args.card ~= &#039;&#039; and ctx.args.card) or (ctx.hero_en .. &#039; card.png&#039;)&lt;br /&gt;
	if not util.page_exists(&#039;File:&#039; .. card) then card = FALLBACK_CARD end&lt;br /&gt;
	parent:tag(&#039;tr&#039;):tag(&#039;th&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-image&#039;)&lt;br /&gt;
		:tag(&#039;div&#039;)&lt;br /&gt;
		:wikitext(string.format(&#039;[[File:%s|150px|alt=%s]]&#039;,&lt;br /&gt;
			card, mw.title.getCurrentTitle().baseText))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_tags(ctx, parent)&lt;br /&gt;
	if not ctx.show_hero_tags then return end&lt;br /&gt;
&lt;br /&gt;
	local tags = {}&lt;br /&gt;
	for i = 1, 3 do&lt;br /&gt;
		local tag = hero_module.get_hero_tag({ args = { ctx.hero_en, tostring(i) } })&lt;br /&gt;
		if tag and tag ~= &#039;&#039; and not tag:find(&#039;Error:&#039;, 1, true) then&lt;br /&gt;
			tags[#tags + 1] = tag&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if #tags == 0 then return end&lt;br /&gt;
&lt;br /&gt;
	local inner = parent:tag(&#039;tr&#039;):tag(&#039;td&#039;)&lt;br /&gt;
		:attr(&#039;colspan&#039;, &#039;2&#039;)&lt;br /&gt;
		:addClass(&#039;infobox-h-herotags-row&#039;)&lt;br /&gt;
		:tag(&#039;div&#039;):addClass(&#039;infobox-h-herotags-inner &#039; .. ctx.hero_key)&lt;br /&gt;
	for _, tag in ipairs(tags) do&lt;br /&gt;
		inner:tag(&#039;div&#039;):addClass(&#039;infobox-h-herotag-pill&#039;):wikitext(tag)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function render_categories(ctx)&lt;br /&gt;
	if mw.ustring.lower(ctx.args.nocat ~= &#039;&#039; and ctx.args.nocat or &#039;false&#039;) ~= &#039;false&#039; then&lt;br /&gt;
		return &#039;&#039;&lt;br /&gt;
	end&lt;br /&gt;
	if get_stat(ctx.hero, &#039;IsDisabled&#039;) ~= false then return &#039;&#039; end&lt;br /&gt;
&lt;br /&gt;
	local out = {}&lt;br /&gt;
	for _, category in ipairs(CATEGORIES) do&lt;br /&gt;
		if category.when == nil or category.when(ctx) then&lt;br /&gt;
			local name = category.name&lt;br /&gt;
			if type(name) == &#039;function&#039; then name = name(ctx) end&lt;br /&gt;
			out[#out + 1] = ctx.frame:expandTemplate{ title = &#039;lang cat&#039;, args = { name } }&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(out)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Entry point&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- |key= accepts a hero_* key or the hero&#039;s English name; unknown values fall back&lt;br /&gt;
-- to the target dummy, the default character in Sandbox mode.&lt;br /&gt;
local function resolve_hero_key(provided)&lt;br /&gt;
	if provided == nil or provided == &#039;&#039; then return DEFAULT_HERO_KEY end&lt;br /&gt;
	if hero_data_all[provided] ~= nil then return provided end&lt;br /&gt;
	local wanted = mw.ustring.lower(provided)&lt;br /&gt;
	for key, hero in pairs(hero_data_all) do&lt;br /&gt;
		if hero.Name and mw.ustring.lower(hero.Name) == wanted then return key end&lt;br /&gt;
	end&lt;br /&gt;
	return DEFAULT_HERO_KEY&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.render(frame)&lt;br /&gt;
	local parent = frame:getParent()&lt;br /&gt;
	local invoke_args = frame.args&lt;br /&gt;
	local template_args = parent and parent.args or {}&lt;br /&gt;
&lt;br /&gt;
	-- Missing parameters read as &#039;&#039; so callers never have to nil-check.&lt;br /&gt;
	local args = setmetatable({}, { __index = function(_, k)&lt;br /&gt;
		local value = invoke_args[k]&lt;br /&gt;
		if value == nil then value = template_args[k] end&lt;br /&gt;
		return value or &#039;&#039;&lt;br /&gt;
	end })&lt;br /&gt;
&lt;br /&gt;
	local hero_key = resolve_hero_key(args.key)&lt;br /&gt;
	local hero = hero_data_all[hero_key]&lt;br /&gt;
&lt;br /&gt;
	local ctx = {&lt;br /&gt;
		frame     = frame,&lt;br /&gt;
		args      = args,&lt;br /&gt;
		hero      = hero,&lt;br /&gt;
		hero_key  = hero_key,&lt;br /&gt;
		hero_en   = hero.Name,&lt;br /&gt;
		lang_code = lang_module.get_lang_code(),&lt;br /&gt;
		if_lang   = frame:expandTemplate{ title = &#039;If lang&#039; },&lt;br /&gt;
	}&lt;br /&gt;
	ctx.display_name = (args.name ~= &#039;&#039; and args.name) or lang_module.get_string(hero_key)&lt;br /&gt;
&lt;br /&gt;
	ctx.show_hero_tags = args.show_hero_tags ~= &#039;false&#039;&lt;br /&gt;
	ctx.show_icon      = args.show_icon ~= &#039;false&#039;&lt;br /&gt;
&lt;br /&gt;
	-- Needed by both row visibility and the category rules.&lt;br /&gt;
	ctx.pellets = stat_number(ctx, &#039;BulletsPerShot&#039;, &#039;pellets_per_shot&#039;)&lt;br /&gt;
	ctx.burst   = stat_number(ctx, &#039;BulletsPerBurst&#039;, &#039;bullets_per_burst&#039;)&lt;br /&gt;
	ctx.dash    = stat_number(ctx, &#039;GroundDashSpeed&#039;)&lt;br /&gt;
	ctx.stamina = (args.stamina ~= &#039;&#039; and args.stamina)&lt;br /&gt;
		or format_value(get_stat(hero, &#039;Stamina&#039;), 2)&lt;br /&gt;
&lt;br /&gt;
	local root = mw.html.create(&#039;div&#039;):addClass(&#039;infobox-h &#039; .. hero_key)&lt;br /&gt;
	root:tag(&#039;div&#039;):addClass(&#039;infobox-h-background&#039;)&lt;br /&gt;
		:wikitext(string.format(&#039;[[File:%s|%s|right|link=|class=notpageimage]]&#039;,&lt;br /&gt;
			theme_var(hero_key, &#039;bg-image&#039;), theme_var(hero_key, &#039;bg-height&#039;)))&lt;br /&gt;
&lt;br /&gt;
	local tbl = root:tag(&#039;table&#039;):addClass(&#039;infobox-h-content&#039;)&lt;br /&gt;
	render_title(ctx, tbl)&lt;br /&gt;
	render_card(ctx, tbl)&lt;br /&gt;
	render_tags(ctx, tbl)&lt;br /&gt;
	for _, section in ipairs(SECTIONS) do&lt;br /&gt;
		render_section(ctx, tbl, section)&lt;br /&gt;
	end&lt;br /&gt;
	render_information(ctx, tbl)&lt;br /&gt;
&lt;br /&gt;
	if mw.title.getCurrentTitle().nsText ~= &#039;Template&#039; then&lt;br /&gt;
		frame:callParserFunction(&#039;DISPLAYTITLE&#039;, ctx.display_name)&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(root) .. render_categories(ctx)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>~2026-GlassGreenKinetic677</name></author>
	</entry>
	<entry>
		<id>https://deadlock.wiki/User:LVL/Sandbox2?diff=93694</id>
		<title>User:LVL/Sandbox2</title>
		<link rel="alternate" type="text/html" href="https://deadlock.wiki/User:LVL/Sandbox2?diff=93694"/>
		<updated>2026-07-16T19:24:22Z</updated>

		<summary type="html">&lt;p&gt;~2026-GlassGreenKinetic677: move headings around for better page structure&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Damage Amplification&#039;&#039;&#039; or &#039;&#039;&#039;Damage Amp&#039;&#039;&#039; is a stat that multiplies the damage caused by the player to affected enemies and NPCs. It can be both positive and negative. In latter case it reduces damage and is also known as &#039;&#039;&#039;Damage Reduction&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|apzBQPZyW_w|360|description=Video explaining Damage Amplification}}&lt;br /&gt;
&lt;br /&gt;
== Damage Amplification ==&lt;br /&gt;
&lt;br /&gt;
=== Sources of Damage Amplification ===&lt;br /&gt;
&lt;br /&gt;
==== Additive Amplifiers ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+List of heroes&lt;br /&gt;
!Hero&lt;br /&gt;
!Ability&lt;br /&gt;
!Tier&lt;br /&gt;
!Stat change&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;1&amp;quot; |{{HeroIcon|Calico}} &lt;br /&gt;
| rowspan=&amp;quot;1&amp;quot; |{{AbilityIcon|Ava}} &lt;br /&gt;
|3&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|Ava|3|OutgoingDamagePercent}}%&#039;&#039;&#039; Ramping Damage Amp&lt;br /&gt;
|-&lt;br /&gt;
|{{HeroIcon|Ivy}}&lt;br /&gt;
|{{AbilityIcon|Air Drop}}&lt;br /&gt;
|0&lt;br /&gt;
|&#039;&#039;&#039;+20%&#039;&#039;&#039; Damage Amp on Ivy and carried ally&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{HeroIcon|Shiv}}&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{AbilityIcon|Killing Blow}}&lt;br /&gt;
|0&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_prop|Killing Blow|BuffDamage}}%&#039;&#039;&#039; Damage Amp&lt;br /&gt;
|-&lt;br /&gt;
|2&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|Killing Blow|2|BuffDamage}}%&#039;&#039;&#039; Damage Amp&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Multiplicative Amplifiers ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+List of heroes&lt;br /&gt;
!Hero&lt;br /&gt;
!Ability&lt;br /&gt;
!Tier&lt;br /&gt;
!Stat change&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{HeroIcon|Infernus}}&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{AbilityIcon|Napalm}}&lt;br /&gt;
|0&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_prop|Napalm|IncomingDamagePercentFromCaster}}%&#039;&#039;&#039; Damage Amp&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|Napalm|3|IncomingDamagePercentFromCaster}}%&#039;&#039;&#039; Damage Amp&lt;br /&gt;
|-&lt;br /&gt;
|{{HeroIcon|McGinnis}}&lt;br /&gt;
|{{AbilityIcon|Spectral Wall}}&lt;br /&gt;
|1&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|Spectral Wall|1|BonusDamagePercent}}%&#039;&#039;&#039; Damage Amp&lt;br /&gt;
|-&lt;br /&gt;
|{{HeroIcon|Mo &amp;amp; Krill}}&lt;br /&gt;
|{{AbilityIcon|Scorn}}&lt;br /&gt;
|3&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|Scorn|3|DamageBonus}}%&#039;&#039;&#039; Damage Amp per Stack&lt;br /&gt;
|-&lt;br /&gt;
|{{HeroIcon|Bebop}}&lt;br /&gt;
|{{AbilityIcon|Hook}}&lt;br /&gt;
|1&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|Grapple Arm|1|BulletAmp}}%&#039;&#039;&#039; Weapon Damage Amp against hooked targets&lt;br /&gt;
|-&lt;br /&gt;
| All Heroes&lt;br /&gt;
| [[Parry]]&lt;br /&gt;
| 0&lt;br /&gt;
| &#039;&#039;&#039;+25%&#039;&#039;&#039; Damage Amp (Parried target takes 25% increased damage from all sources)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Instance Amplifiers ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+List of heroes and items&lt;br /&gt;
!Hero / Item&lt;br /&gt;
!Ability&lt;br /&gt;
!Tier&lt;br /&gt;
!Stat change&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{HeroIcon|Drifter}}&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{AbilityIcon|Bloodscent}}&lt;br /&gt;
|0&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_prop|Bloodscent|AmpDamagePercent}}%&#039;&#039;&#039; Damage Amp on Isolated Heroes&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|Bloodscent|3|AmpDamagePercent}}%&#039;&#039;&#039; Damage Amp on Isolated Heroes&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{HeroIcon|Lady Geist}}&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{AbilityIcon|Malice}}&lt;br /&gt;
|0&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_prop|Malice|VulnerabilityPerStack}}%&#039;&#039;&#039; Damage Amp per Stack&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|Malice|3|VulnerabilityPerStack}}%&#039;&#039;&#039; Damage Amp per Stack&lt;br /&gt;
|-&lt;br /&gt;
|{{HeroIcon|Paradox}}&lt;br /&gt;
|{{AbilityIcon|Pulse Grenade}}&lt;br /&gt;
|0&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_prop|Pulse Grenade|DamageAmplificationPerStack}}%&#039;&#039;&#039; Damage Amp per Stack&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{HeroIcon|Pocket}}&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{AbilityIcon|Barrage}}&lt;br /&gt;
|0&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_prop|Barrage|AmpPercentPerStack}}%&#039;&#039;&#039; Damage Amp per Stack&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|Barrage|3|AmpPercentPerStack}}%&#039;&#039;&#039; Damage Amp per Stack&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{HeroIcon|Sinclair}}&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{AbilityIcon|Rabbit Hex}}&lt;br /&gt;
|0&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_prop|ability_magician_animalhexarea|DamageAmpPercentage}}%&#039;&#039;&#039; Damage Amp&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|&#039;&#039;&#039;+{{#invoke:Abilities|get_ability_upgrade_prop|ability_magician_animalhexarea|3|DamageAmpPercentage}}%&#039;&#039;&#039; Damage Amp&lt;br /&gt;
|-&lt;br /&gt;
|{{ItemIcon|Escalating Exposure}}&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| &#039;&#039;&#039;+{{ItemData|Escalating Exposure|MagicIncreasePerStack}}&#039;&#039;&#039; Damage Amp per Stack&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Damage Reduction ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Damage Reduction&#039;&#039;&#039; is a negative form of Damage Amplification, reducing the damage output of enemies or its user. Mechanically, standard Damage Reductions are treated as negative &#039;&#039;&#039;Additive Amps&#039;&#039;&#039; (see Calculation section).&lt;br /&gt;
&lt;br /&gt;
=== Damage Reduction Stacking ===&lt;br /&gt;
&lt;br /&gt;
Valve&#039;s May 22 patch notes state that Damage Reduction debuffs are intended to stack diminishingly (i.e., multiplicatively, in the same manner as Resistance)&amp;lt;ref&amp;gt;{{Update link|May|22|2026}}&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
However, live testing confirms that standard Damage Reduction debuffs have not been fully migrated to this new system. Currently, Damage Reductions (such as {{ItemIcon|Inhibitor}} or {{AbilityIcon|Fire Scarabs}}) simply act as negative Additive Amps. This means they add directly together with other additive amps (positive or negative) rather than multiplying against each other.&lt;br /&gt;
&lt;br /&gt;
=== Sources of Damage Reduction ===&lt;br /&gt;
&lt;br /&gt;
==== Weapon and Spirit Reduction ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+List of heroes&lt;br /&gt;
!Hero&lt;br /&gt;
!Ability&lt;br /&gt;
!Tier&lt;br /&gt;
!Stat change&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{HeroIcon|Mirage}}&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |{{AbilityIcon|Fire Scarabs}}&lt;br /&gt;
|0&lt;br /&gt;
|&#039;&#039;&#039;{{#invoke:Abilities|get_ability_prop|Fire Scarabs|OutgoingDamagePenaltyPercent}}%&#039;&#039;&#039; Damage Reduction&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|&#039;&#039;&#039;{{#invoke:Abilities|get_ability_upgrade_prop|Fire Scarabs|3|OutgoingDamagePenaltyPercent}}%&#039;&#039;&#039; Damage Reduction&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{{Item stat table|Damage Reduction}}&lt;br /&gt;
&lt;br /&gt;
* {{ItemIcon|Cheat Death}}, {{ItemIcon|Golden Goose Egg}} and {{ItemIcon|Cursed Relic}} reduce damage dealt by its user.&lt;br /&gt;
&lt;br /&gt;
==== Spirit Damage Reduction====&lt;br /&gt;
&lt;br /&gt;
Spirit Damage Reduction from {{HeroIcon|Infernus}}&#039;s {{AbilityIcon|Afterburn}} (T2) and {{ItemIcon|Silencer}} does &#039;&#039;&#039;not&#039;&#039;&#039; behave like standard Damage Reduction. Multiple sources of Spirit‑specific reduction stack additively with each other (e.g., Afterburn T2 + Silencer = sum of percentages). This combined spirit reduction is then applied as a &#039;&#039;&#039;separate multiplier&#039;&#039;&#039;, multiplicative with all other additive amps such as {{ItemIcon|Inhibitor}}.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+List of heroes and items&lt;br /&gt;
!Hero / Item&lt;br /&gt;
!Ability&lt;br /&gt;
!Tier&lt;br /&gt;
!Stat change&lt;br /&gt;
|-&lt;br /&gt;
|{{HeroIcon|Infernus}}&lt;br /&gt;
|{{AbilityIcon|Afterburn}}&lt;br /&gt;
|2&lt;br /&gt;
|&#039;&#039;&#039;{{#invoke:Abilities|get_ability_upgrade_prop|Afterburn|2|OutgoingTechDamagePercent}}%&#039;&#039;&#039; Spirit Damage Reduction &amp;lt;br&amp;gt;&amp;lt;small&amp;gt;&#039;&#039;&#039;Note:&#039;&#039;&#039; Additive with itself, but multiplicative with other additive amps.&amp;lt;/small&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|{{ItemIcon|Silencer}}&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|&#039;&#039;&#039;{{ItemData|Silencer|TechDamageReduction}}&#039;&#039;&#039; Spirit Damage Reduction &amp;lt;small&amp;gt;&amp;lt;br&amp;gt;&#039;&#039;&#039;Note:&#039;&#039;&#039; Additive with itself, but multiplicative with other additive amps.&amp;lt;/small&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Calculation ==&lt;br /&gt;
Contrary to what the percentages might suggest, multiple sources of damage amplification do not all stack in the same way. There are three distinct categories of Damage Amplification, each interacting with the damage formula differently:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Additive Amps:&#039;&#039;&#039; These add together with each other (including additive Damage Reductions) into a single pool.&lt;br /&gt;
* &#039;&#039;&#039;Multiplicative Amps:&#039;&#039;&#039; These multiply the total damage one by one.&lt;br /&gt;
* &#039;&#039;&#039;Instance Amps:&#039;&#039;&#039; These are nearly identical to multiplicative amps, main exception being they create a new instance of damage.&lt;br /&gt;
&lt;br /&gt;
=== The Damage Formula ===&lt;br /&gt;
&lt;br /&gt;
Based on testing, the generalized formula for how these amps apply to weapon damage is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\text{Damage} = [(\text{Base Bullet Damage} \times \text{Weapon Multiplier}) \times (1 + \text{Total Additive Amps}) + \text{Flat Bonus}] \times \text{Multiplicative Amp}_1 \times \text{Multiplicative Amp}_2 \dots \times \text{Instance Amp Damage}_1 \times \text{Instance Amp Damage}_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example:&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
# A hero has a base bullet damage of &#039;&#039;&#039;100&#039;&#039;&#039; and &#039;&#039;&#039;0&#039;&#039;&#039; flat weapon damage for simplicity.&lt;br /&gt;
# The hero is affected by {{ItemIcon|Inhibitor}} (an Additive Damage Reduction of &#039;&#039;&#039;-30%&#039;&#039;&#039;). Their damage drops to &#039;&#039;&#039;70&#039;&#039;&#039; (100 * 0.70 = 70).&lt;br /&gt;
# {{HeroIcon|Ivy}} casts {{AbilityIcon|Air Drop}} on the hero (an Additive Amp of &#039;&#039;&#039;+20%&#039;&#039;&#039;). The additive pool is now -30% + 20% = -10%. The hero deals &#039;&#039;&#039;90&#039;&#039;&#039; damage (100 * 0.90 = 90).&lt;br /&gt;
# The hero parries an enemy hero (Multiplicative Amp of &#039;&#039;&#039;+25%&#039;&#039;&#039; against parried target&#039;&#039;&#039;)&#039;&#039;&#039;. This is applied after the additive pool. The hero now deals &#039;&#039;&#039;112.5&#039;&#039;&#039; damage (90 * 1.25 = 112.5).&lt;br /&gt;
&lt;br /&gt;
== Bugs, Quirks, and Exceptions ==&lt;br /&gt;
* &#039;&#039;&#039;Additive Amps&#039;&#039;&#039; do not apply to many flat values, such as flat weapon damage bonuses, base damage for Melee abilities (Such as the bonus +60 damage from {{HeroIcon|Calico}}&#039;s {{AbilityIcon|Leaping Slash}} T3 upgrade), {{ItemIcon|Capacitor}} active, {{AbilityIcon|Backstabber}}, etc.&lt;br /&gt;
* &#039;&#039;&#039;Headhunter Interaction:&#039;&#039;&#039; Multiplicative non-instance amps are the only amps that work correctly with {{ItemIcon|Headhunter}} damage.&lt;br /&gt;
* &#039;&#039;&#039;Return Fire / Reflection:&#039;&#039;&#039; Because Multiplicative Amps act as defensive stats on the target, if an attacker fires at a target with {{ItemIcon|Return Fire}} while the attacker has an M-amp active against them, the attacker will take increased reflected damage.&lt;br /&gt;
* &#039;&#039;&#039;Barrier Interaction:&#039;&#039;&#039; {{HeroIcon|Billy}}&#039;s bullet amp from {{AbilityIcon|Blasted}} is unique as it is a post-mitigation [[True Damage|true damage]] amp. Because of this, it does not work at all against barriers.&lt;br /&gt;
* &#039;&#039;&#039;Silencer / Infernus Afterburn T2:&#039;&#039;&#039; {{ItemIcon|Silencer}} and {{HeroIcon|Infernus}}&#039;s {{AbilityIcon|Afterburn}} Tier 2 Spirit Damage Reduction are exceptions to the standard Damage Reduction rule. They stack additively with themselves, but multiplicatively with other additive amps.&lt;br /&gt;
* &#039;&#039;&#039;Celeste:&#039;&#039;&#039; {{HeroIcon|Celeste}}&#039;s self Spirit damage amp works like a multiplicative amp but is affected by additive amp bugs.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;br /&gt;
{{Stat navbox}}&lt;/div&gt;</summary>
		<author><name>~2026-GlassGreenKinetic677</name></author>
	</entry>
	<entry>
		<id>https://deadlock.wiki/User:Vergir?diff=93691</id>
		<title>User:Vergir</title>
		<link rel="alternate" type="text/html" href="https://deadlock.wiki/User:Vergir?diff=93691"/>
		<updated>2026-07-16T18:58:19Z</updated>

		<summary type="html">&lt;p&gt;~2026-GlassGreenKinetic677: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
texttexttexttexttexttexttexttexttexttexttexttexttext&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Damage Amplification ==&lt;br /&gt;
&lt;br /&gt;
=== Sources of Damage Amplification ===&lt;br /&gt;
&lt;br /&gt;
==== Additive Amplifiers ====&lt;br /&gt;
&lt;br /&gt;
==== Multiplicative Amplifiers ====&lt;br /&gt;
&lt;br /&gt;
==== Instance Amplifiers ====&lt;br /&gt;
&lt;br /&gt;
== Damage Reduction ==&lt;br /&gt;
&lt;br /&gt;
=== Damage Reduction Stacking ===&lt;br /&gt;
&lt;br /&gt;
=== Sources of Damage Reduction ===&lt;br /&gt;
&lt;br /&gt;
==== Weapon and Spirit Reduction ====&lt;br /&gt;
&lt;br /&gt;
==== Spirit Damage Reduction ====&lt;br /&gt;
&lt;br /&gt;
== Total Damage Calculation ==&lt;br /&gt;
&lt;br /&gt;
=== The Damage Formula ===&lt;br /&gt;
&lt;br /&gt;
== Bugs, Quirks, and Exceptions ==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>~2026-GlassGreenKinetic677</name></author>
	</entry>
	<entry>
		<id>https://deadlock.wiki/User:Vergir?diff=93690</id>
		<title>User:Vergir</title>
		<link rel="alternate" type="text/html" href="https://deadlock.wiki/User:Vergir?diff=93690"/>
		<updated>2026-07-16T18:56:00Z</updated>

		<summary type="html">&lt;p&gt;~2026-GlassGreenKinetic677: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;text&lt;br /&gt;
&lt;br /&gt;
== Damage Amplification ==&lt;br /&gt;
&lt;br /&gt;
=== Sources of Damage Amplification ===&lt;br /&gt;
&lt;br /&gt;
==== Additive Amplifiers ====&lt;br /&gt;
&lt;br /&gt;
==== Multiplicative Amplifiers ====&lt;br /&gt;
&lt;br /&gt;
==== Instance Amplifiers ====&lt;br /&gt;
&lt;br /&gt;
== Damage Reduction ==&lt;br /&gt;
&lt;br /&gt;
=== Damage Reduction Stacking ===&lt;br /&gt;
&lt;br /&gt;
=== Sources of Damage Reduction ===&lt;br /&gt;
&lt;br /&gt;
==== Weapon and Spirit Reduction ====&lt;br /&gt;
&lt;br /&gt;
==== Spirit Damage Reduction ====&lt;br /&gt;
&lt;br /&gt;
== Total Damage Calculation ==&lt;br /&gt;
&lt;br /&gt;
=== The Damage Formula ===&lt;br /&gt;
&lt;br /&gt;
== Bugs, Quirks, and Exceptions ==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>~2026-GlassGreenKinetic677</name></author>
	</entry>
	<entry>
		<id>https://deadlock.wiki/User:Vergir?diff=93689</id>
		<title>User:Vergir</title>
		<link rel="alternate" type="text/html" href="https://deadlock.wiki/User:Vergir?diff=93689"/>
		<updated>2026-07-16T18:54:11Z</updated>

		<summary type="html">&lt;p&gt;~2026-GlassGreenKinetic677: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Damage Amplification&#039;&#039;&#039; or &#039;&#039;&#039;Damage Amp&#039;&#039;&#039; is a stat that multiplies the damage caused by the player to affected enemies and NPCs. &lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|apzBQPZyW_w|360|description=Video explaining Damage Amplification}}&lt;br /&gt;
&lt;br /&gt;
== Damage Reduction ==&lt;br /&gt;
&lt;br /&gt;
== Calculation ==&lt;br /&gt;
&lt;br /&gt;
=== The Damage Formula ===&lt;br /&gt;
&lt;br /&gt;
=== Damage Reduction Stacking ===&lt;br /&gt;
&lt;br /&gt;
== Bugs, Quirks, and Exceptions ==&lt;br /&gt;
&lt;br /&gt;
== Sources of Damage Amplification ==&lt;br /&gt;
&lt;br /&gt;
=== Additive Amplifiers ===&lt;br /&gt;
&lt;br /&gt;
=== Multiplicative Amplifiers ===&lt;br /&gt;
&lt;br /&gt;
=== Instance Amplifiers ===&lt;br /&gt;
&lt;br /&gt;
== Sources of Damage Reduction (Negative Additive Amps) ==&lt;br /&gt;
&lt;br /&gt;
=== Weapon and Spirit Reduction ===&lt;br /&gt;
&lt;br /&gt;
=== Spirit Damage Reduction===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>~2026-GlassGreenKinetic677</name></author>
	</entry>
</feed>