<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://harvestwiki.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Aurelianus</id>
	<title>HarvestWiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://harvestwiki.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Aurelianus"/>
	<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php/Special:Contributions/Aurelianus"/>
	<updated>2026-10-21T00:52:35Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.8</generator>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Herbs&amp;diff=1158</id>
		<title>Herbs</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Herbs&amp;diff=1158"/>
		<updated>2026-09-13T22:16:39Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-archive-banner hw-archive-banner--herbs&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;BOTANICAL ARCHIVE · THE INDEX&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Herb Compendium&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;A centralized index of all documented herb, its location, rarity, gathering profile, and their preferred crystals.&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-archive-stats&amp;quot;&amp;gt;&amp;lt;span&amp;gt;{{#cargo_query:tables=Herbs|fields=COUNT(*)=Count|format=plainlist}} documented herbs&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;Affiliated Crystals&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;Advanced herb filtering&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-page-nav&amp;quot;&amp;gt;[[Main Page|Main Page]] [[Crystals|Crystal Archive]] [[Locations|Location Archive]] [[Mutations|Mutation Index]] [[Enchants|Enchant Atlas]] &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Herbs are the core item of Harvest the Dao, Usually dug up through the use of [[shovels]]. Each location and its sublocations host a variety of herbs that players can dig up and sell to any Merchant for Aureli - the game&#039;s main currency.&lt;br /&gt;
&lt;br /&gt;
Herbs have a chance to be dug with a Mutation that increases their value and alters their appearance. The odds of mutations can be changed with Enchants and through the use of some shovels.&lt;br /&gt;
&lt;br /&gt;
The herb list contains all recorded herbs in the game, You can filter it between locations, rarity, crystals, weight and names.&lt;br /&gt;
&lt;br /&gt;
== Herb Index ==&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-filter-panel&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-filter-kicker&amp;quot;&amp;gt;FILTER THE INDEX&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-filter-label&amp;quot;&amp;gt;Locations&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-herb-filter&amp;quot; role=&amp;quot;group&amp;quot; aria-label=&amp;quot;Filter herbs by location&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;hw-filter-loading&amp;quot;&amp;gt;Loading location filters…&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-herb-catalog&amp;quot;&amp;gt;&lt;br /&gt;
{{#cargo_query:&lt;br /&gt;
tables=Herbs&lt;br /&gt;
|fields=_pageName=Herb,Location,Rarity,PreferredCrystals=Preferred Crystals,Speed,AttackRate=Attack Rate,PriceAvg=Average Price&lt;br /&gt;
|order by=Rarity,Location,Name&lt;br /&gt;
|format=table&lt;br /&gt;
|class=wikitable hw-table sortable&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Trivia ==&lt;br /&gt;
* Some of the herbs are unfinished&lt;br /&gt;
&lt;br /&gt;
[[Category:Herbs]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Herbs/Change_History&amp;diff=1157</id>
		<title>Herbs/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Herbs/Change_History&amp;diff=1157"/>
		<updated>2026-09-13T22:16:39Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJmaWVsZHMiOiJfcGFnZU5hbWU9SGVyYixMb2NhdGlvbixSYXJpdHksUHJlZmVycmVkQ3J5c3RhbHM9UHJlZmVycmVkIENyeXN0YWxzLFNwZWVkLEF0dGFja1JhdGU9QXR0YWNrIFJhdGUsUHJpY2VBdmc9QXZlcmFnZSBQcmljZSIsIm9yZGVyIGJ5IjoiUmFyaXR5LExvY2F0aW9uLE5hbWUiLCJmb3JtYXQiOiJ0YWJsZSIsImNsYXNzIjoid2lraXRhYmxlIGh3LXRhYmxlIHNvcnRhYmxlIn0= --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Mutations&amp;diff=1156</id>
		<title>Mutations</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Mutations&amp;diff=1156"/>
		<updated>2026-09-13T22:16:00Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-archive-banner hw-archive-banner--mutations&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;ALTERATION ARCHIVE · MUTATION INDEX&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Mutations&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;A centralized archive of standard mutation multipliers, calculated occurrence chances, and priority.&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-archive-stats&amp;quot;&amp;gt;&amp;lt;span&amp;gt;{{#cargo_query:tables=Mutations|fields=COUNT(*)=Count|format=plainlist}} standard mutations&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;3 age mutations&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;2 independent modifiers&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-page-nav&amp;quot;&amp;gt;[[Main Page|Main Page]] [[Herbs|Herb Compendium]] [[Crystals|Crystal Archive]]&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Mutations alter the appearance and value of [[Herbs|herbs]]. They operate independently from Universal Luck and Preferred Luck, so those bonuses do not increase mutation chances.&lt;br /&gt;
&lt;br /&gt;
The system is layered. A herb may receive a standard mutation, an age mutation, and an independent modifier. Their value multipliers are applied multiplicatively when more than one layer is present.&lt;br /&gt;
&lt;br /&gt;
== Standard Mutation Index ==&lt;br /&gt;
Standard mutations are selected from a weighted pool totaling 625. Each displayed chance is calculated as &amp;lt;code&amp;gt;(Weight ÷ 625) ÷ 10&amp;lt;/code&amp;gt;, then converted to a percentage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-mutation-catalog&amp;quot;&amp;gt;&lt;br /&gt;
{{#cargo_query:&lt;br /&gt;
tables=Mutations&lt;br /&gt;
|fields=_pageName=Mutation,Multiplier=Multiplier,Priority=Priority&lt;br /&gt;
|order by=OrderIndex&lt;br /&gt;
|format=table&lt;br /&gt;
|class=wikitable hw-table sortable&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Age Mutations ==&lt;br /&gt;
Age mutations form a separate layer and are intentionally excluded from the standard mutation Cargo table.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-special-mutation-grid&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-special-mutation&amp;quot; style=&amp;quot;--hw-mutation-color:#5369FF&amp;quot;&amp;gt;[[Century]]&amp;lt;span&amp;gt;Age · ×1.5&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-special-mutation&amp;quot; style=&amp;quot;--hw-mutation-color:#6C47FF&amp;quot;&amp;gt;[[Millenium]]&amp;lt;span&amp;gt;Age · ×2&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-special-mutation&amp;quot; style=&amp;quot;--hw-mutation-color:#7F15FF&amp;quot;&amp;gt;[[Eon]]&amp;lt;span&amp;gt;Age · ×3&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Modifiers ==&lt;br /&gt;
Sovereign and Supreme form an independent modifier layer. They are also excluded from the standard mutation Cargo table.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-special-mutation-grid&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-special-mutation&amp;quot; style=&amp;quot;--hw-mutation-color:#FF26A5&amp;quot;&amp;gt;[[Sovereign]]&amp;lt;span&amp;gt;Modifier · ×2&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-special-mutation&amp;quot; style=&amp;quot;--hw-mutation-color:#ED13FF&amp;quot;&amp;gt;[[Supreme]]&amp;lt;span&amp;gt;Modifier · ×2&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Mutations]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Mutations/Change_History&amp;diff=1155</id>
		<title>Mutations/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Mutations/Change_History&amp;diff=1155"/>
		<updated>2026-09-13T22:15:59Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJmaWVsZHMiOiJfcGFnZU5hbWU9TXV0YXRpb24sTXVsdGlwbGllcj1NdWx0aXBsaWVyLFByaW9yaXR5PVByaW9yaXR5Iiwib3JkZXIgYnkiOiJPcmRlckluZGV4IiwiZm9ybWF0IjoidGFibGUiLCJjbGFzcyI6Indpa2l0YWJsZSBody10YWJsZSBzb3J0YWJsZSJ9 --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Crystals&amp;diff=1154</id>
		<title>Crystals</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Crystals&amp;diff=1154"/>
		<updated>2026-09-13T22:15:37Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-archive-banner hw-archive-banner--crystals&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;CULTIVATION ARCHIVE · CRYSTAL ARCHIVE&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Crystals&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;A centralized archive of cultivation crystals, effect multipliers, loot weights, and their preferred herbs.&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-archive-stats&amp;quot;&amp;gt;&amp;lt;span&amp;gt;{{#cargo_query:tables=Crystals|fields=COUNT(*)=Count|format=plainlist}} documented crystals&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;Linked herb preferences&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;Form-managed records&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-page-nav&amp;quot;&amp;gt;[[Main Page|Main Page]] [[Herbs|Herb Compendium]] [[Locations|Location Archive]]&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Crystals are useful consumables that can be used to help you get the [[herbs|herb]] that you need, they also give you a universal luck boost and technique boost depending on their type. All of them have one or more preferred [[herbs]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Crystal Index ==&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-crystal-catalog&amp;quot;&amp;gt;&lt;br /&gt;
{{#cargo_query:&lt;br /&gt;
tables=Crystals&lt;br /&gt;
|fields=_pageName=Crystal,Rarity,UniversalLuck=Universal Luck,PreferredLuck=Preferred Luck,Technique &lt;br /&gt;
|order by=Name&lt;br /&gt;
|format=table&lt;br /&gt;
|class=wikitable hw-table sortable&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Loot Weights ==&lt;br /&gt;
The loot weight represents a value of the crystal in the loot pool. The higher it is the higher chance for you to get that crystal.&lt;br /&gt;
&lt;br /&gt;
[[Category:Crystals]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Crystals/Change_History&amp;diff=1153</id>
		<title>Crystals/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Crystals/Change_History&amp;diff=1153"/>
		<updated>2026-09-13T22:15:37Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJmaWVsZHMiOiJfcGFnZU5hbWU9Q3J5c3RhbCxSYXJpdHksVW5pdmVyc2FsTHVjaz1Vbml2ZXJzYWwgTHVjayxQcmVmZXJyZWRMdWNrPVByZWZlcnJlZCBMdWNrLFRlY2huaXF1ZSIsIm9yZGVyIGJ5IjoiTmFtZSIsImZvcm1hdCI6InRhYmxlIiwiY2xhc3MiOiJ3aWtpdGFibGUgaHctdGFibGUgc29ydGFibGUifQ== --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Crystals&amp;diff=1152</id>
		<title>Crystals</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Crystals&amp;diff=1152"/>
		<updated>2026-09-13T22:15:27Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-archive-banner hw-archive-banner--crystals&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;CULTIVATION ARCHIVE · CRYSTAL ARCHIVE&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Crystals&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;A centralized archive of cultivation crystals, effect multipliers, loot weights, and their preferred herbs.&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-archive-stats&amp;quot;&amp;gt;&amp;lt;span&amp;gt;{{#cargo_query:tables=Crystals|fields=COUNT(*)=Count|format=plainlist}} documented crystals&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;Linked herb preferences&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;Form-managed records&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-page-nav&amp;quot;&amp;gt;[[Main Page|Main Page]] [[Herbs|Herb Compendium]] [[Locations|Location Archive]]&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Crystals are useful consumables that can be used to help you get the [[herbs|herb]] that you need, they also give you a universal luck boost and technique boost depending on their type. All of them have one or more preferred [[herbs]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Crystal Index ==&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-crystal-catalog&amp;quot;&amp;gt;&lt;br /&gt;
{{#cargo_query:&lt;br /&gt;
tables=Crystals&lt;br /&gt;
|fields=_pageName=Crystal,Rarity,UniversalLuck=Universal Luck,PreferredLuck=Preferred Luck,Technique,PreferredHerbs=Preferred Herbs&lt;br /&gt;
|order by=Name&lt;br /&gt;
|format=table&lt;br /&gt;
|class=wikitable hw-table sortable&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Loot Weights ==&lt;br /&gt;
The loot weight represents a value of the crystal in the loot pool. The higher it is the higher chance for you to get that crystal.&lt;br /&gt;
&lt;br /&gt;
[[Category:Crystals]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Crystals/Change_History&amp;diff=1151</id>
		<title>Crystals/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Crystals/Change_History&amp;diff=1151"/>
		<updated>2026-09-13T22:15:27Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJmaWVsZHMiOiJfcGFnZU5hbWU9Q3J5c3RhbCxSYXJpdHksVW5pdmVyc2FsTHVjaz1Vbml2ZXJzYWwgTHVjayxQcmVmZXJyZWRMdWNrPVByZWZlcnJlZCBMdWNrLFRlY2huaXF1ZSxQcmVmZXJyZWRIZXJicz1QcmVmZXJyZWQgSGVyYnMiLCJvcmRlciBieSI6Ik5hbWUiLCJmb3JtYXQiOiJ0YWJsZSIsImNsYXNzIjoid2lraXRhYmxlIGh3LXRhYmxlIHNvcnRhYmxlIn0= --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Shovels&amp;diff=1106</id>
		<title>Shovels</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Shovels&amp;diff=1106"/>
		<updated>2026-09-13T22:14:17Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-archive-banner hw-archive-banner--shovels&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;EQUIPMENT ARCHIVE · FIELD TOOLS&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Shovel Index&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;A comparative ledger of gathering tools, tiers, stat profiles, and passive properties.&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-archive-stats&amp;quot;&amp;gt;&amp;lt;span&amp;gt;{{#cargo_query:tables=Shovels|fields=COUNT(*)=Count|format=plainlist}} documented tools&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;3 equipment tiers&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;6 core statistics&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-page-nav&amp;quot;&amp;gt;[[Main Page|Portal]] · [[Herbs]] · [[Enchants]] · [[Locations]] · [[NPCs]] · [[Special:CargoTables/Shovels|Shovel Data]]&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Shovels&#039;&#039;&#039; are tools used to gather herbs throughout the world. Each shovel has its own tier and stat spread, making some shovels better suited for specific herbs, locations, or playstyles.&lt;br /&gt;
&lt;br /&gt;
Shovels have 6 basic stats.&lt;br /&gt;
* Efficiency: Adds to the growth rate of herbs, lowering the ratio of hits to misses required to complete a harvest.&lt;br /&gt;
* Gathering: Gives a certain chance to get an extra herb, but only of equal or lower rarity.&lt;br /&gt;
* Luck: Multiplies the chances of Rare+ herbs.&lt;br /&gt;
* Technique: Decreases the attack rate of herbs (negative technique increases the attack rate).&lt;br /&gt;
* Sight: Increases the length of the harvest bar.&lt;br /&gt;
* Sturdiness: Decreases the degree range from which the next attack comes in.&lt;br /&gt;
&lt;br /&gt;
They can also be [[Enchants|Enchanted]] through the use of [[Runes]]&lt;br /&gt;
&lt;br /&gt;
== Shovels ==&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-shovel-catalog&amp;quot;&amp;gt;&lt;br /&gt;
{{#cargo_query:&lt;br /&gt;
tables=Shovels&lt;br /&gt;
|fields=_pageName=Shovel,Tier,Efficiency,Gathering,Luck,Technique,Sight,Sturdiness&lt;br /&gt;
|order by=OrderIndex&lt;br /&gt;
|format=table&lt;br /&gt;
|class=wikitable hw-table sortable&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Trivia ==&lt;br /&gt;
* Shovels with higher luck are usually better for finding rarer herbs.&lt;br /&gt;
* Some shovels may be better for specific locations or preferred crystal types.&lt;br /&gt;
&lt;br /&gt;
[[Category:Tools]]&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Shovels/Change_History&amp;diff=1105</id>
		<title>Shovels/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Shovels/Change_History&amp;diff=1105"/>
		<updated>2026-09-13T22:14:16Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJmaWVsZHMiOiJfcGFnZU5hbWU9U2hvdmVsLFRpZXIsRWZmaWNpZW5jeSxHYXRoZXJpbmcsTHVjayxUZWNobmlxdWUsU2lnaHQsU3R1cmRpbmVzcyIsIm9yZGVyIGJ5IjoiT3JkZXJJbmRleCIsImZvcm1hdCI6InRhYmxlIiwiY2xhc3MiOiJ3aWtpdGFibGUgaHctdGFibGUgc29ydGFibGUifQ== --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Pointed_Shovel&amp;diff=742</id>
		<title>Pointed Shovel</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Pointed_Shovel&amp;diff=742"/>
		<updated>2026-08-21T15:30:22Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Pointed Shovel&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=4&lt;br /&gt;
|efficiency=-55%&lt;br /&gt;
|gathering=+10%&lt;br /&gt;
|luck=-20%&lt;br /&gt;
|technique=+10%&lt;br /&gt;
|sight=+0.2&lt;br /&gt;
|sturdiness=+40%&lt;br /&gt;
|passive_ability=Yes&lt;br /&gt;
|order=303&lt;br /&gt;
}}&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Steel_Spade&amp;diff=741</id>
		<title>Steel Spade</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Steel_Spade&amp;diff=741"/>
		<updated>2026-08-21T15:30:18Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Steel Spade&lt;br /&gt;
|obtainment=[[North]]&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=2&lt;br /&gt;
|efficiency=+15%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=-35%&lt;br /&gt;
|technique=+15%&lt;br /&gt;
|sight=+0.25&lt;br /&gt;
|sturdiness=+15%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=202&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Steel Spade&#039;&#039;&#039; is a tier 2 [[Shovels|shovel]] emphasizing efficiency, technique, sight, and sturdiness at the cost of luck.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Purchased at the shop ran by [[North]]&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Broken_Sword&amp;diff=740</id>
		<title>Broken Sword</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Broken_Sword&amp;diff=740"/>
		<updated>2026-08-21T15:30:17Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Broken Sword&lt;br /&gt;
|obtainment=Finish a quest given by [[Joe]]&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=5&lt;br /&gt;
|efficiency=+90%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=+50%&lt;br /&gt;
|technique=+10%&lt;br /&gt;
|sight=-0.1&lt;br /&gt;
|sturdiness=+0%&lt;br /&gt;
|passive_ability=Yes&lt;br /&gt;
|order=500&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
The &#039;&#039;&#039;Broken Sword&#039;&#039;&#039; Is one of the [[shovels]] in the game, It has one of the most unique passive abilities in the game,&lt;br /&gt;
&lt;br /&gt;
You feel your &#039;&#039;&#039;Heart of the Sword resonating forth.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Obtainment ==&lt;br /&gt;
It is obtained by finishing a quest given by [[Joe]]&lt;br /&gt;
&lt;br /&gt;
== Passive Ability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Mirrors the sight bar on the other side.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Suggested gameplay ==&lt;br /&gt;
The Broken Sword is recommended for:&lt;br /&gt;
* Collecting [[Herbs|Mythical Herbs]]&lt;br /&gt;
* Harvesting [[Herbs]] quickly&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Cat%27s_Shovel&amp;diff=739</id>
		<title>Cat&#039;s Shovel</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Cat%27s_Shovel&amp;diff=739"/>
		<updated>2026-08-21T15:30:17Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Cat Shovel&lt;br /&gt;
|obtainment=[[Knscat]]&lt;br /&gt;
|location=Mainland&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=0&lt;br /&gt;
|efficiency=-20%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=-650%&lt;br /&gt;
|technique=+20%&lt;br /&gt;
|sight=+1.5&lt;br /&gt;
|sturdiness=+20%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=0&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
Cat&#039;s shovel is one of the many [[Shovels]] in the game.. It&#039;s... really unwieldy and large but seems to do a good job somehow.. You can&#039;t really see what you&#039;re digging though&lt;br /&gt;
&lt;br /&gt;
== Trivia ==&lt;br /&gt;
* The first.. &amp;quot;joke&amp;quot; shovel in the game&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Root_Breaker&amp;diff=738</id>
		<title>Root Breaker</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Root_Breaker&amp;diff=738"/>
		<updated>2026-08-21T15:28:36Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Root Breaker&lt;br /&gt;
|obtainment=[[North]]&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=2&lt;br /&gt;
|efficiency=+0%&lt;br /&gt;
|gathering=+10%&lt;br /&gt;
|luck=+10%&lt;br /&gt;
|technique=+15%&lt;br /&gt;
|sight=+0.1&lt;br /&gt;
|sturdiness=+10%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=201&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Root Breaker&#039;&#039;&#039; is a tier 2 [[Shovels|shovel]] emphasizing gathering, luck, and sturdiness.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Purchased at a shop ran by [[North]].&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Journeyman%27s_Shovel&amp;diff=737</id>
		<title>Journeyman&#039;s Shovel</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Journeyman%27s_Shovel&amp;diff=737"/>
		<updated>2026-08-21T15:28:35Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Journeyman&#039;s Shovel&lt;br /&gt;
|obtainment=[[North]]&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=2&lt;br /&gt;
|efficiency=+20%&lt;br /&gt;
|gathering=+5%&lt;br /&gt;
|luck=+10%&lt;br /&gt;
|technique=+10%&lt;br /&gt;
|sight=+0.1&lt;br /&gt;
|sturdiness=+0%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=200&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Journeyman&#039;s Shovel&#039;&#039;&#039; is a tier 2 [[Shovels|shovel]] with improved efficiency and technique.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Purchased at the shop ran by [[North]].&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=King%27s_Destiny&amp;diff=736</id>
		<title>King&#039;s Destiny</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=King%27s_Destiny&amp;diff=736"/>
		<updated>2026-08-21T15:28:35Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=King&#039;s Destiny&lt;br /&gt;
|obtainment=Finishing the quest given by [[Mizzar]]&lt;br /&gt;
|location=[[Mizzar]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=3&lt;br /&gt;
|efficiency=+40%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=+100%&lt;br /&gt;
|technique=+10%&lt;br /&gt;
|sight=+0&lt;br /&gt;
|sturdiness=+0%&lt;br /&gt;
|passive_ability=Yes&lt;br /&gt;
|order=300&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;King&#039;s Destiny&#039;&#039;&#039; is a tier 3 [[Shovels|shovel]] obtained by finishing the quest given by [[Mizzar]].&lt;br /&gt;
&lt;br /&gt;
== Passive Ability ==&lt;br /&gt;
King&#039;s Destiny has a 25% chance to grant a duplicate herb with the [[Royal]] mutation.&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Autumn_Leaf&amp;diff=735</id>
		<title>Autumn Leaf</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Autumn_Leaf&amp;diff=735"/>
		<updated>2026-08-21T15:28:33Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Autumn Leaf&lt;br /&gt;
|obtainment=[[Jasonspam|JasonSpam]]&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=3&lt;br /&gt;
|efficiency=+60%&lt;br /&gt;
|gathering=+25%&lt;br /&gt;
|luck=+20%&lt;br /&gt;
|technique=-15%&lt;br /&gt;
|sight=+0&lt;br /&gt;
|sturdiness=+0%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=301&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Autumn Leaf&#039;&#039;&#039; is a tier 3 [[Shovels|shovel]]. Its stat spread emphasizes efficiency and gathering.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Obtained from a quest given by [[Jasonspam|JasonSpam]].&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Fragmented_Sword&amp;diff=734</id>
		<title>Fragmented Sword</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Fragmented_Sword&amp;diff=734"/>
		<updated>2026-08-21T15:26:31Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Created page with &amp;quot;{{Shovel |name=Fragmented Sword |location=Mainland |color=#FFFFFF |tier=3 |efficiency=+35% |gathering=+0% |luck=+80% |technique=+20% |sight=-0.1 |sturdiness=+20% |passive_ability=Yes |order=304 }}&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Fragmented Sword&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=3&lt;br /&gt;
|efficiency=+35%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=+80%&lt;br /&gt;
|technique=+20%&lt;br /&gt;
|sight=-0.1&lt;br /&gt;
|sturdiness=+20%&lt;br /&gt;
|passive_ability=Yes&lt;br /&gt;
|order=304&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Swift_Saber&amp;diff=733</id>
		<title>Swift Saber</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Swift_Saber&amp;diff=733"/>
		<updated>2026-08-21T15:24:12Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Created page with &amp;quot;{{Shovel |name=Swift Saber |location=Mainland} |color=#FFFFFF |tier=4 |efficiency=+75% |gathering=+0% |luck=-140% |technique=-20% |sight=+0.1 |sturdiness=-20% |passive_ability=Yes |order=401 }}&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Swift Saber&lt;br /&gt;
|location=[[Mainland}&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=4&lt;br /&gt;
|efficiency=+75%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=-140%&lt;br /&gt;
|technique=-20%&lt;br /&gt;
|sight=+0.1&lt;br /&gt;
|sturdiness=-20%&lt;br /&gt;
|passive_ability=Yes&lt;br /&gt;
|order=401&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Pointed_Shovel&amp;diff=682</id>
		<title>Pointed Shovel</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Pointed_Shovel&amp;diff=682"/>
		<updated>2026-08-13T08:13:15Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Pointed Shovel&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=3&lt;br /&gt;
|efficiency=-30%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=-10%&lt;br /&gt;
|technique=+0%&lt;br /&gt;
|sight=+0.4&lt;br /&gt;
|sturdiness=+10%&lt;br /&gt;
|passive_ability=Yes&lt;br /&gt;
|order=303&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Broken_Sword&amp;diff=681</id>
		<title>Broken Sword</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Broken_Sword&amp;diff=681"/>
		<updated>2026-08-13T08:13:14Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Broken Sword&lt;br /&gt;
|obtainment=Finish a quest given by [[Joe]]&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=4&lt;br /&gt;
|efficiency=+90%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=+50%&lt;br /&gt;
|technique=+10%&lt;br /&gt;
|sight=-0.2&lt;br /&gt;
|sturdiness=+0%&lt;br /&gt;
|passive_ability=Yes&lt;br /&gt;
|order=400&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
The &#039;&#039;&#039;Broken Sword&#039;&#039;&#039; Is one of the [[shovels]] in the game, It has one of the most unique passive abilities in the game,&lt;br /&gt;
&lt;br /&gt;
You feel your &#039;&#039;&#039;Heart of the Sword resonating forth.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Obtainment ==&lt;br /&gt;
It is obtained by finishing a quest given by [[Joe]]&lt;br /&gt;
&lt;br /&gt;
== Passive Ability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Mirrors the sight bar on the other side.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Suggested gameplay ==&lt;br /&gt;
The Broken Sword is recommended for:&lt;br /&gt;
* Collecting [[Herbs|Mythical Herbs]]&lt;br /&gt;
* Harvesting [[Herbs]] quickly&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Steel_Spade&amp;diff=680</id>
		<title>Steel Spade</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Steel_Spade&amp;diff=680"/>
		<updated>2026-08-13T08:13:13Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Steel Spade&lt;br /&gt;
|obtainment=Unknown&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=2&lt;br /&gt;
|efficiency=+30%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=-40%&lt;br /&gt;
|technique=+25%&lt;br /&gt;
|sight=+0.4&lt;br /&gt;
|sturdiness=+30%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=202&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Steel Spade&#039;&#039;&#039; is a tier 2 [[Shovels|shovel]] emphasizing efficiency, technique, sight, and sturdiness at the cost of luck.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Its obtainment location has not yet been documented.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Stats ==&lt;br /&gt;
It provides +30% efficiency, +0% gathering, -40% luck, +25% technique, +0.4 sight, and +30% sturdiness.&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Root_Breaker&amp;diff=679</id>
		<title>Root Breaker</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Root_Breaker&amp;diff=679"/>
		<updated>2026-08-13T08:13:13Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Root Breaker&lt;br /&gt;
|obtainment=Unknown&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=2&lt;br /&gt;
|efficiency=+0%&lt;br /&gt;
|gathering=+20%&lt;br /&gt;
|luck=+20%&lt;br /&gt;
|technique=+0%&lt;br /&gt;
|sight=+0.2&lt;br /&gt;
|sturdiness=+20%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=201&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Root Breaker&#039;&#039;&#039; is a tier 2 [[Shovels|shovel]] emphasizing gathering, luck, and sturdiness.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Its obtainment location has not yet been documented.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Stats ==&lt;br /&gt;
It provides +0% efficiency, +20% gathering, +20% luck, +0% technique, +0.2 sight, and +20% sturdiness.&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Journeyman%27s_Shovel&amp;diff=678</id>
		<title>Journeyman&#039;s Shovel</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Journeyman%27s_Shovel&amp;diff=678"/>
		<updated>2026-08-13T08:13:12Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Journeyman&#039;s Shovel&lt;br /&gt;
|obtainment=Unknown&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=2&lt;br /&gt;
|efficiency=+30%&lt;br /&gt;
|gathering=+5%&lt;br /&gt;
|luck=-10%&lt;br /&gt;
|technique=+10%&lt;br /&gt;
|sight=+0.2&lt;br /&gt;
|sturdiness=+0%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=200&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Journeyman&#039;s Shovel&#039;&#039;&#039; is a tier 2 [[Shovels|shovel]] with improved efficiency and technique.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Its obtainment location has not yet been documented.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Stats ==&lt;br /&gt;
It provides +30% efficiency, +5% gathering, -10% luck, +10% technique, +0.2 sight, and +0% sturdiness.&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=King%27s_Destiny&amp;diff=677</id>
		<title>King&#039;s Destiny</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=King%27s_Destiny&amp;diff=677"/>
		<updated>2026-08-13T08:13:12Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=King&#039;s Destiny&lt;br /&gt;
|obtainment=Finishing the quest given by [[Mizzar]]&lt;br /&gt;
|location=[[Mizzar]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=3&lt;br /&gt;
|efficiency=+50%&lt;br /&gt;
|gathering=+0%&lt;br /&gt;
|luck=+150%&lt;br /&gt;
|technique=+20%&lt;br /&gt;
|sight=-0.2&lt;br /&gt;
|sturdiness=+0%&lt;br /&gt;
|passive_ability=Yes&lt;br /&gt;
|order=300&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;King&#039;s Destiny&#039;&#039;&#039; is a tier 3 [[Shovels|shovel]] obtained by finishing the quest given by [[Mizzar]].&lt;br /&gt;
&lt;br /&gt;
== Passive Ability ==&lt;br /&gt;
King&#039;s Destiny has a 25% chance to grant a duplicate herb with the [[Royal]] mutation.&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Basic_Shovel&amp;diff=676</id>
		<title>Basic Shovel</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Basic_Shovel&amp;diff=676"/>
		<updated>2026-08-13T08:13:12Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Basic Shovel&lt;br /&gt;
|obtainment=Starter Shovel&lt;br /&gt;
|location=Starter areas&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=1&lt;br /&gt;
|efficiency=+0%&lt;br /&gt;
|gathering=+10%&lt;br /&gt;
|luck=+20%&lt;br /&gt;
|technique=-10%&lt;br /&gt;
|sight=+0.2&lt;br /&gt;
|sturdiness=+10%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=100&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
The &#039;&#039;&#039;Basic Shovel&#039;&#039;&#039; is a starter shovel given to new players at the beginning of the game. Its straightforward stat spread makes it useful for learning the gathering system and beginning the journey as a Herbalist.&lt;br /&gt;
&lt;br /&gt;
== Obtainment ==&lt;br /&gt;
The Basic Shovel is obtained automatically when starting the game.&lt;br /&gt;
&lt;br /&gt;
== Suggested gameplay ==&lt;br /&gt;
The Basic Shovel is recommended for:&lt;br /&gt;
* Learning how shovel stats work&lt;br /&gt;
* Gathering common herbs&lt;br /&gt;
* Completing the tutorial&lt;br /&gt;
* Exploring starter areas&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Autumn_Leaf&amp;diff=675</id>
		<title>Autumn Leaf</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Autumn_Leaf&amp;diff=675"/>
		<updated>2026-08-13T08:13:11Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Autumn Leaf&lt;br /&gt;
|obtainment=Unknown&lt;br /&gt;
|location=[[Mainland]]&lt;br /&gt;
|color=#FFFFFF&lt;br /&gt;
|tier=3&lt;br /&gt;
|efficiency=+70%&lt;br /&gt;
|gathering=+50%&lt;br /&gt;
|luck=+10%&lt;br /&gt;
|technique=-10%&lt;br /&gt;
|sight=+0.2&lt;br /&gt;
|sturdiness=+0%&lt;br /&gt;
|passive_ability=No&lt;br /&gt;
|order=301&lt;br /&gt;
}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Autumn Leaf&#039;&#039;&#039; is a tier 3 [[Shovels|shovel]]. Its stat spread emphasizes efficiency and gathering.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Its obtainment location has not yet been documented.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Stats ==&lt;br /&gt;
Autumn Leaf provides +70% efficiency, +50% gathering, +10% luck, -10% technique, +0.2 sight, and +0% sturdiness.&lt;br /&gt;
&lt;br /&gt;
[[Category:Shovels]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Pointed_Shovel&amp;diff=674</id>
		<title>Pointed Shovel</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Pointed_Shovel&amp;diff=674"/>
		<updated>2026-08-13T08:11:18Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Created page with &amp;quot;{{Shovel |name=Pointed Shovel |tier=3 }}&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Shovel&lt;br /&gt;
|name=Pointed Shovel&lt;br /&gt;
|tier=3&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=HarvestWiki:Version_control&amp;diff=575</id>
		<title>HarvestWiki:Version control</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=HarvestWiki:Version_control&amp;diff=575"/>
		<updated>2026-07-29T16:01:42Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Protected &amp;quot;HarvestWiki:Version control&amp;quot;: Version administration restricted to administrators ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite))&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOINDEX__&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-version-control-shell&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-version-control-hero&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-kicker&amp;quot;&amp;gt;SITE ADMINISTRATION&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;h2&amp;gt;Version Control&amp;lt;/h2&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Change the sitewide release number, create release pages, and manage automatic page snapshots from one place.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;hw-version-control-root&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Loading administrator controls…&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=MediaWiki:Common.js&amp;diff=572</id>
		<title>MediaWiki:Common.js</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=MediaWiki:Common.js&amp;diff=572"/>
		<updated>2026-07-29T15:51:07Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Use native form submission safely after version history&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Any JavaScript here will be loaded for all users on every page load. */ /* HarvestWiki dynamic Cargo archive filters */ (function () { &#039;use strict&#039;; function initHerbLocationFilters(root) { var catalogs = []; if (root &amp;amp;&amp;amp; root.matches &amp;amp;&amp;amp; root.matches(&#039;.hw-herb-catalog&#039;)) { catalogs.push(root); } if (root &amp;amp;&amp;amp; root.querySelectorAll) { catalogs = catalogs.concat(Array.prototype.slice.call(root.querySelectorAll(&#039;.hw-herb-catalog&#039;))); } catalogs.forEach(function (catalog) { if (catalog.dataset.hwLocationFiltersReady === &#039;1&#039;) { return; } var table = catalog.querySelector(&#039;table&#039;); var filterBar = document.querySelector(&#039;.hw-herb-filter&#039;); if (!table || !filterBar) { return; } var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;)); var locationIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;location&#039;; }); if (locationIndex &amp;lt; 0) { return; } var rows = Array.prototype.slice.call(table.querySelectorAll(&#039;tbody tr&#039;)); var counts = {}; rows.forEach(function (row) { var cell = row.querySelectorAll(&#039;td&#039;)[locationIndex]; var location = cell ? cell.textContent.trim() : &#039;&#039;; row.dataset.hwLocation = location; if (location) { counts[location] = (counts[location] || 0) + 1; } }); var locations = Object.keys(counts).sort(); var enabled = new Set(locations); filterBar.innerHTML = &#039;&#039;; var showAll = document.createElement(&#039;button&#039;); showAll.type = &#039;button&#039;; showAll.className = &#039;hw-filter-chip hw-filter-chip--all&#039;; showAll.textContent = &#039;Show all&#039;; filterBar.appendChild(showAll); var locationButtons = {}; locations.forEach(function (location) { var button = document.createElement(&#039;button&#039;); button.type = &#039;button&#039;; button.className = &#039;hw-filter-chip is-active&#039;; button.setAttribute(&#039;aria-pressed&#039;, &#039;true&#039;); button.textContent = location + &#039; (&#039; + counts[location] + &#039;)&#039;; button.addEventListener(&#039;click&#039;, function () { if (enabled.has(location)) { enabled.delete(location); } else { enabled.add(location); } applyFilters(); }); locationButtons[location] = button; filterBar.appendChild(button); }); function applyFilters() { rows.forEach(function (row) { row.hidden = !enabled.has(row.dataset.hwLocation); }); locations.forEach(function (location) { var active = enabled.has(location); locationButtons[location].classList.toggle(&#039;is-active&#039;, active); locationButtons[location].setAttribute(&#039;aria-pressed&#039;, active ? &#039;true&#039; : &#039;false&#039;); }); showAll.disabled = enabled.size === locations.length; showAll.classList.toggle(&#039;is-active&#039;, enabled.size === locations.length); } showAll.addEventListener(&#039;click&#039;, function () { enabled = new Set(locations); applyFilters(); }); catalog.dataset.hwLocationFiltersReady = &#039;1&#039;; applyFilters(); }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { initHerbLocationFilters($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki admin-only Herb form UI */ (function () { &#039;use strict&#039;; var groups = mw.config.get(&#039;wgUserGroups&#039;) || []; var categories = mw.config.get(&#039;wgCategories&#039;) || []; if (groups.indexOf(&#039;sysop&#039;) !== -1 || categories.indexOf(&#039;Herbs&#039;) === -1) { return; } function hideHerbFormActions() { [&#039;ca-formedit&#039;, &#039;ca-formcreate&#039;, &#039;ca-formedit-sticky-header&#039;, &#039;ca-formcreate-sticky-header&#039;].forEach(function (id) { var element = document.getElementById(id); if (element) { element.hidden = true; } }); } if (document.readyState === &#039;loading&#039;) { document.addEventListener(&#039;DOMContentLoaded&#039;, hideHerbFormActions); } else { hideHerbFormActions(); } }()); /* HarvestWiki rarity-colored Herb and Crystal table rows */ (function () { &#039;use strict&#039;; var rarities = { common: true, uncommon: true, rare: true, legendary: true, mythic: true, secret: true, mortal: true, refined: true, spirit: true }; function normalizeRarity(value) { var rarity = (value || &#039;&#039;).trim().toLowerCase(); return rarities[rarity] ? rarity : &#039;&#039;; } function applyRarityColors(root) { var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document; var tables = []; if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) { tables.push(scope); } tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;))); tables.forEach(function (table) { var headerCells = Array.prototype.slice.call(table.querySelectorAll(&#039;tr:first-child th, tr:first-child td&#039;)); var itemIndex = headerCells.findIndex(function (cell) { var label = cell.textContent.trim().toLowerCase(); return label === &#039;herb&#039; || label === &#039;crystal&#039; || cell.classList.contains(&#039;field_Herb&#039;) || cell.classList.contains(&#039;field_Crystal&#039;); }); var rarityIndex = headerCells.findIndex(function (cell) { return cell.textContent.trim().toLowerCase() === &#039;rarity&#039; || cell.classList.contains(&#039;field_Rarity&#039;); }); Array.prototype.slice.call(table.rows).forEach(function (row) { var cells = Array.prototype.slice.call(row.cells); var itemCell = row.querySelector(&#039;td.field_Herb, td.field_Crystal&#039;); var rarityCell = row.querySelector(&#039;td.field_Rarity&#039;); if (!itemCell &amp;amp;&amp;amp; itemIndex &amp;gt;= 0) { itemCell = cells[itemIndex]; } if (!rarityCell &amp;amp;&amp;amp; rarityIndex &amp;gt;= 0) { rarityCell = cells[rarityIndex]; } if (!itemCell || !rarityCell || itemCell.tagName !== &#039;TD&#039;) { return; } var rarity = normalizeRarity(rarityCell.textContent); if (!rarity) { return; } row.classList.add(&#039;hw-rarity-row&#039;); row.dataset.hwRarity = rarity; itemCell.classList.add(&#039;hw-rarity-item&#039;); rarityCell.classList.add(&#039;hw-rarity-label&#039;); }); table.dataset.hwRarityColorsReady = &#039;1&#039;; }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { applyRarityColors($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki default Herb rarity order */ (function () { &#039;use strict&#039;; var rarityRank = { common: 0, uncommon: 1, rare: 2, legendary: 3, mythic: 4, secret: 5 }; function sortHerbTables(root) { var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document; var tables = []; if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) { tables.push(scope); } tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;))); tables.forEach(function (table) { if (table.dataset.hwHerbRaritySorted === &#039;1&#039;) { return; } var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;)); if (!headers.length &amp;amp;&amp;amp; table.rows.length) { headers = Array.prototype.slice.call(table.rows[0].cells); } var herbIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;herb&#039; || header.classList.contains(&#039;field_Herb&#039;); }); var rarityIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;rarity&#039; || header.classList.contains(&#039;field_Rarity&#039;); }); if (herbIndex &amp;lt; 0 || rarityIndex &amp;lt; 0) { return; } Array.prototype.slice.call(table.tBodies).forEach(function (body) { var rows = Array.prototype.slice.call(body.rows).map(function (row, index) { var rarityCell = row.cells[rarityIndex]; var rarity = rarityCell ? rarityCell.textContent.trim().toLowerCase() : &#039;&#039;; return { row: row, index: index, rank: Object.prototype.hasOwnProperty.call(rarityRank, rarity) ? rarityRank[rarity] : 999 }; }); rows.sort(function (a, b) { return a.rank - b.rank || a.index - b.index; }); var fragment = document.createDocumentFragment(); rows.forEach(function (entry) { fragment.appendChild(entry.row); }); body.appendChild(fragment); }); table.dataset.hwHerbRaritySorted = &#039;1&#039;; }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { sortHerbTables($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki default Crystal rarity order */ (function () { &#039;use strict&#039;; var crystalRarityRank = { mortal: 0, refined: 1, spirit: 2 }; function sortCrystalTables(root) { var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document; var tables = []; if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) { tables.push(scope); } tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;))); tables.forEach(function (table) { if (table.dataset.hwCrystalRaritySorted === &#039;1&#039;) { return; } var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;)); if (!headers.length &amp;amp;&amp;amp; table.rows.length) { headers = Array.prototype.slice.call(table.rows[0].cells); } var crystalIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;crystal&#039; || header.classList.contains(&#039;field_Crystal&#039;); }); var rarityIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;rarity&#039; || header.classList.contains(&#039;field_Rarity&#039;); }); if (crystalIndex &amp;lt; 0 || rarityIndex &amp;lt; 0) { return; } Array.prototype.slice.call(table.tBodies).forEach(function (body) { var rows = Array.prototype.slice.call(body.rows).map(function (row, index) { var rarityCell = row.cells[rarityIndex]; var rarity = rarityCell ? rarityCell.textContent.trim().toLowerCase() : &#039;&#039;; return { row: row, index: index, rank: Object.prototype.hasOwnProperty.call(crystalRarityRank, rarity) ? crystalRarityRank[rarity] : 999 }; }); rows.sort(function (a, b) { return a.rank - b.rank || a.index - b.index; }); var fragment = document.createDocumentFragment(); rows.forEach(function (entry) { fragment.appendChild(entry.row); }); body.appendChild(fragment); }); table.dataset.hwCrystalRaritySorted = &#039;1&#039;; }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { sortCrystalTables($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki admin-only Crystal form UI */ (function () { &#039;use strict&#039;; var groups = mw.config.get(&#039;wgUserGroups&#039;) || []; var categories = mw.config.get(&#039;wgCategories&#039;) || []; if (groups.indexOf(&#039;sysop&#039;) !== -1 || categories.indexOf(&#039;Crystals&#039;) === -1) { return; } function hideCrystalFormActions() { [&#039;ca-formedit&#039;, &#039;ca-formcreate&#039;, &#039;ca-formedit-sticky-header&#039;, &#039;ca-formcreate-sticky-header&#039;].forEach(function (id) { var element = document.getElementById(id); if (element) { element.hidden = true; } }); } if (document.readyState === &#039;loading&#039;) { document.addEventListener(&#039;DOMContentLoaded&#039;, hideCrystalFormActions); } else { hideCrystalFormActions(); } }()); /* HarvestWiki admin-only Weather form UI */ (function () { &#039;use strict&#039;; var groups = mw.config.get(&#039;wgUserGroups&#039;) || []; var categories = mw.config.get(&#039;wgCategories&#039;) || []; if (groups.indexOf(&#039;sysop&#039;) !== -1 || categories.indexOf(&#039;Weather&#039;) === -1) { return; } function hideWeatherFormActions() { [&#039;ca-formedit&#039;, &#039;ca-formcreate&#039;, &#039;ca-formedit-sticky-header&#039;, &#039;ca-formcreate-sticky-header&#039;].forEach(function (id) { var element = document.getElementById(id); if (element) { element.hidden = true; } }); } if (document.readyState === &#039;loading&#039;) { document.addEventListener(&#039;DOMContentLoaded&#039;, hideWeatherFormActions); } else { hideWeatherFormActions(); } }()); /* HarvestWiki four-zodiac celestial calendar */ (function () { &#039;use strict&#039;; var anchor = Date.UTC(2026, 0, 1, 0, 0, 0); var seasonLength = 8 * 60 * 60 * 1000; var seasonNames = [ &#039;Year of the Dragon&#039;, &#039;Year of the Tiger&#039;, &#039;Year of the Phoenix&#039;, &#039;Year of the Tortoise&#039; ]; function formatDuration(milliseconds) { var seconds = Math.max(0, Math.ceil(milliseconds / 1000)); var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds % 3600) / 60); var remainder = seconds % 60; return String(hours).padStart(2, &#039;0&#039;) + &#039;:&#039; + String(minutes).padStart(2, &#039;0&#039;) + &#039;:&#039; + String(remainder).padStart(2, &#039;0&#039;); } function initZodiacCycles(root) { var widgets = []; if (root &amp;amp;&amp;amp; root.matches &amp;amp;&amp;amp; root.matches(&#039;[data-hw-zodiac-cycle]&#039;)) { widgets.push(root); } if (root &amp;amp;&amp;amp; root.querySelectorAll) { widgets = widgets.concat(Array.prototype.slice.call(root.querySelectorAll(&#039;[data-hw-zodiac-cycle]&#039;))); } widgets.forEach(function (widget) { if (widget.dataset.hwZodiacReady === &#039;1&#039;) { return; } widget.dataset.hwZodiacReady = &#039;1&#039;; var currentLabel = widget.querySelector(&#039;[data-hw-zodiac-current]&#039;); var countdown = widget.querySelector(&#039;[data-hw-zodiac-countdown]&#039;); var progress = widget.querySelector(&#039;[data-hw-zodiac-progress]&#039;); var seasons = Array.prototype.slice.call(widget.querySelectorAll(&#039;[data-hw-zodiac-season]&#039;)); function updateZodiacCycle() { var now = Date.now(); var step = Math.floor((now - anchor) / seasonLength); var currentIndex = ((step % seasonNames.length) + seasonNames.length) % seasonNames.length; var nextBoundary = anchor + ((step + 1) * seasonLength); var remaining = Math.max(0, nextBoundary - now); var elapsedInSeason = ((now - anchor) % seasonLength + seasonLength) % seasonLength; if (currentLabel) { currentLabel.textContent = seasonNames[currentIndex]; } if (countdown) { countdown.textContent = formatDuration(remaining); countdown.title = &#039;Changes at &#039; + new Date(nextBoundary).toLocaleString(); } if (progress) { progress.style.width = ((elapsedInSeason / seasonLength) * 100).toFixed(3) + &#039;%&#039;; } seasons.forEach(function (season, index) { var offset = (index - currentIndex + seasonNames.length) % seasonNames.length; var state = season.querySelector(&#039;[data-hw-zodiac-state]&#039;); var startsIn = remaining + (Math.max(0, offset - 1) * seasonLength); var changeAt = offset === 0 ? nextBoundary : now + startsIn; season.classList.toggle(&#039;is-current&#039;, offset === 0); season.setAttribute(&#039;aria-current&#039;, offset === 0 ? &#039;true&#039; : &#039;false&#039;); season.title = (offset === 0 ? &#039;Ends &#039; : &#039;Begins &#039;) + new Date(changeAt).toLocaleString(); if (state) { if (offset === 0) { state.textContent = &#039;Current · ends in &#039; + formatDuration(remaining); } else if (offset === 1) { state.textContent = &#039;Next · starts in &#039; + formatDuration(startsIn); } else { state.textContent = &#039;Starts in &#039; + formatDuration(startsIn); } } }); } updateZodiacCycle(); window.setInterval(updateZodiacCycle, 1000); }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { initZodiacCycles($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki multi-page editor controls and archive refresh */ (function () { &#039;use strict&#039;; function initBulkEditors(root) { var grids = []; if (root &amp;amp;&amp;amp; root.matches &amp;amp;&amp;amp; root.matches(&#039;.pfSpreadsheet&#039;)) { grids.push(root); } if (root &amp;amp;&amp;amp; root.querySelectorAll) { grids = grids.concat(Array.prototype.slice.call(root.querySelectorAll(&#039;.pfSpreadsheet&#039;))); } grids.forEach(function (grid) { if (grid.dataset.hwBulkEditorReady === &#039;1&#039;) { return; } grid.dataset.hwBulkEditorReady = &#039;1&#039;; var parameters = new URLSearchParams(window.location.search); var templateName = parameters.get(&#039;template&#039;) || grid.id.replace(/Grid$/, &#039;&#039;); var archivePage = templateName === &#039;Herb&#039; ? &#039;Herbs&#039; : (templateName === &#039;Crystal&#039; ? &#039;Crystals&#039; : null); var userGroups = mw.config.get(&#039;wgUserGroups&#039;) || []; var canDelete = archivePage &amp;amp;&amp;amp; userGroups.indexOf(&#039;sysop&#039;) !== -1; function purgeArchive() { if (!archivePage || typeof mw.Api !== &#039;function&#039;) { return Promise.resolve(); } return new mw.Api().post({ action: &#039;purge&#039;, titles: archivePage, formatversion: 2 }); } function wireRowActions() { Array.prototype.slice.call(grid.querySelectorAll(&#039;a.save-changes&#039;)).forEach(function (link) { if (link.dataset.hwBulkSaveReady === &#039;1&#039;) { return; } link.dataset.hwBulkSaveReady = &#039;1&#039;; link.setAttribute(&#039;title&#039;, &#039;Save this row&#039;); link.setAttribute(&#039;aria-label&#039;, &#039;Save this row&#039;); link.addEventListener(&#039;click&#039;, function () { link.classList.remove(&#039;is-saved&#039;); link.classList.add(&#039;is-saving&#039;); function refreshArchive() { purgeArchive().then(function () { link.classList.remove(&#039;is-saving&#039;); link.classList.add(&#039;is-saved&#039;); window.setTimeout(function () { link.classList.remove(&#039;is-saved&#039;); }, 2200); }); } window.setTimeout(refreshArchive, 1600); window.setTimeout(refreshArchive, 4200); }); }); if (!canDelete) { return; } Array.prototype.slice.call(grid.querySelectorAll(&#039;tbody tr&#039;)).forEach(function (row) { var pageCell = row.querySelector(&#039;td[data-x=&amp;quot;0&amp;quot;]&#039;); var actionBox = row.querySelector(&#039;.save-or-cancel&#039;); if (!pageCell || !actionBox || actionBox.querySelector(&#039;.hw-delete-row&#039;)) { return; } var pageTitle = pageCell.textContent.trim(); if (!pageTitle) { return; } var deleteButton = document.createElement(&#039;button&#039;); deleteButton.type = &#039;button&#039;; deleteButton.className = &#039;hw-delete-row&#039;; deleteButton.textContent = &#039;Delete&#039;; deleteButton.title = &#039;Delete page &#039; + pageTitle; deleteButton.setAttribute(&#039;aria-label&#039;, &#039;Delete page &#039; + pageTitle); deleteButton.addEventListener(&#039;click&#039;, function () { var confirmed = window.confirm( &#039;Permanently delete &amp;quot;&#039; + pageTitle + &#039;&amp;quot;?\n\n&#039; + &#039;This removes its wiki page and all table data. This cannot be undone.&#039; ); if (!confirmed) { return; } deleteButton.disabled = true; deleteButton.classList.add(&#039;is-deleting&#039;); deleteButton.textContent = &#039;Deleting…&#039;; new mw.Api().postWithToken(&#039;csrf&#039;, { action: &#039;delete&#039;, title: pageTitle, reason: &#039;Deleted from the multi-page editor&#039;, formatversion: 2 }).then(function () { return purgeArchive(); }).then(function () { deleteButton.textContent = &#039;Deleted&#039;; window.setTimeout(function () { window.location.reload(); }, 700); }, function () { deleteButton.disabled = false; deleteButton.classList.remove(&#039;is-deleting&#039;); deleteButton.textContent = &#039;Delete&#039;; mw.notify(&#039;Could not delete &amp;quot;&#039; + pageTitle + &#039;&amp;quot;. The record was not removed.&#039;, { type: &#039;error&#039; }); }); }); actionBox.appendChild(deleteButton); }); } wireRowActions(); if (window.MutationObserver) { new MutationObserver(wireRowActions).observe(grid, { childList: true, subtree: true }); } if (!grid.querySelector(&#039;.hw-bulk-editor-note&#039;)) { var note = document.createElement(&#039;div&#039;); note.className = &#039;hw-bulk-editor-note&#039;; note.textContent = &#039;Scroll sideways to reach every field. Use Save at the right of each row.&#039; + (archivePage ? &#039; Saved rows automatically refresh the &#039; + archivePage + &#039; index.&#039; : &#039;&#039;) + (canDelete ? &#039; Administrators can permanently remove a record with Delete.&#039; : &#039;&#039;); grid.appendChild(note); } }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { initBulkEditors($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }());&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki automatic Herb, Crystal, Mutation, Shovel, Weather, TOD, and Zodiac link colors */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  var rarityColors = {&lt;br /&gt;
    common: &#039;#e5e7e7&#039;,&lt;br /&gt;
    uncommon: &#039;#87ff97&#039;,&lt;br /&gt;
    rare: &#039;#6d90ff&#039;,&lt;br /&gt;
    legendary: &#039;#fff687&#039;,&lt;br /&gt;
    mythic: &#039;#ff79cb&#039;,&lt;br /&gt;
    secret: &#039;#12005c&#039;,&lt;br /&gt;
    mortal: &#039;#cef1e8&#039;,&lt;br /&gt;
    refined: &#039;#464747&#039;,&lt;br /&gt;
    spirit: &#039;#00c3f2&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var mutationColors = {&lt;br /&gt;
    silver: &#039;#eef1f1&#039;,&lt;br /&gt;
    golden: &#039;#f4d05b&#039;,&lt;br /&gt;
    vitality: &#039;#e96869&#039;,&lt;br /&gt;
    vampiric: &#039;#de002b&#039;,&lt;br /&gt;
    icy: &#039;#9ee7d8&#039;,&lt;br /&gt;
    frozen: &#039;#6ee8ff&#039;,&lt;br /&gt;
    spirit: &#039;#ffabf6&#039;,&lt;br /&gt;
    royal: &#039;#ffec31&#039;,&lt;br /&gt;
    imperial: &#039;#b600ff&#039;,&lt;br /&gt;
    century: &#039;#5369ff&#039;,&lt;br /&gt;
    millenium: &#039;#6c47ff&#039;,&lt;br /&gt;
    eon: &#039;#7f15ff&#039;,&lt;br /&gt;
    sovereign: &#039;#ff26a5&#039;,&lt;br /&gt;
    supreme: &#039;#ed13ff&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var worldColors = {&lt;br /&gt;
    clear: &#039;#f1cb4f&#039;,&lt;br /&gt;
    cloudy: &#039;#949ba5&#039;,&lt;br /&gt;
    rain: &#039;#60a5e2&#039;,&lt;br /&gt;
    storm: &#039;#525964&#039;,&lt;br /&gt;
    thunderstorm: &#039;#f8d652&#039;,&lt;br /&gt;
    &#039;dao tempest&#039;: &#039;#b9422f&#039;,&lt;br /&gt;
    sunrise: &#039;#ef9d49&#039;,&lt;br /&gt;
    daytime: &#039;#63ace0&#039;,&lt;br /&gt;
    sunset: &#039;#df6447&#039;,&lt;br /&gt;
    night: &#039;#abb6f9&#039;,&lt;br /&gt;
    &#039;year of the dragon&#039;: &#039;#ffb86f&#039;,&lt;br /&gt;
    &#039;year of the tiger&#039;: &#039;#f7f672&#039;,&lt;br /&gt;
    &#039;year of the phoenix&#039;: &#039;#f87b84&#039;,&lt;br /&gt;
    &#039;year of the tortoise&#039;: &#039;#4fc57d&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var shovelColors = {&lt;br /&gt;
    &#039;basic shovel&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &amp;quot;journeyman&#039;s shovel&amp;quot;: &#039;#ffffff&#039;,&lt;br /&gt;
    &#039;root breaker&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &#039;steel spade&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &#039;autumn leaf&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &amp;quot;king&#039;s destiny&amp;quot;: &#039;#ffffff&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var entityColors = {&lt;br /&gt;
    &#039;ancient woe herb&#039;: rarityColors.rare,&lt;br /&gt;
    &#039;breathless dawn herb&#039;: rarityColors.uncommon,&lt;br /&gt;
    &#039;crimson sun grass&#039;: rarityColors.common,&lt;br /&gt;
    &#039;fleeting dusk herb&#039;: rarityColors.uncommon,&lt;br /&gt;
    &#039;frigid moon grass&#039;: rarityColors.common,&lt;br /&gt;
    &#039;pursuing longevity herb&#039;: rarityColors.mythic,&lt;br /&gt;
    &#039;sparkling joy herb&#039;: rarityColors.rare,&lt;br /&gt;
    &#039;yang lotus&#039;: rarityColors.legendary,&lt;br /&gt;
    &#039;yin lotus&#039;: rarityColors.legendary,&lt;br /&gt;
    &#039;air crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;earth crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;flame crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;water crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;wood crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;magma crystal&#039;: rarityColors.refined,&lt;br /&gt;
    &#039;mist crystal&#039;: rarityColors.refined,&lt;br /&gt;
    &#039;longevity crystal&#039;: rarityColors.spirit,&lt;br /&gt;
    clear: worldColors.clear,&lt;br /&gt;
    cloudy: worldColors.cloudy,&lt;br /&gt;
    rain: worldColors.rain,&lt;br /&gt;
    storm: worldColors.storm,&lt;br /&gt;
    thunderstorm: worldColors.thunderstorm,&lt;br /&gt;
    &#039;dao tempest&#039;: worldColors[&#039;dao tempest&#039;],&lt;br /&gt;
    sunrise: worldColors.sunrise,&lt;br /&gt;
    daytime: worldColors.daytime,&lt;br /&gt;
    sunset: worldColors.sunset,&lt;br /&gt;
    night: worldColors.night,&lt;br /&gt;
    &#039;year of the dragon&#039;: worldColors[&#039;year of the dragon&#039;],&lt;br /&gt;
    &#039;year of the tiger&#039;: worldColors[&#039;year of the tiger&#039;],&lt;br /&gt;
    &#039;year of the phoenix&#039;: worldColors[&#039;year of the phoenix&#039;],&lt;br /&gt;
    &#039;year of the tortoise&#039;: worldColors[&#039;year of the tortoise&#039;],&lt;br /&gt;
    &#039;basic shovel&#039;: shovelColors[&#039;basic shovel&#039;],&lt;br /&gt;
    &amp;quot;journeyman&#039;s shovel&amp;quot;: shovelColors[&amp;quot;journeyman&#039;s shovel&amp;quot;],&lt;br /&gt;
    &#039;root breaker&#039;: shovelColors[&#039;root breaker&#039;],&lt;br /&gt;
    &#039;steel spade&#039;: shovelColors[&#039;steel spade&#039;],&lt;br /&gt;
    &#039;autumn leaf&#039;: shovelColors[&#039;autumn leaf&#039;],&lt;br /&gt;
    &amp;quot;king&#039;s destiny&amp;quot;: shovelColors[&amp;quot;king&#039;s destiny&amp;quot;]&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  function normalizeTitle(value) {&lt;br /&gt;
    return (value || &#039;&#039;).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).trim().toLowerCase();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function titleFromLink(link) {&lt;br /&gt;
    var href = link.getAttribute(&#039;href&#039;) || &#039;&#039;;&lt;br /&gt;
    if (!href || href.charAt(0) === &#039;#&#039;) {&lt;br /&gt;
      return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
      var url = new URL(href, window.location.origin);&lt;br /&gt;
      if (url.origin !== window.location.origin) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
      if (!title &amp;amp;&amp;amp; url.pathname.indexOf(&#039;/index.php/&#039;) === 0) {&lt;br /&gt;
        title = decodeURIComponent(url.pathname.slice(&#039;/index.php/&#039;.length));&lt;br /&gt;
      }&lt;br /&gt;
      return normalizeTitle((title || &#039;&#039;).split(&#039;#&#039;)[0]);&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function cargoQuery(table, fields) {&lt;br /&gt;
    return mw.loader.using(&#039;mediawiki.api&#039;).then(function () {&lt;br /&gt;
      return new mw.Api().get({&lt;br /&gt;
        action: &#039;cargoquery&#039;,&lt;br /&gt;
        tables: table,&lt;br /&gt;
        fields: fields,&lt;br /&gt;
        limit: 500,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
      });&lt;br /&gt;
    }).then(function (data) {&lt;br /&gt;
      return data &amp;amp;&amp;amp; data.cargoquery ? data.cargoquery : [];&lt;br /&gt;
    }, function () {&lt;br /&gt;
      return [];&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function buildColorMap() {&lt;br /&gt;
    return Promise.all([&lt;br /&gt;
      cargoQuery(&#039;Herbs&#039;, &#039;_pageName=Page,Rarity&#039;),&lt;br /&gt;
      cargoQuery(&#039;Crystals&#039;, &#039;_pageName=Page,Rarity&#039;),&lt;br /&gt;
      cargoQuery(&#039;Mutations&#039;, &#039;_pageName=Page,Color&#039;),&lt;br /&gt;
      cargoQuery(&#039;Weathers&#039;, &#039;_pageName=Page,Color&#039;),&lt;br /&gt;
      cargoQuery(&#039;Shovels&#039;, &#039;_pageName=Page,Color&#039;)&lt;br /&gt;
    ]).then(function (groups) {&lt;br /&gt;
      var colorMap = Object.assign({}, entityColors);&lt;br /&gt;
&lt;br /&gt;
      groups[0].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        var color = rarityColors[normalizeTitle(data.Rarity)];&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[1].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        var color = rarityColors[normalizeTitle(data.Rarity)];&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[2].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; data.Color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = data.Color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[3].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; data.Color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = data.Color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[4].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; data.Color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = data.Color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      Object.keys(mutationColors).forEach(function (title) {&lt;br /&gt;
        colorMap[title] = mutationColors[title];&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      return colorMap;&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyLinkColors(root, colorMap) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var links = [];&lt;br /&gt;
    if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;a[href]&#039;)) {&lt;br /&gt;
      links.push(scope);&lt;br /&gt;
    }&lt;br /&gt;
    links = links.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;a[href]&#039;)));&lt;br /&gt;
&lt;br /&gt;
    links.forEach(function (link) {&lt;br /&gt;
      var color = colorMap[titleFromLink(link)];&lt;br /&gt;
      if (!color || link.classList.contains(&#039;new&#039;)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      link.classList.add(&#039;hw-colored-link&#039;);&lt;br /&gt;
      link.style.setProperty(&#039;--hw-link-color&#039;, color);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyMutationRows(root, colorMap) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var tables = [];&lt;br /&gt;
    if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) {&lt;br /&gt;
      tables.push(scope);&lt;br /&gt;
    }&lt;br /&gt;
    tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;)));&lt;br /&gt;
&lt;br /&gt;
    tables.forEach(function (table) {&lt;br /&gt;
      var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;));&lt;br /&gt;
      if (!headers.length &amp;amp;&amp;amp; table.rows.length) {&lt;br /&gt;
        headers = Array.prototype.slice.call(table.rows[0].cells);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var mutationIndex = headers.findIndex(function (header) {&lt;br /&gt;
        return header.textContent.trim().toLowerCase() === &#039;mutation&#039; ||&lt;br /&gt;
          header.classList.contains(&#039;field_Mutation&#039;);&lt;br /&gt;
      });&lt;br /&gt;
      if (mutationIndex &amp;lt; 0) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      Array.prototype.slice.call(table.tBodies).forEach(function (body) {&lt;br /&gt;
        Array.prototype.slice.call(body.rows).forEach(function (row) {&lt;br /&gt;
          var mutationCell = row.cells[mutationIndex];&lt;br /&gt;
          var mutationLink = mutationCell ? mutationCell.querySelector(&#039;a[href]&#039;) : null;&lt;br /&gt;
          var color = mutationLink ? colorMap[titleFromLink(mutationLink)] : &#039;&#039;;&lt;br /&gt;
          if (!mutationCell || !color) {&lt;br /&gt;
            return;&lt;br /&gt;
          }&lt;br /&gt;
          row.classList.add(&#039;hw-mutation-row&#039;);&lt;br /&gt;
          row.style.setProperty(&#039;--hw-mutation-row-color&#039;, color);&lt;br /&gt;
          mutationCell.classList.add(&#039;hw-mutation-item&#039;);&lt;br /&gt;
        });&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyShovelRows(root, colorMap) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var tables = [];&lt;br /&gt;
    if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) {&lt;br /&gt;
      tables.push(scope);&lt;br /&gt;
    }&lt;br /&gt;
    tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;)));&lt;br /&gt;
&lt;br /&gt;
    tables.forEach(function (table) {&lt;br /&gt;
      var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;));&lt;br /&gt;
      if (!headers.length &amp;amp;&amp;amp; table.rows.length) {&lt;br /&gt;
        headers = Array.prototype.slice.call(table.rows[0].cells);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var shovelIndex = headers.findIndex(function (header) {&lt;br /&gt;
        return header.textContent.trim().toLowerCase() === &#039;shovel&#039; ||&lt;br /&gt;
          header.classList.contains(&#039;field_Shovel&#039;);&lt;br /&gt;
      });&lt;br /&gt;
      if (shovelIndex &amp;lt; 0) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      Array.prototype.slice.call(table.tBodies).forEach(function (body) {&lt;br /&gt;
        Array.prototype.slice.call(body.rows).forEach(function (row) {&lt;br /&gt;
          var shovelCell = row.cells[shovelIndex];&lt;br /&gt;
          var shovelLink = shovelCell ? shovelCell.querySelector(&#039;a[href]&#039;) : null;&lt;br /&gt;
          var color = shovelLink ? colorMap[titleFromLink(shovelLink)] : &#039;&#039;;&lt;br /&gt;
          if (!shovelCell || !color) {&lt;br /&gt;
            return;&lt;br /&gt;
          }&lt;br /&gt;
          row.classList.add(&#039;hw-shovel-row&#039;);&lt;br /&gt;
          row.style.setProperty(&#039;--hw-shovel-row-color&#039;, color);&lt;br /&gt;
          shovelCell.classList.add(&#039;hw-shovel-item&#039;);&lt;br /&gt;
        });&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  var immediateColorMap = Object.assign({}, entityColors);&lt;br /&gt;
  Object.keys(mutationColors).forEach(function (title) {&lt;br /&gt;
    immediateColorMap[title] = mutationColors[title];&lt;br /&gt;
  });&lt;br /&gt;
  Object.keys(worldColors).forEach(function (title) {&lt;br /&gt;
    immediateColorMap[title] = worldColors[title];&lt;br /&gt;
  });&lt;br /&gt;
  Object.keys(shovelColors).forEach(function (title) {&lt;br /&gt;
    immediateColorMap[title] = shovelColors[title];&lt;br /&gt;
  });&lt;br /&gt;
  var colorMapPromise = buildColorMap();&lt;br /&gt;
&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
    var root = $content &amp;amp;&amp;amp; $content[0] ? $content[0] : document;&lt;br /&gt;
    applyMutationRows(root, immediateColorMap);&lt;br /&gt;
    applyShovelRows(root, immediateColorMap);&lt;br /&gt;
    applyLinkColors(root, immediateColorMap);&lt;br /&gt;
    colorMapPromise.then(function (colorMap) {&lt;br /&gt;
      applyMutationRows(root, colorMap);&lt;br /&gt;
      applyShovelRows(root, colorMap);&lt;br /&gt;
      applyLinkColors(root, colorMap);&lt;br /&gt;
    });&lt;br /&gt;
  });&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki hierarchical Citizen popout menu */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  var groups = [&lt;br /&gt;
    {&lt;br /&gt;
      key: &#039;herb&#039;,&lt;br /&gt;
      title: &#039;Herb&#039;,&lt;br /&gt;
      note: &#039;Harvesting &amp;amp; cultivation&#039;,&lt;br /&gt;
      glyph: &#039;草&#039;,&lt;br /&gt;
      pages: [&#039;Herbs&#039;, &#039;Mutations&#039;, &#039;Crystals&#039;, &#039;Shovels&#039;]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
      key: &#039;world&#039;,&lt;br /&gt;
      title: &#039;World&#039;,&lt;br /&gt;
      note: &#039;Places, people &amp;amp; conditions&#039;,&lt;br /&gt;
      glyph: &#039;界&#039;,&lt;br /&gt;
      pages: [&#039;Weather&#039;, &#039;Locations&#039;, &#039;NPCs&#039;]&lt;br /&gt;
    }&lt;br /&gt;
  ];&lt;br /&gt;
&lt;br /&gt;
  function directLink(item) {&lt;br /&gt;
    var children = item ? item.children : null;&lt;br /&gt;
    var index;&lt;br /&gt;
    if (!children) {&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
    for (index = 0; index &amp;lt; children.length; index += 1) {&lt;br /&gt;
      if (children[index].tagName === &#039;A&#039;) {&lt;br /&gt;
        return children[index];&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    return null;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function linkTitle(link) {&lt;br /&gt;
    var path;&lt;br /&gt;
    if (!link) {&lt;br /&gt;
      return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    try {&lt;br /&gt;
      path = new URL(link.href, location.href).pathname.split(&#039;/&#039;).pop() || &#039;&#039;;&lt;br /&gt;
      return decodeURIComponent(path).replace(/_/g, &#039; &#039;);&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      return (link.textContent || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function closeOtherGroups(root, keep) {&lt;br /&gt;
    Array.prototype.slice.call(root.querySelectorAll(&#039;.hw-menu-group.is-open&#039;)).forEach(function (group) {&lt;br /&gt;
      if (group !== keep) {&lt;br /&gt;
        group.classList.remove(&#039;is-open&#039;);&lt;br /&gt;
        group.querySelector(&#039;.hw-menu-trigger&#039;).setAttribute(&#039;aria-expanded&#039;, &#039;false&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function createGroup(config, items, root) {&lt;br /&gt;
    var group = document.createElement(&#039;li&#039;);&lt;br /&gt;
    var trigger = document.createElement(&#039;button&#039;);&lt;br /&gt;
    var glyph = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var copy = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var title = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var note = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var chevron = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var submenu = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    var closeTimer = null;&lt;br /&gt;
&lt;br /&gt;
    function cancelClose() {&lt;br /&gt;
      if (closeTimer !== null) {&lt;br /&gt;
        window.clearTimeout(closeTimer);&lt;br /&gt;
        closeTimer = null;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function closeGroup() {&lt;br /&gt;
      group.classList.remove(&#039;is-open&#039;);&lt;br /&gt;
      trigger.setAttribute(&#039;aria-expanded&#039;, &#039;false&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleClose() {&lt;br /&gt;
      cancelClose();&lt;br /&gt;
      closeTimer = window.setTimeout(function () {&lt;br /&gt;
        closeTimer = null;&lt;br /&gt;
        if (!group.matches(&#039;:hover&#039;) &amp;amp;&amp;amp; !group.contains(document.activeElement)) {&lt;br /&gt;
          closeGroup();&lt;br /&gt;
        }&lt;br /&gt;
      }, 240);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    group.className = &#039;hw-menu-group hw-menu-group--&#039; + config.key;&lt;br /&gt;
    trigger.type = &#039;button&#039;;&lt;br /&gt;
    trigger.className = &#039;hw-menu-trigger&#039;;&lt;br /&gt;
    trigger.setAttribute(&#039;aria-expanded&#039;, &#039;false&#039;);&lt;br /&gt;
    trigger.setAttribute(&#039;aria-haspopup&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
    glyph.className = &#039;hw-menu-glyph&#039;;&lt;br /&gt;
    glyph.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
    glyph.textContent = config.glyph;&lt;br /&gt;
&lt;br /&gt;
    copy.className = &#039;hw-menu-trigger-copy&#039;;&lt;br /&gt;
    title.className = &#039;hw-menu-trigger-title&#039;;&lt;br /&gt;
    title.textContent = config.title;&lt;br /&gt;
    note.className = &#039;hw-menu-trigger-note&#039;;&lt;br /&gt;
    note.textContent = config.note;&lt;br /&gt;
    copy.appendChild(title);&lt;br /&gt;
    copy.appendChild(note);&lt;br /&gt;
&lt;br /&gt;
    chevron.className = &#039;hw-menu-chevron&#039;;&lt;br /&gt;
    chevron.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
    chevron.textContent = &#039;›&#039;;&lt;br /&gt;
&lt;br /&gt;
    trigger.appendChild(glyph);&lt;br /&gt;
    trigger.appendChild(copy);&lt;br /&gt;
    trigger.appendChild(chevron);&lt;br /&gt;
&lt;br /&gt;
    submenu.className = &#039;hw-menu-submenu&#039;;&lt;br /&gt;
    submenu.setAttribute(&#039;aria-label&#039;, config.title + &#039; pages&#039;);&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
      submenu.appendChild(item);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    trigger.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      var willOpen = !group.classList.contains(&#039;is-open&#039;);&lt;br /&gt;
      cancelClose();&lt;br /&gt;
      closeOtherGroups(root, group);&lt;br /&gt;
      group.classList.toggle(&#039;is-open&#039;, willOpen);&lt;br /&gt;
      trigger.setAttribute(&#039;aria-expanded&#039;, willOpen ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
      cancelClose();&lt;br /&gt;
      closeOtherGroups(root, group);&lt;br /&gt;
      group.classList.add(&#039;is-open&#039;);&lt;br /&gt;
      trigger.setAttribute(&#039;aria-expanded&#039;, &#039;true&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;mouseleave&#039;, scheduleClose);&lt;br /&gt;
    submenu.addEventListener(&#039;mouseenter&#039;, cancelClose);&lt;br /&gt;
    submenu.addEventListener(&#039;mouseleave&#039;, scheduleClose);&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;focusout&#039;, function (event) {&lt;br /&gt;
      if (!group.contains(event.relatedTarget)) {&lt;br /&gt;
        cancelClose();&lt;br /&gt;
        closeGroup();&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    group.appendChild(trigger);&lt;br /&gt;
    group.appendChild(submenu);&lt;br /&gt;
    return group;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function enhanceMenu() {&lt;br /&gt;
    var portlet = document.querySelector(&#039;#p-HarvestWiki&#039;);&lt;br /&gt;
    var list;&lt;br /&gt;
    var items;&lt;br /&gt;
    var byTitle = {};&lt;br /&gt;
    var newList;&lt;br /&gt;
    var complete;&lt;br /&gt;
&lt;br /&gt;
    if (!portlet || portlet.classList.contains(&#039;hw-menu-ready&#039;)) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    list = portlet.querySelector(&#039;.citizen-menu__content &amp;gt; ul&#039;);&lt;br /&gt;
    if (!list) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items = Array.prototype.slice.call(list.children);&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
      var link = directLink(item);&lt;br /&gt;
      var title = linkTitle(link);&lt;br /&gt;
      if (title) {&lt;br /&gt;
        byTitle[title.toLowerCase()] = item;&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    complete = groups.every(function (config) {&lt;br /&gt;
      return config.pages.every(function (page) {&lt;br /&gt;
        return Boolean(byTitle[page.toLowerCase()]);&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
    if (!complete) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    newList = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    newList.className = &#039;hw-menu-root&#039;;&lt;br /&gt;
    groups.forEach(function (config) {&lt;br /&gt;
      var groupItems = config.pages.map(function (page) {&lt;br /&gt;
        return byTitle[page.toLowerCase()];&lt;br /&gt;
      });&lt;br /&gt;
      newList.appendChild(createGroup(config, groupItems, newList));&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    list.replaceWith(newList);&lt;br /&gt;
    portlet.classList.add(&#039;hw-menu-ready&#039;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initMenu() {&lt;br /&gt;
    enhanceMenu();&lt;br /&gt;
    new MutationObserver(enhanceMenu).observe(document.body, {&lt;br /&gt;
      childList: true,&lt;br /&gt;
      subtree: true&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
      var root;&lt;br /&gt;
      if (event.key !== &#039;Escape&#039;) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      root = document.querySelector(&#039;.hw-menu-root&#039;);&lt;br /&gt;
      if (root) {&lt;br /&gt;
        closeOtherGroups(root, null);&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initMenu, { once: true });&lt;br /&gt;
  } else {&lt;br /&gt;
    initMenu();&lt;br /&gt;
  }&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki automatic page release history */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function addHistoryToNewPage() {&lt;br /&gt;
    var action = mw.config.get(&#039;wgAction&#039;);&lt;br /&gt;
    var articleId = Number(mw.config.get(&#039;wgArticleId&#039;) || 0);&lt;br /&gt;
    var namespace = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
    var pageName = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;);&lt;br /&gt;
    var textbox;&lt;br /&gt;
    var marker;&lt;br /&gt;
&lt;br /&gt;
    if (action !== &#039;edit&#039; || articleId !== 0 || namespace !== 0 || /\/Change_History$/.test(pageName)) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    textbox = document.getElementById(&#039;wpTextbox1&#039;);&lt;br /&gt;
    if (!textbox || /\{\{\s*Change History\b/i.test(textbox.value)) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    marker = &#039;\n\n== Change History ==\n{{Change History|{{&#039; + &#039;subst:Current version}}|{{&#039; + &#039;subst:Current version page}}}}\n&#039;;&lt;br /&gt;
    textbox.value = textbox.value.replace(/\s+$/, &#039;&#039;) + marker;&lt;br /&gt;
    textbox.dispatchEvent(new Event(&#039;input&#039;, { bubbles: true }));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function enableHistoryEditorTools() {&lt;br /&gt;
    var userName = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
    var probablyEditable = mw.config.get(&#039;wgIsProbablyEditable&#039;);&lt;br /&gt;
    var releases;&lt;br /&gt;
&lt;br /&gt;
    if (!userName || probablyEditable === false) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    releases = document.querySelectorAll(&#039;.hw-change-history-release&#039;);&lt;br /&gt;
    if (!releases.length) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;hw-history-tools-enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
    releases.forEach(function (release) {&lt;br /&gt;
      var addLink = release.querySelector(&#039;.hw-history-add a, a.hw-history-add&#039;);&lt;br /&gt;
      var version = String(release.getAttribute(&#039;data-current-version&#039;) || &#039;&#039;).trim();&lt;br /&gt;
      var series = String(release.getAttribute(&#039;data-current-series&#039;) || &#039;&#039;).trim();&lt;br /&gt;
      var historyPage = String(release.getAttribute(&#039;data-history-page&#039;) || &#039;&#039;).trim();&lt;br /&gt;
      var params;&lt;br /&gt;
&lt;br /&gt;
      if (!addLink || !version || !series || !historyPage) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      params = new URLSearchParams();&lt;br /&gt;
      params.set(&#039;title&#039;, historyPage);&lt;br /&gt;
      params.set(&#039;action&#039;, &#039;edit&#039;);&lt;br /&gt;
      params.set(&#039;section&#039;, &#039;new&#039;);&lt;br /&gt;
      params.set(&#039;preload&#039;, &#039;Template:Change History/entry&#039;);&lt;br /&gt;
      params.set(&#039;preloadtitle&#039;, &#039;Version &#039; + version);&lt;br /&gt;
      params.append(&#039;preloadparams[]&#039;, version);&lt;br /&gt;
      params.append(&#039;preloadparams[]&#039;, series);&lt;br /&gt;
      addLink.href = mw.util.wikiScript(&#039;index&#039;) + &#039;?&#039; + params.toString();&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initVersionHistory() {&lt;br /&gt;
    addHistoryToNewPage();&lt;br /&gt;
    enableHistoryEditorTools();&lt;br /&gt;
    window.setTimeout(addHistoryToNewPage, 350);&lt;br /&gt;
    window.setTimeout(enableHistoryEditorTools, 350);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.editform&#039;).add(function () {&lt;br /&gt;
      window.setTimeout(addHistoryToNewPage, 100);&lt;br /&gt;
    });&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
      window.setTimeout(enableHistoryEditorTools, 100);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initVersionHistory, { once: true });&lt;br /&gt;
  } else {&lt;br /&gt;
    initVersionHistory();&lt;br /&gt;
  }&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki version snapshots for source edits, Page Forms, and administrators */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  var MARKER_KEY = &#039;hwVersionHistoryPending&#039;;&lt;br /&gt;
  var ADMIN_PAGE = &#039;HarvestWiki:Version control&#039;;&lt;br /&gt;
  var AUTO_START = &#039;&amp;lt;!-- HW-AUTO-START:&#039;;&lt;br /&gt;
&lt;br /&gt;
  function api() {&lt;br /&gt;
    return new mw.Api();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function isSignedInEditor() {&lt;br /&gt;
    return Boolean(mw.config.get(&#039;wgUserName&#039;)) &amp;amp;&amp;amp; mw.config.get(&#039;wgIsProbablyEditable&#039;) !== false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function isVersionAdmin() {&lt;br /&gt;
    var groups = mw.config.get(&#039;wgUserGroups&#039;) || [];&lt;br /&gt;
    return groups.indexOf(&#039;sysop&#039;) !== -1 || groups.indexOf(&#039;interface-admin&#039;) !== -1 || groups.indexOf(&#039;bureaucrat&#039;) !== -1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function normalTitle(title) {&lt;br /&gt;
    return String(title || &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function seriesFor(version) {&lt;br /&gt;
    var parts = String(version || &#039;&#039;).split(&#039;.&#039;);&lt;br /&gt;
    return parts.length === 3 ? parts[0] + &#039;.&#039; + parts[1] : &#039;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function validVersion(version) {&lt;br /&gt;
    return /^\d+\.\d{2}\.\d+$/.test(String(version || &#039;&#039;).trim());&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function getPageText(title) {&lt;br /&gt;
    return api().get({&lt;br /&gt;
      action: &#039;query&#039;,&lt;br /&gt;
      prop: &#039;revisions&#039;,&lt;br /&gt;
      titles: title,&lt;br /&gt;
      rvprop: &#039;content&#039;,&lt;br /&gt;
      rvslots: &#039;main&#039;,&lt;br /&gt;
      formatversion: 2&lt;br /&gt;
    }).then(function (data) {&lt;br /&gt;
      var page = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages &amp;amp;&amp;amp; data.query.pages[0];&lt;br /&gt;
      var revision = page &amp;amp;&amp;amp; page.revisions &amp;amp;&amp;amp; page.revisions[0];&lt;br /&gt;
      var slots = revision &amp;amp;&amp;amp; revision.slots;&lt;br /&gt;
      return {&lt;br /&gt;
        exists: Boolean(page &amp;amp;&amp;amp; !page.missing),&lt;br /&gt;
        text: slots &amp;amp;&amp;amp; slots.main &amp;amp;&amp;amp; typeof slots.main.content === &#039;string&#039; ? slots.main.content : &#039;&#039;&lt;br /&gt;
      };&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function savePage(title, text, summary, createOnly) {&lt;br /&gt;
    var params = {&lt;br /&gt;
      action: &#039;edit&#039;,&lt;br /&gt;
      title: title,&lt;br /&gt;
      text: text,&lt;br /&gt;
      summary: summary,&lt;br /&gt;
      watchlist: &#039;nochange&#039;,&lt;br /&gt;
      formatversion: 2&lt;br /&gt;
    };&lt;br /&gt;
    if (createOnly) {&lt;br /&gt;
      params.createonly = 1;&lt;br /&gt;
    }&lt;br /&gt;
    return api().postWithToken(&#039;csrf&#039;, params);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function readCurrentVersion() {&lt;br /&gt;
    return Promise.all([&lt;br /&gt;
      getPageText(&#039;Template:Current version&#039;),&lt;br /&gt;
      getPageText(&#039;Template:Current version page&#039;)&lt;br /&gt;
    ]).then(function (pages) {&lt;br /&gt;
      var versionMatch = pages[0].text.match(/\d+\.\d{2}\.\d+/);&lt;br /&gt;
      var seriesMatch = pages[1].text.match(/\d+\.\d{2}/);&lt;br /&gt;
      var version = versionMatch ? versionMatch[0] : &#039;&#039;;&lt;br /&gt;
      var series = seriesMatch ? seriesMatch[0] : seriesFor(version);&lt;br /&gt;
      if (!validVersion(version) || !series) {&lt;br /&gt;
        throw new Error(&#039;The current version templates do not contain a valid x.xx.x version.&#039;);&lt;br /&gt;
      }&lt;br /&gt;
      return { version: version, series: series };&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function cleanFieldName(name) {&lt;br /&gt;
    var raw = String(name || &#039;&#039;);&lt;br /&gt;
    var matches = raw.match(/\[([^\]]+)\]/g);&lt;br /&gt;
    var last;&lt;br /&gt;
    if (matches &amp;amp;&amp;amp; matches.length) {&lt;br /&gt;
      last = matches[matches.length - 1].replace(/^\[|\]$/g, &#039;&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
      last = raw;&lt;br /&gt;
    }&lt;br /&gt;
    last = last.replace(/^pf_free_text$/i, &#039;Article content&#039;)&lt;br /&gt;
      .replace(/^free_text$/i, &#039;Article content&#039;)&lt;br /&gt;
      .replace(/_/g, &#039; &#039;)&lt;br /&gt;
      .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
      .trim();&lt;br /&gt;
    return last;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function labelFor(key) {&lt;br /&gt;
    return String(key || &#039;&#039;)&lt;br /&gt;
      .replace(/[_-]+/g, &#039; &#039;)&lt;br /&gt;
      .replace(/\b\w/g, function (letter) { return letter.toUpperCase(); });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function normalizeValue(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value).replace(/\r\n?/g, &#039;\n&#039;).replace(/[ \t]+/g, &#039; &#039;).trim();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function collectFormSnapshot(form) {&lt;br /&gt;
    var values = {};&lt;br /&gt;
    var ignored = /^(wp(EditToken|Starttime|Edittime|AutoSummary|Summary|Section|UltimateParam)|title|action|format|oldid)$/i;&lt;br /&gt;
    Array.prototype.forEach.call(form.querySelectorAll(&#039;input[name], select[name], textarea[name]&#039;), function (field) {&lt;br /&gt;
      var type = String(field.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
      var name = String(field.name || &#039;&#039;);&lt;br /&gt;
      var key;&lt;br /&gt;
      var value;&lt;br /&gt;
      if (!name || field.disabled || ignored.test(name) || /submit|button|reset|image|file/.test(type)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if ((type === &#039;checkbox&#039; || type === &#039;radio&#039;) &amp;amp;&amp;amp; !field.checked) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if (type === &#039;hidden&#039; &amp;amp;&amp;amp; name.indexOf(&#039;[&#039;) === -1 &amp;amp;&amp;amp; name !== &#039;pf_free_text&#039;) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      key = cleanFieldName(name);&lt;br /&gt;
      if (!key || /token|timestamp|summary/i.test(key)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if (field.tagName === &#039;SELECT&#039; &amp;amp;&amp;amp; field.multiple) {&lt;br /&gt;
        value = Array.prototype.filter.call(field.options, function (option) { return option.selected; })&lt;br /&gt;
          .map(function (option) { return option.value; }).join(&#039;; &#039;);&lt;br /&gt;
      } else {&lt;br /&gt;
        value = field.value;&lt;br /&gt;
      }&lt;br /&gt;
      value = normalizeValue(value);&lt;br /&gt;
      if (Object.prototype.hasOwnProperty.call(values, key) &amp;amp;&amp;amp; value &amp;amp;&amp;amp; values[key] !== value) {&lt;br /&gt;
        values[key] = normalizeValue(values[key] + &#039;; &#039; + value);&lt;br /&gt;
      } else if (!Object.prototype.hasOwnProperty.call(values, key) || value) {&lt;br /&gt;
        values[key] = value;&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
    return values;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function snapshotFromWikitext(text) {&lt;br /&gt;
    var source = String(text || &#039;&#039;);&lt;br /&gt;
    var snapshot = {};&lt;br /&gt;
    var match;&lt;br /&gt;
    var paramPattern = /^\s*\|\s*([^=\n]+?)\s*=\s*([\s\S]*?)(?=^\s*\|\s*[^=\n]+?\s*=|^\s*}}|\z)/gm;&lt;br /&gt;
    var cleaned = source.replace(/==\s*Change History\s*==[\s\S]*$/i, &#039;&#039;).trim();&lt;br /&gt;
    while ((match = paramPattern.exec(cleaned))) {&lt;br /&gt;
      snapshot[cleanFieldName(match[1])] = normalizeValue(match[2]);&lt;br /&gt;
    }&lt;br /&gt;
    if (!Object.keys(snapshot).length) {&lt;br /&gt;
      snapshot[&#039;Article content&#039;] = normalizeValue(cleaned);&lt;br /&gt;
    }&lt;br /&gt;
    return snapshot;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function snapshotForForm(form) {&lt;br /&gt;
    var textbox = form.querySelector(&#039;#wpTextbox1, textarea[name=&amp;quot;wpTextbox1&amp;quot;]&#039;);&lt;br /&gt;
    if (textbox &amp;amp;&amp;amp; String(mw.config.get(&#039;wgAction&#039;)) === &#039;edit&#039;) {&lt;br /&gt;
      return snapshotFromWikitext(textbox.value);&lt;br /&gt;
    }&lt;br /&gt;
    return collectFormSnapshot(form);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function sameSnapshot(a, b) {&lt;br /&gt;
    var aKeys = Object.keys(a || {}).sort();&lt;br /&gt;
    var bKeys = Object.keys(b || {}).sort();&lt;br /&gt;
    if (aKeys.length !== bKeys.length) {&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
    return aKeys.every(function (key, index) {&lt;br /&gt;
      return key === bKeys[index] &amp;amp;&amp;amp; normalizeValue(a[key]) === normalizeValue(b[key]);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function encodeSnapshot(snapshot) {&lt;br /&gt;
    return btoa(unescape(encodeURIComponent(JSON.stringify(snapshot || {}))));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function decodeSnapshot(encoded) {&lt;br /&gt;
    try {&lt;br /&gt;
      return JSON.parse(decodeURIComponent(escape(atob(encoded))));&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      return {};&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function historyBlocks(text) {&lt;br /&gt;
    var blocks = [];&lt;br /&gt;
    var pattern = /&amp;lt;!-- HW-AUTO-START:([0-9]+\.[0-9]{2}\.[0-9]+) --&amp;gt;([\s\S]*?)&amp;lt;!-- HW-AUTO-END:\1 --&amp;gt;/g;&lt;br /&gt;
    var match;&lt;br /&gt;
    var snapshotMatch;&lt;br /&gt;
    while ((match = pattern.exec(String(text || &#039;&#039;)))) {&lt;br /&gt;
      snapshotMatch = match[2].match(/&amp;lt;!-- HW-SNAPSHOT:([A-Za-z0-9+/=]+) --&amp;gt;/);&lt;br /&gt;
      blocks.push({&lt;br /&gt;
        version: match[1],&lt;br /&gt;
        full: match[0],&lt;br /&gt;
        index: match.index,&lt;br /&gt;
        created: /\*\s*Page created\./i.test(match[2]),&lt;br /&gt;
        snapshot: snapshotMatch ? decodeSnapshot(snapshotMatch[1]) : {}&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    return blocks;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function shortValue(value) {&lt;br /&gt;
    var clean = normalizeValue(value);&lt;br /&gt;
    if (clean.length &amp;gt; 120) {&lt;br /&gt;
      clean = clean.slice(0, 117) + &#039;...&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    return clean;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function changeMessages(before, after) {&lt;br /&gt;
    var keys = {};&lt;br /&gt;
    var messages = [];&lt;br /&gt;
    Object.keys(before || {}).forEach(function (key) { keys[key] = true; });&lt;br /&gt;
    Object.keys(after || {}).forEach(function (key) { keys[key] = true; });&lt;br /&gt;
    Object.keys(keys).sort().forEach(function (key) {&lt;br /&gt;
      var oldValue = normalizeValue((before || {})[key]);&lt;br /&gt;
      var newValue = normalizeValue((after || {})[key]);&lt;br /&gt;
      var label = labelFor(key);&lt;br /&gt;
      if (oldValue === newValue) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if (!oldValue &amp;amp;&amp;amp; newValue) {&lt;br /&gt;
        messages.push(&#039;Added &#039; + label + &#039;: “&#039; + shortValue(newValue) + &#039;”.&#039;);&lt;br /&gt;
      } else if (oldValue &amp;amp;&amp;amp; !newValue) {&lt;br /&gt;
        messages.push(&#039;Removed &#039; + label + &#039; (previously “&#039; + shortValue(oldValue) + &#039;”).&#039;);&lt;br /&gt;
      } else if (key === &#039;Article content&#039;) {&lt;br /&gt;
        messages.push(&#039;Updated the article content.&#039;);&lt;br /&gt;
      } else {&lt;br /&gt;
        messages.push(&#039;Changed &#039; + label + &#039; from “&#039; + shortValue(oldValue) + &#039;” to “&#039; + shortValue(newValue) + &#039;”.&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
    if (messages.length &amp;gt; 12) {&lt;br /&gt;
      messages = messages.slice(0, 12).concat([&#039;Updated &#039; + (messages.length - 12) + &#039; additional fields.&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return messages;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function buildBlock(version, series, messages, snapshot) {&lt;br /&gt;
    return AUTO_START + version + &#039; --&amp;gt;\n&#039; +&lt;br /&gt;
      &#039;== Version &#039; + version + &#039; ==\n&#039; +&lt;br /&gt;
      &amp;quot;&#039;&#039;&#039;{{Version link|&amp;quot; + version + &#039;|&#039; + series + &amp;quot;}}&#039;&#039;&#039;\n&amp;quot; +&lt;br /&gt;
      messages.map(function (message) { return &#039;* &#039; + message; }).join(&#039;\n&#039;) + &#039;\n&#039; +&lt;br /&gt;
      &#039;&amp;lt;!-- HW-SNAPSHOT:&#039; + encodeSnapshot(snapshot) + &#039; --&amp;gt;\n&#039; +&lt;br /&gt;
      &#039;&amp;lt;!-- HW-AUTO-END:&#039; + version + &#039; --&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function upsertHistory(existingText, versionInfo, afterSnapshot, isNewPage, forceCreated) {&lt;br /&gt;
    var text = String(existingText || &#039;&#039;).trim();&lt;br /&gt;
    var blocks = historyBlocks(text);&lt;br /&gt;
    var current = blocks.filter(function (block) { return block.version === versionInfo.version; })[0];&lt;br /&gt;
    var previous = blocks.filter(function (block) { return block.version !== versionInfo.version &amp;amp;&amp;amp; Object.keys(block.snapshot).length; })[0];&lt;br /&gt;
    var beforeSnapshot = previous ? previous.snapshot : {};&lt;br /&gt;
    var messages;&lt;br /&gt;
    var replacement;&lt;br /&gt;
&lt;br /&gt;
    if (current &amp;amp;&amp;amp; current.created) {&lt;br /&gt;
      messages = [&#039;Page created.&#039;];&lt;br /&gt;
    } else if ((isNewPage || forceCreated) &amp;amp;&amp;amp; !blocks.length) {&lt;br /&gt;
      messages = [&#039;Page created.&#039;];&lt;br /&gt;
    } else {&lt;br /&gt;
      messages = changeMessages(beforeSnapshot, afterSnapshot);&lt;br /&gt;
      if (!messages.length) {&lt;br /&gt;
        return { changed: false, text: text };&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    replacement = buildBlock(versionInfo.version, versionInfo.series, messages, afterSnapshot);&lt;br /&gt;
    if (current) {&lt;br /&gt;
      return { changed: replacement !== current.full, text: text.replace(current.full, replacement) };&lt;br /&gt;
    }&lt;br /&gt;
    return { changed: true, text: replacement + (text ? &#039;\n\n&#039; + text : &#039;&#039;) };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function historyMarkup(versionInfo) {&lt;br /&gt;
    return &#039;\n\n== Change History ==\n{{Change History|&#039; + versionInfo.version + &#039;|&#039; + versionInfo.series + &#039;}}\n&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function ensureHistorySection(title, versionInfo) {&lt;br /&gt;
    return getPageText(title).then(function (page) {&lt;br /&gt;
      if (!page.exists || /{{\s*Change History\b/i.test(page.text)) {&lt;br /&gt;
        return false;&lt;br /&gt;
      }&lt;br /&gt;
      return savePage(title, page.text.replace(/\s+$/, &#039;&#039;) + historyMarkup(versionInfo), &#039;Add version change history section&#039;).then(function () {&lt;br /&gt;
        return true;&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function recordAutomaticHistory(title, versionInfo, afterSnapshot, isNewPage, forceCreated) {&lt;br /&gt;
    var historyTitle = title + &#039;/Change History&#039;;&lt;br /&gt;
    return getPageText(historyTitle).then(function (historyPage) {&lt;br /&gt;
      var updated = upsertHistory(historyPage.text, versionInfo, afterSnapshot, isNewPage, forceCreated);&lt;br /&gt;
      if (!updated.changed) {&lt;br /&gt;
        return false;&lt;br /&gt;
      }&lt;br /&gt;
      return savePage(historyTitle, updated.text + &#039;\n&#039;, &#039;Record automatic change history for &#039; + versionInfo.version).then(function () {&lt;br /&gt;
        return true;&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function setPendingEnsure(title, versionInfo) {&lt;br /&gt;
    try {&lt;br /&gt;
      sessionStorage.setItem(MARKER_KEY, JSON.stringify({&lt;br /&gt;
        title: title,&lt;br /&gt;
        version: versionInfo.version,&lt;br /&gt;
        series: versionInfo.series,&lt;br /&gt;
        time: Date.now()&lt;br /&gt;
      }));&lt;br /&gt;
    } catch (error) {}&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function completePendingEnsure() {&lt;br /&gt;
    var raw;&lt;br /&gt;
    var pending;&lt;br /&gt;
    try {&lt;br /&gt;
      raw = sessionStorage.getItem(MARKER_KEY);&lt;br /&gt;
      pending = raw ? JSON.parse(raw) : null;&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      pending = null;&lt;br /&gt;
    }&lt;br /&gt;
    if (!pending || !pending.title || Date.now() - Number(pending.time || 0) &amp;gt; 300000) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    if (normalTitle(mw.config.get(&#039;wgPageName&#039;)) !== normalTitle(pending.title) || Number(mw.config.get(&#039;wgArticleId&#039;) || 0) === 0) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    sessionStorage.removeItem(MARKER_KEY);&lt;br /&gt;
    ensureHistorySection(pending.title, { version: pending.version, series: pending.series }).then(function (changed) {&lt;br /&gt;
      if (changed) {&lt;br /&gt;
        mw.notify(&#039;The page was saved and its change-history section was added.&#039;, { type: &#039;success&#039; });&lt;br /&gt;
        window.setTimeout(function () { window.location.reload(); }, 700);&lt;br /&gt;
      }&lt;br /&gt;
    }).catch(function (error) {&lt;br /&gt;
      mw.notify(&#039;The page saved, but its change-history section could not be added: &#039; + error.message, { type: &#039;error&#039; });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initTrackedForm() {&lt;br /&gt;
    var namespace = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
    var specialFormMatch = String(window.location.pathname || &#039;&#039;).match(/\/Special:FormEdit\/[^/]+\/(.+)$/i);&lt;br /&gt;
    var form;&lt;br /&gt;
    var initialSnapshot;&lt;br /&gt;
    var isNewPage;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;) || (!specialFormMatch &amp;amp;&amp;amp; namespace !== 0) || /\/Change_History$/.test(String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;))) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    form = document.querySelector(&#039;form#pfForm, form#sfForm, form#editform&#039;);&lt;br /&gt;
    if (!form || form.dataset.hwVersionTrackingReady === &#039;1&#039;) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    form.dataset.hwVersionTrackingReady = &#039;1&#039;;&lt;br /&gt;
    initialSnapshot = snapshotForForm(form);&lt;br /&gt;
    isNewPage = Boolean(specialFormMatch) || Number(mw.config.get(&#039;wgArticleId&#039;) || 0) === 0;&lt;br /&gt;
    pageTitle = specialFormMatch ? normalTitle(decodeURIComponent(specialFormMatch[1])) : normalTitle(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
&lt;br /&gt;
    form.addEventListener(&#039;submit&#039;, function (event) {&lt;br /&gt;
      var submitter = event.submitter;&lt;br /&gt;
      var outgoing;&lt;br /&gt;
      var submitName = submitter ? String(submitter.name || submitter.value || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
      if (form.dataset.hwVersionSubmitting === &#039;1&#039; || /preview|diff|changes|cancel/i.test(submitName)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      outgoing = snapshotForForm(form);&lt;br /&gt;
      if (!isNewPage &amp;amp;&amp;amp; sameSnapshot(initialSnapshot, outgoing)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      event.preventDefault();&lt;br /&gt;
      form.dataset.hwVersionSubmitting = &#039;1&#039;;&lt;br /&gt;
      mw.notify(&#039;Saving the page and recording its version history…&#039;);&lt;br /&gt;
&lt;br /&gt;
      readCurrentVersion().then(function (versionInfo) {&lt;br /&gt;
        return recordAutomaticHistory(pageTitle, versionInfo, outgoing, isNewPage, false).then(function () {&lt;br /&gt;
          setPendingEnsure(pageTitle, versionInfo);&lt;br /&gt;
        });&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        mw.notify(&#039;The page will still be saved, but automatic history failed: &#039; + error.message, { type: &#039;warn&#039; });&lt;br /&gt;
      }).then(function () {&lt;br /&gt;
        var saveField = form.querySelector(&#039;input[type=&amp;quot;hidden&amp;quot;][name=&amp;quot;wpSave&amp;quot;]&#039;);&lt;br /&gt;
        if (!saveField) {&lt;br /&gt;
          saveField = document.createElement(&#039;input&#039;);&lt;br /&gt;
          saveField.type = &#039;hidden&#039;;&lt;br /&gt;
          saveField.name = &#039;wpSave&#039;;&lt;br /&gt;
          saveField.value = &#039;Save page&#039;;&lt;br /&gt;
          form.appendChild(saveField);&lt;br /&gt;
        }&lt;br /&gt;
        HTMLFormElement.prototype.submit.call(form);&lt;br /&gt;
      });&lt;br /&gt;
    }, true);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function allMainPages() {&lt;br /&gt;
    var results = [];&lt;br /&gt;
    function next(apcontinue) {&lt;br /&gt;
      var params = { action: &#039;query&#039;, list: &#039;allpages&#039;, apnamespace: 0, aplimit: &#039;max&#039;, formatversion: 2 };&lt;br /&gt;
      if (apcontinue) {&lt;br /&gt;
        params.apcontinue = apcontinue;&lt;br /&gt;
      }&lt;br /&gt;
      return api().get(params).then(function (data) {&lt;br /&gt;
        results = results.concat((data.query &amp;amp;&amp;amp; data.query.allpages) || []);&lt;br /&gt;
        return data.continue &amp;amp;&amp;amp; data.continue.apcontinue ? next(data.continue.apcontinue) : results;&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    return next();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function backfillOne(title, versionInfo, forceCreated) {&lt;br /&gt;
    if (title === &#039;Change History&#039; || /\/Change History$/.test(title)) {&lt;br /&gt;
      return Promise.resolve({ title: title, skipped: true });&lt;br /&gt;
    }&lt;br /&gt;
    return getPageText(title).then(function (page) {&lt;br /&gt;
      if (!page.exists) {&lt;br /&gt;
        return { title: title, skipped: true };&lt;br /&gt;
      }&lt;br /&gt;
      var snapshot = snapshotFromWikitext(page.text);&lt;br /&gt;
      return Promise.all([&lt;br /&gt;
        ensureHistorySection(title, versionInfo),&lt;br /&gt;
        recordAutomaticHistory(title, versionInfo, snapshot, false, forceCreated)&lt;br /&gt;
      ]).then(function () {&lt;br /&gt;
        return { title: title, skipped: false };&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function runBackfill(versionInfo, forceCreated, progress) {&lt;br /&gt;
    return allMainPages().then(function (pages) {&lt;br /&gt;
      var queue = pages.map(function (page) { return normalTitle(page.title); });&lt;br /&gt;
      var done = 0;&lt;br /&gt;
      var failures = [];&lt;br /&gt;
      var workers = [];&lt;br /&gt;
&lt;br /&gt;
      function worker() {&lt;br /&gt;
        var title = queue.shift();&lt;br /&gt;
        if (!title) {&lt;br /&gt;
          return Promise.resolve();&lt;br /&gt;
        }&lt;br /&gt;
        return backfillOne(title, versionInfo, forceCreated).catch(function (error) {&lt;br /&gt;
          failures.push(title + &#039;: &#039; + error.message);&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
          done += 1;&lt;br /&gt;
          progress(done, pages.length, title, failures.length);&lt;br /&gt;
          return worker();&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      for (var i = 0; i &amp;lt; 3; i += 1) {&lt;br /&gt;
        workers.push(worker());&lt;br /&gt;
      }&lt;br /&gt;
      return Promise.all(workers).then(function () {&lt;br /&gt;
        return { total: pages.length, failures: failures };&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function currentTemplateText(version) {&lt;br /&gt;
    return &#039;&amp;lt;includeonly&amp;gt;&#039; + version + &#039;&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;Sitewide current patch version. Change this through [[HarvestWiki:Version control]]. Version numbers use x.xx.x. [[Category:Version templates]]&amp;lt;/noinclude&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function currentSeriesText(series) {&lt;br /&gt;
    return &#039;&amp;lt;includeonly&amp;gt;&#039; + series + &#039;&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;Parent release-series number for the current patch. Change this through [[HarvestWiki:Version control]]. [[Category:Version templates]]&amp;lt;/noinclude&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function releasePageText(versionInfo, title, date) {&lt;br /&gt;
    return &#039;{{Version\n|version=&#039; + versionInfo.series + &#039;\n|title=&#039; + title + &#039;\n|release date=&#039; + date + &#039;\n|current patch=&#039; + versionInfo.version + &#039;\n}}\n\n== Overview ==\nDescribe the goals and scope of this release series.\n\n== Changelogs ==\n=== &#039; + versionInfo.version + &#039; ===\n&#039; + title + &#039; — &#039; + date + &#039;\n* Add release changes here.\n\n[[Category:Versions]]\n&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function updateReleasePage(versionInfo, title, date) {&lt;br /&gt;
    var pageTitle = &#039;Version &#039; + versionInfo.series;&lt;br /&gt;
    return getPageText(pageTitle).then(function (page) {&lt;br /&gt;
      var text;&lt;br /&gt;
      if (!page.exists) {&lt;br /&gt;
        return savePage(pageTitle, releasePageText(versionInfo, title, date), &#039;Create release page for &#039; + versionInfo.version);&lt;br /&gt;
      }&lt;br /&gt;
      text = page.text;&lt;br /&gt;
      if (/\|\s*current patch\s*=/.test(text)) {&lt;br /&gt;
        text = text.replace(/(\|\s*current patch\s*=\s*)[^\n|}]*/i, &#039;$1&#039; + versionInfo.version);&lt;br /&gt;
      }&lt;br /&gt;
      if (text.indexOf(&#039;=== &#039; + versionInfo.version + &#039; ===&#039;) === -1) {&lt;br /&gt;
        text = text.replace(/\s*\[\[Category:Versions\]\]\s*$/i, &#039;&#039;) +&lt;br /&gt;
          &#039;\n\n=== &#039; + versionInfo.version + &#039; ===\n&#039; + title + &#039; — &#039; + date + &#039;\n* Add release changes here.\n\n[[Category:Versions]]\n&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      return savePage(pageTitle, text, &#039;Set current patch to &#039; + versionInfo.version);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyVersion(version, title, date) {&lt;br /&gt;
    var versionInfo = { version: version, series: seriesFor(version) };&lt;br /&gt;
    return Promise.all([&lt;br /&gt;
      savePage(&#039;Template:Current version&#039;, currentTemplateText(version), &#039;Set current version to &#039; + version),&lt;br /&gt;
      savePage(&#039;Template:Current version page&#039;, currentSeriesText(versionInfo.series), &#039;Set current version series to &#039; + versionInfo.series),&lt;br /&gt;
      updateReleasePage(versionInfo, title, date)&lt;br /&gt;
    ]).then(function () {&lt;br /&gt;
      return versionInfo;&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initVersionAdmin() {&lt;br /&gt;
    var root;&lt;br /&gt;
    var status;&lt;br /&gt;
    var versionInput;&lt;br /&gt;
    var titleInput;&lt;br /&gt;
    var dateInput;&lt;br /&gt;
    var applyButton;&lt;br /&gt;
    var backfillButton;&lt;br /&gt;
    var snapshotButton;&lt;br /&gt;
&lt;br /&gt;
    if (normalTitle(mw.config.get(&#039;wgPageName&#039;)) !== ADMIN_PAGE) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    root = document.getElementById(&#039;hw-version-control-root&#039;);&lt;br /&gt;
    if (!root) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!isVersionAdmin()) {&lt;br /&gt;
      root.innerHTML = &#039;&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Administrator access is required.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;hw-version-admin-authorized&#039;);&lt;br /&gt;
    root.innerHTML = &#039;&amp;lt;div class=&amp;quot;hw-version-admin-card&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;h2&amp;gt;Current release&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;hw-version-admin-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;label&amp;gt;Version number &amp;lt;input id=&amp;quot;hw-version-number&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;0.01.0&amp;quot; pattern=&amp;quot;\d+\.\d{2}\.\d+&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;label&amp;gt;Release title &amp;lt;input id=&amp;quot;hw-version-title&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;Early Development&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;label&amp;gt;Release date &amp;lt;input id=&amp;quot;hw-version-date&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;29 July 2026&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;hw-version-admin-actions&amp;quot;&amp;gt;&amp;lt;button id=&amp;quot;hw-version-apply&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;Apply version and create release page&amp;lt;/button&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;hw-version-admin-card&amp;quot;&amp;gt;&amp;lt;h2&amp;gt;Page history tools&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;p&amp;gt;&amp;lt;button id=&amp;quot;hw-version-backfill&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;Backfill untracked pages as created now&amp;lt;/button&amp;gt;&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;p&amp;gt;&amp;lt;button id=&amp;quot;hw-version-snapshot&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;Snapshot every page in the current version&amp;lt;/button&amp;gt;&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;p class=&amp;quot;hw-version-admin-help&amp;quot;&amp;gt;Backfill is for legacy pages. Snapshot creates a current-version baseline only where the page differs from its last stored snapshot.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div id=&amp;quot;hw-version-status&amp;quot; class=&amp;quot;hw-version-status&amp;quot; role=&amp;quot;status&amp;quot; aria-live=&amp;quot;polite&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    status = document.getElementById(&#039;hw-version-status&#039;);&lt;br /&gt;
    versionInput = document.getElementById(&#039;hw-version-number&#039;);&lt;br /&gt;
    titleInput = document.getElementById(&#039;hw-version-title&#039;);&lt;br /&gt;
    dateInput = document.getElementById(&#039;hw-version-date&#039;);&lt;br /&gt;
    applyButton = document.getElementById(&#039;hw-version-apply&#039;);&lt;br /&gt;
    backfillButton = document.getElementById(&#039;hw-version-backfill&#039;);&lt;br /&gt;
    snapshotButton = document.getElementById(&#039;hw-version-snapshot&#039;);&lt;br /&gt;
&lt;br /&gt;
    readCurrentVersion().then(function (info) {&lt;br /&gt;
      versionInput.value = info.version;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function setBusy(busy) {&lt;br /&gt;
      applyButton.disabled = busy;&lt;br /&gt;
      backfillButton.disabled = busy;&lt;br /&gt;
      snapshotButton.disabled = busy;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function progress(done, total, title, failures) {&lt;br /&gt;
      status.textContent = &#039;Processed &#039; + done + &#039; of &#039; + total + &#039;: &#039; + title + (failures ? &#039; (&#039; + failures + &#039; failed)&#039; : &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    applyButton.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      var version = versionInput.value.trim();&lt;br /&gt;
      var title = titleInput.value.trim();&lt;br /&gt;
      var date = dateInput.value.trim();&lt;br /&gt;
      if (!validVersion(version) || !title || !date) {&lt;br /&gt;
        status.textContent = &#039;Enter a version in x.xx.x format, a title, and a release date.&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      setBusy(true);&lt;br /&gt;
      status.textContent = &#039;Applying &#039; + version + &#039;…&#039;;&lt;br /&gt;
      applyVersion(version, title, date).then(function () {&lt;br /&gt;
        status.textContent = &#039;Current version is now &#039; + version + &#039;. The release page is ready.&#039;;&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        status.textContent = &#039;Version update failed: &#039; + error.message;&lt;br /&gt;
      }).then(function () { setBusy(false); });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    backfillButton.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      setBusy(true);&lt;br /&gt;
      status.textContent = &#039;Reading current version…&#039;;&lt;br /&gt;
      readCurrentVersion().then(function (info) {&lt;br /&gt;
        return runBackfill(info, true, progress);&lt;br /&gt;
      }).then(function (result) {&lt;br /&gt;
        status.textContent = &#039;Backfill complete: &#039; + result.total + &#039; pages checked, &#039; + result.failures.length + &#039; failed.&#039;;&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        status.textContent = &#039;Backfill failed: &#039; + error.message;&lt;br /&gt;
      }).then(function () { setBusy(false); });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    snapshotButton.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      setBusy(true);&lt;br /&gt;
      status.textContent = &#039;Reading current version…&#039;;&lt;br /&gt;
      readCurrentVersion().then(function (info) {&lt;br /&gt;
        return runBackfill(info, false, progress);&lt;br /&gt;
      }).then(function (result) {&lt;br /&gt;
        status.textContent = &#039;Snapshot complete: &#039; + result.total + &#039; pages checked, &#039; + result.failures.length + &#039; failed.&#039;;&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        status.textContent = &#039;Snapshot failed: &#039; + error.message;&lt;br /&gt;
      }).then(function () { setBusy(false); });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initAutomaticVersioning() {&lt;br /&gt;
    completePendingEnsure();&lt;br /&gt;
    initTrackedForm();&lt;br /&gt;
    initVersionAdmin();&lt;br /&gt;
    window.setTimeout(initTrackedForm, 500);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function startAutomaticVersioning() {&lt;br /&gt;
    if (mw.hook) {&lt;br /&gt;
      mw.hook(&#039;wikipage.editform&#039;).add(function () { window.setTimeout(initTrackedForm, 100); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
      document.addEventListener(&#039;DOMContentLoaded&#039;, initAutomaticVersioning, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
      initAutomaticVersioning();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  mw.loader.using([&#039;mediawiki.api&#039;, &#039;mediawiki.util&#039;], startAutomaticVersioning, function (error) {&lt;br /&gt;
    if (window.console &amp;amp;&amp;amp; console.error) {&lt;br /&gt;
      console.error(&#039;HarvestWiki version tracking could not load:&#039;, error);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=MediaWiki:Common.js&amp;diff=571</id>
		<title>MediaWiki:Common.js</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=MediaWiki:Common.js&amp;diff=571"/>
		<updated>2026-07-29T15:46:59Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Fix Page Forms save handoff after automatic history&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Any JavaScript here will be loaded for all users on every page load. */ /* HarvestWiki dynamic Cargo archive filters */ (function () { &#039;use strict&#039;; function initHerbLocationFilters(root) { var catalogs = []; if (root &amp;amp;&amp;amp; root.matches &amp;amp;&amp;amp; root.matches(&#039;.hw-herb-catalog&#039;)) { catalogs.push(root); } if (root &amp;amp;&amp;amp; root.querySelectorAll) { catalogs = catalogs.concat(Array.prototype.slice.call(root.querySelectorAll(&#039;.hw-herb-catalog&#039;))); } catalogs.forEach(function (catalog) { if (catalog.dataset.hwLocationFiltersReady === &#039;1&#039;) { return; } var table = catalog.querySelector(&#039;table&#039;); var filterBar = document.querySelector(&#039;.hw-herb-filter&#039;); if (!table || !filterBar) { return; } var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;)); var locationIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;location&#039;; }); if (locationIndex &amp;lt; 0) { return; } var rows = Array.prototype.slice.call(table.querySelectorAll(&#039;tbody tr&#039;)); var counts = {}; rows.forEach(function (row) { var cell = row.querySelectorAll(&#039;td&#039;)[locationIndex]; var location = cell ? cell.textContent.trim() : &#039;&#039;; row.dataset.hwLocation = location; if (location) { counts[location] = (counts[location] || 0) + 1; } }); var locations = Object.keys(counts).sort(); var enabled = new Set(locations); filterBar.innerHTML = &#039;&#039;; var showAll = document.createElement(&#039;button&#039;); showAll.type = &#039;button&#039;; showAll.className = &#039;hw-filter-chip hw-filter-chip--all&#039;; showAll.textContent = &#039;Show all&#039;; filterBar.appendChild(showAll); var locationButtons = {}; locations.forEach(function (location) { var button = document.createElement(&#039;button&#039;); button.type = &#039;button&#039;; button.className = &#039;hw-filter-chip is-active&#039;; button.setAttribute(&#039;aria-pressed&#039;, &#039;true&#039;); button.textContent = location + &#039; (&#039; + counts[location] + &#039;)&#039;; button.addEventListener(&#039;click&#039;, function () { if (enabled.has(location)) { enabled.delete(location); } else { enabled.add(location); } applyFilters(); }); locationButtons[location] = button; filterBar.appendChild(button); }); function applyFilters() { rows.forEach(function (row) { row.hidden = !enabled.has(row.dataset.hwLocation); }); locations.forEach(function (location) { var active = enabled.has(location); locationButtons[location].classList.toggle(&#039;is-active&#039;, active); locationButtons[location].setAttribute(&#039;aria-pressed&#039;, active ? &#039;true&#039; : &#039;false&#039;); }); showAll.disabled = enabled.size === locations.length; showAll.classList.toggle(&#039;is-active&#039;, enabled.size === locations.length); } showAll.addEventListener(&#039;click&#039;, function () { enabled = new Set(locations); applyFilters(); }); catalog.dataset.hwLocationFiltersReady = &#039;1&#039;; applyFilters(); }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { initHerbLocationFilters($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki admin-only Herb form UI */ (function () { &#039;use strict&#039;; var groups = mw.config.get(&#039;wgUserGroups&#039;) || []; var categories = mw.config.get(&#039;wgCategories&#039;) || []; if (groups.indexOf(&#039;sysop&#039;) !== -1 || categories.indexOf(&#039;Herbs&#039;) === -1) { return; } function hideHerbFormActions() { [&#039;ca-formedit&#039;, &#039;ca-formcreate&#039;, &#039;ca-formedit-sticky-header&#039;, &#039;ca-formcreate-sticky-header&#039;].forEach(function (id) { var element = document.getElementById(id); if (element) { element.hidden = true; } }); } if (document.readyState === &#039;loading&#039;) { document.addEventListener(&#039;DOMContentLoaded&#039;, hideHerbFormActions); } else { hideHerbFormActions(); } }()); /* HarvestWiki rarity-colored Herb and Crystal table rows */ (function () { &#039;use strict&#039;; var rarities = { common: true, uncommon: true, rare: true, legendary: true, mythic: true, secret: true, mortal: true, refined: true, spirit: true }; function normalizeRarity(value) { var rarity = (value || &#039;&#039;).trim().toLowerCase(); return rarities[rarity] ? rarity : &#039;&#039;; } function applyRarityColors(root) { var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document; var tables = []; if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) { tables.push(scope); } tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;))); tables.forEach(function (table) { var headerCells = Array.prototype.slice.call(table.querySelectorAll(&#039;tr:first-child th, tr:first-child td&#039;)); var itemIndex = headerCells.findIndex(function (cell) { var label = cell.textContent.trim().toLowerCase(); return label === &#039;herb&#039; || label === &#039;crystal&#039; || cell.classList.contains(&#039;field_Herb&#039;) || cell.classList.contains(&#039;field_Crystal&#039;); }); var rarityIndex = headerCells.findIndex(function (cell) { return cell.textContent.trim().toLowerCase() === &#039;rarity&#039; || cell.classList.contains(&#039;field_Rarity&#039;); }); Array.prototype.slice.call(table.rows).forEach(function (row) { var cells = Array.prototype.slice.call(row.cells); var itemCell = row.querySelector(&#039;td.field_Herb, td.field_Crystal&#039;); var rarityCell = row.querySelector(&#039;td.field_Rarity&#039;); if (!itemCell &amp;amp;&amp;amp; itemIndex &amp;gt;= 0) { itemCell = cells[itemIndex]; } if (!rarityCell &amp;amp;&amp;amp; rarityIndex &amp;gt;= 0) { rarityCell = cells[rarityIndex]; } if (!itemCell || !rarityCell || itemCell.tagName !== &#039;TD&#039;) { return; } var rarity = normalizeRarity(rarityCell.textContent); if (!rarity) { return; } row.classList.add(&#039;hw-rarity-row&#039;); row.dataset.hwRarity = rarity; itemCell.classList.add(&#039;hw-rarity-item&#039;); rarityCell.classList.add(&#039;hw-rarity-label&#039;); }); table.dataset.hwRarityColorsReady = &#039;1&#039;; }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { applyRarityColors($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki default Herb rarity order */ (function () { &#039;use strict&#039;; var rarityRank = { common: 0, uncommon: 1, rare: 2, legendary: 3, mythic: 4, secret: 5 }; function sortHerbTables(root) { var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document; var tables = []; if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) { tables.push(scope); } tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;))); tables.forEach(function (table) { if (table.dataset.hwHerbRaritySorted === &#039;1&#039;) { return; } var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;)); if (!headers.length &amp;amp;&amp;amp; table.rows.length) { headers = Array.prototype.slice.call(table.rows[0].cells); } var herbIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;herb&#039; || header.classList.contains(&#039;field_Herb&#039;); }); var rarityIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;rarity&#039; || header.classList.contains(&#039;field_Rarity&#039;); }); if (herbIndex &amp;lt; 0 || rarityIndex &amp;lt; 0) { return; } Array.prototype.slice.call(table.tBodies).forEach(function (body) { var rows = Array.prototype.slice.call(body.rows).map(function (row, index) { var rarityCell = row.cells[rarityIndex]; var rarity = rarityCell ? rarityCell.textContent.trim().toLowerCase() : &#039;&#039;; return { row: row, index: index, rank: Object.prototype.hasOwnProperty.call(rarityRank, rarity) ? rarityRank[rarity] : 999 }; }); rows.sort(function (a, b) { return a.rank - b.rank || a.index - b.index; }); var fragment = document.createDocumentFragment(); rows.forEach(function (entry) { fragment.appendChild(entry.row); }); body.appendChild(fragment); }); table.dataset.hwHerbRaritySorted = &#039;1&#039;; }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { sortHerbTables($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki default Crystal rarity order */ (function () { &#039;use strict&#039;; var crystalRarityRank = { mortal: 0, refined: 1, spirit: 2 }; function sortCrystalTables(root) { var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document; var tables = []; if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) { tables.push(scope); } tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;))); tables.forEach(function (table) { if (table.dataset.hwCrystalRaritySorted === &#039;1&#039;) { return; } var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;)); if (!headers.length &amp;amp;&amp;amp; table.rows.length) { headers = Array.prototype.slice.call(table.rows[0].cells); } var crystalIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;crystal&#039; || header.classList.contains(&#039;field_Crystal&#039;); }); var rarityIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;rarity&#039; || header.classList.contains(&#039;field_Rarity&#039;); }); if (crystalIndex &amp;lt; 0 || rarityIndex &amp;lt; 0) { return; } Array.prototype.slice.call(table.tBodies).forEach(function (body) { var rows = Array.prototype.slice.call(body.rows).map(function (row, index) { var rarityCell = row.cells[rarityIndex]; var rarity = rarityCell ? rarityCell.textContent.trim().toLowerCase() : &#039;&#039;; return { row: row, index: index, rank: Object.prototype.hasOwnProperty.call(crystalRarityRank, rarity) ? crystalRarityRank[rarity] : 999 }; }); rows.sort(function (a, b) { return a.rank - b.rank || a.index - b.index; }); var fragment = document.createDocumentFragment(); rows.forEach(function (entry) { fragment.appendChild(entry.row); }); body.appendChild(fragment); }); table.dataset.hwCrystalRaritySorted = &#039;1&#039;; }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { sortCrystalTables($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki admin-only Crystal form UI */ (function () { &#039;use strict&#039;; var groups = mw.config.get(&#039;wgUserGroups&#039;) || []; var categories = mw.config.get(&#039;wgCategories&#039;) || []; if (groups.indexOf(&#039;sysop&#039;) !== -1 || categories.indexOf(&#039;Crystals&#039;) === -1) { return; } function hideCrystalFormActions() { [&#039;ca-formedit&#039;, &#039;ca-formcreate&#039;, &#039;ca-formedit-sticky-header&#039;, &#039;ca-formcreate-sticky-header&#039;].forEach(function (id) { var element = document.getElementById(id); if (element) { element.hidden = true; } }); } if (document.readyState === &#039;loading&#039;) { document.addEventListener(&#039;DOMContentLoaded&#039;, hideCrystalFormActions); } else { hideCrystalFormActions(); } }()); /* HarvestWiki admin-only Weather form UI */ (function () { &#039;use strict&#039;; var groups = mw.config.get(&#039;wgUserGroups&#039;) || []; var categories = mw.config.get(&#039;wgCategories&#039;) || []; if (groups.indexOf(&#039;sysop&#039;) !== -1 || categories.indexOf(&#039;Weather&#039;) === -1) { return; } function hideWeatherFormActions() { [&#039;ca-formedit&#039;, &#039;ca-formcreate&#039;, &#039;ca-formedit-sticky-header&#039;, &#039;ca-formcreate-sticky-header&#039;].forEach(function (id) { var element = document.getElementById(id); if (element) { element.hidden = true; } }); } if (document.readyState === &#039;loading&#039;) { document.addEventListener(&#039;DOMContentLoaded&#039;, hideWeatherFormActions); } else { hideWeatherFormActions(); } }()); /* HarvestWiki four-zodiac celestial calendar */ (function () { &#039;use strict&#039;; var anchor = Date.UTC(2026, 0, 1, 0, 0, 0); var seasonLength = 8 * 60 * 60 * 1000; var seasonNames = [ &#039;Year of the Dragon&#039;, &#039;Year of the Tiger&#039;, &#039;Year of the Phoenix&#039;, &#039;Year of the Tortoise&#039; ]; function formatDuration(milliseconds) { var seconds = Math.max(0, Math.ceil(milliseconds / 1000)); var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds % 3600) / 60); var remainder = seconds % 60; return String(hours).padStart(2, &#039;0&#039;) + &#039;:&#039; + String(minutes).padStart(2, &#039;0&#039;) + &#039;:&#039; + String(remainder).padStart(2, &#039;0&#039;); } function initZodiacCycles(root) { var widgets = []; if (root &amp;amp;&amp;amp; root.matches &amp;amp;&amp;amp; root.matches(&#039;[data-hw-zodiac-cycle]&#039;)) { widgets.push(root); } if (root &amp;amp;&amp;amp; root.querySelectorAll) { widgets = widgets.concat(Array.prototype.slice.call(root.querySelectorAll(&#039;[data-hw-zodiac-cycle]&#039;))); } widgets.forEach(function (widget) { if (widget.dataset.hwZodiacReady === &#039;1&#039;) { return; } widget.dataset.hwZodiacReady = &#039;1&#039;; var currentLabel = widget.querySelector(&#039;[data-hw-zodiac-current]&#039;); var countdown = widget.querySelector(&#039;[data-hw-zodiac-countdown]&#039;); var progress = widget.querySelector(&#039;[data-hw-zodiac-progress]&#039;); var seasons = Array.prototype.slice.call(widget.querySelectorAll(&#039;[data-hw-zodiac-season]&#039;)); function updateZodiacCycle() { var now = Date.now(); var step = Math.floor((now - anchor) / seasonLength); var currentIndex = ((step % seasonNames.length) + seasonNames.length) % seasonNames.length; var nextBoundary = anchor + ((step + 1) * seasonLength); var remaining = Math.max(0, nextBoundary - now); var elapsedInSeason = ((now - anchor) % seasonLength + seasonLength) % seasonLength; if (currentLabel) { currentLabel.textContent = seasonNames[currentIndex]; } if (countdown) { countdown.textContent = formatDuration(remaining); countdown.title = &#039;Changes at &#039; + new Date(nextBoundary).toLocaleString(); } if (progress) { progress.style.width = ((elapsedInSeason / seasonLength) * 100).toFixed(3) + &#039;%&#039;; } seasons.forEach(function (season, index) { var offset = (index - currentIndex + seasonNames.length) % seasonNames.length; var state = season.querySelector(&#039;[data-hw-zodiac-state]&#039;); var startsIn = remaining + (Math.max(0, offset - 1) * seasonLength); var changeAt = offset === 0 ? nextBoundary : now + startsIn; season.classList.toggle(&#039;is-current&#039;, offset === 0); season.setAttribute(&#039;aria-current&#039;, offset === 0 ? &#039;true&#039; : &#039;false&#039;); season.title = (offset === 0 ? &#039;Ends &#039; : &#039;Begins &#039;) + new Date(changeAt).toLocaleString(); if (state) { if (offset === 0) { state.textContent = &#039;Current · ends in &#039; + formatDuration(remaining); } else if (offset === 1) { state.textContent = &#039;Next · starts in &#039; + formatDuration(startsIn); } else { state.textContent = &#039;Starts in &#039; + formatDuration(startsIn); } } }); } updateZodiacCycle(); window.setInterval(updateZodiacCycle, 1000); }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { initZodiacCycles($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki multi-page editor controls and archive refresh */ (function () { &#039;use strict&#039;; function initBulkEditors(root) { var grids = []; if (root &amp;amp;&amp;amp; root.matches &amp;amp;&amp;amp; root.matches(&#039;.pfSpreadsheet&#039;)) { grids.push(root); } if (root &amp;amp;&amp;amp; root.querySelectorAll) { grids = grids.concat(Array.prototype.slice.call(root.querySelectorAll(&#039;.pfSpreadsheet&#039;))); } grids.forEach(function (grid) { if (grid.dataset.hwBulkEditorReady === &#039;1&#039;) { return; } grid.dataset.hwBulkEditorReady = &#039;1&#039;; var parameters = new URLSearchParams(window.location.search); var templateName = parameters.get(&#039;template&#039;) || grid.id.replace(/Grid$/, &#039;&#039;); var archivePage = templateName === &#039;Herb&#039; ? &#039;Herbs&#039; : (templateName === &#039;Crystal&#039; ? &#039;Crystals&#039; : null); var userGroups = mw.config.get(&#039;wgUserGroups&#039;) || []; var canDelete = archivePage &amp;amp;&amp;amp; userGroups.indexOf(&#039;sysop&#039;) !== -1; function purgeArchive() { if (!archivePage || typeof mw.Api !== &#039;function&#039;) { return Promise.resolve(); } return new mw.Api().post({ action: &#039;purge&#039;, titles: archivePage, formatversion: 2 }); } function wireRowActions() { Array.prototype.slice.call(grid.querySelectorAll(&#039;a.save-changes&#039;)).forEach(function (link) { if (link.dataset.hwBulkSaveReady === &#039;1&#039;) { return; } link.dataset.hwBulkSaveReady = &#039;1&#039;; link.setAttribute(&#039;title&#039;, &#039;Save this row&#039;); link.setAttribute(&#039;aria-label&#039;, &#039;Save this row&#039;); link.addEventListener(&#039;click&#039;, function () { link.classList.remove(&#039;is-saved&#039;); link.classList.add(&#039;is-saving&#039;); function refreshArchive() { purgeArchive().then(function () { link.classList.remove(&#039;is-saving&#039;); link.classList.add(&#039;is-saved&#039;); window.setTimeout(function () { link.classList.remove(&#039;is-saved&#039;); }, 2200); }); } window.setTimeout(refreshArchive, 1600); window.setTimeout(refreshArchive, 4200); }); }); if (!canDelete) { return; } Array.prototype.slice.call(grid.querySelectorAll(&#039;tbody tr&#039;)).forEach(function (row) { var pageCell = row.querySelector(&#039;td[data-x=&amp;quot;0&amp;quot;]&#039;); var actionBox = row.querySelector(&#039;.save-or-cancel&#039;); if (!pageCell || !actionBox || actionBox.querySelector(&#039;.hw-delete-row&#039;)) { return; } var pageTitle = pageCell.textContent.trim(); if (!pageTitle) { return; } var deleteButton = document.createElement(&#039;button&#039;); deleteButton.type = &#039;button&#039;; deleteButton.className = &#039;hw-delete-row&#039;; deleteButton.textContent = &#039;Delete&#039;; deleteButton.title = &#039;Delete page &#039; + pageTitle; deleteButton.setAttribute(&#039;aria-label&#039;, &#039;Delete page &#039; + pageTitle); deleteButton.addEventListener(&#039;click&#039;, function () { var confirmed = window.confirm( &#039;Permanently delete &amp;quot;&#039; + pageTitle + &#039;&amp;quot;?\n\n&#039; + &#039;This removes its wiki page and all table data. This cannot be undone.&#039; ); if (!confirmed) { return; } deleteButton.disabled = true; deleteButton.classList.add(&#039;is-deleting&#039;); deleteButton.textContent = &#039;Deleting…&#039;; new mw.Api().postWithToken(&#039;csrf&#039;, { action: &#039;delete&#039;, title: pageTitle, reason: &#039;Deleted from the multi-page editor&#039;, formatversion: 2 }).then(function () { return purgeArchive(); }).then(function () { deleteButton.textContent = &#039;Deleted&#039;; window.setTimeout(function () { window.location.reload(); }, 700); }, function () { deleteButton.disabled = false; deleteButton.classList.remove(&#039;is-deleting&#039;); deleteButton.textContent = &#039;Delete&#039;; mw.notify(&#039;Could not delete &amp;quot;&#039; + pageTitle + &#039;&amp;quot;. The record was not removed.&#039;, { type: &#039;error&#039; }); }); }); actionBox.appendChild(deleteButton); }); } wireRowActions(); if (window.MutationObserver) { new MutationObserver(wireRowActions).observe(grid, { childList: true, subtree: true }); } if (!grid.querySelector(&#039;.hw-bulk-editor-note&#039;)) { var note = document.createElement(&#039;div&#039;); note.className = &#039;hw-bulk-editor-note&#039;; note.textContent = &#039;Scroll sideways to reach every field. Use Save at the right of each row.&#039; + (archivePage ? &#039; Saved rows automatically refresh the &#039; + archivePage + &#039; index.&#039; : &#039;&#039;) + (canDelete ? &#039; Administrators can permanently remove a record with Delete.&#039; : &#039;&#039;); grid.appendChild(note); } }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { initBulkEditors($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }());&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki automatic Herb, Crystal, Mutation, Shovel, Weather, TOD, and Zodiac link colors */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  var rarityColors = {&lt;br /&gt;
    common: &#039;#e5e7e7&#039;,&lt;br /&gt;
    uncommon: &#039;#87ff97&#039;,&lt;br /&gt;
    rare: &#039;#6d90ff&#039;,&lt;br /&gt;
    legendary: &#039;#fff687&#039;,&lt;br /&gt;
    mythic: &#039;#ff79cb&#039;,&lt;br /&gt;
    secret: &#039;#12005c&#039;,&lt;br /&gt;
    mortal: &#039;#cef1e8&#039;,&lt;br /&gt;
    refined: &#039;#464747&#039;,&lt;br /&gt;
    spirit: &#039;#00c3f2&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var mutationColors = {&lt;br /&gt;
    silver: &#039;#eef1f1&#039;,&lt;br /&gt;
    golden: &#039;#f4d05b&#039;,&lt;br /&gt;
    vitality: &#039;#e96869&#039;,&lt;br /&gt;
    vampiric: &#039;#de002b&#039;,&lt;br /&gt;
    icy: &#039;#9ee7d8&#039;,&lt;br /&gt;
    frozen: &#039;#6ee8ff&#039;,&lt;br /&gt;
    spirit: &#039;#ffabf6&#039;,&lt;br /&gt;
    royal: &#039;#ffec31&#039;,&lt;br /&gt;
    imperial: &#039;#b600ff&#039;,&lt;br /&gt;
    century: &#039;#5369ff&#039;,&lt;br /&gt;
    millenium: &#039;#6c47ff&#039;,&lt;br /&gt;
    eon: &#039;#7f15ff&#039;,&lt;br /&gt;
    sovereign: &#039;#ff26a5&#039;,&lt;br /&gt;
    supreme: &#039;#ed13ff&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var worldColors = {&lt;br /&gt;
    clear: &#039;#f1cb4f&#039;,&lt;br /&gt;
    cloudy: &#039;#949ba5&#039;,&lt;br /&gt;
    rain: &#039;#60a5e2&#039;,&lt;br /&gt;
    storm: &#039;#525964&#039;,&lt;br /&gt;
    thunderstorm: &#039;#f8d652&#039;,&lt;br /&gt;
    &#039;dao tempest&#039;: &#039;#b9422f&#039;,&lt;br /&gt;
    sunrise: &#039;#ef9d49&#039;,&lt;br /&gt;
    daytime: &#039;#63ace0&#039;,&lt;br /&gt;
    sunset: &#039;#df6447&#039;,&lt;br /&gt;
    night: &#039;#abb6f9&#039;,&lt;br /&gt;
    &#039;year of the dragon&#039;: &#039;#ffb86f&#039;,&lt;br /&gt;
    &#039;year of the tiger&#039;: &#039;#f7f672&#039;,&lt;br /&gt;
    &#039;year of the phoenix&#039;: &#039;#f87b84&#039;,&lt;br /&gt;
    &#039;year of the tortoise&#039;: &#039;#4fc57d&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var shovelColors = {&lt;br /&gt;
    &#039;basic shovel&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &amp;quot;journeyman&#039;s shovel&amp;quot;: &#039;#ffffff&#039;,&lt;br /&gt;
    &#039;root breaker&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &#039;steel spade&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &#039;autumn leaf&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &amp;quot;king&#039;s destiny&amp;quot;: &#039;#ffffff&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var entityColors = {&lt;br /&gt;
    &#039;ancient woe herb&#039;: rarityColors.rare,&lt;br /&gt;
    &#039;breathless dawn herb&#039;: rarityColors.uncommon,&lt;br /&gt;
    &#039;crimson sun grass&#039;: rarityColors.common,&lt;br /&gt;
    &#039;fleeting dusk herb&#039;: rarityColors.uncommon,&lt;br /&gt;
    &#039;frigid moon grass&#039;: rarityColors.common,&lt;br /&gt;
    &#039;pursuing longevity herb&#039;: rarityColors.mythic,&lt;br /&gt;
    &#039;sparkling joy herb&#039;: rarityColors.rare,&lt;br /&gt;
    &#039;yang lotus&#039;: rarityColors.legendary,&lt;br /&gt;
    &#039;yin lotus&#039;: rarityColors.legendary,&lt;br /&gt;
    &#039;air crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;earth crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;flame crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;water crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;wood crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;magma crystal&#039;: rarityColors.refined,&lt;br /&gt;
    &#039;mist crystal&#039;: rarityColors.refined,&lt;br /&gt;
    &#039;longevity crystal&#039;: rarityColors.spirit,&lt;br /&gt;
    clear: worldColors.clear,&lt;br /&gt;
    cloudy: worldColors.cloudy,&lt;br /&gt;
    rain: worldColors.rain,&lt;br /&gt;
    storm: worldColors.storm,&lt;br /&gt;
    thunderstorm: worldColors.thunderstorm,&lt;br /&gt;
    &#039;dao tempest&#039;: worldColors[&#039;dao tempest&#039;],&lt;br /&gt;
    sunrise: worldColors.sunrise,&lt;br /&gt;
    daytime: worldColors.daytime,&lt;br /&gt;
    sunset: worldColors.sunset,&lt;br /&gt;
    night: worldColors.night,&lt;br /&gt;
    &#039;year of the dragon&#039;: worldColors[&#039;year of the dragon&#039;],&lt;br /&gt;
    &#039;year of the tiger&#039;: worldColors[&#039;year of the tiger&#039;],&lt;br /&gt;
    &#039;year of the phoenix&#039;: worldColors[&#039;year of the phoenix&#039;],&lt;br /&gt;
    &#039;year of the tortoise&#039;: worldColors[&#039;year of the tortoise&#039;],&lt;br /&gt;
    &#039;basic shovel&#039;: shovelColors[&#039;basic shovel&#039;],&lt;br /&gt;
    &amp;quot;journeyman&#039;s shovel&amp;quot;: shovelColors[&amp;quot;journeyman&#039;s shovel&amp;quot;],&lt;br /&gt;
    &#039;root breaker&#039;: shovelColors[&#039;root breaker&#039;],&lt;br /&gt;
    &#039;steel spade&#039;: shovelColors[&#039;steel spade&#039;],&lt;br /&gt;
    &#039;autumn leaf&#039;: shovelColors[&#039;autumn leaf&#039;],&lt;br /&gt;
    &amp;quot;king&#039;s destiny&amp;quot;: shovelColors[&amp;quot;king&#039;s destiny&amp;quot;]&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  function normalizeTitle(value) {&lt;br /&gt;
    return (value || &#039;&#039;).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).trim().toLowerCase();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function titleFromLink(link) {&lt;br /&gt;
    var href = link.getAttribute(&#039;href&#039;) || &#039;&#039;;&lt;br /&gt;
    if (!href || href.charAt(0) === &#039;#&#039;) {&lt;br /&gt;
      return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
      var url = new URL(href, window.location.origin);&lt;br /&gt;
      if (url.origin !== window.location.origin) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
      if (!title &amp;amp;&amp;amp; url.pathname.indexOf(&#039;/index.php/&#039;) === 0) {&lt;br /&gt;
        title = decodeURIComponent(url.pathname.slice(&#039;/index.php/&#039;.length));&lt;br /&gt;
      }&lt;br /&gt;
      return normalizeTitle((title || &#039;&#039;).split(&#039;#&#039;)[0]);&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function cargoQuery(table, fields) {&lt;br /&gt;
    return mw.loader.using(&#039;mediawiki.api&#039;).then(function () {&lt;br /&gt;
      return new mw.Api().get({&lt;br /&gt;
        action: &#039;cargoquery&#039;,&lt;br /&gt;
        tables: table,&lt;br /&gt;
        fields: fields,&lt;br /&gt;
        limit: 500,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
      });&lt;br /&gt;
    }).then(function (data) {&lt;br /&gt;
      return data &amp;amp;&amp;amp; data.cargoquery ? data.cargoquery : [];&lt;br /&gt;
    }, function () {&lt;br /&gt;
      return [];&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function buildColorMap() {&lt;br /&gt;
    return Promise.all([&lt;br /&gt;
      cargoQuery(&#039;Herbs&#039;, &#039;_pageName=Page,Rarity&#039;),&lt;br /&gt;
      cargoQuery(&#039;Crystals&#039;, &#039;_pageName=Page,Rarity&#039;),&lt;br /&gt;
      cargoQuery(&#039;Mutations&#039;, &#039;_pageName=Page,Color&#039;),&lt;br /&gt;
      cargoQuery(&#039;Weathers&#039;, &#039;_pageName=Page,Color&#039;),&lt;br /&gt;
      cargoQuery(&#039;Shovels&#039;, &#039;_pageName=Page,Color&#039;)&lt;br /&gt;
    ]).then(function (groups) {&lt;br /&gt;
      var colorMap = Object.assign({}, entityColors);&lt;br /&gt;
&lt;br /&gt;
      groups[0].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        var color = rarityColors[normalizeTitle(data.Rarity)];&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[1].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        var color = rarityColors[normalizeTitle(data.Rarity)];&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[2].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; data.Color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = data.Color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[3].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; data.Color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = data.Color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[4].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; data.Color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = data.Color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      Object.keys(mutationColors).forEach(function (title) {&lt;br /&gt;
        colorMap[title] = mutationColors[title];&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      return colorMap;&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyLinkColors(root, colorMap) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var links = [];&lt;br /&gt;
    if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;a[href]&#039;)) {&lt;br /&gt;
      links.push(scope);&lt;br /&gt;
    }&lt;br /&gt;
    links = links.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;a[href]&#039;)));&lt;br /&gt;
&lt;br /&gt;
    links.forEach(function (link) {&lt;br /&gt;
      var color = colorMap[titleFromLink(link)];&lt;br /&gt;
      if (!color || link.classList.contains(&#039;new&#039;)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      link.classList.add(&#039;hw-colored-link&#039;);&lt;br /&gt;
      link.style.setProperty(&#039;--hw-link-color&#039;, color);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyMutationRows(root, colorMap) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var tables = [];&lt;br /&gt;
    if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) {&lt;br /&gt;
      tables.push(scope);&lt;br /&gt;
    }&lt;br /&gt;
    tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;)));&lt;br /&gt;
&lt;br /&gt;
    tables.forEach(function (table) {&lt;br /&gt;
      var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;));&lt;br /&gt;
      if (!headers.length &amp;amp;&amp;amp; table.rows.length) {&lt;br /&gt;
        headers = Array.prototype.slice.call(table.rows[0].cells);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var mutationIndex = headers.findIndex(function (header) {&lt;br /&gt;
        return header.textContent.trim().toLowerCase() === &#039;mutation&#039; ||&lt;br /&gt;
          header.classList.contains(&#039;field_Mutation&#039;);&lt;br /&gt;
      });&lt;br /&gt;
      if (mutationIndex &amp;lt; 0) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      Array.prototype.slice.call(table.tBodies).forEach(function (body) {&lt;br /&gt;
        Array.prototype.slice.call(body.rows).forEach(function (row) {&lt;br /&gt;
          var mutationCell = row.cells[mutationIndex];&lt;br /&gt;
          var mutationLink = mutationCell ? mutationCell.querySelector(&#039;a[href]&#039;) : null;&lt;br /&gt;
          var color = mutationLink ? colorMap[titleFromLink(mutationLink)] : &#039;&#039;;&lt;br /&gt;
          if (!mutationCell || !color) {&lt;br /&gt;
            return;&lt;br /&gt;
          }&lt;br /&gt;
          row.classList.add(&#039;hw-mutation-row&#039;);&lt;br /&gt;
          row.style.setProperty(&#039;--hw-mutation-row-color&#039;, color);&lt;br /&gt;
          mutationCell.classList.add(&#039;hw-mutation-item&#039;);&lt;br /&gt;
        });&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyShovelRows(root, colorMap) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var tables = [];&lt;br /&gt;
    if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) {&lt;br /&gt;
      tables.push(scope);&lt;br /&gt;
    }&lt;br /&gt;
    tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;)));&lt;br /&gt;
&lt;br /&gt;
    tables.forEach(function (table) {&lt;br /&gt;
      var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;));&lt;br /&gt;
      if (!headers.length &amp;amp;&amp;amp; table.rows.length) {&lt;br /&gt;
        headers = Array.prototype.slice.call(table.rows[0].cells);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var shovelIndex = headers.findIndex(function (header) {&lt;br /&gt;
        return header.textContent.trim().toLowerCase() === &#039;shovel&#039; ||&lt;br /&gt;
          header.classList.contains(&#039;field_Shovel&#039;);&lt;br /&gt;
      });&lt;br /&gt;
      if (shovelIndex &amp;lt; 0) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      Array.prototype.slice.call(table.tBodies).forEach(function (body) {&lt;br /&gt;
        Array.prototype.slice.call(body.rows).forEach(function (row) {&lt;br /&gt;
          var shovelCell = row.cells[shovelIndex];&lt;br /&gt;
          var shovelLink = shovelCell ? shovelCell.querySelector(&#039;a[href]&#039;) : null;&lt;br /&gt;
          var color = shovelLink ? colorMap[titleFromLink(shovelLink)] : &#039;&#039;;&lt;br /&gt;
          if (!shovelCell || !color) {&lt;br /&gt;
            return;&lt;br /&gt;
          }&lt;br /&gt;
          row.classList.add(&#039;hw-shovel-row&#039;);&lt;br /&gt;
          row.style.setProperty(&#039;--hw-shovel-row-color&#039;, color);&lt;br /&gt;
          shovelCell.classList.add(&#039;hw-shovel-item&#039;);&lt;br /&gt;
        });&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  var immediateColorMap = Object.assign({}, entityColors);&lt;br /&gt;
  Object.keys(mutationColors).forEach(function (title) {&lt;br /&gt;
    immediateColorMap[title] = mutationColors[title];&lt;br /&gt;
  });&lt;br /&gt;
  Object.keys(worldColors).forEach(function (title) {&lt;br /&gt;
    immediateColorMap[title] = worldColors[title];&lt;br /&gt;
  });&lt;br /&gt;
  Object.keys(shovelColors).forEach(function (title) {&lt;br /&gt;
    immediateColorMap[title] = shovelColors[title];&lt;br /&gt;
  });&lt;br /&gt;
  var colorMapPromise = buildColorMap();&lt;br /&gt;
&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
    var root = $content &amp;amp;&amp;amp; $content[0] ? $content[0] : document;&lt;br /&gt;
    applyMutationRows(root, immediateColorMap);&lt;br /&gt;
    applyShovelRows(root, immediateColorMap);&lt;br /&gt;
    applyLinkColors(root, immediateColorMap);&lt;br /&gt;
    colorMapPromise.then(function (colorMap) {&lt;br /&gt;
      applyMutationRows(root, colorMap);&lt;br /&gt;
      applyShovelRows(root, colorMap);&lt;br /&gt;
      applyLinkColors(root, colorMap);&lt;br /&gt;
    });&lt;br /&gt;
  });&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki hierarchical Citizen popout menu */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  var groups = [&lt;br /&gt;
    {&lt;br /&gt;
      key: &#039;herb&#039;,&lt;br /&gt;
      title: &#039;Herb&#039;,&lt;br /&gt;
      note: &#039;Harvesting &amp;amp; cultivation&#039;,&lt;br /&gt;
      glyph: &#039;草&#039;,&lt;br /&gt;
      pages: [&#039;Herbs&#039;, &#039;Mutations&#039;, &#039;Crystals&#039;, &#039;Shovels&#039;]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
      key: &#039;world&#039;,&lt;br /&gt;
      title: &#039;World&#039;,&lt;br /&gt;
      note: &#039;Places, people &amp;amp; conditions&#039;,&lt;br /&gt;
      glyph: &#039;界&#039;,&lt;br /&gt;
      pages: [&#039;Weather&#039;, &#039;Locations&#039;, &#039;NPCs&#039;]&lt;br /&gt;
    }&lt;br /&gt;
  ];&lt;br /&gt;
&lt;br /&gt;
  function directLink(item) {&lt;br /&gt;
    var children = item ? item.children : null;&lt;br /&gt;
    var index;&lt;br /&gt;
    if (!children) {&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
    for (index = 0; index &amp;lt; children.length; index += 1) {&lt;br /&gt;
      if (children[index].tagName === &#039;A&#039;) {&lt;br /&gt;
        return children[index];&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    return null;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function linkTitle(link) {&lt;br /&gt;
    var path;&lt;br /&gt;
    if (!link) {&lt;br /&gt;
      return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    try {&lt;br /&gt;
      path = new URL(link.href, location.href).pathname.split(&#039;/&#039;).pop() || &#039;&#039;;&lt;br /&gt;
      return decodeURIComponent(path).replace(/_/g, &#039; &#039;);&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      return (link.textContent || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function closeOtherGroups(root, keep) {&lt;br /&gt;
    Array.prototype.slice.call(root.querySelectorAll(&#039;.hw-menu-group.is-open&#039;)).forEach(function (group) {&lt;br /&gt;
      if (group !== keep) {&lt;br /&gt;
        group.classList.remove(&#039;is-open&#039;);&lt;br /&gt;
        group.querySelector(&#039;.hw-menu-trigger&#039;).setAttribute(&#039;aria-expanded&#039;, &#039;false&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function createGroup(config, items, root) {&lt;br /&gt;
    var group = document.createElement(&#039;li&#039;);&lt;br /&gt;
    var trigger = document.createElement(&#039;button&#039;);&lt;br /&gt;
    var glyph = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var copy = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var title = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var note = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var chevron = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var submenu = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    var closeTimer = null;&lt;br /&gt;
&lt;br /&gt;
    function cancelClose() {&lt;br /&gt;
      if (closeTimer !== null) {&lt;br /&gt;
        window.clearTimeout(closeTimer);&lt;br /&gt;
        closeTimer = null;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function closeGroup() {&lt;br /&gt;
      group.classList.remove(&#039;is-open&#039;);&lt;br /&gt;
      trigger.setAttribute(&#039;aria-expanded&#039;, &#039;false&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleClose() {&lt;br /&gt;
      cancelClose();&lt;br /&gt;
      closeTimer = window.setTimeout(function () {&lt;br /&gt;
        closeTimer = null;&lt;br /&gt;
        if (!group.matches(&#039;:hover&#039;) &amp;amp;&amp;amp; !group.contains(document.activeElement)) {&lt;br /&gt;
          closeGroup();&lt;br /&gt;
        }&lt;br /&gt;
      }, 240);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    group.className = &#039;hw-menu-group hw-menu-group--&#039; + config.key;&lt;br /&gt;
    trigger.type = &#039;button&#039;;&lt;br /&gt;
    trigger.className = &#039;hw-menu-trigger&#039;;&lt;br /&gt;
    trigger.setAttribute(&#039;aria-expanded&#039;, &#039;false&#039;);&lt;br /&gt;
    trigger.setAttribute(&#039;aria-haspopup&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
    glyph.className = &#039;hw-menu-glyph&#039;;&lt;br /&gt;
    glyph.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
    glyph.textContent = config.glyph;&lt;br /&gt;
&lt;br /&gt;
    copy.className = &#039;hw-menu-trigger-copy&#039;;&lt;br /&gt;
    title.className = &#039;hw-menu-trigger-title&#039;;&lt;br /&gt;
    title.textContent = config.title;&lt;br /&gt;
    note.className = &#039;hw-menu-trigger-note&#039;;&lt;br /&gt;
    note.textContent = config.note;&lt;br /&gt;
    copy.appendChild(title);&lt;br /&gt;
    copy.appendChild(note);&lt;br /&gt;
&lt;br /&gt;
    chevron.className = &#039;hw-menu-chevron&#039;;&lt;br /&gt;
    chevron.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
    chevron.textContent = &#039;›&#039;;&lt;br /&gt;
&lt;br /&gt;
    trigger.appendChild(glyph);&lt;br /&gt;
    trigger.appendChild(copy);&lt;br /&gt;
    trigger.appendChild(chevron);&lt;br /&gt;
&lt;br /&gt;
    submenu.className = &#039;hw-menu-submenu&#039;;&lt;br /&gt;
    submenu.setAttribute(&#039;aria-label&#039;, config.title + &#039; pages&#039;);&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
      submenu.appendChild(item);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    trigger.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      var willOpen = !group.classList.contains(&#039;is-open&#039;);&lt;br /&gt;
      cancelClose();&lt;br /&gt;
      closeOtherGroups(root, group);&lt;br /&gt;
      group.classList.toggle(&#039;is-open&#039;, willOpen);&lt;br /&gt;
      trigger.setAttribute(&#039;aria-expanded&#039;, willOpen ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
      cancelClose();&lt;br /&gt;
      closeOtherGroups(root, group);&lt;br /&gt;
      group.classList.add(&#039;is-open&#039;);&lt;br /&gt;
      trigger.setAttribute(&#039;aria-expanded&#039;, &#039;true&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;mouseleave&#039;, scheduleClose);&lt;br /&gt;
    submenu.addEventListener(&#039;mouseenter&#039;, cancelClose);&lt;br /&gt;
    submenu.addEventListener(&#039;mouseleave&#039;, scheduleClose);&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;focusout&#039;, function (event) {&lt;br /&gt;
      if (!group.contains(event.relatedTarget)) {&lt;br /&gt;
        cancelClose();&lt;br /&gt;
        closeGroup();&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    group.appendChild(trigger);&lt;br /&gt;
    group.appendChild(submenu);&lt;br /&gt;
    return group;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function enhanceMenu() {&lt;br /&gt;
    var portlet = document.querySelector(&#039;#p-HarvestWiki&#039;);&lt;br /&gt;
    var list;&lt;br /&gt;
    var items;&lt;br /&gt;
    var byTitle = {};&lt;br /&gt;
    var newList;&lt;br /&gt;
    var complete;&lt;br /&gt;
&lt;br /&gt;
    if (!portlet || portlet.classList.contains(&#039;hw-menu-ready&#039;)) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    list = portlet.querySelector(&#039;.citizen-menu__content &amp;gt; ul&#039;);&lt;br /&gt;
    if (!list) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items = Array.prototype.slice.call(list.children);&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
      var link = directLink(item);&lt;br /&gt;
      var title = linkTitle(link);&lt;br /&gt;
      if (title) {&lt;br /&gt;
        byTitle[title.toLowerCase()] = item;&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    complete = groups.every(function (config) {&lt;br /&gt;
      return config.pages.every(function (page) {&lt;br /&gt;
        return Boolean(byTitle[page.toLowerCase()]);&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
    if (!complete) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    newList = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    newList.className = &#039;hw-menu-root&#039;;&lt;br /&gt;
    groups.forEach(function (config) {&lt;br /&gt;
      var groupItems = config.pages.map(function (page) {&lt;br /&gt;
        return byTitle[page.toLowerCase()];&lt;br /&gt;
      });&lt;br /&gt;
      newList.appendChild(createGroup(config, groupItems, newList));&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    list.replaceWith(newList);&lt;br /&gt;
    portlet.classList.add(&#039;hw-menu-ready&#039;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initMenu() {&lt;br /&gt;
    enhanceMenu();&lt;br /&gt;
    new MutationObserver(enhanceMenu).observe(document.body, {&lt;br /&gt;
      childList: true,&lt;br /&gt;
      subtree: true&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
      var root;&lt;br /&gt;
      if (event.key !== &#039;Escape&#039;) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      root = document.querySelector(&#039;.hw-menu-root&#039;);&lt;br /&gt;
      if (root) {&lt;br /&gt;
        closeOtherGroups(root, null);&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initMenu, { once: true });&lt;br /&gt;
  } else {&lt;br /&gt;
    initMenu();&lt;br /&gt;
  }&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki automatic page release history */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function addHistoryToNewPage() {&lt;br /&gt;
    var action = mw.config.get(&#039;wgAction&#039;);&lt;br /&gt;
    var articleId = Number(mw.config.get(&#039;wgArticleId&#039;) || 0);&lt;br /&gt;
    var namespace = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
    var pageName = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;);&lt;br /&gt;
    var textbox;&lt;br /&gt;
    var marker;&lt;br /&gt;
&lt;br /&gt;
    if (action !== &#039;edit&#039; || articleId !== 0 || namespace !== 0 || /\/Change_History$/.test(pageName)) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    textbox = document.getElementById(&#039;wpTextbox1&#039;);&lt;br /&gt;
    if (!textbox || /\{\{\s*Change History\b/i.test(textbox.value)) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    marker = &#039;\n\n== Change History ==\n{{Change History|{{&#039; + &#039;subst:Current version}}|{{&#039; + &#039;subst:Current version page}}}}\n&#039;;&lt;br /&gt;
    textbox.value = textbox.value.replace(/\s+$/, &#039;&#039;) + marker;&lt;br /&gt;
    textbox.dispatchEvent(new Event(&#039;input&#039;, { bubbles: true }));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function enableHistoryEditorTools() {&lt;br /&gt;
    var userName = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
    var probablyEditable = mw.config.get(&#039;wgIsProbablyEditable&#039;);&lt;br /&gt;
    var releases;&lt;br /&gt;
&lt;br /&gt;
    if (!userName || probablyEditable === false) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    releases = document.querySelectorAll(&#039;.hw-change-history-release&#039;);&lt;br /&gt;
    if (!releases.length) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;hw-history-tools-enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
    releases.forEach(function (release) {&lt;br /&gt;
      var addLink = release.querySelector(&#039;.hw-history-add a, a.hw-history-add&#039;);&lt;br /&gt;
      var version = String(release.getAttribute(&#039;data-current-version&#039;) || &#039;&#039;).trim();&lt;br /&gt;
      var series = String(release.getAttribute(&#039;data-current-series&#039;) || &#039;&#039;).trim();&lt;br /&gt;
      var historyPage = String(release.getAttribute(&#039;data-history-page&#039;) || &#039;&#039;).trim();&lt;br /&gt;
      var params;&lt;br /&gt;
&lt;br /&gt;
      if (!addLink || !version || !series || !historyPage) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      params = new URLSearchParams();&lt;br /&gt;
      params.set(&#039;title&#039;, historyPage);&lt;br /&gt;
      params.set(&#039;action&#039;, &#039;edit&#039;);&lt;br /&gt;
      params.set(&#039;section&#039;, &#039;new&#039;);&lt;br /&gt;
      params.set(&#039;preload&#039;, &#039;Template:Change History/entry&#039;);&lt;br /&gt;
      params.set(&#039;preloadtitle&#039;, &#039;Version &#039; + version);&lt;br /&gt;
      params.append(&#039;preloadparams[]&#039;, version);&lt;br /&gt;
      params.append(&#039;preloadparams[]&#039;, series);&lt;br /&gt;
      addLink.href = mw.util.wikiScript(&#039;index&#039;) + &#039;?&#039; + params.toString();&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initVersionHistory() {&lt;br /&gt;
    addHistoryToNewPage();&lt;br /&gt;
    enableHistoryEditorTools();&lt;br /&gt;
    window.setTimeout(addHistoryToNewPage, 350);&lt;br /&gt;
    window.setTimeout(enableHistoryEditorTools, 350);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.editform&#039;).add(function () {&lt;br /&gt;
      window.setTimeout(addHistoryToNewPage, 100);&lt;br /&gt;
    });&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
      window.setTimeout(enableHistoryEditorTools, 100);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initVersionHistory, { once: true });&lt;br /&gt;
  } else {&lt;br /&gt;
    initVersionHistory();&lt;br /&gt;
  }&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki version snapshots for source edits, Page Forms, and administrators */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  var MARKER_KEY = &#039;hwVersionHistoryPending&#039;;&lt;br /&gt;
  var ADMIN_PAGE = &#039;HarvestWiki:Version control&#039;;&lt;br /&gt;
  var AUTO_START = &#039;&amp;lt;!-- HW-AUTO-START:&#039;;&lt;br /&gt;
&lt;br /&gt;
  function api() {&lt;br /&gt;
    return new mw.Api();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function isSignedInEditor() {&lt;br /&gt;
    return Boolean(mw.config.get(&#039;wgUserName&#039;)) &amp;amp;&amp;amp; mw.config.get(&#039;wgIsProbablyEditable&#039;) !== false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function isVersionAdmin() {&lt;br /&gt;
    var groups = mw.config.get(&#039;wgUserGroups&#039;) || [];&lt;br /&gt;
    return groups.indexOf(&#039;sysop&#039;) !== -1 || groups.indexOf(&#039;interface-admin&#039;) !== -1 || groups.indexOf(&#039;bureaucrat&#039;) !== -1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function normalTitle(title) {&lt;br /&gt;
    return String(title || &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function seriesFor(version) {&lt;br /&gt;
    var parts = String(version || &#039;&#039;).split(&#039;.&#039;);&lt;br /&gt;
    return parts.length === 3 ? parts[0] + &#039;.&#039; + parts[1] : &#039;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function validVersion(version) {&lt;br /&gt;
    return /^\d+\.\d{2}\.\d+$/.test(String(version || &#039;&#039;).trim());&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function getPageText(title) {&lt;br /&gt;
    return api().get({&lt;br /&gt;
      action: &#039;query&#039;,&lt;br /&gt;
      prop: &#039;revisions&#039;,&lt;br /&gt;
      titles: title,&lt;br /&gt;
      rvprop: &#039;content&#039;,&lt;br /&gt;
      rvslots: &#039;main&#039;,&lt;br /&gt;
      formatversion: 2&lt;br /&gt;
    }).then(function (data) {&lt;br /&gt;
      var page = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages &amp;amp;&amp;amp; data.query.pages[0];&lt;br /&gt;
      var revision = page &amp;amp;&amp;amp; page.revisions &amp;amp;&amp;amp; page.revisions[0];&lt;br /&gt;
      var slots = revision &amp;amp;&amp;amp; revision.slots;&lt;br /&gt;
      return {&lt;br /&gt;
        exists: Boolean(page &amp;amp;&amp;amp; !page.missing),&lt;br /&gt;
        text: slots &amp;amp;&amp;amp; slots.main &amp;amp;&amp;amp; typeof slots.main.content === &#039;string&#039; ? slots.main.content : &#039;&#039;&lt;br /&gt;
      };&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function savePage(title, text, summary, createOnly) {&lt;br /&gt;
    var params = {&lt;br /&gt;
      action: &#039;edit&#039;,&lt;br /&gt;
      title: title,&lt;br /&gt;
      text: text,&lt;br /&gt;
      summary: summary,&lt;br /&gt;
      watchlist: &#039;nochange&#039;,&lt;br /&gt;
      formatversion: 2&lt;br /&gt;
    };&lt;br /&gt;
    if (createOnly) {&lt;br /&gt;
      params.createonly = 1;&lt;br /&gt;
    }&lt;br /&gt;
    return api().postWithToken(&#039;csrf&#039;, params);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function readCurrentVersion() {&lt;br /&gt;
    return Promise.all([&lt;br /&gt;
      getPageText(&#039;Template:Current version&#039;),&lt;br /&gt;
      getPageText(&#039;Template:Current version page&#039;)&lt;br /&gt;
    ]).then(function (pages) {&lt;br /&gt;
      var versionMatch = pages[0].text.match(/\d+\.\d{2}\.\d+/);&lt;br /&gt;
      var seriesMatch = pages[1].text.match(/\d+\.\d{2}/);&lt;br /&gt;
      var version = versionMatch ? versionMatch[0] : &#039;&#039;;&lt;br /&gt;
      var series = seriesMatch ? seriesMatch[0] : seriesFor(version);&lt;br /&gt;
      if (!validVersion(version) || !series) {&lt;br /&gt;
        throw new Error(&#039;The current version templates do not contain a valid x.xx.x version.&#039;);&lt;br /&gt;
      }&lt;br /&gt;
      return { version: version, series: series };&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function cleanFieldName(name) {&lt;br /&gt;
    var raw = String(name || &#039;&#039;);&lt;br /&gt;
    var matches = raw.match(/\[([^\]]+)\]/g);&lt;br /&gt;
    var last;&lt;br /&gt;
    if (matches &amp;amp;&amp;amp; matches.length) {&lt;br /&gt;
      last = matches[matches.length - 1].replace(/^\[|\]$/g, &#039;&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
      last = raw;&lt;br /&gt;
    }&lt;br /&gt;
    last = last.replace(/^pf_free_text$/i, &#039;Article content&#039;)&lt;br /&gt;
      .replace(/^free_text$/i, &#039;Article content&#039;)&lt;br /&gt;
      .replace(/_/g, &#039; &#039;)&lt;br /&gt;
      .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
      .trim();&lt;br /&gt;
    return last;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function labelFor(key) {&lt;br /&gt;
    return String(key || &#039;&#039;)&lt;br /&gt;
      .replace(/[_-]+/g, &#039; &#039;)&lt;br /&gt;
      .replace(/\b\w/g, function (letter) { return letter.toUpperCase(); });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function normalizeValue(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value).replace(/\r\n?/g, &#039;\n&#039;).replace(/[ \t]+/g, &#039; &#039;).trim();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function collectFormSnapshot(form) {&lt;br /&gt;
    var values = {};&lt;br /&gt;
    var ignored = /^(wp(EditToken|Starttime|Edittime|AutoSummary|Summary|Section|UltimateParam)|title|action|format|oldid)$/i;&lt;br /&gt;
    Array.prototype.forEach.call(form.querySelectorAll(&#039;input[name], select[name], textarea[name]&#039;), function (field) {&lt;br /&gt;
      var type = String(field.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
      var name = String(field.name || &#039;&#039;);&lt;br /&gt;
      var key;&lt;br /&gt;
      var value;&lt;br /&gt;
      if (!name || field.disabled || ignored.test(name) || /submit|button|reset|image|file/.test(type)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if ((type === &#039;checkbox&#039; || type === &#039;radio&#039;) &amp;amp;&amp;amp; !field.checked) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if (type === &#039;hidden&#039; &amp;amp;&amp;amp; name.indexOf(&#039;[&#039;) === -1 &amp;amp;&amp;amp; name !== &#039;pf_free_text&#039;) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      key = cleanFieldName(name);&lt;br /&gt;
      if (!key || /token|timestamp|summary/i.test(key)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if (field.tagName === &#039;SELECT&#039; &amp;amp;&amp;amp; field.multiple) {&lt;br /&gt;
        value = Array.prototype.filter.call(field.options, function (option) { return option.selected; })&lt;br /&gt;
          .map(function (option) { return option.value; }).join(&#039;; &#039;);&lt;br /&gt;
      } else {&lt;br /&gt;
        value = field.value;&lt;br /&gt;
      }&lt;br /&gt;
      value = normalizeValue(value);&lt;br /&gt;
      if (Object.prototype.hasOwnProperty.call(values, key) &amp;amp;&amp;amp; value &amp;amp;&amp;amp; values[key] !== value) {&lt;br /&gt;
        values[key] = normalizeValue(values[key] + &#039;; &#039; + value);&lt;br /&gt;
      } else if (!Object.prototype.hasOwnProperty.call(values, key) || value) {&lt;br /&gt;
        values[key] = value;&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
    return values;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function snapshotFromWikitext(text) {&lt;br /&gt;
    var source = String(text || &#039;&#039;);&lt;br /&gt;
    var snapshot = {};&lt;br /&gt;
    var match;&lt;br /&gt;
    var paramPattern = /^\s*\|\s*([^=\n]+?)\s*=\s*([\s\S]*?)(?=^\s*\|\s*[^=\n]+?\s*=|^\s*}}|\z)/gm;&lt;br /&gt;
    var cleaned = source.replace(/==\s*Change History\s*==[\s\S]*$/i, &#039;&#039;).trim();&lt;br /&gt;
    while ((match = paramPattern.exec(cleaned))) {&lt;br /&gt;
      snapshot[cleanFieldName(match[1])] = normalizeValue(match[2]);&lt;br /&gt;
    }&lt;br /&gt;
    if (!Object.keys(snapshot).length) {&lt;br /&gt;
      snapshot[&#039;Article content&#039;] = normalizeValue(cleaned);&lt;br /&gt;
    }&lt;br /&gt;
    return snapshot;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function snapshotForForm(form) {&lt;br /&gt;
    var textbox = form.querySelector(&#039;#wpTextbox1, textarea[name=&amp;quot;wpTextbox1&amp;quot;]&#039;);&lt;br /&gt;
    if (textbox &amp;amp;&amp;amp; String(mw.config.get(&#039;wgAction&#039;)) === &#039;edit&#039;) {&lt;br /&gt;
      return snapshotFromWikitext(textbox.value);&lt;br /&gt;
    }&lt;br /&gt;
    return collectFormSnapshot(form);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function sameSnapshot(a, b) {&lt;br /&gt;
    var aKeys = Object.keys(a || {}).sort();&lt;br /&gt;
    var bKeys = Object.keys(b || {}).sort();&lt;br /&gt;
    if (aKeys.length !== bKeys.length) {&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
    return aKeys.every(function (key, index) {&lt;br /&gt;
      return key === bKeys[index] &amp;amp;&amp;amp; normalizeValue(a[key]) === normalizeValue(b[key]);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function encodeSnapshot(snapshot) {&lt;br /&gt;
    return btoa(unescape(encodeURIComponent(JSON.stringify(snapshot || {}))));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function decodeSnapshot(encoded) {&lt;br /&gt;
    try {&lt;br /&gt;
      return JSON.parse(decodeURIComponent(escape(atob(encoded))));&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      return {};&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function historyBlocks(text) {&lt;br /&gt;
    var blocks = [];&lt;br /&gt;
    var pattern = /&amp;lt;!-- HW-AUTO-START:([0-9]+\.[0-9]{2}\.[0-9]+) --&amp;gt;([\s\S]*?)&amp;lt;!-- HW-AUTO-END:\1 --&amp;gt;/g;&lt;br /&gt;
    var match;&lt;br /&gt;
    var snapshotMatch;&lt;br /&gt;
    while ((match = pattern.exec(String(text || &#039;&#039;)))) {&lt;br /&gt;
      snapshotMatch = match[2].match(/&amp;lt;!-- HW-SNAPSHOT:([A-Za-z0-9+/=]+) --&amp;gt;/);&lt;br /&gt;
      blocks.push({&lt;br /&gt;
        version: match[1],&lt;br /&gt;
        full: match[0],&lt;br /&gt;
        index: match.index,&lt;br /&gt;
        created: /\*\s*Page created\./i.test(match[2]),&lt;br /&gt;
        snapshot: snapshotMatch ? decodeSnapshot(snapshotMatch[1]) : {}&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    return blocks;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function shortValue(value) {&lt;br /&gt;
    var clean = normalizeValue(value);&lt;br /&gt;
    if (clean.length &amp;gt; 120) {&lt;br /&gt;
      clean = clean.slice(0, 117) + &#039;...&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    return clean;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function changeMessages(before, after) {&lt;br /&gt;
    var keys = {};&lt;br /&gt;
    var messages = [];&lt;br /&gt;
    Object.keys(before || {}).forEach(function (key) { keys[key] = true; });&lt;br /&gt;
    Object.keys(after || {}).forEach(function (key) { keys[key] = true; });&lt;br /&gt;
    Object.keys(keys).sort().forEach(function (key) {&lt;br /&gt;
      var oldValue = normalizeValue((before || {})[key]);&lt;br /&gt;
      var newValue = normalizeValue((after || {})[key]);&lt;br /&gt;
      var label = labelFor(key);&lt;br /&gt;
      if (oldValue === newValue) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if (!oldValue &amp;amp;&amp;amp; newValue) {&lt;br /&gt;
        messages.push(&#039;Added &#039; + label + &#039;: “&#039; + shortValue(newValue) + &#039;”.&#039;);&lt;br /&gt;
      } else if (oldValue &amp;amp;&amp;amp; !newValue) {&lt;br /&gt;
        messages.push(&#039;Removed &#039; + label + &#039; (previously “&#039; + shortValue(oldValue) + &#039;”).&#039;);&lt;br /&gt;
      } else if (key === &#039;Article content&#039;) {&lt;br /&gt;
        messages.push(&#039;Updated the article content.&#039;);&lt;br /&gt;
      } else {&lt;br /&gt;
        messages.push(&#039;Changed &#039; + label + &#039; from “&#039; + shortValue(oldValue) + &#039;” to “&#039; + shortValue(newValue) + &#039;”.&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
    if (messages.length &amp;gt; 12) {&lt;br /&gt;
      messages = messages.slice(0, 12).concat([&#039;Updated &#039; + (messages.length - 12) + &#039; additional fields.&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return messages;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function buildBlock(version, series, messages, snapshot) {&lt;br /&gt;
    return AUTO_START + version + &#039; --&amp;gt;\n&#039; +&lt;br /&gt;
      &#039;== Version &#039; + version + &#039; ==\n&#039; +&lt;br /&gt;
      &amp;quot;&#039;&#039;&#039;{{Version link|&amp;quot; + version + &#039;|&#039; + series + &amp;quot;}}&#039;&#039;&#039;\n&amp;quot; +&lt;br /&gt;
      messages.map(function (message) { return &#039;* &#039; + message; }).join(&#039;\n&#039;) + &#039;\n&#039; +&lt;br /&gt;
      &#039;&amp;lt;!-- HW-SNAPSHOT:&#039; + encodeSnapshot(snapshot) + &#039; --&amp;gt;\n&#039; +&lt;br /&gt;
      &#039;&amp;lt;!-- HW-AUTO-END:&#039; + version + &#039; --&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function upsertHistory(existingText, versionInfo, afterSnapshot, isNewPage, forceCreated) {&lt;br /&gt;
    var text = String(existingText || &#039;&#039;).trim();&lt;br /&gt;
    var blocks = historyBlocks(text);&lt;br /&gt;
    var current = blocks.filter(function (block) { return block.version === versionInfo.version; })[0];&lt;br /&gt;
    var previous = blocks.filter(function (block) { return block.version !== versionInfo.version &amp;amp;&amp;amp; Object.keys(block.snapshot).length; })[0];&lt;br /&gt;
    var beforeSnapshot = previous ? previous.snapshot : {};&lt;br /&gt;
    var messages;&lt;br /&gt;
    var replacement;&lt;br /&gt;
&lt;br /&gt;
    if (current &amp;amp;&amp;amp; current.created) {&lt;br /&gt;
      messages = [&#039;Page created.&#039;];&lt;br /&gt;
    } else if ((isNewPage || forceCreated) &amp;amp;&amp;amp; !blocks.length) {&lt;br /&gt;
      messages = [&#039;Page created.&#039;];&lt;br /&gt;
    } else {&lt;br /&gt;
      messages = changeMessages(beforeSnapshot, afterSnapshot);&lt;br /&gt;
      if (!messages.length) {&lt;br /&gt;
        return { changed: false, text: text };&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    replacement = buildBlock(versionInfo.version, versionInfo.series, messages, afterSnapshot);&lt;br /&gt;
    if (current) {&lt;br /&gt;
      return { changed: replacement !== current.full, text: text.replace(current.full, replacement) };&lt;br /&gt;
    }&lt;br /&gt;
    return { changed: true, text: replacement + (text ? &#039;\n\n&#039; + text : &#039;&#039;) };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function historyMarkup(versionInfo) {&lt;br /&gt;
    return &#039;\n\n== Change History ==\n{{Change History|&#039; + versionInfo.version + &#039;|&#039; + versionInfo.series + &#039;}}\n&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function ensureHistorySection(title, versionInfo) {&lt;br /&gt;
    return getPageText(title).then(function (page) {&lt;br /&gt;
      if (!page.exists || /{{\s*Change History\b/i.test(page.text)) {&lt;br /&gt;
        return false;&lt;br /&gt;
      }&lt;br /&gt;
      return savePage(title, page.text.replace(/\s+$/, &#039;&#039;) + historyMarkup(versionInfo), &#039;Add version change history section&#039;).then(function () {&lt;br /&gt;
        return true;&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function recordAutomaticHistory(title, versionInfo, afterSnapshot, isNewPage, forceCreated) {&lt;br /&gt;
    var historyTitle = title + &#039;/Change History&#039;;&lt;br /&gt;
    return getPageText(historyTitle).then(function (historyPage) {&lt;br /&gt;
      var updated = upsertHistory(historyPage.text, versionInfo, afterSnapshot, isNewPage, forceCreated);&lt;br /&gt;
      if (!updated.changed) {&lt;br /&gt;
        return false;&lt;br /&gt;
      }&lt;br /&gt;
      return savePage(historyTitle, updated.text + &#039;\n&#039;, &#039;Record automatic change history for &#039; + versionInfo.version).then(function () {&lt;br /&gt;
        return true;&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function setPendingEnsure(title, versionInfo) {&lt;br /&gt;
    try {&lt;br /&gt;
      sessionStorage.setItem(MARKER_KEY, JSON.stringify({&lt;br /&gt;
        title: title,&lt;br /&gt;
        version: versionInfo.version,&lt;br /&gt;
        series: versionInfo.series,&lt;br /&gt;
        time: Date.now()&lt;br /&gt;
      }));&lt;br /&gt;
    } catch (error) {}&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function completePendingEnsure() {&lt;br /&gt;
    var raw;&lt;br /&gt;
    var pending;&lt;br /&gt;
    try {&lt;br /&gt;
      raw = sessionStorage.getItem(MARKER_KEY);&lt;br /&gt;
      pending = raw ? JSON.parse(raw) : null;&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      pending = null;&lt;br /&gt;
    }&lt;br /&gt;
    if (!pending || !pending.title || Date.now() - Number(pending.time || 0) &amp;gt; 300000) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    if (normalTitle(mw.config.get(&#039;wgPageName&#039;)) !== normalTitle(pending.title) || Number(mw.config.get(&#039;wgArticleId&#039;) || 0) === 0) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    sessionStorage.removeItem(MARKER_KEY);&lt;br /&gt;
    ensureHistorySection(pending.title, { version: pending.version, series: pending.series }).then(function (changed) {&lt;br /&gt;
      if (changed) {&lt;br /&gt;
        mw.notify(&#039;The page was saved and its change-history section was added.&#039;, { type: &#039;success&#039; });&lt;br /&gt;
        window.setTimeout(function () { window.location.reload(); }, 700);&lt;br /&gt;
      }&lt;br /&gt;
    }).catch(function (error) {&lt;br /&gt;
      mw.notify(&#039;The page saved, but its change-history section could not be added: &#039; + error.message, { type: &#039;error&#039; });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initTrackedForm() {&lt;br /&gt;
    var namespace = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
    var specialFormMatch = String(window.location.pathname || &#039;&#039;).match(/\/Special:FormEdit\/[^/]+\/(.+)$/i);&lt;br /&gt;
    var form;&lt;br /&gt;
    var initialSnapshot;&lt;br /&gt;
    var isNewPage;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;) || (!specialFormMatch &amp;amp;&amp;amp; namespace !== 0) || /\/Change_History$/.test(String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;))) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    form = document.querySelector(&#039;form#pfForm, form#sfForm, form#editform&#039;);&lt;br /&gt;
    if (!form || form.dataset.hwVersionTrackingReady === &#039;1&#039;) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    form.dataset.hwVersionTrackingReady = &#039;1&#039;;&lt;br /&gt;
    initialSnapshot = snapshotForForm(form);&lt;br /&gt;
    isNewPage = Boolean(specialFormMatch) || Number(mw.config.get(&#039;wgArticleId&#039;) || 0) === 0;&lt;br /&gt;
    pageTitle = specialFormMatch ? normalTitle(decodeURIComponent(specialFormMatch[1])) : normalTitle(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
&lt;br /&gt;
    form.addEventListener(&#039;submit&#039;, function (event) {&lt;br /&gt;
      var submitter = event.submitter;&lt;br /&gt;
      var outgoing;&lt;br /&gt;
      var submitName = submitter ? String(submitter.name || submitter.value || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
      if (form.dataset.hwVersionSubmitting === &#039;1&#039; || /preview|diff|changes|cancel/i.test(submitName)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      outgoing = snapshotForForm(form);&lt;br /&gt;
      if (!isNewPage &amp;amp;&amp;amp; sameSnapshot(initialSnapshot, outgoing)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      event.preventDefault();&lt;br /&gt;
      form.dataset.hwVersionSubmitting = &#039;1&#039;;&lt;br /&gt;
      mw.notify(&#039;Saving the page and recording its version history…&#039;);&lt;br /&gt;
&lt;br /&gt;
      readCurrentVersion().then(function (versionInfo) {&lt;br /&gt;
        return recordAutomaticHistory(pageTitle, versionInfo, outgoing, isNewPage, false).then(function () {&lt;br /&gt;
          setPendingEnsure(pageTitle, versionInfo);&lt;br /&gt;
        });&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        mw.notify(&#039;The page will still be saved, but automatic history failed: &#039; + error.message, { type: &#039;warn&#039; });&lt;br /&gt;
      }).then(function () {&lt;br /&gt;
        var saveField = form.querySelector(&#039;input[type=&amp;quot;hidden&amp;quot;][name=&amp;quot;wpSave&amp;quot;]&#039;);&lt;br /&gt;
        if (!saveField) {&lt;br /&gt;
          saveField = document.createElement(&#039;input&#039;);&lt;br /&gt;
          saveField.type = &#039;hidden&#039;;&lt;br /&gt;
          saveField.name = &#039;wpSave&#039;;&lt;br /&gt;
          saveField.value = &#039;Save page&#039;;&lt;br /&gt;
          form.appendChild(saveField);&lt;br /&gt;
        }&lt;br /&gt;
        form.submit();&lt;br /&gt;
      });&lt;br /&gt;
    }, true);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function allMainPages() {&lt;br /&gt;
    var results = [];&lt;br /&gt;
    function next(apcontinue) {&lt;br /&gt;
      var params = { action: &#039;query&#039;, list: &#039;allpages&#039;, apnamespace: 0, aplimit: &#039;max&#039;, formatversion: 2 };&lt;br /&gt;
      if (apcontinue) {&lt;br /&gt;
        params.apcontinue = apcontinue;&lt;br /&gt;
      }&lt;br /&gt;
      return api().get(params).then(function (data) {&lt;br /&gt;
        results = results.concat((data.query &amp;amp;&amp;amp; data.query.allpages) || []);&lt;br /&gt;
        return data.continue &amp;amp;&amp;amp; data.continue.apcontinue ? next(data.continue.apcontinue) : results;&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    return next();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function backfillOne(title, versionInfo, forceCreated) {&lt;br /&gt;
    if (title === &#039;Change History&#039; || /\/Change History$/.test(title)) {&lt;br /&gt;
      return Promise.resolve({ title: title, skipped: true });&lt;br /&gt;
    }&lt;br /&gt;
    return getPageText(title).then(function (page) {&lt;br /&gt;
      if (!page.exists) {&lt;br /&gt;
        return { title: title, skipped: true };&lt;br /&gt;
      }&lt;br /&gt;
      var snapshot = snapshotFromWikitext(page.text);&lt;br /&gt;
      return Promise.all([&lt;br /&gt;
        ensureHistorySection(title, versionInfo),&lt;br /&gt;
        recordAutomaticHistory(title, versionInfo, snapshot, false, forceCreated)&lt;br /&gt;
      ]).then(function () {&lt;br /&gt;
        return { title: title, skipped: false };&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function runBackfill(versionInfo, forceCreated, progress) {&lt;br /&gt;
    return allMainPages().then(function (pages) {&lt;br /&gt;
      var queue = pages.map(function (page) { return normalTitle(page.title); });&lt;br /&gt;
      var done = 0;&lt;br /&gt;
      var failures = [];&lt;br /&gt;
      var workers = [];&lt;br /&gt;
&lt;br /&gt;
      function worker() {&lt;br /&gt;
        var title = queue.shift();&lt;br /&gt;
        if (!title) {&lt;br /&gt;
          return Promise.resolve();&lt;br /&gt;
        }&lt;br /&gt;
        return backfillOne(title, versionInfo, forceCreated).catch(function (error) {&lt;br /&gt;
          failures.push(title + &#039;: &#039; + error.message);&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
          done += 1;&lt;br /&gt;
          progress(done, pages.length, title, failures.length);&lt;br /&gt;
          return worker();&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      for (var i = 0; i &amp;lt; 3; i += 1) {&lt;br /&gt;
        workers.push(worker());&lt;br /&gt;
      }&lt;br /&gt;
      return Promise.all(workers).then(function () {&lt;br /&gt;
        return { total: pages.length, failures: failures };&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function currentTemplateText(version) {&lt;br /&gt;
    return &#039;&amp;lt;includeonly&amp;gt;&#039; + version + &#039;&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;Sitewide current patch version. Change this through [[HarvestWiki:Version control]]. Version numbers use x.xx.x. [[Category:Version templates]]&amp;lt;/noinclude&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function currentSeriesText(series) {&lt;br /&gt;
    return &#039;&amp;lt;includeonly&amp;gt;&#039; + series + &#039;&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;Parent release-series number for the current patch. Change this through [[HarvestWiki:Version control]]. [[Category:Version templates]]&amp;lt;/noinclude&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function releasePageText(versionInfo, title, date) {&lt;br /&gt;
    return &#039;{{Version\n|version=&#039; + versionInfo.series + &#039;\n|title=&#039; + title + &#039;\n|release date=&#039; + date + &#039;\n|current patch=&#039; + versionInfo.version + &#039;\n}}\n\n== Overview ==\nDescribe the goals and scope of this release series.\n\n== Changelogs ==\n=== &#039; + versionInfo.version + &#039; ===\n&#039; + title + &#039; — &#039; + date + &#039;\n* Add release changes here.\n\n[[Category:Versions]]\n&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function updateReleasePage(versionInfo, title, date) {&lt;br /&gt;
    var pageTitle = &#039;Version &#039; + versionInfo.series;&lt;br /&gt;
    return getPageText(pageTitle).then(function (page) {&lt;br /&gt;
      var text;&lt;br /&gt;
      if (!page.exists) {&lt;br /&gt;
        return savePage(pageTitle, releasePageText(versionInfo, title, date), &#039;Create release page for &#039; + versionInfo.version);&lt;br /&gt;
      }&lt;br /&gt;
      text = page.text;&lt;br /&gt;
      if (/\|\s*current patch\s*=/.test(text)) {&lt;br /&gt;
        text = text.replace(/(\|\s*current patch\s*=\s*)[^\n|}]*/i, &#039;$1&#039; + versionInfo.version);&lt;br /&gt;
      }&lt;br /&gt;
      if (text.indexOf(&#039;=== &#039; + versionInfo.version + &#039; ===&#039;) === -1) {&lt;br /&gt;
        text = text.replace(/\s*\[\[Category:Versions\]\]\s*$/i, &#039;&#039;) +&lt;br /&gt;
          &#039;\n\n=== &#039; + versionInfo.version + &#039; ===\n&#039; + title + &#039; — &#039; + date + &#039;\n* Add release changes here.\n\n[[Category:Versions]]\n&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      return savePage(pageTitle, text, &#039;Set current patch to &#039; + versionInfo.version);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyVersion(version, title, date) {&lt;br /&gt;
    var versionInfo = { version: version, series: seriesFor(version) };&lt;br /&gt;
    return Promise.all([&lt;br /&gt;
      savePage(&#039;Template:Current version&#039;, currentTemplateText(version), &#039;Set current version to &#039; + version),&lt;br /&gt;
      savePage(&#039;Template:Current version page&#039;, currentSeriesText(versionInfo.series), &#039;Set current version series to &#039; + versionInfo.series),&lt;br /&gt;
      updateReleasePage(versionInfo, title, date)&lt;br /&gt;
    ]).then(function () {&lt;br /&gt;
      return versionInfo;&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initVersionAdmin() {&lt;br /&gt;
    var root;&lt;br /&gt;
    var status;&lt;br /&gt;
    var versionInput;&lt;br /&gt;
    var titleInput;&lt;br /&gt;
    var dateInput;&lt;br /&gt;
    var applyButton;&lt;br /&gt;
    var backfillButton;&lt;br /&gt;
    var snapshotButton;&lt;br /&gt;
&lt;br /&gt;
    if (normalTitle(mw.config.get(&#039;wgPageName&#039;)) !== ADMIN_PAGE) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    root = document.getElementById(&#039;hw-version-control-root&#039;);&lt;br /&gt;
    if (!root) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!isVersionAdmin()) {&lt;br /&gt;
      root.innerHTML = &#039;&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Administrator access is required.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;hw-version-admin-authorized&#039;);&lt;br /&gt;
    root.innerHTML = &#039;&amp;lt;div class=&amp;quot;hw-version-admin-card&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;h2&amp;gt;Current release&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;hw-version-admin-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;label&amp;gt;Version number &amp;lt;input id=&amp;quot;hw-version-number&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;0.01.0&amp;quot; pattern=&amp;quot;\d+\.\d{2}\.\d+&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;label&amp;gt;Release title &amp;lt;input id=&amp;quot;hw-version-title&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;Early Development&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;label&amp;gt;Release date &amp;lt;input id=&amp;quot;hw-version-date&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;29 July 2026&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;hw-version-admin-actions&amp;quot;&amp;gt;&amp;lt;button id=&amp;quot;hw-version-apply&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;Apply version and create release page&amp;lt;/button&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;hw-version-admin-card&amp;quot;&amp;gt;&amp;lt;h2&amp;gt;Page history tools&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;p&amp;gt;&amp;lt;button id=&amp;quot;hw-version-backfill&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;Backfill untracked pages as created now&amp;lt;/button&amp;gt;&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;p&amp;gt;&amp;lt;button id=&amp;quot;hw-version-snapshot&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;Snapshot every page in the current version&amp;lt;/button&amp;gt;&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;p class=&amp;quot;hw-version-admin-help&amp;quot;&amp;gt;Backfill is for legacy pages. Snapshot creates a current-version baseline only where the page differs from its last stored snapshot.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div id=&amp;quot;hw-version-status&amp;quot; class=&amp;quot;hw-version-status&amp;quot; role=&amp;quot;status&amp;quot; aria-live=&amp;quot;polite&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    status = document.getElementById(&#039;hw-version-status&#039;);&lt;br /&gt;
    versionInput = document.getElementById(&#039;hw-version-number&#039;);&lt;br /&gt;
    titleInput = document.getElementById(&#039;hw-version-title&#039;);&lt;br /&gt;
    dateInput = document.getElementById(&#039;hw-version-date&#039;);&lt;br /&gt;
    applyButton = document.getElementById(&#039;hw-version-apply&#039;);&lt;br /&gt;
    backfillButton = document.getElementById(&#039;hw-version-backfill&#039;);&lt;br /&gt;
    snapshotButton = document.getElementById(&#039;hw-version-snapshot&#039;);&lt;br /&gt;
&lt;br /&gt;
    readCurrentVersion().then(function (info) {&lt;br /&gt;
      versionInput.value = info.version;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function setBusy(busy) {&lt;br /&gt;
      applyButton.disabled = busy;&lt;br /&gt;
      backfillButton.disabled = busy;&lt;br /&gt;
      snapshotButton.disabled = busy;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function progress(done, total, title, failures) {&lt;br /&gt;
      status.textContent = &#039;Processed &#039; + done + &#039; of &#039; + total + &#039;: &#039; + title + (failures ? &#039; (&#039; + failures + &#039; failed)&#039; : &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    applyButton.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      var version = versionInput.value.trim();&lt;br /&gt;
      var title = titleInput.value.trim();&lt;br /&gt;
      var date = dateInput.value.trim();&lt;br /&gt;
      if (!validVersion(version) || !title || !date) {&lt;br /&gt;
        status.textContent = &#039;Enter a version in x.xx.x format, a title, and a release date.&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      setBusy(true);&lt;br /&gt;
      status.textContent = &#039;Applying &#039; + version + &#039;…&#039;;&lt;br /&gt;
      applyVersion(version, title, date).then(function () {&lt;br /&gt;
        status.textContent = &#039;Current version is now &#039; + version + &#039;. The release page is ready.&#039;;&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        status.textContent = &#039;Version update failed: &#039; + error.message;&lt;br /&gt;
      }).then(function () { setBusy(false); });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    backfillButton.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      setBusy(true);&lt;br /&gt;
      status.textContent = &#039;Reading current version…&#039;;&lt;br /&gt;
      readCurrentVersion().then(function (info) {&lt;br /&gt;
        return runBackfill(info, true, progress);&lt;br /&gt;
      }).then(function (result) {&lt;br /&gt;
        status.textContent = &#039;Backfill complete: &#039; + result.total + &#039; pages checked, &#039; + result.failures.length + &#039; failed.&#039;;&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        status.textContent = &#039;Backfill failed: &#039; + error.message;&lt;br /&gt;
      }).then(function () { setBusy(false); });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    snapshotButton.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      setBusy(true);&lt;br /&gt;
      status.textContent = &#039;Reading current version…&#039;;&lt;br /&gt;
      readCurrentVersion().then(function (info) {&lt;br /&gt;
        return runBackfill(info, false, progress);&lt;br /&gt;
      }).then(function (result) {&lt;br /&gt;
        status.textContent = &#039;Snapshot complete: &#039; + result.total + &#039; pages checked, &#039; + result.failures.length + &#039; failed.&#039;;&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        status.textContent = &#039;Snapshot failed: &#039; + error.message;&lt;br /&gt;
      }).then(function () { setBusy(false); });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initAutomaticVersioning() {&lt;br /&gt;
    completePendingEnsure();&lt;br /&gt;
    initTrackedForm();&lt;br /&gt;
    initVersionAdmin();&lt;br /&gt;
    window.setTimeout(initTrackedForm, 500);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function startAutomaticVersioning() {&lt;br /&gt;
    if (mw.hook) {&lt;br /&gt;
      mw.hook(&#039;wikipage.editform&#039;).add(function () { window.setTimeout(initTrackedForm, 100); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
      document.addEventListener(&#039;DOMContentLoaded&#039;, initAutomaticVersioning, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
      initAutomaticVersioning();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  mw.loader.using([&#039;mediawiki.api&#039;, &#039;mediawiki.util&#039;], startAutomaticVersioning, function (error) {&lt;br /&gt;
    if (window.console &amp;amp;&amp;amp; console.error) {&lt;br /&gt;
      console.error(&#039;HarvestWiki version tracking could not load:&#039;, error);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=MediaWiki:Common.js&amp;diff=566</id>
		<title>MediaWiki:Common.js</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=MediaWiki:Common.js&amp;diff=566"/>
		<updated>2026-07-29T15:32:17Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Track brand-new pages created through Page Forms&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Any JavaScript here will be loaded for all users on every page load. */ /* HarvestWiki dynamic Cargo archive filters */ (function () { &#039;use strict&#039;; function initHerbLocationFilters(root) { var catalogs = []; if (root &amp;amp;&amp;amp; root.matches &amp;amp;&amp;amp; root.matches(&#039;.hw-herb-catalog&#039;)) { catalogs.push(root); } if (root &amp;amp;&amp;amp; root.querySelectorAll) { catalogs = catalogs.concat(Array.prototype.slice.call(root.querySelectorAll(&#039;.hw-herb-catalog&#039;))); } catalogs.forEach(function (catalog) { if (catalog.dataset.hwLocationFiltersReady === &#039;1&#039;) { return; } var table = catalog.querySelector(&#039;table&#039;); var filterBar = document.querySelector(&#039;.hw-herb-filter&#039;); if (!table || !filterBar) { return; } var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;)); var locationIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;location&#039;; }); if (locationIndex &amp;lt; 0) { return; } var rows = Array.prototype.slice.call(table.querySelectorAll(&#039;tbody tr&#039;)); var counts = {}; rows.forEach(function (row) { var cell = row.querySelectorAll(&#039;td&#039;)[locationIndex]; var location = cell ? cell.textContent.trim() : &#039;&#039;; row.dataset.hwLocation = location; if (location) { counts[location] = (counts[location] || 0) + 1; } }); var locations = Object.keys(counts).sort(); var enabled = new Set(locations); filterBar.innerHTML = &#039;&#039;; var showAll = document.createElement(&#039;button&#039;); showAll.type = &#039;button&#039;; showAll.className = &#039;hw-filter-chip hw-filter-chip--all&#039;; showAll.textContent = &#039;Show all&#039;; filterBar.appendChild(showAll); var locationButtons = {}; locations.forEach(function (location) { var button = document.createElement(&#039;button&#039;); button.type = &#039;button&#039;; button.className = &#039;hw-filter-chip is-active&#039;; button.setAttribute(&#039;aria-pressed&#039;, &#039;true&#039;); button.textContent = location + &#039; (&#039; + counts[location] + &#039;)&#039;; button.addEventListener(&#039;click&#039;, function () { if (enabled.has(location)) { enabled.delete(location); } else { enabled.add(location); } applyFilters(); }); locationButtons[location] = button; filterBar.appendChild(button); }); function applyFilters() { rows.forEach(function (row) { row.hidden = !enabled.has(row.dataset.hwLocation); }); locations.forEach(function (location) { var active = enabled.has(location); locationButtons[location].classList.toggle(&#039;is-active&#039;, active); locationButtons[location].setAttribute(&#039;aria-pressed&#039;, active ? &#039;true&#039; : &#039;false&#039;); }); showAll.disabled = enabled.size === locations.length; showAll.classList.toggle(&#039;is-active&#039;, enabled.size === locations.length); } showAll.addEventListener(&#039;click&#039;, function () { enabled = new Set(locations); applyFilters(); }); catalog.dataset.hwLocationFiltersReady = &#039;1&#039;; applyFilters(); }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { initHerbLocationFilters($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki admin-only Herb form UI */ (function () { &#039;use strict&#039;; var groups = mw.config.get(&#039;wgUserGroups&#039;) || []; var categories = mw.config.get(&#039;wgCategories&#039;) || []; if (groups.indexOf(&#039;sysop&#039;) !== -1 || categories.indexOf(&#039;Herbs&#039;) === -1) { return; } function hideHerbFormActions() { [&#039;ca-formedit&#039;, &#039;ca-formcreate&#039;, &#039;ca-formedit-sticky-header&#039;, &#039;ca-formcreate-sticky-header&#039;].forEach(function (id) { var element = document.getElementById(id); if (element) { element.hidden = true; } }); } if (document.readyState === &#039;loading&#039;) { document.addEventListener(&#039;DOMContentLoaded&#039;, hideHerbFormActions); } else { hideHerbFormActions(); } }()); /* HarvestWiki rarity-colored Herb and Crystal table rows */ (function () { &#039;use strict&#039;; var rarities = { common: true, uncommon: true, rare: true, legendary: true, mythic: true, secret: true, mortal: true, refined: true, spirit: true }; function normalizeRarity(value) { var rarity = (value || &#039;&#039;).trim().toLowerCase(); return rarities[rarity] ? rarity : &#039;&#039;; } function applyRarityColors(root) { var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document; var tables = []; if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) { tables.push(scope); } tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;))); tables.forEach(function (table) { var headerCells = Array.prototype.slice.call(table.querySelectorAll(&#039;tr:first-child th, tr:first-child td&#039;)); var itemIndex = headerCells.findIndex(function (cell) { var label = cell.textContent.trim().toLowerCase(); return label === &#039;herb&#039; || label === &#039;crystal&#039; || cell.classList.contains(&#039;field_Herb&#039;) || cell.classList.contains(&#039;field_Crystal&#039;); }); var rarityIndex = headerCells.findIndex(function (cell) { return cell.textContent.trim().toLowerCase() === &#039;rarity&#039; || cell.classList.contains(&#039;field_Rarity&#039;); }); Array.prototype.slice.call(table.rows).forEach(function (row) { var cells = Array.prototype.slice.call(row.cells); var itemCell = row.querySelector(&#039;td.field_Herb, td.field_Crystal&#039;); var rarityCell = row.querySelector(&#039;td.field_Rarity&#039;); if (!itemCell &amp;amp;&amp;amp; itemIndex &amp;gt;= 0) { itemCell = cells[itemIndex]; } if (!rarityCell &amp;amp;&amp;amp; rarityIndex &amp;gt;= 0) { rarityCell = cells[rarityIndex]; } if (!itemCell || !rarityCell || itemCell.tagName !== &#039;TD&#039;) { return; } var rarity = normalizeRarity(rarityCell.textContent); if (!rarity) { return; } row.classList.add(&#039;hw-rarity-row&#039;); row.dataset.hwRarity = rarity; itemCell.classList.add(&#039;hw-rarity-item&#039;); rarityCell.classList.add(&#039;hw-rarity-label&#039;); }); table.dataset.hwRarityColorsReady = &#039;1&#039;; }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { applyRarityColors($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki default Herb rarity order */ (function () { &#039;use strict&#039;; var rarityRank = { common: 0, uncommon: 1, rare: 2, legendary: 3, mythic: 4, secret: 5 }; function sortHerbTables(root) { var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document; var tables = []; if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) { tables.push(scope); } tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;))); tables.forEach(function (table) { if (table.dataset.hwHerbRaritySorted === &#039;1&#039;) { return; } var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;)); if (!headers.length &amp;amp;&amp;amp; table.rows.length) { headers = Array.prototype.slice.call(table.rows[0].cells); } var herbIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;herb&#039; || header.classList.contains(&#039;field_Herb&#039;); }); var rarityIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;rarity&#039; || header.classList.contains(&#039;field_Rarity&#039;); }); if (herbIndex &amp;lt; 0 || rarityIndex &amp;lt; 0) { return; } Array.prototype.slice.call(table.tBodies).forEach(function (body) { var rows = Array.prototype.slice.call(body.rows).map(function (row, index) { var rarityCell = row.cells[rarityIndex]; var rarity = rarityCell ? rarityCell.textContent.trim().toLowerCase() : &#039;&#039;; return { row: row, index: index, rank: Object.prototype.hasOwnProperty.call(rarityRank, rarity) ? rarityRank[rarity] : 999 }; }); rows.sort(function (a, b) { return a.rank - b.rank || a.index - b.index; }); var fragment = document.createDocumentFragment(); rows.forEach(function (entry) { fragment.appendChild(entry.row); }); body.appendChild(fragment); }); table.dataset.hwHerbRaritySorted = &#039;1&#039;; }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { sortHerbTables($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki default Crystal rarity order */ (function () { &#039;use strict&#039;; var crystalRarityRank = { mortal: 0, refined: 1, spirit: 2 }; function sortCrystalTables(root) { var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document; var tables = []; if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) { tables.push(scope); } tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;))); tables.forEach(function (table) { if (table.dataset.hwCrystalRaritySorted === &#039;1&#039;) { return; } var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;)); if (!headers.length &amp;amp;&amp;amp; table.rows.length) { headers = Array.prototype.slice.call(table.rows[0].cells); } var crystalIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;crystal&#039; || header.classList.contains(&#039;field_Crystal&#039;); }); var rarityIndex = headers.findIndex(function (header) { return header.textContent.trim().toLowerCase() === &#039;rarity&#039; || header.classList.contains(&#039;field_Rarity&#039;); }); if (crystalIndex &amp;lt; 0 || rarityIndex &amp;lt; 0) { return; } Array.prototype.slice.call(table.tBodies).forEach(function (body) { var rows = Array.prototype.slice.call(body.rows).map(function (row, index) { var rarityCell = row.cells[rarityIndex]; var rarity = rarityCell ? rarityCell.textContent.trim().toLowerCase() : &#039;&#039;; return { row: row, index: index, rank: Object.prototype.hasOwnProperty.call(crystalRarityRank, rarity) ? crystalRarityRank[rarity] : 999 }; }); rows.sort(function (a, b) { return a.rank - b.rank || a.index - b.index; }); var fragment = document.createDocumentFragment(); rows.forEach(function (entry) { fragment.appendChild(entry.row); }); body.appendChild(fragment); }); table.dataset.hwCrystalRaritySorted = &#039;1&#039;; }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { sortCrystalTables($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki admin-only Crystal form UI */ (function () { &#039;use strict&#039;; var groups = mw.config.get(&#039;wgUserGroups&#039;) || []; var categories = mw.config.get(&#039;wgCategories&#039;) || []; if (groups.indexOf(&#039;sysop&#039;) !== -1 || categories.indexOf(&#039;Crystals&#039;) === -1) { return; } function hideCrystalFormActions() { [&#039;ca-formedit&#039;, &#039;ca-formcreate&#039;, &#039;ca-formedit-sticky-header&#039;, &#039;ca-formcreate-sticky-header&#039;].forEach(function (id) { var element = document.getElementById(id); if (element) { element.hidden = true; } }); } if (document.readyState === &#039;loading&#039;) { document.addEventListener(&#039;DOMContentLoaded&#039;, hideCrystalFormActions); } else { hideCrystalFormActions(); } }()); /* HarvestWiki admin-only Weather form UI */ (function () { &#039;use strict&#039;; var groups = mw.config.get(&#039;wgUserGroups&#039;) || []; var categories = mw.config.get(&#039;wgCategories&#039;) || []; if (groups.indexOf(&#039;sysop&#039;) !== -1 || categories.indexOf(&#039;Weather&#039;) === -1) { return; } function hideWeatherFormActions() { [&#039;ca-formedit&#039;, &#039;ca-formcreate&#039;, &#039;ca-formedit-sticky-header&#039;, &#039;ca-formcreate-sticky-header&#039;].forEach(function (id) { var element = document.getElementById(id); if (element) { element.hidden = true; } }); } if (document.readyState === &#039;loading&#039;) { document.addEventListener(&#039;DOMContentLoaded&#039;, hideWeatherFormActions); } else { hideWeatherFormActions(); } }()); /* HarvestWiki four-zodiac celestial calendar */ (function () { &#039;use strict&#039;; var anchor = Date.UTC(2026, 0, 1, 0, 0, 0); var seasonLength = 8 * 60 * 60 * 1000; var seasonNames = [ &#039;Year of the Dragon&#039;, &#039;Year of the Tiger&#039;, &#039;Year of the Phoenix&#039;, &#039;Year of the Tortoise&#039; ]; function formatDuration(milliseconds) { var seconds = Math.max(0, Math.ceil(milliseconds / 1000)); var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds % 3600) / 60); var remainder = seconds % 60; return String(hours).padStart(2, &#039;0&#039;) + &#039;:&#039; + String(minutes).padStart(2, &#039;0&#039;) + &#039;:&#039; + String(remainder).padStart(2, &#039;0&#039;); } function initZodiacCycles(root) { var widgets = []; if (root &amp;amp;&amp;amp; root.matches &amp;amp;&amp;amp; root.matches(&#039;[data-hw-zodiac-cycle]&#039;)) { widgets.push(root); } if (root &amp;amp;&amp;amp; root.querySelectorAll) { widgets = widgets.concat(Array.prototype.slice.call(root.querySelectorAll(&#039;[data-hw-zodiac-cycle]&#039;))); } widgets.forEach(function (widget) { if (widget.dataset.hwZodiacReady === &#039;1&#039;) { return; } widget.dataset.hwZodiacReady = &#039;1&#039;; var currentLabel = widget.querySelector(&#039;[data-hw-zodiac-current]&#039;); var countdown = widget.querySelector(&#039;[data-hw-zodiac-countdown]&#039;); var progress = widget.querySelector(&#039;[data-hw-zodiac-progress]&#039;); var seasons = Array.prototype.slice.call(widget.querySelectorAll(&#039;[data-hw-zodiac-season]&#039;)); function updateZodiacCycle() { var now = Date.now(); var step = Math.floor((now - anchor) / seasonLength); var currentIndex = ((step % seasonNames.length) + seasonNames.length) % seasonNames.length; var nextBoundary = anchor + ((step + 1) * seasonLength); var remaining = Math.max(0, nextBoundary - now); var elapsedInSeason = ((now - anchor) % seasonLength + seasonLength) % seasonLength; if (currentLabel) { currentLabel.textContent = seasonNames[currentIndex]; } if (countdown) { countdown.textContent = formatDuration(remaining); countdown.title = &#039;Changes at &#039; + new Date(nextBoundary).toLocaleString(); } if (progress) { progress.style.width = ((elapsedInSeason / seasonLength) * 100).toFixed(3) + &#039;%&#039;; } seasons.forEach(function (season, index) { var offset = (index - currentIndex + seasonNames.length) % seasonNames.length; var state = season.querySelector(&#039;[data-hw-zodiac-state]&#039;); var startsIn = remaining + (Math.max(0, offset - 1) * seasonLength); var changeAt = offset === 0 ? nextBoundary : now + startsIn; season.classList.toggle(&#039;is-current&#039;, offset === 0); season.setAttribute(&#039;aria-current&#039;, offset === 0 ? &#039;true&#039; : &#039;false&#039;); season.title = (offset === 0 ? &#039;Ends &#039; : &#039;Begins &#039;) + new Date(changeAt).toLocaleString(); if (state) { if (offset === 0) { state.textContent = &#039;Current · ends in &#039; + formatDuration(remaining); } else if (offset === 1) { state.textContent = &#039;Next · starts in &#039; + formatDuration(startsIn); } else { state.textContent = &#039;Starts in &#039; + formatDuration(startsIn); } } }); } updateZodiacCycle(); window.setInterval(updateZodiacCycle, 1000); }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { initZodiacCycles($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }()); /* HarvestWiki multi-page editor controls and archive refresh */ (function () { &#039;use strict&#039;; function initBulkEditors(root) { var grids = []; if (root &amp;amp;&amp;amp; root.matches &amp;amp;&amp;amp; root.matches(&#039;.pfSpreadsheet&#039;)) { grids.push(root); } if (root &amp;amp;&amp;amp; root.querySelectorAll) { grids = grids.concat(Array.prototype.slice.call(root.querySelectorAll(&#039;.pfSpreadsheet&#039;))); } grids.forEach(function (grid) { if (grid.dataset.hwBulkEditorReady === &#039;1&#039;) { return; } grid.dataset.hwBulkEditorReady = &#039;1&#039;; var parameters = new URLSearchParams(window.location.search); var templateName = parameters.get(&#039;template&#039;) || grid.id.replace(/Grid$/, &#039;&#039;); var archivePage = templateName === &#039;Herb&#039; ? &#039;Herbs&#039; : (templateName === &#039;Crystal&#039; ? &#039;Crystals&#039; : null); var userGroups = mw.config.get(&#039;wgUserGroups&#039;) || []; var canDelete = archivePage &amp;amp;&amp;amp; userGroups.indexOf(&#039;sysop&#039;) !== -1; function purgeArchive() { if (!archivePage || typeof mw.Api !== &#039;function&#039;) { return Promise.resolve(); } return new mw.Api().post({ action: &#039;purge&#039;, titles: archivePage, formatversion: 2 }); } function wireRowActions() { Array.prototype.slice.call(grid.querySelectorAll(&#039;a.save-changes&#039;)).forEach(function (link) { if (link.dataset.hwBulkSaveReady === &#039;1&#039;) { return; } link.dataset.hwBulkSaveReady = &#039;1&#039;; link.setAttribute(&#039;title&#039;, &#039;Save this row&#039;); link.setAttribute(&#039;aria-label&#039;, &#039;Save this row&#039;); link.addEventListener(&#039;click&#039;, function () { link.classList.remove(&#039;is-saved&#039;); link.classList.add(&#039;is-saving&#039;); function refreshArchive() { purgeArchive().then(function () { link.classList.remove(&#039;is-saving&#039;); link.classList.add(&#039;is-saved&#039;); window.setTimeout(function () { link.classList.remove(&#039;is-saved&#039;); }, 2200); }); } window.setTimeout(refreshArchive, 1600); window.setTimeout(refreshArchive, 4200); }); }); if (!canDelete) { return; } Array.prototype.slice.call(grid.querySelectorAll(&#039;tbody tr&#039;)).forEach(function (row) { var pageCell = row.querySelector(&#039;td[data-x=&amp;quot;0&amp;quot;]&#039;); var actionBox = row.querySelector(&#039;.save-or-cancel&#039;); if (!pageCell || !actionBox || actionBox.querySelector(&#039;.hw-delete-row&#039;)) { return; } var pageTitle = pageCell.textContent.trim(); if (!pageTitle) { return; } var deleteButton = document.createElement(&#039;button&#039;); deleteButton.type = &#039;button&#039;; deleteButton.className = &#039;hw-delete-row&#039;; deleteButton.textContent = &#039;Delete&#039;; deleteButton.title = &#039;Delete page &#039; + pageTitle; deleteButton.setAttribute(&#039;aria-label&#039;, &#039;Delete page &#039; + pageTitle); deleteButton.addEventListener(&#039;click&#039;, function () { var confirmed = window.confirm( &#039;Permanently delete &amp;quot;&#039; + pageTitle + &#039;&amp;quot;?\n\n&#039; + &#039;This removes its wiki page and all table data. This cannot be undone.&#039; ); if (!confirmed) { return; } deleteButton.disabled = true; deleteButton.classList.add(&#039;is-deleting&#039;); deleteButton.textContent = &#039;Deleting…&#039;; new mw.Api().postWithToken(&#039;csrf&#039;, { action: &#039;delete&#039;, title: pageTitle, reason: &#039;Deleted from the multi-page editor&#039;, formatversion: 2 }).then(function () { return purgeArchive(); }).then(function () { deleteButton.textContent = &#039;Deleted&#039;; window.setTimeout(function () { window.location.reload(); }, 700); }, function () { deleteButton.disabled = false; deleteButton.classList.remove(&#039;is-deleting&#039;); deleteButton.textContent = &#039;Delete&#039;; mw.notify(&#039;Could not delete &amp;quot;&#039; + pageTitle + &#039;&amp;quot;. The record was not removed.&#039;, { type: &#039;error&#039; }); }); }); actionBox.appendChild(deleteButton); }); } wireRowActions(); if (window.MutationObserver) { new MutationObserver(wireRowActions).observe(grid, { childList: true, subtree: true }); } if (!grid.querySelector(&#039;.hw-bulk-editor-note&#039;)) { var note = document.createElement(&#039;div&#039;); note.className = &#039;hw-bulk-editor-note&#039;; note.textContent = &#039;Scroll sideways to reach every field. Use Save at the right of each row.&#039; + (archivePage ? &#039; Saved rows automatically refresh the &#039; + archivePage + &#039; index.&#039; : &#039;&#039;) + (canDelete ? &#039; Administrators can permanently remove a record with Delete.&#039; : &#039;&#039;); grid.appendChild(note); } }); } mw.hook(&#039;wikipage.content&#039;).add(function ($content) { initBulkEditors($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document); }); }());&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki automatic Herb, Crystal, Mutation, Shovel, Weather, TOD, and Zodiac link colors */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  var rarityColors = {&lt;br /&gt;
    common: &#039;#e5e7e7&#039;,&lt;br /&gt;
    uncommon: &#039;#87ff97&#039;,&lt;br /&gt;
    rare: &#039;#6d90ff&#039;,&lt;br /&gt;
    legendary: &#039;#fff687&#039;,&lt;br /&gt;
    mythic: &#039;#ff79cb&#039;,&lt;br /&gt;
    secret: &#039;#12005c&#039;,&lt;br /&gt;
    mortal: &#039;#cef1e8&#039;,&lt;br /&gt;
    refined: &#039;#464747&#039;,&lt;br /&gt;
    spirit: &#039;#00c3f2&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var mutationColors = {&lt;br /&gt;
    silver: &#039;#eef1f1&#039;,&lt;br /&gt;
    golden: &#039;#f4d05b&#039;,&lt;br /&gt;
    vitality: &#039;#e96869&#039;,&lt;br /&gt;
    vampiric: &#039;#de002b&#039;,&lt;br /&gt;
    icy: &#039;#9ee7d8&#039;,&lt;br /&gt;
    frozen: &#039;#6ee8ff&#039;,&lt;br /&gt;
    spirit: &#039;#ffabf6&#039;,&lt;br /&gt;
    royal: &#039;#ffec31&#039;,&lt;br /&gt;
    imperial: &#039;#b600ff&#039;,&lt;br /&gt;
    century: &#039;#5369ff&#039;,&lt;br /&gt;
    millenium: &#039;#6c47ff&#039;,&lt;br /&gt;
    eon: &#039;#7f15ff&#039;,&lt;br /&gt;
    sovereign: &#039;#ff26a5&#039;,&lt;br /&gt;
    supreme: &#039;#ed13ff&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var worldColors = {&lt;br /&gt;
    clear: &#039;#f1cb4f&#039;,&lt;br /&gt;
    cloudy: &#039;#949ba5&#039;,&lt;br /&gt;
    rain: &#039;#60a5e2&#039;,&lt;br /&gt;
    storm: &#039;#525964&#039;,&lt;br /&gt;
    thunderstorm: &#039;#f8d652&#039;,&lt;br /&gt;
    &#039;dao tempest&#039;: &#039;#b9422f&#039;,&lt;br /&gt;
    sunrise: &#039;#ef9d49&#039;,&lt;br /&gt;
    daytime: &#039;#63ace0&#039;,&lt;br /&gt;
    sunset: &#039;#df6447&#039;,&lt;br /&gt;
    night: &#039;#abb6f9&#039;,&lt;br /&gt;
    &#039;year of the dragon&#039;: &#039;#ffb86f&#039;,&lt;br /&gt;
    &#039;year of the tiger&#039;: &#039;#f7f672&#039;,&lt;br /&gt;
    &#039;year of the phoenix&#039;: &#039;#f87b84&#039;,&lt;br /&gt;
    &#039;year of the tortoise&#039;: &#039;#4fc57d&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var shovelColors = {&lt;br /&gt;
    &#039;basic shovel&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &amp;quot;journeyman&#039;s shovel&amp;quot;: &#039;#ffffff&#039;,&lt;br /&gt;
    &#039;root breaker&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &#039;steel spade&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &#039;autumn leaf&#039;: &#039;#ffffff&#039;,&lt;br /&gt;
    &amp;quot;king&#039;s destiny&amp;quot;: &#039;#ffffff&#039;&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  var entityColors = {&lt;br /&gt;
    &#039;ancient woe herb&#039;: rarityColors.rare,&lt;br /&gt;
    &#039;breathless dawn herb&#039;: rarityColors.uncommon,&lt;br /&gt;
    &#039;crimson sun grass&#039;: rarityColors.common,&lt;br /&gt;
    &#039;fleeting dusk herb&#039;: rarityColors.uncommon,&lt;br /&gt;
    &#039;frigid moon grass&#039;: rarityColors.common,&lt;br /&gt;
    &#039;pursuing longevity herb&#039;: rarityColors.mythic,&lt;br /&gt;
    &#039;sparkling joy herb&#039;: rarityColors.rare,&lt;br /&gt;
    &#039;yang lotus&#039;: rarityColors.legendary,&lt;br /&gt;
    &#039;yin lotus&#039;: rarityColors.legendary,&lt;br /&gt;
    &#039;air crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;earth crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;flame crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;water crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;wood crystal&#039;: rarityColors.mortal,&lt;br /&gt;
    &#039;magma crystal&#039;: rarityColors.refined,&lt;br /&gt;
    &#039;mist crystal&#039;: rarityColors.refined,&lt;br /&gt;
    &#039;longevity crystal&#039;: rarityColors.spirit,&lt;br /&gt;
    clear: worldColors.clear,&lt;br /&gt;
    cloudy: worldColors.cloudy,&lt;br /&gt;
    rain: worldColors.rain,&lt;br /&gt;
    storm: worldColors.storm,&lt;br /&gt;
    thunderstorm: worldColors.thunderstorm,&lt;br /&gt;
    &#039;dao tempest&#039;: worldColors[&#039;dao tempest&#039;],&lt;br /&gt;
    sunrise: worldColors.sunrise,&lt;br /&gt;
    daytime: worldColors.daytime,&lt;br /&gt;
    sunset: worldColors.sunset,&lt;br /&gt;
    night: worldColors.night,&lt;br /&gt;
    &#039;year of the dragon&#039;: worldColors[&#039;year of the dragon&#039;],&lt;br /&gt;
    &#039;year of the tiger&#039;: worldColors[&#039;year of the tiger&#039;],&lt;br /&gt;
    &#039;year of the phoenix&#039;: worldColors[&#039;year of the phoenix&#039;],&lt;br /&gt;
    &#039;year of the tortoise&#039;: worldColors[&#039;year of the tortoise&#039;],&lt;br /&gt;
    &#039;basic shovel&#039;: shovelColors[&#039;basic shovel&#039;],&lt;br /&gt;
    &amp;quot;journeyman&#039;s shovel&amp;quot;: shovelColors[&amp;quot;journeyman&#039;s shovel&amp;quot;],&lt;br /&gt;
    &#039;root breaker&#039;: shovelColors[&#039;root breaker&#039;],&lt;br /&gt;
    &#039;steel spade&#039;: shovelColors[&#039;steel spade&#039;],&lt;br /&gt;
    &#039;autumn leaf&#039;: shovelColors[&#039;autumn leaf&#039;],&lt;br /&gt;
    &amp;quot;king&#039;s destiny&amp;quot;: shovelColors[&amp;quot;king&#039;s destiny&amp;quot;]&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  function normalizeTitle(value) {&lt;br /&gt;
    return (value || &#039;&#039;).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).trim().toLowerCase();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function titleFromLink(link) {&lt;br /&gt;
    var href = link.getAttribute(&#039;href&#039;) || &#039;&#039;;&lt;br /&gt;
    if (!href || href.charAt(0) === &#039;#&#039;) {&lt;br /&gt;
      return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
      var url = new URL(href, window.location.origin);&lt;br /&gt;
      if (url.origin !== window.location.origin) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
      if (!title &amp;amp;&amp;amp; url.pathname.indexOf(&#039;/index.php/&#039;) === 0) {&lt;br /&gt;
        title = decodeURIComponent(url.pathname.slice(&#039;/index.php/&#039;.length));&lt;br /&gt;
      }&lt;br /&gt;
      return normalizeTitle((title || &#039;&#039;).split(&#039;#&#039;)[0]);&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function cargoQuery(table, fields) {&lt;br /&gt;
    return mw.loader.using(&#039;mediawiki.api&#039;).then(function () {&lt;br /&gt;
      return new mw.Api().get({&lt;br /&gt;
        action: &#039;cargoquery&#039;,&lt;br /&gt;
        tables: table,&lt;br /&gt;
        fields: fields,&lt;br /&gt;
        limit: 500,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
      });&lt;br /&gt;
    }).then(function (data) {&lt;br /&gt;
      return data &amp;amp;&amp;amp; data.cargoquery ? data.cargoquery : [];&lt;br /&gt;
    }, function () {&lt;br /&gt;
      return [];&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function buildColorMap() {&lt;br /&gt;
    return Promise.all([&lt;br /&gt;
      cargoQuery(&#039;Herbs&#039;, &#039;_pageName=Page,Rarity&#039;),&lt;br /&gt;
      cargoQuery(&#039;Crystals&#039;, &#039;_pageName=Page,Rarity&#039;),&lt;br /&gt;
      cargoQuery(&#039;Mutations&#039;, &#039;_pageName=Page,Color&#039;),&lt;br /&gt;
      cargoQuery(&#039;Weathers&#039;, &#039;_pageName=Page,Color&#039;),&lt;br /&gt;
      cargoQuery(&#039;Shovels&#039;, &#039;_pageName=Page,Color&#039;)&lt;br /&gt;
    ]).then(function (groups) {&lt;br /&gt;
      var colorMap = Object.assign({}, entityColors);&lt;br /&gt;
&lt;br /&gt;
      groups[0].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        var color = rarityColors[normalizeTitle(data.Rarity)];&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[1].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        var color = rarityColors[normalizeTitle(data.Rarity)];&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[2].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; data.Color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = data.Color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[3].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; data.Color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = data.Color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      groups[4].forEach(function (row) {&lt;br /&gt;
        var data = row.title || {};&lt;br /&gt;
        if (data.Page &amp;amp;&amp;amp; data.Color) {&lt;br /&gt;
          colorMap[normalizeTitle(data.Page)] = data.Color;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      Object.keys(mutationColors).forEach(function (title) {&lt;br /&gt;
        colorMap[title] = mutationColors[title];&lt;br /&gt;
      });&lt;br /&gt;
&lt;br /&gt;
      return colorMap;&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyLinkColors(root, colorMap) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var links = [];&lt;br /&gt;
    if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;a[href]&#039;)) {&lt;br /&gt;
      links.push(scope);&lt;br /&gt;
    }&lt;br /&gt;
    links = links.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;a[href]&#039;)));&lt;br /&gt;
&lt;br /&gt;
    links.forEach(function (link) {&lt;br /&gt;
      var color = colorMap[titleFromLink(link)];&lt;br /&gt;
      if (!color || link.classList.contains(&#039;new&#039;)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      link.classList.add(&#039;hw-colored-link&#039;);&lt;br /&gt;
      link.style.setProperty(&#039;--hw-link-color&#039;, color);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyMutationRows(root, colorMap) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var tables = [];&lt;br /&gt;
    if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) {&lt;br /&gt;
      tables.push(scope);&lt;br /&gt;
    }&lt;br /&gt;
    tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;)));&lt;br /&gt;
&lt;br /&gt;
    tables.forEach(function (table) {&lt;br /&gt;
      var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;));&lt;br /&gt;
      if (!headers.length &amp;amp;&amp;amp; table.rows.length) {&lt;br /&gt;
        headers = Array.prototype.slice.call(table.rows[0].cells);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var mutationIndex = headers.findIndex(function (header) {&lt;br /&gt;
        return header.textContent.trim().toLowerCase() === &#039;mutation&#039; ||&lt;br /&gt;
          header.classList.contains(&#039;field_Mutation&#039;);&lt;br /&gt;
      });&lt;br /&gt;
      if (mutationIndex &amp;lt; 0) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      Array.prototype.slice.call(table.tBodies).forEach(function (body) {&lt;br /&gt;
        Array.prototype.slice.call(body.rows).forEach(function (row) {&lt;br /&gt;
          var mutationCell = row.cells[mutationIndex];&lt;br /&gt;
          var mutationLink = mutationCell ? mutationCell.querySelector(&#039;a[href]&#039;) : null;&lt;br /&gt;
          var color = mutationLink ? colorMap[titleFromLink(mutationLink)] : &#039;&#039;;&lt;br /&gt;
          if (!mutationCell || !color) {&lt;br /&gt;
            return;&lt;br /&gt;
          }&lt;br /&gt;
          row.classList.add(&#039;hw-mutation-row&#039;);&lt;br /&gt;
          row.style.setProperty(&#039;--hw-mutation-row-color&#039;, color);&lt;br /&gt;
          mutationCell.classList.add(&#039;hw-mutation-item&#039;);&lt;br /&gt;
        });&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyShovelRows(root, colorMap) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var tables = [];&lt;br /&gt;
    if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;table&#039;)) {&lt;br /&gt;
      tables.push(scope);&lt;br /&gt;
    }&lt;br /&gt;
    tables = tables.concat(Array.prototype.slice.call(scope.querySelectorAll(&#039;table&#039;)));&lt;br /&gt;
&lt;br /&gt;
    tables.forEach(function (table) {&lt;br /&gt;
      var headers = Array.prototype.slice.call(table.querySelectorAll(&#039;thead th&#039;));&lt;br /&gt;
      if (!headers.length &amp;amp;&amp;amp; table.rows.length) {&lt;br /&gt;
        headers = Array.prototype.slice.call(table.rows[0].cells);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var shovelIndex = headers.findIndex(function (header) {&lt;br /&gt;
        return header.textContent.trim().toLowerCase() === &#039;shovel&#039; ||&lt;br /&gt;
          header.classList.contains(&#039;field_Shovel&#039;);&lt;br /&gt;
      });&lt;br /&gt;
      if (shovelIndex &amp;lt; 0) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      Array.prototype.slice.call(table.tBodies).forEach(function (body) {&lt;br /&gt;
        Array.prototype.slice.call(body.rows).forEach(function (row) {&lt;br /&gt;
          var shovelCell = row.cells[shovelIndex];&lt;br /&gt;
          var shovelLink = shovelCell ? shovelCell.querySelector(&#039;a[href]&#039;) : null;&lt;br /&gt;
          var color = shovelLink ? colorMap[titleFromLink(shovelLink)] : &#039;&#039;;&lt;br /&gt;
          if (!shovelCell || !color) {&lt;br /&gt;
            return;&lt;br /&gt;
          }&lt;br /&gt;
          row.classList.add(&#039;hw-shovel-row&#039;);&lt;br /&gt;
          row.style.setProperty(&#039;--hw-shovel-row-color&#039;, color);&lt;br /&gt;
          shovelCell.classList.add(&#039;hw-shovel-item&#039;);&lt;br /&gt;
        });&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  var immediateColorMap = Object.assign({}, entityColors);&lt;br /&gt;
  Object.keys(mutationColors).forEach(function (title) {&lt;br /&gt;
    immediateColorMap[title] = mutationColors[title];&lt;br /&gt;
  });&lt;br /&gt;
  Object.keys(worldColors).forEach(function (title) {&lt;br /&gt;
    immediateColorMap[title] = worldColors[title];&lt;br /&gt;
  });&lt;br /&gt;
  Object.keys(shovelColors).forEach(function (title) {&lt;br /&gt;
    immediateColorMap[title] = shovelColors[title];&lt;br /&gt;
  });&lt;br /&gt;
  var colorMapPromise = buildColorMap();&lt;br /&gt;
&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
    var root = $content &amp;amp;&amp;amp; $content[0] ? $content[0] : document;&lt;br /&gt;
    applyMutationRows(root, immediateColorMap);&lt;br /&gt;
    applyShovelRows(root, immediateColorMap);&lt;br /&gt;
    applyLinkColors(root, immediateColorMap);&lt;br /&gt;
    colorMapPromise.then(function (colorMap) {&lt;br /&gt;
      applyMutationRows(root, colorMap);&lt;br /&gt;
      applyShovelRows(root, colorMap);&lt;br /&gt;
      applyLinkColors(root, colorMap);&lt;br /&gt;
    });&lt;br /&gt;
  });&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki hierarchical Citizen popout menu */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  var groups = [&lt;br /&gt;
    {&lt;br /&gt;
      key: &#039;herb&#039;,&lt;br /&gt;
      title: &#039;Herb&#039;,&lt;br /&gt;
      note: &#039;Harvesting &amp;amp; cultivation&#039;,&lt;br /&gt;
      glyph: &#039;草&#039;,&lt;br /&gt;
      pages: [&#039;Herbs&#039;, &#039;Mutations&#039;, &#039;Crystals&#039;, &#039;Shovels&#039;]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
      key: &#039;world&#039;,&lt;br /&gt;
      title: &#039;World&#039;,&lt;br /&gt;
      note: &#039;Places, people &amp;amp; conditions&#039;,&lt;br /&gt;
      glyph: &#039;界&#039;,&lt;br /&gt;
      pages: [&#039;Weather&#039;, &#039;Locations&#039;, &#039;NPCs&#039;]&lt;br /&gt;
    }&lt;br /&gt;
  ];&lt;br /&gt;
&lt;br /&gt;
  function directLink(item) {&lt;br /&gt;
    var children = item ? item.children : null;&lt;br /&gt;
    var index;&lt;br /&gt;
    if (!children) {&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
    for (index = 0; index &amp;lt; children.length; index += 1) {&lt;br /&gt;
      if (children[index].tagName === &#039;A&#039;) {&lt;br /&gt;
        return children[index];&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    return null;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function linkTitle(link) {&lt;br /&gt;
    var path;&lt;br /&gt;
    if (!link) {&lt;br /&gt;
      return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    try {&lt;br /&gt;
      path = new URL(link.href, location.href).pathname.split(&#039;/&#039;).pop() || &#039;&#039;;&lt;br /&gt;
      return decodeURIComponent(path).replace(/_/g, &#039; &#039;);&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      return (link.textContent || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function closeOtherGroups(root, keep) {&lt;br /&gt;
    Array.prototype.slice.call(root.querySelectorAll(&#039;.hw-menu-group.is-open&#039;)).forEach(function (group) {&lt;br /&gt;
      if (group !== keep) {&lt;br /&gt;
        group.classList.remove(&#039;is-open&#039;);&lt;br /&gt;
        group.querySelector(&#039;.hw-menu-trigger&#039;).setAttribute(&#039;aria-expanded&#039;, &#039;false&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function createGroup(config, items, root) {&lt;br /&gt;
    var group = document.createElement(&#039;li&#039;);&lt;br /&gt;
    var trigger = document.createElement(&#039;button&#039;);&lt;br /&gt;
    var glyph = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var copy = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var title = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var note = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var chevron = document.createElement(&#039;span&#039;);&lt;br /&gt;
    var submenu = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    var closeTimer = null;&lt;br /&gt;
&lt;br /&gt;
    function cancelClose() {&lt;br /&gt;
      if (closeTimer !== null) {&lt;br /&gt;
        window.clearTimeout(closeTimer);&lt;br /&gt;
        closeTimer = null;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function closeGroup() {&lt;br /&gt;
      group.classList.remove(&#039;is-open&#039;);&lt;br /&gt;
      trigger.setAttribute(&#039;aria-expanded&#039;, &#039;false&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleClose() {&lt;br /&gt;
      cancelClose();&lt;br /&gt;
      closeTimer = window.setTimeout(function () {&lt;br /&gt;
        closeTimer = null;&lt;br /&gt;
        if (!group.matches(&#039;:hover&#039;) &amp;amp;&amp;amp; !group.contains(document.activeElement)) {&lt;br /&gt;
          closeGroup();&lt;br /&gt;
        }&lt;br /&gt;
      }, 240);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    group.className = &#039;hw-menu-group hw-menu-group--&#039; + config.key;&lt;br /&gt;
    trigger.type = &#039;button&#039;;&lt;br /&gt;
    trigger.className = &#039;hw-menu-trigger&#039;;&lt;br /&gt;
    trigger.setAttribute(&#039;aria-expanded&#039;, &#039;false&#039;);&lt;br /&gt;
    trigger.setAttribute(&#039;aria-haspopup&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
    glyph.className = &#039;hw-menu-glyph&#039;;&lt;br /&gt;
    glyph.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
    glyph.textContent = config.glyph;&lt;br /&gt;
&lt;br /&gt;
    copy.className = &#039;hw-menu-trigger-copy&#039;;&lt;br /&gt;
    title.className = &#039;hw-menu-trigger-title&#039;;&lt;br /&gt;
    title.textContent = config.title;&lt;br /&gt;
    note.className = &#039;hw-menu-trigger-note&#039;;&lt;br /&gt;
    note.textContent = config.note;&lt;br /&gt;
    copy.appendChild(title);&lt;br /&gt;
    copy.appendChild(note);&lt;br /&gt;
&lt;br /&gt;
    chevron.className = &#039;hw-menu-chevron&#039;;&lt;br /&gt;
    chevron.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
    chevron.textContent = &#039;›&#039;;&lt;br /&gt;
&lt;br /&gt;
    trigger.appendChild(glyph);&lt;br /&gt;
    trigger.appendChild(copy);&lt;br /&gt;
    trigger.appendChild(chevron);&lt;br /&gt;
&lt;br /&gt;
    submenu.className = &#039;hw-menu-submenu&#039;;&lt;br /&gt;
    submenu.setAttribute(&#039;aria-label&#039;, config.title + &#039; pages&#039;);&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
      submenu.appendChild(item);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    trigger.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      var willOpen = !group.classList.contains(&#039;is-open&#039;);&lt;br /&gt;
      cancelClose();&lt;br /&gt;
      closeOtherGroups(root, group);&lt;br /&gt;
      group.classList.toggle(&#039;is-open&#039;, willOpen);&lt;br /&gt;
      trigger.setAttribute(&#039;aria-expanded&#039;, willOpen ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
      cancelClose();&lt;br /&gt;
      closeOtherGroups(root, group);&lt;br /&gt;
      group.classList.add(&#039;is-open&#039;);&lt;br /&gt;
      trigger.setAttribute(&#039;aria-expanded&#039;, &#039;true&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;mouseleave&#039;, scheduleClose);&lt;br /&gt;
    submenu.addEventListener(&#039;mouseenter&#039;, cancelClose);&lt;br /&gt;
    submenu.addEventListener(&#039;mouseleave&#039;, scheduleClose);&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;focusout&#039;, function (event) {&lt;br /&gt;
      if (!group.contains(event.relatedTarget)) {&lt;br /&gt;
        cancelClose();&lt;br /&gt;
        closeGroup();&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    group.appendChild(trigger);&lt;br /&gt;
    group.appendChild(submenu);&lt;br /&gt;
    return group;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function enhanceMenu() {&lt;br /&gt;
    var portlet = document.querySelector(&#039;#p-HarvestWiki&#039;);&lt;br /&gt;
    var list;&lt;br /&gt;
    var items;&lt;br /&gt;
    var byTitle = {};&lt;br /&gt;
    var newList;&lt;br /&gt;
    var complete;&lt;br /&gt;
&lt;br /&gt;
    if (!portlet || portlet.classList.contains(&#039;hw-menu-ready&#039;)) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    list = portlet.querySelector(&#039;.citizen-menu__content &amp;gt; ul&#039;);&lt;br /&gt;
    if (!list) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items = Array.prototype.slice.call(list.children);&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
      var link = directLink(item);&lt;br /&gt;
      var title = linkTitle(link);&lt;br /&gt;
      if (title) {&lt;br /&gt;
        byTitle[title.toLowerCase()] = item;&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    complete = groups.every(function (config) {&lt;br /&gt;
      return config.pages.every(function (page) {&lt;br /&gt;
        return Boolean(byTitle[page.toLowerCase()]);&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
    if (!complete) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    newList = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    newList.className = &#039;hw-menu-root&#039;;&lt;br /&gt;
    groups.forEach(function (config) {&lt;br /&gt;
      var groupItems = config.pages.map(function (page) {&lt;br /&gt;
        return byTitle[page.toLowerCase()];&lt;br /&gt;
      });&lt;br /&gt;
      newList.appendChild(createGroup(config, groupItems, newList));&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    list.replaceWith(newList);&lt;br /&gt;
    portlet.classList.add(&#039;hw-menu-ready&#039;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initMenu() {&lt;br /&gt;
    enhanceMenu();&lt;br /&gt;
    new MutationObserver(enhanceMenu).observe(document.body, {&lt;br /&gt;
      childList: true,&lt;br /&gt;
      subtree: true&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
      var root;&lt;br /&gt;
      if (event.key !== &#039;Escape&#039;) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      root = document.querySelector(&#039;.hw-menu-root&#039;);&lt;br /&gt;
      if (root) {&lt;br /&gt;
        closeOtherGroups(root, null);&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initMenu, { once: true });&lt;br /&gt;
  } else {&lt;br /&gt;
    initMenu();&lt;br /&gt;
  }&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki automatic page release history */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function addHistoryToNewPage() {&lt;br /&gt;
    var action = mw.config.get(&#039;wgAction&#039;);&lt;br /&gt;
    var articleId = Number(mw.config.get(&#039;wgArticleId&#039;) || 0);&lt;br /&gt;
    var namespace = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
    var pageName = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;);&lt;br /&gt;
    var textbox;&lt;br /&gt;
    var marker;&lt;br /&gt;
&lt;br /&gt;
    if (action !== &#039;edit&#039; || articleId !== 0 || namespace !== 0 || /\/Change_History$/.test(pageName)) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    textbox = document.getElementById(&#039;wpTextbox1&#039;);&lt;br /&gt;
    if (!textbox || /\{\{\s*Change History\b/i.test(textbox.value)) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    marker = &#039;\n\n== Change History ==\n{{Change History|{{&#039; + &#039;subst:Current version}}|{{&#039; + &#039;subst:Current version page}}}}\n&#039;;&lt;br /&gt;
    textbox.value = textbox.value.replace(/\s+$/, &#039;&#039;) + marker;&lt;br /&gt;
    textbox.dispatchEvent(new Event(&#039;input&#039;, { bubbles: true }));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function enableHistoryEditorTools() {&lt;br /&gt;
    var userName = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
    var probablyEditable = mw.config.get(&#039;wgIsProbablyEditable&#039;);&lt;br /&gt;
    var releases;&lt;br /&gt;
&lt;br /&gt;
    if (!userName || probablyEditable === false) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    releases = document.querySelectorAll(&#039;.hw-change-history-release&#039;);&lt;br /&gt;
    if (!releases.length) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;hw-history-tools-enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
    releases.forEach(function (release) {&lt;br /&gt;
      var addLink = release.querySelector(&#039;.hw-history-add a, a.hw-history-add&#039;);&lt;br /&gt;
      var version = String(release.getAttribute(&#039;data-current-version&#039;) || &#039;&#039;).trim();&lt;br /&gt;
      var series = String(release.getAttribute(&#039;data-current-series&#039;) || &#039;&#039;).trim();&lt;br /&gt;
      var historyPage = String(release.getAttribute(&#039;data-history-page&#039;) || &#039;&#039;).trim();&lt;br /&gt;
      var params;&lt;br /&gt;
&lt;br /&gt;
      if (!addLink || !version || !series || !historyPage) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      params = new URLSearchParams();&lt;br /&gt;
      params.set(&#039;title&#039;, historyPage);&lt;br /&gt;
      params.set(&#039;action&#039;, &#039;edit&#039;);&lt;br /&gt;
      params.set(&#039;section&#039;, &#039;new&#039;);&lt;br /&gt;
      params.set(&#039;preload&#039;, &#039;Template:Change History/entry&#039;);&lt;br /&gt;
      params.set(&#039;preloadtitle&#039;, &#039;Version &#039; + version);&lt;br /&gt;
      params.append(&#039;preloadparams[]&#039;, version);&lt;br /&gt;
      params.append(&#039;preloadparams[]&#039;, series);&lt;br /&gt;
      addLink.href = mw.util.wikiScript(&#039;index&#039;) + &#039;?&#039; + params.toString();&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initVersionHistory() {&lt;br /&gt;
    addHistoryToNewPage();&lt;br /&gt;
    enableHistoryEditorTools();&lt;br /&gt;
    window.setTimeout(addHistoryToNewPage, 350);&lt;br /&gt;
    window.setTimeout(enableHistoryEditorTools, 350);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.editform&#039;).add(function () {&lt;br /&gt;
      window.setTimeout(addHistoryToNewPage, 100);&lt;br /&gt;
    });&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
      window.setTimeout(enableHistoryEditorTools, 100);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initVersionHistory, { once: true });&lt;br /&gt;
  } else {&lt;br /&gt;
    initVersionHistory();&lt;br /&gt;
  }&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
/* HarvestWiki version snapshots for source edits, Page Forms, and administrators */&lt;br /&gt;
(function () {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  var MARKER_KEY = &#039;hwVersionHistoryPending&#039;;&lt;br /&gt;
  var ADMIN_PAGE = &#039;HarvestWiki:Version control&#039;;&lt;br /&gt;
  var AUTO_START = &#039;&amp;lt;!-- HW-AUTO-START:&#039;;&lt;br /&gt;
&lt;br /&gt;
  function api() {&lt;br /&gt;
    return new mw.Api();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function isSignedInEditor() {&lt;br /&gt;
    return Boolean(mw.config.get(&#039;wgUserName&#039;)) &amp;amp;&amp;amp; mw.config.get(&#039;wgIsProbablyEditable&#039;) !== false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function isVersionAdmin() {&lt;br /&gt;
    var groups = mw.config.get(&#039;wgUserGroups&#039;) || [];&lt;br /&gt;
    return groups.indexOf(&#039;sysop&#039;) !== -1 || groups.indexOf(&#039;interface-admin&#039;) !== -1 || groups.indexOf(&#039;bureaucrat&#039;) !== -1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function normalTitle(title) {&lt;br /&gt;
    return String(title || &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function seriesFor(version) {&lt;br /&gt;
    var parts = String(version || &#039;&#039;).split(&#039;.&#039;);&lt;br /&gt;
    return parts.length === 3 ? parts[0] + &#039;.&#039; + parts[1] : &#039;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function validVersion(version) {&lt;br /&gt;
    return /^\d+\.\d{2}\.\d+$/.test(String(version || &#039;&#039;).trim());&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function getPageText(title) {&lt;br /&gt;
    return api().get({&lt;br /&gt;
      action: &#039;query&#039;,&lt;br /&gt;
      prop: &#039;revisions&#039;,&lt;br /&gt;
      titles: title,&lt;br /&gt;
      rvprop: &#039;content&#039;,&lt;br /&gt;
      rvslots: &#039;main&#039;,&lt;br /&gt;
      formatversion: 2&lt;br /&gt;
    }).then(function (data) {&lt;br /&gt;
      var page = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages &amp;amp;&amp;amp; data.query.pages[0];&lt;br /&gt;
      var revision = page &amp;amp;&amp;amp; page.revisions &amp;amp;&amp;amp; page.revisions[0];&lt;br /&gt;
      var slots = revision &amp;amp;&amp;amp; revision.slots;&lt;br /&gt;
      return {&lt;br /&gt;
        exists: Boolean(page &amp;amp;&amp;amp; !page.missing),&lt;br /&gt;
        text: slots &amp;amp;&amp;amp; slots.main &amp;amp;&amp;amp; typeof slots.main.content === &#039;string&#039; ? slots.main.content : &#039;&#039;&lt;br /&gt;
      };&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function savePage(title, text, summary, createOnly) {&lt;br /&gt;
    var params = {&lt;br /&gt;
      action: &#039;edit&#039;,&lt;br /&gt;
      title: title,&lt;br /&gt;
      text: text,&lt;br /&gt;
      summary: summary,&lt;br /&gt;
      watchlist: &#039;nochange&#039;,&lt;br /&gt;
      formatversion: 2&lt;br /&gt;
    };&lt;br /&gt;
    if (createOnly) {&lt;br /&gt;
      params.createonly = 1;&lt;br /&gt;
    }&lt;br /&gt;
    return api().postWithToken(&#039;csrf&#039;, params);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function readCurrentVersion() {&lt;br /&gt;
    return Promise.all([&lt;br /&gt;
      getPageText(&#039;Template:Current version&#039;),&lt;br /&gt;
      getPageText(&#039;Template:Current version page&#039;)&lt;br /&gt;
    ]).then(function (pages) {&lt;br /&gt;
      var versionMatch = pages[0].text.match(/\d+\.\d{2}\.\d+/);&lt;br /&gt;
      var seriesMatch = pages[1].text.match(/\d+\.\d{2}/);&lt;br /&gt;
      var version = versionMatch ? versionMatch[0] : &#039;&#039;;&lt;br /&gt;
      var series = seriesMatch ? seriesMatch[0] : seriesFor(version);&lt;br /&gt;
      if (!validVersion(version) || !series) {&lt;br /&gt;
        throw new Error(&#039;The current version templates do not contain a valid x.xx.x version.&#039;);&lt;br /&gt;
      }&lt;br /&gt;
      return { version: version, series: series };&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function cleanFieldName(name) {&lt;br /&gt;
    var raw = String(name || &#039;&#039;);&lt;br /&gt;
    var matches = raw.match(/\[([^\]]+)\]/g);&lt;br /&gt;
    var last;&lt;br /&gt;
    if (matches &amp;amp;&amp;amp; matches.length) {&lt;br /&gt;
      last = matches[matches.length - 1].replace(/^\[|\]$/g, &#039;&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
      last = raw;&lt;br /&gt;
    }&lt;br /&gt;
    last = last.replace(/^pf_free_text$/i, &#039;Article content&#039;)&lt;br /&gt;
      .replace(/^free_text$/i, &#039;Article content&#039;)&lt;br /&gt;
      .replace(/_/g, &#039; &#039;)&lt;br /&gt;
      .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
      .trim();&lt;br /&gt;
    return last;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function labelFor(key) {&lt;br /&gt;
    return String(key || &#039;&#039;)&lt;br /&gt;
      .replace(/[_-]+/g, &#039; &#039;)&lt;br /&gt;
      .replace(/\b\w/g, function (letter) { return letter.toUpperCase(); });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function normalizeValue(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value).replace(/\r\n?/g, &#039;\n&#039;).replace(/[ \t]+/g, &#039; &#039;).trim();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function collectFormSnapshot(form) {&lt;br /&gt;
    var values = {};&lt;br /&gt;
    var ignored = /^(wp(EditToken|Starttime|Edittime|AutoSummary|Summary|Section|UltimateParam)|title|action|format|oldid)$/i;&lt;br /&gt;
    Array.prototype.forEach.call(form.querySelectorAll(&#039;input[name], select[name], textarea[name]&#039;), function (field) {&lt;br /&gt;
      var type = String(field.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
      var name = String(field.name || &#039;&#039;);&lt;br /&gt;
      var key;&lt;br /&gt;
      var value;&lt;br /&gt;
      if (!name || field.disabled || ignored.test(name) || /submit|button|reset|image|file/.test(type)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if ((type === &#039;checkbox&#039; || type === &#039;radio&#039;) &amp;amp;&amp;amp; !field.checked) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if (type === &#039;hidden&#039; &amp;amp;&amp;amp; name.indexOf(&#039;[&#039;) === -1 &amp;amp;&amp;amp; name !== &#039;pf_free_text&#039;) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      key = cleanFieldName(name);&lt;br /&gt;
      if (!key || /token|timestamp|summary/i.test(key)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if (field.tagName === &#039;SELECT&#039; &amp;amp;&amp;amp; field.multiple) {&lt;br /&gt;
        value = Array.prototype.filter.call(field.options, function (option) { return option.selected; })&lt;br /&gt;
          .map(function (option) { return option.value; }).join(&#039;; &#039;);&lt;br /&gt;
      } else {&lt;br /&gt;
        value = field.value;&lt;br /&gt;
      }&lt;br /&gt;
      value = normalizeValue(value);&lt;br /&gt;
      if (Object.prototype.hasOwnProperty.call(values, key) &amp;amp;&amp;amp; value &amp;amp;&amp;amp; values[key] !== value) {&lt;br /&gt;
        values[key] = normalizeValue(values[key] + &#039;; &#039; + value);&lt;br /&gt;
      } else if (!Object.prototype.hasOwnProperty.call(values, key) || value) {&lt;br /&gt;
        values[key] = value;&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
    return values;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function snapshotFromWikitext(text) {&lt;br /&gt;
    var source = String(text || &#039;&#039;);&lt;br /&gt;
    var snapshot = {};&lt;br /&gt;
    var match;&lt;br /&gt;
    var paramPattern = /^\s*\|\s*([^=\n]+?)\s*=\s*([\s\S]*?)(?=^\s*\|\s*[^=\n]+?\s*=|^\s*}}|\z)/gm;&lt;br /&gt;
    var cleaned = source.replace(/==\s*Change History\s*==[\s\S]*$/i, &#039;&#039;).trim();&lt;br /&gt;
    while ((match = paramPattern.exec(cleaned))) {&lt;br /&gt;
      snapshot[cleanFieldName(match[1])] = normalizeValue(match[2]);&lt;br /&gt;
    }&lt;br /&gt;
    if (!Object.keys(snapshot).length) {&lt;br /&gt;
      snapshot[&#039;Article content&#039;] = normalizeValue(cleaned);&lt;br /&gt;
    }&lt;br /&gt;
    return snapshot;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function snapshotForForm(form) {&lt;br /&gt;
    var textbox = form.querySelector(&#039;#wpTextbox1, textarea[name=&amp;quot;wpTextbox1&amp;quot;]&#039;);&lt;br /&gt;
    if (textbox &amp;amp;&amp;amp; String(mw.config.get(&#039;wgAction&#039;)) === &#039;edit&#039;) {&lt;br /&gt;
      return snapshotFromWikitext(textbox.value);&lt;br /&gt;
    }&lt;br /&gt;
    return collectFormSnapshot(form);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function sameSnapshot(a, b) {&lt;br /&gt;
    var aKeys = Object.keys(a || {}).sort();&lt;br /&gt;
    var bKeys = Object.keys(b || {}).sort();&lt;br /&gt;
    if (aKeys.length !== bKeys.length) {&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
    return aKeys.every(function (key, index) {&lt;br /&gt;
      return key === bKeys[index] &amp;amp;&amp;amp; normalizeValue(a[key]) === normalizeValue(b[key]);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function encodeSnapshot(snapshot) {&lt;br /&gt;
    return btoa(unescape(encodeURIComponent(JSON.stringify(snapshot || {}))));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function decodeSnapshot(encoded) {&lt;br /&gt;
    try {&lt;br /&gt;
      return JSON.parse(decodeURIComponent(escape(atob(encoded))));&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      return {};&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function historyBlocks(text) {&lt;br /&gt;
    var blocks = [];&lt;br /&gt;
    var pattern = /&amp;lt;!-- HW-AUTO-START:([0-9]+\.[0-9]{2}\.[0-9]+) --&amp;gt;([\s\S]*?)&amp;lt;!-- HW-AUTO-END:\1 --&amp;gt;/g;&lt;br /&gt;
    var match;&lt;br /&gt;
    var snapshotMatch;&lt;br /&gt;
    while ((match = pattern.exec(String(text || &#039;&#039;)))) {&lt;br /&gt;
      snapshotMatch = match[2].match(/&amp;lt;!-- HW-SNAPSHOT:([A-Za-z0-9+/=]+) --&amp;gt;/);&lt;br /&gt;
      blocks.push({&lt;br /&gt;
        version: match[1],&lt;br /&gt;
        full: match[0],&lt;br /&gt;
        index: match.index,&lt;br /&gt;
        created: /\*\s*Page created\./i.test(match[2]),&lt;br /&gt;
        snapshot: snapshotMatch ? decodeSnapshot(snapshotMatch[1]) : {}&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    return blocks;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function shortValue(value) {&lt;br /&gt;
    var clean = normalizeValue(value);&lt;br /&gt;
    if (clean.length &amp;gt; 120) {&lt;br /&gt;
      clean = clean.slice(0, 117) + &#039;...&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    return clean;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function changeMessages(before, after) {&lt;br /&gt;
    var keys = {};&lt;br /&gt;
    var messages = [];&lt;br /&gt;
    Object.keys(before || {}).forEach(function (key) { keys[key] = true; });&lt;br /&gt;
    Object.keys(after || {}).forEach(function (key) { keys[key] = true; });&lt;br /&gt;
    Object.keys(keys).sort().forEach(function (key) {&lt;br /&gt;
      var oldValue = normalizeValue((before || {})[key]);&lt;br /&gt;
      var newValue = normalizeValue((after || {})[key]);&lt;br /&gt;
      var label = labelFor(key);&lt;br /&gt;
      if (oldValue === newValue) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      if (!oldValue &amp;amp;&amp;amp; newValue) {&lt;br /&gt;
        messages.push(&#039;Added &#039; + label + &#039;: “&#039; + shortValue(newValue) + &#039;”.&#039;);&lt;br /&gt;
      } else if (oldValue &amp;amp;&amp;amp; !newValue) {&lt;br /&gt;
        messages.push(&#039;Removed &#039; + label + &#039; (previously “&#039; + shortValue(oldValue) + &#039;”).&#039;);&lt;br /&gt;
      } else if (key === &#039;Article content&#039;) {&lt;br /&gt;
        messages.push(&#039;Updated the article content.&#039;);&lt;br /&gt;
      } else {&lt;br /&gt;
        messages.push(&#039;Changed &#039; + label + &#039; from “&#039; + shortValue(oldValue) + &#039;” to “&#039; + shortValue(newValue) + &#039;”.&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
    if (messages.length &amp;gt; 12) {&lt;br /&gt;
      messages = messages.slice(0, 12).concat([&#039;Updated &#039; + (messages.length - 12) + &#039; additional fields.&#039;]);&lt;br /&gt;
    }&lt;br /&gt;
    return messages;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function buildBlock(version, series, messages, snapshot) {&lt;br /&gt;
    return AUTO_START + version + &#039; --&amp;gt;\n&#039; +&lt;br /&gt;
      &#039;== Version &#039; + version + &#039; ==\n&#039; +&lt;br /&gt;
      &amp;quot;&#039;&#039;&#039;{{Version link|&amp;quot; + version + &#039;|&#039; + series + &amp;quot;}}&#039;&#039;&#039;\n&amp;quot; +&lt;br /&gt;
      messages.map(function (message) { return &#039;* &#039; + message; }).join(&#039;\n&#039;) + &#039;\n&#039; +&lt;br /&gt;
      &#039;&amp;lt;!-- HW-SNAPSHOT:&#039; + encodeSnapshot(snapshot) + &#039; --&amp;gt;\n&#039; +&lt;br /&gt;
      &#039;&amp;lt;!-- HW-AUTO-END:&#039; + version + &#039; --&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function upsertHistory(existingText, versionInfo, afterSnapshot, isNewPage, forceCreated) {&lt;br /&gt;
    var text = String(existingText || &#039;&#039;).trim();&lt;br /&gt;
    var blocks = historyBlocks(text);&lt;br /&gt;
    var current = blocks.filter(function (block) { return block.version === versionInfo.version; })[0];&lt;br /&gt;
    var previous = blocks.filter(function (block) { return block.version !== versionInfo.version &amp;amp;&amp;amp; Object.keys(block.snapshot).length; })[0];&lt;br /&gt;
    var beforeSnapshot = previous ? previous.snapshot : {};&lt;br /&gt;
    var messages;&lt;br /&gt;
    var replacement;&lt;br /&gt;
&lt;br /&gt;
    if (current &amp;amp;&amp;amp; current.created) {&lt;br /&gt;
      messages = [&#039;Page created.&#039;];&lt;br /&gt;
    } else if ((isNewPage || forceCreated) &amp;amp;&amp;amp; !blocks.length) {&lt;br /&gt;
      messages = [&#039;Page created.&#039;];&lt;br /&gt;
    } else {&lt;br /&gt;
      messages = changeMessages(beforeSnapshot, afterSnapshot);&lt;br /&gt;
      if (!messages.length) {&lt;br /&gt;
        return { changed: false, text: text };&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    replacement = buildBlock(versionInfo.version, versionInfo.series, messages, afterSnapshot);&lt;br /&gt;
    if (current) {&lt;br /&gt;
      return { changed: replacement !== current.full, text: text.replace(current.full, replacement) };&lt;br /&gt;
    }&lt;br /&gt;
    return { changed: true, text: replacement + (text ? &#039;\n\n&#039; + text : &#039;&#039;) };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function historyMarkup(versionInfo) {&lt;br /&gt;
    return &#039;\n\n== Change History ==\n{{Change History|&#039; + versionInfo.version + &#039;|&#039; + versionInfo.series + &#039;}}\n&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function ensureHistorySection(title, versionInfo) {&lt;br /&gt;
    return getPageText(title).then(function (page) {&lt;br /&gt;
      if (!page.exists || /{{\s*Change History\b/i.test(page.text)) {&lt;br /&gt;
        return false;&lt;br /&gt;
      }&lt;br /&gt;
      return savePage(title, page.text.replace(/\s+$/, &#039;&#039;) + historyMarkup(versionInfo), &#039;Add version change history section&#039;).then(function () {&lt;br /&gt;
        return true;&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function recordAutomaticHistory(title, versionInfo, afterSnapshot, isNewPage, forceCreated) {&lt;br /&gt;
    var historyTitle = title + &#039;/Change History&#039;;&lt;br /&gt;
    return getPageText(historyTitle).then(function (historyPage) {&lt;br /&gt;
      var updated = upsertHistory(historyPage.text, versionInfo, afterSnapshot, isNewPage, forceCreated);&lt;br /&gt;
      if (!updated.changed) {&lt;br /&gt;
        return false;&lt;br /&gt;
      }&lt;br /&gt;
      return savePage(historyTitle, updated.text + &#039;\n&#039;, &#039;Record automatic change history for &#039; + versionInfo.version).then(function () {&lt;br /&gt;
        return true;&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function setPendingEnsure(title, versionInfo) {&lt;br /&gt;
    try {&lt;br /&gt;
      sessionStorage.setItem(MARKER_KEY, JSON.stringify({&lt;br /&gt;
        title: title,&lt;br /&gt;
        version: versionInfo.version,&lt;br /&gt;
        series: versionInfo.series,&lt;br /&gt;
        time: Date.now()&lt;br /&gt;
      }));&lt;br /&gt;
    } catch (error) {}&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function completePendingEnsure() {&lt;br /&gt;
    var raw;&lt;br /&gt;
    var pending;&lt;br /&gt;
    try {&lt;br /&gt;
      raw = sessionStorage.getItem(MARKER_KEY);&lt;br /&gt;
      pending = raw ? JSON.parse(raw) : null;&lt;br /&gt;
    } catch (error) {&lt;br /&gt;
      pending = null;&lt;br /&gt;
    }&lt;br /&gt;
    if (!pending || !pending.title || Date.now() - Number(pending.time || 0) &amp;gt; 300000) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    if (normalTitle(mw.config.get(&#039;wgPageName&#039;)) !== normalTitle(pending.title) || Number(mw.config.get(&#039;wgArticleId&#039;) || 0) === 0) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    sessionStorage.removeItem(MARKER_KEY);&lt;br /&gt;
    ensureHistorySection(pending.title, { version: pending.version, series: pending.series }).then(function (changed) {&lt;br /&gt;
      if (changed) {&lt;br /&gt;
        mw.notify(&#039;The page was saved and its change-history section was added.&#039;, { type: &#039;success&#039; });&lt;br /&gt;
        window.setTimeout(function () { window.location.reload(); }, 700);&lt;br /&gt;
      }&lt;br /&gt;
    }).catch(function (error) {&lt;br /&gt;
      mw.notify(&#039;The page saved, but its change-history section could not be added: &#039; + error.message, { type: &#039;error&#039; });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initTrackedForm() {&lt;br /&gt;
    var namespace = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
    var specialFormMatch = String(window.location.pathname || &#039;&#039;).match(/\/Special:FormEdit\/[^/]+\/(.+)$/i);&lt;br /&gt;
    var form;&lt;br /&gt;
    var initialSnapshot;&lt;br /&gt;
    var isNewPage;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;) || (!specialFormMatch &amp;amp;&amp;amp; namespace !== 0) || /\/Change_History$/.test(String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;))) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    form = document.querySelector(&#039;form#pfForm, form#sfForm, form#editform&#039;);&lt;br /&gt;
    if (!form || form.dataset.hwVersionTrackingReady === &#039;1&#039;) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    form.dataset.hwVersionTrackingReady = &#039;1&#039;;&lt;br /&gt;
    initialSnapshot = snapshotForForm(form);&lt;br /&gt;
    isNewPage = Boolean(specialFormMatch) || Number(mw.config.get(&#039;wgArticleId&#039;) || 0) === 0;&lt;br /&gt;
    pageTitle = specialFormMatch ? normalTitle(decodeURIComponent(specialFormMatch[1])) : normalTitle(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
&lt;br /&gt;
    form.addEventListener(&#039;submit&#039;, function (event) {&lt;br /&gt;
      var submitter = event.submitter;&lt;br /&gt;
      var outgoing;&lt;br /&gt;
      var submitName = submitter ? String(submitter.name || submitter.value || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
      if (form.dataset.hwVersionSubmitting === &#039;1&#039; || /preview|diff|changes|cancel/i.test(submitName)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      outgoing = snapshotForForm(form);&lt;br /&gt;
      if (!isNewPage &amp;amp;&amp;amp; sameSnapshot(initialSnapshot, outgoing)) {&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      event.preventDefault();&lt;br /&gt;
      form.dataset.hwVersionSubmitting = &#039;1&#039;;&lt;br /&gt;
      mw.notify(&#039;Saving the page and recording its version history…&#039;);&lt;br /&gt;
&lt;br /&gt;
      readCurrentVersion().then(function (versionInfo) {&lt;br /&gt;
        return recordAutomaticHistory(pageTitle, versionInfo, outgoing, isNewPage, false).then(function () {&lt;br /&gt;
          setPendingEnsure(pageTitle, versionInfo);&lt;br /&gt;
        });&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        mw.notify(&#039;The page will still be saved, but automatic history failed: &#039; + error.message, { type: &#039;warn&#039; });&lt;br /&gt;
      }).then(function () {&lt;br /&gt;
        if (form.requestSubmit) {&lt;br /&gt;
          form.requestSubmit(submitter || undefined);&lt;br /&gt;
        } else {&lt;br /&gt;
          form.submit();&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
    }, true);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function allMainPages() {&lt;br /&gt;
    var results = [];&lt;br /&gt;
    function next(apcontinue) {&lt;br /&gt;
      var params = { action: &#039;query&#039;, list: &#039;allpages&#039;, apnamespace: 0, aplimit: &#039;max&#039;, formatversion: 2 };&lt;br /&gt;
      if (apcontinue) {&lt;br /&gt;
        params.apcontinue = apcontinue;&lt;br /&gt;
      }&lt;br /&gt;
      return api().get(params).then(function (data) {&lt;br /&gt;
        results = results.concat((data.query &amp;amp;&amp;amp; data.query.allpages) || []);&lt;br /&gt;
        return data.continue &amp;amp;&amp;amp; data.continue.apcontinue ? next(data.continue.apcontinue) : results;&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    return next();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function backfillOne(title, versionInfo, forceCreated) {&lt;br /&gt;
    if (title === &#039;Change History&#039; || /\/Change History$/.test(title)) {&lt;br /&gt;
      return Promise.resolve({ title: title, skipped: true });&lt;br /&gt;
    }&lt;br /&gt;
    return getPageText(title).then(function (page) {&lt;br /&gt;
      if (!page.exists) {&lt;br /&gt;
        return { title: title, skipped: true };&lt;br /&gt;
      }&lt;br /&gt;
      var snapshot = snapshotFromWikitext(page.text);&lt;br /&gt;
      return Promise.all([&lt;br /&gt;
        ensureHistorySection(title, versionInfo),&lt;br /&gt;
        recordAutomaticHistory(title, versionInfo, snapshot, false, forceCreated)&lt;br /&gt;
      ]).then(function () {&lt;br /&gt;
        return { title: title, skipped: false };&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function runBackfill(versionInfo, forceCreated, progress) {&lt;br /&gt;
    return allMainPages().then(function (pages) {&lt;br /&gt;
      var queue = pages.map(function (page) { return normalTitle(page.title); });&lt;br /&gt;
      var done = 0;&lt;br /&gt;
      var failures = [];&lt;br /&gt;
      var workers = [];&lt;br /&gt;
&lt;br /&gt;
      function worker() {&lt;br /&gt;
        var title = queue.shift();&lt;br /&gt;
        if (!title) {&lt;br /&gt;
          return Promise.resolve();&lt;br /&gt;
        }&lt;br /&gt;
        return backfillOne(title, versionInfo, forceCreated).catch(function (error) {&lt;br /&gt;
          failures.push(title + &#039;: &#039; + error.message);&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
          done += 1;&lt;br /&gt;
          progress(done, pages.length, title, failures.length);&lt;br /&gt;
          return worker();&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      for (var i = 0; i &amp;lt; 3; i += 1) {&lt;br /&gt;
        workers.push(worker());&lt;br /&gt;
      }&lt;br /&gt;
      return Promise.all(workers).then(function () {&lt;br /&gt;
        return { total: pages.length, failures: failures };&lt;br /&gt;
      });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function currentTemplateText(version) {&lt;br /&gt;
    return &#039;&amp;lt;includeonly&amp;gt;&#039; + version + &#039;&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;Sitewide current patch version. Change this through [[HarvestWiki:Version control]]. Version numbers use x.xx.x. [[Category:Version templates]]&amp;lt;/noinclude&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function currentSeriesText(series) {&lt;br /&gt;
    return &#039;&amp;lt;includeonly&amp;gt;&#039; + series + &#039;&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;Parent release-series number for the current patch. Change this through [[HarvestWiki:Version control]]. [[Category:Version templates]]&amp;lt;/noinclude&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function releasePageText(versionInfo, title, date) {&lt;br /&gt;
    return &#039;{{Version\n|version=&#039; + versionInfo.series + &#039;\n|title=&#039; + title + &#039;\n|release date=&#039; + date + &#039;\n|current patch=&#039; + versionInfo.version + &#039;\n}}\n\n== Overview ==\nDescribe the goals and scope of this release series.\n\n== Changelogs ==\n=== &#039; + versionInfo.version + &#039; ===\n&#039; + title + &#039; — &#039; + date + &#039;\n* Add release changes here.\n\n[[Category:Versions]]\n&#039;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function updateReleasePage(versionInfo, title, date) {&lt;br /&gt;
    var pageTitle = &#039;Version &#039; + versionInfo.series;&lt;br /&gt;
    return getPageText(pageTitle).then(function (page) {&lt;br /&gt;
      var text;&lt;br /&gt;
      if (!page.exists) {&lt;br /&gt;
        return savePage(pageTitle, releasePageText(versionInfo, title, date), &#039;Create release page for &#039; + versionInfo.version);&lt;br /&gt;
      }&lt;br /&gt;
      text = page.text;&lt;br /&gt;
      if (/\|\s*current patch\s*=/.test(text)) {&lt;br /&gt;
        text = text.replace(/(\|\s*current patch\s*=\s*)[^\n|}]*/i, &#039;$1&#039; + versionInfo.version);&lt;br /&gt;
      }&lt;br /&gt;
      if (text.indexOf(&#039;=== &#039; + versionInfo.version + &#039; ===&#039;) === -1) {&lt;br /&gt;
        text = text.replace(/\s*\[\[Category:Versions\]\]\s*$/i, &#039;&#039;) +&lt;br /&gt;
          &#039;\n\n=== &#039; + versionInfo.version + &#039; ===\n&#039; + title + &#039; — &#039; + date + &#039;\n* Add release changes here.\n\n[[Category:Versions]]\n&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      return savePage(pageTitle, text, &#039;Set current patch to &#039; + versionInfo.version);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function applyVersion(version, title, date) {&lt;br /&gt;
    var versionInfo = { version: version, series: seriesFor(version) };&lt;br /&gt;
    return Promise.all([&lt;br /&gt;
      savePage(&#039;Template:Current version&#039;, currentTemplateText(version), &#039;Set current version to &#039; + version),&lt;br /&gt;
      savePage(&#039;Template:Current version page&#039;, currentSeriesText(versionInfo.series), &#039;Set current version series to &#039; + versionInfo.series),&lt;br /&gt;
      updateReleasePage(versionInfo, title, date)&lt;br /&gt;
    ]).then(function () {&lt;br /&gt;
      return versionInfo;&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initVersionAdmin() {&lt;br /&gt;
    var root;&lt;br /&gt;
    var status;&lt;br /&gt;
    var versionInput;&lt;br /&gt;
    var titleInput;&lt;br /&gt;
    var dateInput;&lt;br /&gt;
    var applyButton;&lt;br /&gt;
    var backfillButton;&lt;br /&gt;
    var snapshotButton;&lt;br /&gt;
&lt;br /&gt;
    if (normalTitle(mw.config.get(&#039;wgPageName&#039;)) !== ADMIN_PAGE) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    root = document.getElementById(&#039;hw-version-control-root&#039;);&lt;br /&gt;
    if (!root) {&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!isVersionAdmin()) {&lt;br /&gt;
      root.innerHTML = &#039;&amp;lt;div class=&amp;quot;hw-note&amp;quot;&amp;gt;Administrator access is required.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;hw-version-admin-authorized&#039;);&lt;br /&gt;
    root.innerHTML = &#039;&amp;lt;div class=&amp;quot;hw-version-admin-card&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;h2&amp;gt;Current release&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;hw-version-admin-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;label&amp;gt;Version number &amp;lt;input id=&amp;quot;hw-version-number&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;0.01.0&amp;quot; pattern=&amp;quot;\d+\.\d{2}\.\d+&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;label&amp;gt;Release title &amp;lt;input id=&amp;quot;hw-version-title&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;Early Development&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;label&amp;gt;Release date &amp;lt;input id=&amp;quot;hw-version-date&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;29 July 2026&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;hw-version-admin-actions&amp;quot;&amp;gt;&amp;lt;button id=&amp;quot;hw-version-apply&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;Apply version and create release page&amp;lt;/button&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;hw-version-admin-card&amp;quot;&amp;gt;&amp;lt;h2&amp;gt;Page history tools&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;p&amp;gt;&amp;lt;button id=&amp;quot;hw-version-backfill&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;Backfill untracked pages as created now&amp;lt;/button&amp;gt;&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;p&amp;gt;&amp;lt;button id=&amp;quot;hw-version-snapshot&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;Snapshot every page in the current version&amp;lt;/button&amp;gt;&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;p class=&amp;quot;hw-version-admin-help&amp;quot;&amp;gt;Backfill is for legacy pages. Snapshot creates a current-version baseline only where the page differs from its last stored snapshot.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;div id=&amp;quot;hw-version-status&amp;quot; class=&amp;quot;hw-version-status&amp;quot; role=&amp;quot;status&amp;quot; aria-live=&amp;quot;polite&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    status = document.getElementById(&#039;hw-version-status&#039;);&lt;br /&gt;
    versionInput = document.getElementById(&#039;hw-version-number&#039;);&lt;br /&gt;
    titleInput = document.getElementById(&#039;hw-version-title&#039;);&lt;br /&gt;
    dateInput = document.getElementById(&#039;hw-version-date&#039;);&lt;br /&gt;
    applyButton = document.getElementById(&#039;hw-version-apply&#039;);&lt;br /&gt;
    backfillButton = document.getElementById(&#039;hw-version-backfill&#039;);&lt;br /&gt;
    snapshotButton = document.getElementById(&#039;hw-version-snapshot&#039;);&lt;br /&gt;
&lt;br /&gt;
    readCurrentVersion().then(function (info) {&lt;br /&gt;
      versionInput.value = info.version;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function setBusy(busy) {&lt;br /&gt;
      applyButton.disabled = busy;&lt;br /&gt;
      backfillButton.disabled = busy;&lt;br /&gt;
      snapshotButton.disabled = busy;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function progress(done, total, title, failures) {&lt;br /&gt;
      status.textContent = &#039;Processed &#039; + done + &#039; of &#039; + total + &#039;: &#039; + title + (failures ? &#039; (&#039; + failures + &#039; failed)&#039; : &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    applyButton.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      var version = versionInput.value.trim();&lt;br /&gt;
      var title = titleInput.value.trim();&lt;br /&gt;
      var date = dateInput.value.trim();&lt;br /&gt;
      if (!validVersion(version) || !title || !date) {&lt;br /&gt;
        status.textContent = &#039;Enter a version in x.xx.x format, a title, and a release date.&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      setBusy(true);&lt;br /&gt;
      status.textContent = &#039;Applying &#039; + version + &#039;…&#039;;&lt;br /&gt;
      applyVersion(version, title, date).then(function () {&lt;br /&gt;
        status.textContent = &#039;Current version is now &#039; + version + &#039;. The release page is ready.&#039;;&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        status.textContent = &#039;Version update failed: &#039; + error.message;&lt;br /&gt;
      }).then(function () { setBusy(false); });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    backfillButton.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      setBusy(true);&lt;br /&gt;
      status.textContent = &#039;Reading current version…&#039;;&lt;br /&gt;
      readCurrentVersion().then(function (info) {&lt;br /&gt;
        return runBackfill(info, true, progress);&lt;br /&gt;
      }).then(function (result) {&lt;br /&gt;
        status.textContent = &#039;Backfill complete: &#039; + result.total + &#039; pages checked, &#039; + result.failures.length + &#039; failed.&#039;;&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        status.textContent = &#039;Backfill failed: &#039; + error.message;&lt;br /&gt;
      }).then(function () { setBusy(false); });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    snapshotButton.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
      setBusy(true);&lt;br /&gt;
      status.textContent = &#039;Reading current version…&#039;;&lt;br /&gt;
      readCurrentVersion().then(function (info) {&lt;br /&gt;
        return runBackfill(info, false, progress);&lt;br /&gt;
      }).then(function (result) {&lt;br /&gt;
        status.textContent = &#039;Snapshot complete: &#039; + result.total + &#039; pages checked, &#039; + result.failures.length + &#039; failed.&#039;;&lt;br /&gt;
      }).catch(function (error) {&lt;br /&gt;
        status.textContent = &#039;Snapshot failed: &#039; + error.message;&lt;br /&gt;
      }).then(function () { setBusy(false); });&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function initAutomaticVersioning() {&lt;br /&gt;
    completePendingEnsure();&lt;br /&gt;
    initTrackedForm();&lt;br /&gt;
    initVersionAdmin();&lt;br /&gt;
    window.setTimeout(initTrackedForm, 500);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function startAutomaticVersioning() {&lt;br /&gt;
    if (mw.hook) {&lt;br /&gt;
      mw.hook(&#039;wikipage.editform&#039;).add(function () { window.setTimeout(initTrackedForm, 100); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
      document.addEventListener(&#039;DOMContentLoaded&#039;, initAutomaticVersioning, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
      initAutomaticVersioning();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  mw.loader.using([&#039;mediawiki.api&#039;, &#039;mediawiki.util&#039;], startAutomaticVersioning, function (error) {&lt;br /&gt;
    if (window.console &amp;amp;&amp;amp; console.error) {&lt;br /&gt;
      console.error(&#039;HarvestWiki version tracking could not load:&#039;, error);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Yin_Lotus/Change_History&amp;diff=564</id>
		<title>Yin Lotus/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Yin_Lotus/Change_History&amp;diff=564"/>
		<updated>2026-07-29T15:00:47Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJuYW1lIjoiWWluIExvdHVzIiwia2V5IjoiWWluIiwibG9jYXRpb24iOiJWYXN0IFdvcmxkIiwicmFyaXR5IjoiTGVnZW5kYXJ5IiwicHJlZmVycmVkIGNyeXN0YWxzIjoiV2F0ZXIgQ3J5c3RhbDtNaXN0IENyeXN0YWwiLCJwcmVmZXJyZWQgd2VhdGhlciI6IkNsZWFyIiwicHJlZmVycmVkIHRpbWUgb2YgZGF5IjoiTmlnaHQiLCJ3ZWlnaHQiOiIxMCIsInNwZWVkIjoiMC43IiwiYXR0YWNrIHJhdGUiOiIyIiwiYXZlcmFnZSBzaXplIjoiMiIsInNpemUgcmFuZ2UiOiIwLjUiLCJhdmVyYWdlIHByaWNlIjoiMTAwIiwiZGVncmVlIHJhbmdlIjoiMTIwIiwiZGVzY3JpcHRpb24iOiJBIGxlZ2VuZGFyeSBoZXJiIGRvY3VtZW50ZWQgaW4gdGhlIFZhc3QgV29ybGQuIn0= --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Yin_Lotus&amp;diff=563</id>
		<title>Yin Lotus</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Yin_Lotus&amp;diff=563"/>
		<updated>2026-07-29T15:00:46Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Herb&lt;br /&gt;
| name = Yin Lotus&lt;br /&gt;
| key = Yin&lt;br /&gt;
| location = Vast World&lt;br /&gt;
| rarity = Legendary&lt;br /&gt;
| preferred crystals = Water Crystal;Mist Crystal&lt;br /&gt;
| preferred weather = Clear&lt;br /&gt;
| preferred time of day = Night&lt;br /&gt;
| weight = 10&lt;br /&gt;
| speed = 0.7&lt;br /&gt;
| attack rate = 2&lt;br /&gt;
| average size = 2&lt;br /&gt;
| size range = 0.5&lt;br /&gt;
| average price = 100&lt;br /&gt;
| degree range = 120&lt;br /&gt;
| description = A legendary herb documented in the Vast World.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Yin Lotus&#039;&#039;&#039; is a legendary herb found in the [[Vast World]]. Its centralized gathering profile is displayed in the record panel.&lt;br /&gt;
&lt;br /&gt;
== Crystal Affinity ==&lt;br /&gt;
Preferred crystals: [[Water Crystal]], [[Mist Crystal]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Herbs]]&lt;br /&gt;
[[Category:Legendary Herbs]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Year_of_the_Tortoise/Change_History&amp;diff=562</id>
		<title>Year of the Tortoise/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Year_of_the_Tortoise/Change_History&amp;diff=562"/>
		<updated>2026-07-29T15:00:45Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJBcnRpY2xlIGNvbnRlbnQiOiI8ZGl2IGNsYXNzPVwiaHctcmVjb3JkLWJhbm5lciBody1yZWNvcmQtYmFubmVyLS1jeWNsZVwiIHN0eWxlPVwiLS1ody1jeWNsZS1jb2xvcjojNEZDNTdEXCI+XG48ZGl2IGNsYXNzPVwiaHctcmVjb3JkLWNvZGVcIj5DRUxFU1RJQUwgQ1lDTEUgwrcgWk9ESUFDIFlFQVI8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1yZWNvcmQtbmFtZVwiPlllYXIgb2YgdGhlIFRvcnRvaXNlPC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctcmVjb3JkLXN1bW1hcnlcIj5GbGFtZXMgb2YgcmViaXJ0aCBpbGx1bWluYXRlIHRoZSBoZWF2ZW5zPC9kaXY+XG48L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94IGh3LWN5Y2xlLWluZm9ib3hcIiBzdHlsZT1cIi0taHctY3ljbGUtY29sb3I6IzRGQzU3RFwiPlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtdGl0bGVcIj5ZZWFyIG9mIHRoZSBUb3J0b2lzZTwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtYmFkZ2VcIj5ab2RpYWMgeWVhcjwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWN5Y2xlLWNvbG9yLXN0cmlwXCIgYXJpYS1oaWRkZW49XCJ0cnVlXCI+PC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctaW5mb2JveC1oZWFkaW5nXCI+Q3ljbGUgRGF0YTwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtcm93XCI+PGRpdiBjbGFzcz1cImh3LWluZm9ib3gtbGFiZWxcIj5EdXJhdGlvbjwvZGl2PjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXZhbHVlXCI+OCBob3VyczwvZGl2PjwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtcm93XCI+PGRpdiBjbGFzcz1cImh3LWluZm9ib3gtbGFiZWxcIj5JY29uIEFzc2V0PC9kaXY+PGRpdiBjbGFzcz1cImh3LWluZm9ib3gtdmFsdWVcIj48Y29kZT4xMzgzNjA1MjI0ODc2MDU8L2NvZGU+PC9kaXY+PC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctaW5mb2JveC1oZWFkaW5nXCI+VGhlbWU8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LWJsdXJiXCI+RmxhbWVzIG9mIHJlYmlydGggaWxsdW1pbmF0ZSB0aGUgaGVhdmVuczwvZGl2PlxuPC9kaXY+XG5cbj09IE92ZXJ2aWV3ID09XG4nJydZZWFyIG9mIHRoZSBUb3J0b2lzZScnJyBpcyBvbmUgb2YgdGhlIGZvdXIgW1tXZWF0aGVyI1pvZGlhYyBZZWFyc3x6b2RpYWMgeWVhcnNdXS5cblxuW1tDYXRlZ29yeTpab2RpYWMgWWVhcnNdXSJ9 --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Year_of_the_Tiger&amp;diff=561</id>
		<title>Year of the Tiger</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Year_of_the_Tiger&amp;diff=561"/>
		<updated>2026-07-29T15:00:45Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-record-banner hw-record-banner--cycle&amp;quot; style=&amp;quot;--hw-cycle-color:#F7F672&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;CELESTIAL CYCLE · ZODIAC YEAR&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Year of the Tiger&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;Ferocious spirits carve paths through the chaos&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox hw-cycle-infobox&amp;quot; style=&amp;quot;--hw-cycle-color:#F7F672&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-title&amp;quot;&amp;gt;Year of the Tiger&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-badge&amp;quot;&amp;gt;Zodiac year&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-cycle-color-strip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-heading&amp;quot;&amp;gt;Cycle Data&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-row&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-label&amp;quot;&amp;gt;Duration&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-value&amp;quot;&amp;gt;8 hours&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-row&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-label&amp;quot;&amp;gt;Icon Asset&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-value&amp;quot;&amp;gt;&amp;lt;code&amp;gt;139765541495239&amp;lt;/code&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-heading&amp;quot;&amp;gt;Theme&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-blurb&amp;quot;&amp;gt;Ferocious spirits carve paths through the chaos&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Year of the Tiger&#039;&#039;&#039; is one of the four [[Weather#Zodiac Years|zodiac years]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Zodiac Years]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Year_of_the_Tiger/Change_History&amp;diff=560</id>
		<title>Year of the Tiger/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Year_of_the_Tiger/Change_History&amp;diff=560"/>
		<updated>2026-07-29T15:00:45Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJBcnRpY2xlIGNvbnRlbnQiOiI8ZGl2IGNsYXNzPVwiaHctcmVjb3JkLWJhbm5lciBody1yZWNvcmQtYmFubmVyLS1jeWNsZVwiIHN0eWxlPVwiLS1ody1jeWNsZS1jb2xvcjojRjdGNjcyXCI+XG48ZGl2IGNsYXNzPVwiaHctcmVjb3JkLWNvZGVcIj5DRUxFU1RJQUwgQ1lDTEUgwrcgWk9ESUFDIFlFQVI8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1yZWNvcmQtbmFtZVwiPlllYXIgb2YgdGhlIFRpZ2VyPC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctcmVjb3JkLXN1bW1hcnlcIj5GZXJvY2lvdXMgc3Bpcml0cyBjYXJ2ZSBwYXRocyB0aHJvdWdoIHRoZSBjaGFvczwvZGl2PlxuPC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctaW5mb2JveCBody1jeWNsZS1pbmZvYm94XCIgc3R5bGU9XCItLWh3LWN5Y2xlLWNvbG9yOiNGN0Y2NzJcIj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXRpdGxlXCI+WWVhciBvZiB0aGUgVGlnZXI8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LWJhZGdlXCI+Wm9kaWFjIHllYXI8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1jeWNsZS1jb2xvci1zdHJpcFwiIGFyaWEtaGlkZGVuPVwidHJ1ZVwiPjwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtaGVhZGluZ1wiPkN5Y2xlIERhdGE8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXJvd1wiPjxkaXYgY2xhc3M9XCJody1pbmZvYm94LWxhYmVsXCI+RHVyYXRpb248L2Rpdj48ZGl2IGNsYXNzPVwiaHctaW5mb2JveC12YWx1ZVwiPjggaG91cnM8L2Rpdj48L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXJvd1wiPjxkaXYgY2xhc3M9XCJody1pbmZvYm94LWxhYmVsXCI+SWNvbiBBc3NldDwvZGl2PjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXZhbHVlXCI+PGNvZGU+MTM5NzY1NTQxNDk1MjM5PC9jb2RlPjwvZGl2PjwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtaGVhZGluZ1wiPlRoZW1lPC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctaW5mb2JveC1ibHVyYlwiPkZlcm9jaW91cyBzcGlyaXRzIGNhcnZlIHBhdGhzIHRocm91Z2ggdGhlIGNoYW9zPC9kaXY+XG48L2Rpdj5cblxuPT0gT3ZlcnZpZXcgPT1cbicnJ1llYXIgb2YgdGhlIFRpZ2VyJycnIGlzIG9uZSBvZiB0aGUgZm91ciBbW1dlYXRoZXIjWm9kaWFjIFllYXJzfHpvZGlhYyB5ZWFyc11dLlxuXG5bW0NhdGVnb3J5OlpvZGlhYyBZZWFyc11dIn0= --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Year_of_the_Tortoise&amp;diff=559</id>
		<title>Year of the Tortoise</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Year_of_the_Tortoise&amp;diff=559"/>
		<updated>2026-07-29T15:00:45Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-record-banner hw-record-banner--cycle&amp;quot; style=&amp;quot;--hw-cycle-color:#4FC57D&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;CELESTIAL CYCLE · ZODIAC YEAR&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Year of the Tortoise&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;Flames of rebirth illuminate the heavens&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox hw-cycle-infobox&amp;quot; style=&amp;quot;--hw-cycle-color:#4FC57D&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-title&amp;quot;&amp;gt;Year of the Tortoise&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-badge&amp;quot;&amp;gt;Zodiac year&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-cycle-color-strip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-heading&amp;quot;&amp;gt;Cycle Data&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-row&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-label&amp;quot;&amp;gt;Duration&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-value&amp;quot;&amp;gt;8 hours&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-row&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-label&amp;quot;&amp;gt;Icon Asset&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-value&amp;quot;&amp;gt;&amp;lt;code&amp;gt;138360522487605&amp;lt;/code&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-heading&amp;quot;&amp;gt;Theme&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-blurb&amp;quot;&amp;gt;Flames of rebirth illuminate the heavens&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Year of the Tortoise&#039;&#039;&#039; is one of the four [[Weather#Zodiac Years|zodiac years]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Zodiac Years]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Year_of_the_Phoenix/Change_History&amp;diff=558</id>
		<title>Year of the Phoenix/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Year_of_the_Phoenix/Change_History&amp;diff=558"/>
		<updated>2026-07-29T15:00:44Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJBcnRpY2xlIGNvbnRlbnQiOiI8ZGl2IGNsYXNzPVwiaHctcmVjb3JkLWJhbm5lciBody1yZWNvcmQtYmFubmVyLS1jeWNsZVwiIHN0eWxlPVwiLS1ody1jeWNsZS1jb2xvcjojRjg3Qjg0XCI+XG48ZGl2IGNsYXNzPVwiaHctcmVjb3JkLWNvZGVcIj5DRUxFU1RJQUwgQ1lDTEUgwrcgWk9ESUFDIFlFQVI8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1yZWNvcmQtbmFtZVwiPlllYXIgb2YgdGhlIFBob2VuaXg8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1yZWNvcmQtc3VtbWFyeVwiPlBhdGllbmNlIGFuZCBsb25nZXZpdHkgYnJpbmcgZXRlcm5hbCBzdHJlbmd0aDwvZGl2PlxuPC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctaW5mb2JveCBody1jeWNsZS1pbmZvYm94XCIgc3R5bGU9XCItLWh3LWN5Y2xlLWNvbG9yOiNGODdCODRcIj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXRpdGxlXCI+WWVhciBvZiB0aGUgUGhvZW5peDwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtYmFkZ2VcIj5ab2RpYWMgeWVhcjwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWN5Y2xlLWNvbG9yLXN0cmlwXCIgYXJpYS1oaWRkZW49XCJ0cnVlXCI+PC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctaW5mb2JveC1oZWFkaW5nXCI+Q3ljbGUgRGF0YTwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtcm93XCI+PGRpdiBjbGFzcz1cImh3LWluZm9ib3gtbGFiZWxcIj5EdXJhdGlvbjwvZGl2PjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXZhbHVlXCI+OCBob3VyczwvZGl2PjwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtcm93XCI+PGRpdiBjbGFzcz1cImh3LWluZm9ib3gtbGFiZWxcIj5JY29uIEFzc2V0PC9kaXY+PGRpdiBjbGFzcz1cImh3LWluZm9ib3gtdmFsdWVcIj48Y29kZT4xMTQ2MDc2OTEyNjkzMjY8L2NvZGU+PC9kaXY+PC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctaW5mb2JveC1oZWFkaW5nXCI+VGhlbWU8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LWJsdXJiXCI+UGF0aWVuY2UgYW5kIGxvbmdldml0eSBicmluZyBldGVybmFsIHN0cmVuZ3RoPC9kaXY+XG48L2Rpdj5cblxuPT0gT3ZlcnZpZXcgPT1cbicnJ1llYXIgb2YgdGhlIFBob2VuaXgnJycgaXMgb25lIG9mIHRoZSBmb3VyIFtbV2VhdGhlciNab2RpYWMgWWVhcnN8em9kaWFjIHllYXJzXV0uXG5cbltbQ2F0ZWdvcnk6Wm9kaWFjIFllYXJzXV0ifQ== --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Year_of_the_Phoenix&amp;diff=557</id>
		<title>Year of the Phoenix</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Year_of_the_Phoenix&amp;diff=557"/>
		<updated>2026-07-29T15:00:44Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-record-banner hw-record-banner--cycle&amp;quot; style=&amp;quot;--hw-cycle-color:#F87B84&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;CELESTIAL CYCLE · ZODIAC YEAR&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Year of the Phoenix&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;Patience and longevity bring eternal strength&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox hw-cycle-infobox&amp;quot; style=&amp;quot;--hw-cycle-color:#F87B84&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-title&amp;quot;&amp;gt;Year of the Phoenix&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-badge&amp;quot;&amp;gt;Zodiac year&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-cycle-color-strip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-heading&amp;quot;&amp;gt;Cycle Data&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-row&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-label&amp;quot;&amp;gt;Duration&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-value&amp;quot;&amp;gt;8 hours&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-row&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-label&amp;quot;&amp;gt;Icon Asset&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-value&amp;quot;&amp;gt;&amp;lt;code&amp;gt;114607691269326&amp;lt;/code&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-heading&amp;quot;&amp;gt;Theme&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-blurb&amp;quot;&amp;gt;Patience and longevity bring eternal strength&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Year of the Phoenix&#039;&#039;&#039; is one of the four [[Weather#Zodiac Years|zodiac years]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Zodiac Years]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Yang_Lotus&amp;diff=556</id>
		<title>Yang Lotus</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Yang_Lotus&amp;diff=556"/>
		<updated>2026-07-29T15:00:43Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Herb&lt;br /&gt;
| name = Yang Lotus&lt;br /&gt;
| key = Yang&lt;br /&gt;
| location = Vast World&lt;br /&gt;
| rarity = Legendary&lt;br /&gt;
| preferred crystals = Flame Crystal;Magma Crystal&lt;br /&gt;
| preferred weather = Clear&lt;br /&gt;
| preferred time of day = Daylight&lt;br /&gt;
| weight = 10&lt;br /&gt;
| speed = 0.7&lt;br /&gt;
| attack rate = 2&lt;br /&gt;
| average size = 2&lt;br /&gt;
| size range = 0.5&lt;br /&gt;
| average price = 100&lt;br /&gt;
| degree range = 120&lt;br /&gt;
| description = A legendary herb documented in the Vast World.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Yang Lotus&#039;&#039;&#039; is a legendary herb found in the [[Vast World]]. Its centralized gathering profile is displayed in the record panel.&lt;br /&gt;
&lt;br /&gt;
== Crystal Affinity ==&lt;br /&gt;
Preferred crystals: [[Flame Crystal]], [[Magma Crystal]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Herbs]]&lt;br /&gt;
[[Category:Legendary Herbs]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Year_of_the_Dragon&amp;diff=555</id>
		<title>Year of the Dragon</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Year_of_the_Dragon&amp;diff=555"/>
		<updated>2026-07-29T15:00:43Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-record-banner hw-record-banner--cycle&amp;quot; style=&amp;quot;--hw-cycle-color:#FFB86F&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;CELESTIAL CYCLE · ZODIAC YEAR&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Year of the Dragon&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;Ancient might awakens beneath celestial skies&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox hw-cycle-infobox&amp;quot; style=&amp;quot;--hw-cycle-color:#FFB86F&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-title&amp;quot;&amp;gt;Year of the Dragon&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-badge&amp;quot;&amp;gt;Zodiac year&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-cycle-color-strip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-heading&amp;quot;&amp;gt;Cycle Data&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-row&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-label&amp;quot;&amp;gt;Duration&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-value&amp;quot;&amp;gt;8 hours&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-row&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-label&amp;quot;&amp;gt;Icon Asset&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;hw-infobox-value&amp;quot;&amp;gt;&amp;lt;code&amp;gt;71863856355848&amp;lt;/code&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-heading&amp;quot;&amp;gt;Theme&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-infobox-blurb&amp;quot;&amp;gt;Ancient might awakens beneath celestial skies&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Year of the Dragon&#039;&#039;&#039; is one of the four [[Weather#Zodiac Years|zodiac years]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Zodiac Years]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Year_of_the_Dragon/Change_History&amp;diff=554</id>
		<title>Year of the Dragon/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Year_of_the_Dragon/Change_History&amp;diff=554"/>
		<updated>2026-07-29T15:00:43Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJBcnRpY2xlIGNvbnRlbnQiOiI8ZGl2IGNsYXNzPVwiaHctcmVjb3JkLWJhbm5lciBody1yZWNvcmQtYmFubmVyLS1jeWNsZVwiIHN0eWxlPVwiLS1ody1jeWNsZS1jb2xvcjojRkZCODZGXCI+XG48ZGl2IGNsYXNzPVwiaHctcmVjb3JkLWNvZGVcIj5DRUxFU1RJQUwgQ1lDTEUgwrcgWk9ESUFDIFlFQVI8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1yZWNvcmQtbmFtZVwiPlllYXIgb2YgdGhlIERyYWdvbjwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LXJlY29yZC1zdW1tYXJ5XCI+QW5jaWVudCBtaWdodCBhd2FrZW5zIGJlbmVhdGggY2VsZXN0aWFsIHNraWVzPC9kaXY+XG48L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94IGh3LWN5Y2xlLWluZm9ib3hcIiBzdHlsZT1cIi0taHctY3ljbGUtY29sb3I6I0ZGQjg2RlwiPlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtdGl0bGVcIj5ZZWFyIG9mIHRoZSBEcmFnb248L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LWJhZGdlXCI+Wm9kaWFjIHllYXI8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1jeWNsZS1jb2xvci1zdHJpcFwiIGFyaWEtaGlkZGVuPVwidHJ1ZVwiPjwvZGl2PlxuPGRpdiBjbGFzcz1cImh3LWluZm9ib3gtaGVhZGluZ1wiPkN5Y2xlIERhdGE8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXJvd1wiPjxkaXYgY2xhc3M9XCJody1pbmZvYm94LWxhYmVsXCI+RHVyYXRpb248L2Rpdj48ZGl2IGNsYXNzPVwiaHctaW5mb2JveC12YWx1ZVwiPjggaG91cnM8L2Rpdj48L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXJvd1wiPjxkaXYgY2xhc3M9XCJody1pbmZvYm94LWxhYmVsXCI+SWNvbiBBc3NldDwvZGl2PjxkaXYgY2xhc3M9XCJody1pbmZvYm94LXZhbHVlXCI+PGNvZGU+NzE4NjM4NTYzNTU4NDg8L2NvZGU+PC9kaXY+PC9kaXY+XG48ZGl2IGNsYXNzPVwiaHctaW5mb2JveC1oZWFkaW5nXCI+VGhlbWU8L2Rpdj5cbjxkaXYgY2xhc3M9XCJody1pbmZvYm94LWJsdXJiXCI+QW5jaWVudCBtaWdodCBhd2FrZW5zIGJlbmVhdGggY2VsZXN0aWFsIHNraWVzPC9kaXY+XG48L2Rpdj5cblxuPT0gT3ZlcnZpZXcgPT1cbicnJ1llYXIgb2YgdGhlIERyYWdvbicnJyBpcyBvbmUgb2YgdGhlIGZvdXIgW1tXZWF0aGVyI1pvZGlhYyBZZWFyc3x6b2RpYWMgeWVhcnNdXS5cblxuW1tDYXRlZ29yeTpab2RpYWMgWWVhcnNdXSJ9 --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Yang_Lotus/Change_History&amp;diff=553</id>
		<title>Yang Lotus/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Yang_Lotus/Change_History&amp;diff=553"/>
		<updated>2026-07-29T15:00:43Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJuYW1lIjoiWWFuZyBMb3R1cyIsImtleSI6IllhbmciLCJsb2NhdGlvbiI6IlZhc3QgV29ybGQiLCJyYXJpdHkiOiJMZWdlbmRhcnkiLCJwcmVmZXJyZWQgY3J5c3RhbHMiOiJGbGFtZSBDcnlzdGFsO01hZ21hIENyeXN0YWwiLCJwcmVmZXJyZWQgd2VhdGhlciI6IkNsZWFyIiwicHJlZmVycmVkIHRpbWUgb2YgZGF5IjoiRGF5bGlnaHQiLCJ3ZWlnaHQiOiIxMCIsInNwZWVkIjoiMC43IiwiYXR0YWNrIHJhdGUiOiIyIiwiYXZlcmFnZSBzaXplIjoiMiIsInNpemUgcmFuZ2UiOiIwLjUiLCJhdmVyYWdlIHByaWNlIjoiMTAwIiwiZGVncmVlIHJhbmdlIjoiMTIwIiwiZGVzY3JpcHRpb24iOiJBIGxlZ2VuZGFyeSBoZXJiIGRvY3VtZW50ZWQgaW4gdGhlIFZhc3QgV29ybGQuIn0= --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Wood_Crystal&amp;diff=552</id>
		<title>Wood Crystal</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Wood_Crystal&amp;diff=552"/>
		<updated>2026-07-29T15:00:42Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Crystal&lt;br /&gt;
| name = Wood Crystal&lt;br /&gt;
| rarity = Mortal&lt;br /&gt;
| universal luck = 1.2&lt;br /&gt;
| preferred luck = 1.5&lt;br /&gt;
| technique = 1.1&lt;br /&gt;
| weight = 12&lt;br /&gt;
| preferred herbs = Pursuing Longevity Herb&lt;br /&gt;
| description = A basic wood aspect crystal.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
A basic wood aspect crystal.&lt;br /&gt;
&lt;br /&gt;
== Preferred Herbs ==&lt;br /&gt;
This crystal is preferred by [[Pursuing Longevity Herb]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Crystals]]&lt;br /&gt;
[[Category:Mortal Crystals]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Wood_Crystal/Change_History&amp;diff=551</id>
		<title>Wood Crystal/Change History</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Wood_Crystal/Change_History&amp;diff=551"/>
		<updated>2026-07-29T15:00:42Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Record automatic change history for 0.01.0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- HW-AUTO-START:0.01.0 --&amp;gt;&lt;br /&gt;
== Version 0.01.0 ==&lt;br /&gt;
&#039;&#039;&#039;{{Version link|0.01.0|0.01}}&#039;&#039;&#039;&lt;br /&gt;
* Page created.&lt;br /&gt;
&amp;lt;!-- HW-SNAPSHOT:eyJuYW1lIjoiV29vZCBDcnlzdGFsIiwicmFyaXR5IjoiTW9ydGFsIiwidW5pdmVyc2FsIGx1Y2siOiIxLjIiLCJwcmVmZXJyZWQgbHVjayI6IjEuNSIsInRlY2huaXF1ZSI6IjEuMSIsIndlaWdodCI6IjEyIiwicHJlZmVycmVkIGhlcmJzIjoiUHVyc3VpbmcgTG9uZ2V2aXR5IEhlcmIiLCJkZXNjcmlwdGlvbiI6IkEgYmFzaWMgd29vZCBhc3BlY3QgY3J5c3RhbC4ifQ== --&amp;gt;&lt;br /&gt;
&amp;lt;!-- HW-AUTO-END:0.01.0 --&amp;gt;&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Water_Crystal&amp;diff=550</id>
		<title>Water Crystal</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Water_Crystal&amp;diff=550"/>
		<updated>2026-07-29T15:00:40Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Crystal&lt;br /&gt;
| name = Water Crystal&lt;br /&gt;
| rarity = Mortal&lt;br /&gt;
| universal luck = 1.2&lt;br /&gt;
| preferred luck = 1.5&lt;br /&gt;
| technique = 1.1&lt;br /&gt;
| weight = 12&lt;br /&gt;
| preferred herbs = Frigid Moon Grass;Yin Lotus&lt;br /&gt;
| description = A basic water aspect crystal.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
A basic water aspect crystal.&lt;br /&gt;
&lt;br /&gt;
== Preferred Herbs ==&lt;br /&gt;
This crystal is preferred by [[Frigid Moon Grass]], [[Yin Lotus]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Crystals]]&lt;br /&gt;
[[Category:Mortal Crystals]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Vitality&amp;diff=549</id>
		<title>Vitality</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Vitality&amp;diff=549"/>
		<updated>2026-07-29T15:00:40Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Mutation&lt;br /&gt;
| name = Vitality&lt;br /&gt;
| type = Standard&lt;br /&gt;
| color = #E96869&lt;br /&gt;
| multiplier = 1.5&lt;br /&gt;
| weight = 100&lt;br /&gt;
| chance = 1.6%&lt;br /&gt;
| priority = 1&lt;br /&gt;
| appears normally = Yes&lt;br /&gt;
| appearance = Turns the herb green and adds green particles.&lt;br /&gt;
| description = A life-aspected mutation with a ×1.5 value multiplier.&lt;br /&gt;
| order = 3&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&#039;&#039;&#039;Vitality&#039;&#039;&#039; is a standard [[Mutations|mutation]]. It gives a herb a green appearance with green particles and applies a ×1.5 value multiplier.&lt;br /&gt;
&lt;br /&gt;
[[Category:Mutations]]&lt;br /&gt;
[[Category:Standard Mutations]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
	<entry>
		<id>https://harvestwiki.com/index.php?title=Weather&amp;diff=548</id>
		<title>Weather</title>
		<link rel="alternate" type="text/html" href="https://harvestwiki.com/index.php?title=Weather&amp;diff=548"/>
		<updated>2026-07-29T15:00:40Z</updated>

		<summary type="html">&lt;p&gt;Aurelianus: Add version change history section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;hw-archive-banner hw-archive-banner--weather&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-code&amp;quot;&amp;gt;WORLD SYSTEMS · WEATHER ARCHIVE&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-name&amp;quot;&amp;gt;Weather&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-record-summary&amp;quot;&amp;gt;A centralized archive of weather, time-of-day states, and eight-hour zodiac cycles.&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-archive-stats&amp;quot;&amp;gt;&amp;lt;span&amp;gt;{{#cargo_query:tables=Weathers|fields=COUNT(*)=Count|format=plainlist}} weather states&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;4 times of day&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;4 zodiac years&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-page-nav&amp;quot;&amp;gt;[[Main Page|Main Page]] [[Herbs|Herb Compendium]] [[Crystals|Crystal Archive]] [[Locations|Location Archive]] [[Special:CargoTables/Weathers|Weather Data]]&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
Weather changes gathering conditions throughout the world. Each weather, time of day, and zodiac year has an assigned color used automatically whenever that page is linked. Link colors remain presentation metadata and are not displayed as table columns.&lt;br /&gt;
&lt;br /&gt;
== Weather Index ==&lt;br /&gt;
&amp;lt;div class=&amp;quot;hw-weather-catalog&amp;quot;&amp;gt;&lt;br /&gt;
{{#cargo_query:&lt;br /&gt;
tables=Weathers&lt;br /&gt;
|fields=_pageName=Weather,Type,Effect,LuckFactor=Luck Multiplier&lt;br /&gt;
|order by=LuckFactor,Name&lt;br /&gt;
|format=table&lt;br /&gt;
|class=wikitable hw-table sortable&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Time of Day ==&lt;br /&gt;
{| class=&amp;quot;wikitable hw-table hw-cycle-table&amp;quot;&lt;br /&gt;
! Time !! Icon Asset !! Theme&lt;br /&gt;
|-&lt;br /&gt;
| [[Sunrise]] || &amp;lt;code&amp;gt;82905955225039&amp;lt;/code&amp;gt; || The early morning qi stirs the sleeping earth&lt;br /&gt;
|-&lt;br /&gt;
| [[Daytime]] || &amp;lt;code&amp;gt;84054109474145&amp;lt;/code&amp;gt; || Nature&#039;s bounty reveals itself under heaven&lt;br /&gt;
|-&lt;br /&gt;
| [[Sunset]] || &amp;lt;code&amp;gt;116882580916150&amp;lt;/code&amp;gt; || Twilight gathers the day&#039;s lingering essence&lt;br /&gt;
|-&lt;br /&gt;
| [[Night]] || &amp;lt;code&amp;gt;136363504808281&amp;lt;/code&amp;gt; || The heavens whisper through sleeping forests&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Zodiac Years ==&lt;br /&gt;
Each zodiac year lasts &#039;&#039;&#039;8 hours&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable hw-table hw-cycle-table&amp;quot;&lt;br /&gt;
! Zodiac Year !! Icon Asset !! Theme&lt;br /&gt;
|-&lt;br /&gt;
| [[Year of the Dragon]] || &amp;lt;code&amp;gt;71863856355848&amp;lt;/code&amp;gt; || Ancient might awakens beneath celestial skies&lt;br /&gt;
|-&lt;br /&gt;
| [[Year of the Tiger]] || &amp;lt;code&amp;gt;139765541495239&amp;lt;/code&amp;gt; || Ferocious spirits carve paths through the chaos&lt;br /&gt;
|-&lt;br /&gt;
| [[Year of the Phoenix]] || &amp;lt;code&amp;gt;114607691269326&amp;lt;/code&amp;gt; || Patience and longevity bring eternal strength&lt;br /&gt;
|-&lt;br /&gt;
| [[Year of the Tortoise]] || &amp;lt;code&amp;gt;138360522487605&amp;lt;/code&amp;gt; || Flames of rebirth illuminate the heavens&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Standard Weather ==&lt;br /&gt;
Clear, Cloudy, Rain, and Storm make up the normal weather cycle. Storm is the only standard state with a luck modifier.&lt;br /&gt;
&lt;br /&gt;
== Special Weather ==&lt;br /&gt;
Thunderstorm and Dao Tempest are rare world events with dramatically increased luck: ×4 and ×8 respectively.&lt;br /&gt;
&lt;br /&gt;
[[Category:Weather]]&lt;br /&gt;
&lt;br /&gt;
== Change History ==&lt;br /&gt;
{{Change History|0.01.0|0.01}}&lt;/div&gt;</summary>
		<author><name>Aurelianus</name></author>
	</entry>
</feed>