Transformania Time Main Page Remake

Reshapes, restyles the Transformania Time main page a bit.

  1. // ==UserScript==
  2. // @name Transformania Time Main Page Remake
  3. // @namespace http://steamcommunity.com/id/siggo/
  4. // @version 1.0.6
  5. // @description Reshapes, restyles the Transformania Time main page a bit.
  6. // @author Prios
  7. // @match https://www.transformaniatime.com/
  8. // @match https://test.transformaniatime.com/
  9. // @copyright 2019, Prios (https://openuserjs.org/users/Prios)
  10. // @grant none
  11. // @license MIT
  12. // jshint multistr: true
  13. // ==/UserScript==
  14.  
  15. // PROBLEM: Browser widths between 1200px and 1400px look terrible. All things considered, best to let the components on the left shrink a little.
  16.  
  17. /*
  18. function linkifyLogEntries( i ) {
  19.  
  20. var logActionsMatch = /\b([\wü]*)(?: '?.*')?( .*?)(?: entered from | left toward | cleansed here\.| meditated here\.| searched here\.| cast | dropped | picked up | threw a | shouted |, a | tamed | released a | consumed a )/;
  21. var logSubject = $( this ).text().match(logActionsMatch); // [1] is first name, [2] is anything beyond the quote-designated nickname (if any) but before the matched action
  22.  
  23. if ( logSubject !== null ) {
  24. logSubject = logSubject.slice(1).join(''); // squash the two subgroups together
  25. // if ( ( logSubject != 'You' )) {
  26. $(this).wrapInner('<a style="font-weight:normal;color:inherit !important;" href="https://www.transformaniatime.com/PvP/LookAtPlayerItem?vicname=' + logSubject + '"></a>');
  27. // }
  28. }
  29. if ( i >= 300 ) { return false; }
  30. }
  31. */
  32.  
  33. function pseudoShoutClass() { // just simulates a:hover color:black, a color:white
  34. var $this = $(this);
  35. var whichEvent = event.type;
  36. if (whichEvent === 'mouseover') {
  37. $this.attr('style', $this.attr('style').replace('white', 'black')); // Using .attr because .css doesn't support !important
  38. }
  39. else if (whichEvent === 'mouseout') {
  40. $this.attr('style', $this.attr('style').replace('black', 'white'));
  41. }
  42. }
  43.  
  44. // constructor
  45. // storage: key used by TfT to store the setting in localStorage; required
  46. // span: the span tag itself as a jquery object; required
  47. // friendlyName: string, the public 'end user' name for the setting; required
  48. // switches: an object with 'true' and 'false' key entries, and corresponding span tag display text as paired values; optional, uses 'ON' and 'OFF' as default
  49. function DynamicMenuItem(storage, span, friendlyName, switches) {
  50. this.storageKey = storage;
  51. this.$spanJQO = span;
  52. this.myFriendlyName = friendlyName;
  53. this.switchSettings = switches || {
  54. 'true': 'ON',
  55. 'false': 'OFF'
  56. };
  57. this.updateSpan = function () {
  58. var curSetting = localStorage.getItem(this.storageKey) || 'false';
  59. this.$spanJQO.html(this.switchSettings[curSetting]);
  60. };
  61. this.alertStatus = function () {
  62. var curSetting = localStorage.getItem(this.storageKey) || 'false';
  63. alert(this.myFriendlyName + ' now ' + this.switchSettings[curSetting] + '!');
  64. };
  65. this.flipSwitch = function () {
  66. var locStoKey = this.storageKey;
  67. var curSetting = localStorage.getItem(locStoKey) || 'false';
  68. if (curSetting === 'false') { // this setting is stored as a string in localStorage by TfT
  69. localStorage.setItem(locStoKey, 'true');
  70. }
  71. else {
  72. localStorage.setItem(locStoKey, 'false');
  73. }
  74. this.updateSpan();
  75. this.alertStatus(); // curSetting will be determined all over again twice but that's okay, this isn't really a super time sensitive operation
  76. // Plus we'll find out if .setItem did something unexpected, instead of assuming it worked exactly as desired
  77. };
  78. this.updateSpan(); // initialize
  79. }
  80.  
  81. $(function () {
  82.  
  83. // var startTime = Date.now();
  84. 'use strict';
  85.  
  86. // ACTION BUTTONS - DETACH, AND CANCEL SCRIPT IF NOT PRESENT
  87. var $playerActions = $('#playerActionBox').detach(); // The original action buttons
  88. if ($playerActions.length === 0) return; // Stop the script right here if we're not on the right kind of page
  89.  
  90. // MAJOR COMPONENT EXTRACTION
  91.  
  92. var $tftSite = $('body').detach(); // Detaching the page to avoid making lots of updates to the DOM itself. Counterintuitively, it's fastest and least intensive to do .finds() from here, when possible.
  93. var $siteTop = $tftSite.find('#siteTop'); // everything in the body except the footer
  94. var $siteFooter = $tftSite.find('footer'); // footer tag inside a container-class div
  95.  
  96. // SUBCOMPONENT VARIABLES
  97.  
  98. var $settingsWrenchListitem = $tftSite.find('.glyphicon-wrench').parent().parent();
  99. var $newSettingsMenu = $('<li id="settingsMenuItem" class="dropdown">\
  100. <a id="settingsMenuAnchor" href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Settings <span class="caret"></span></a>\
  101. <ul id="settingsMenuList" class="dropdown-menu">\
  102. <li><a id="settingsTurnAudio" href="#">Turn Alerts are <span id="dynTurnSpan">UNKNOWN (error!)</span></a></li>\
  103. <li><a id="settingsAttackAudio" href="#">Attack Alerts are <span id="dynAttackSpan">UNKNOWN (error!)</span></a></li>\
  104. <li><a id="settingsMsgAudio" href="#">Message Alerts are <span id="dynMsgSpan">UNKNOWN (error!)</span></a></li>\
  105. <li><a id="settingsPopups" href="#">Popup Notifications are <span id="dynPopSpan">UNKNOWN (error!)</span></a></li>\
  106. <li><a id="settingsBioItem" href="/Settings/SetBio">Update Bio</a></li>\
  107. <li><a id="settingsBlacklistItem" href="/Settings/MyBlacklistEntries">Manage Blacklist</a></li>\
  108. <li><a id="settingsReserveNameItem" href="/PvP/ReserveName">Reserve Name</a></li>\
  109. <li><a id="settingsNicknameItem" href="/Settings/SetNickname">Set your Nickname</a></li>\
  110. <li><a id="settingsFullPageItem" href="/Settings/Settings">More Settings...</a></li>\
  111. </ul>\
  112. </li>'); // ids make finding these elements much faster
  113.  
  114. var $specialBox = $tftSite.find('.specialBox'); // stats on people playing, boss announcements, chaos round announcement
  115.  
  116. var $containerInner = $tftSite.find('.containerInner'); // Area with: Avatar info, self portrait, stat bars, action buttons, movement grid, area description
  117.  
  118. var $innerRows = $containerInner.find('.row'); // Row two starts before the movement grid, row one ends before the action buttons (action buttons are not in a row)
  119. var $frontOuters = $tftSite.find('.frontOuter'); // the three columns in first row, avatar info/portrait/stat bars
  120. var $avatarText = $tftSite.find('.avatarText'); // First column's single child, Name/Form/Covenant avatar info box
  121. var $actionCounts = $frontOuters.find('.avatarCount').slice(0, 2); // first and second, this is number/max of attacks and restorations
  122.  
  123. var $allIcons = $tftSite.find('.col-sm-12').find('.icon'); // The attack, meditation, and money icons
  124. var $attackIcon = $tftSite.find('.icon-timesattacking');
  125. var $recoverIcon = $tftSite.find('.icon-cleansemeditate');
  126. var $moneyIcon = $tftSite.find('.icon-money');
  127.  
  128. var $moveGrid = $tftSite.find('.tableLines'); // Movement grid
  129. var $moveGridCells = $moveGrid.find('td');
  130.  
  131. var $knownSpells = $tftSite.find('.knownspells'); // spells known in this area
  132.  
  133. var $covController = $tftSite.find('.covController'); // The sentence stating which covenant has enchanted the area
  134.  
  135. var $activityLog = $tftSite.find('#RecentActivityLog'); // Original log of room's events, just above footer
  136. var $activityLogWide; // Widescreen-only clone to place on right side -- cloned later, after some alterations to original
  137.  
  138. var $lindellaLatest = $activityLog.find('li:contains(Lindella the Soul Vendor )').first();
  139. var $wuffieLatest = $activityLog.find('li:contains(Wüffie the Soul Pet Vendor )').first();
  140. var lindellaTrail;
  141. var wuffieTrail;
  142.  
  143. // UTILITY VARIABLES
  144.  
  145. var selfLookURL = $tftSite.find('a:contains(Look at Yourself)').attr('href');
  146.  
  147. var busStop = $tftSite.find('.bus').length; // 1/true if at a bus stop, 0/false if not
  148. var covSafeground = $tftSite.find('.covSafeground').length; // 1/true if a safeground, 0/false if not
  149.  
  150. var barButtonsData = [{
  151. 'target': 'Cleanse',
  152. 'text': '',
  153. 'isBlocked': false
  154. },
  155. {
  156. 'target': 'Meditate',
  157. 'text': '',
  158. 'isBlocked': false
  159. },
  160. {
  161. 'target': 'Search',
  162. 'text': '',
  163. 'isBlocked': false
  164. }
  165. ];
  166.  
  167. // creating objects associated with the on/off settings menu items, for setting and updating them
  168. var switchTurnSound = new DynamicMenuItem('play_updateSoundEnabled', $newSettingsMenu.find('#dynTurnSpan'), 'Audible Turn Alerts');
  169. var switchAttackSound = new DynamicMenuItem('play_AttackSoundEnabled', $newSettingsMenu.find('#dynAttackSpan'), 'Audible Attack Alerts');
  170. var switchMsgSound = new DynamicMenuItem('play_MessageSoundEnabled', $newSettingsMenu.find('#dynMsgSpan'), 'Audible Message Alerts');
  171. var switchPopups = new DynamicMenuItem('play_html5PushEnabled', $newSettingsMenu.find('#dynPopSpan'), 'HTML5 Popup Notifications');
  172.  
  173. // -----------
  174. // MAIN SCRIPT
  175.  
  176. // attaching click listeners to dynamic settings menu items
  177. $newSettingsMenu.find('#settingsTurnAudio').click(function () {
  178. switchTurnSound.flipSwitch();
  179. playUpdateSound = localStorage.getItem(switchTurnSound.storageKey) === 'true'; // converting the 'true' or 'false' string into a real boolean
  180. });
  181. $newSettingsMenu.find('#settingsAttackAudio').click(function () {
  182. switchAttackSound.flipSwitch();
  183. playAttackSound = localStorage.getItem(switchAttackSound.storageKey) === 'true';
  184. });
  185. $newSettingsMenu.find('#settingsMsgAudio').click(function () {
  186. switchMsgSound.flipSwitch();
  187. playMessageSound = localStorage.getItem(switchMsgSound.storageKey) === 'true';
  188. });
  189. $newSettingsMenu.find('#settingsPopups').click(function () {
  190. switchPopups.flipSwitch();
  191. notificationsEnabled = localStorage.getItem(switchPopups.storageKey) === 'true';
  192. });
  193.  
  194. // Replacing the wrench with the new settings menu
  195. $settingsWrenchListitem.replaceWith($newSettingsMenu);
  196.  
  197. // populate barButtonsData[0-2].text with actionButton texts
  198. $playerActions.find('.actionButton').each(function (i) {
  199. barButtonsData[i].text = $(this).text();
  200. });
  201.  
  202. // Checking whether player has insufficient AP for these actions
  203. if ((cleanseCost > ap) || (playerMana < 3.0)) {
  204. barButtonsData[0].isBlocked = true;
  205. }
  206. if (meditateCost > ap) {
  207. barButtonsData[1].isBlocked = true;
  208. }
  209. if (searchCost > ap) {
  210. barButtonsData[2].isBlocked = true;
  211. }
  212.  
  213. // General Activity Log alterations
  214. if ($lindellaLatest.length) { // if Lindella has at least one log entry
  215. $lindellaLatest.css({
  216. 'color': 'cyan'
  217. });
  218. lindellaTrail = $lindellaLatest.text();
  219. if (lindellaTrail.includes(" left toward ")) { // check that it's a leaving entry
  220. lindellaTrail = lindellaTrail.match(/Lindella the Soul Vendor left toward (.*)\n/)[1]; // Now make it just the location she's leaving towards
  221. }
  222. else {
  223. lindellaTrail = null;
  224. }
  225. // alert('lindellaTrail is ' + lindellaTrail);
  226. }
  227.  
  228. if ($wuffieLatest.length) { // if Wüffie has at least one log entry
  229. $wuffieLatest.css({
  230. 'color': 'deeppink'
  231. });
  232. wuffieTrail = $wuffieLatest.text();
  233. if (wuffieTrail.includes(" left toward ")) { // check that it's a leaving entry
  234. wuffieTrail = wuffieTrail.match(/Wüffie the Soul Pet Vendor left toward (.*)\n/)[1]; // Now make it just the location she's leaving towards
  235. }
  236. else {
  237. wuffieTrail = null;
  238. }
  239. // alert('wuffieTrail is ' + wuffieTrail);
  240. }
  241.  
  242. // $activityLog.find('li').each( linkifyLogEntries );
  243.  
  244. $activityLogWide = $activityLog.clone();
  245.  
  246. // Activity Log alterations (original only, post-cloning)
  247. $activityLog
  248. .addClass('hidden-lg');
  249.  
  250. // Activity Log alterations (sidebar only)
  251. $activityLogWide
  252. .attr('id', 'RecentActivityLogSide') // giving the clone a unique ID, unfortunately this removes the id-targeted style rules and forces me to re-add them with the .css method
  253. .addClass('visible-lg-block') // Bootstrap class thingy that makes this only show up when the page is at least 1200px wide.
  254. .css({
  255. 'background': '#C2A9AF',
  256. 'text-align': 'justify',
  257. 'overflow-y': 'scroll',
  258. 'padding': 10,
  259. 'width': '31%',
  260. 'position': 'fixed',
  261. 'resize': 'none',
  262. 'height': 'initial',
  263. 'top': 15,
  264. 'bottom': 15
  265. }) // the 31% width is a kludge.
  266. ;
  267.  
  268. // $specialBox
  269. // .css({'font-size': 'small'})
  270. // ;
  271.  
  272. $siteFooter
  273. .unwrap() // stripping the footer's container div, footer will be moved into another container later
  274. ;
  275.  
  276. $tftSite.find('.offlinePlayersWrapperBG') // this is the inventory row
  277. .css({
  278. 'border-bottom': 0,
  279. 'background': '#ebe8e0'
  280. });
  281.  
  282. /*
  283. $tftSite.find('.formerPlayer') // links to player profiles of ground items
  284. .wrap(function() {
  285. var $this = $(this);
  286. var victimName = $this.text().slice(10, -1);
  287. return ('<a href="https://www.transformaniatime.com/PvP/LookAtPlayerItem?vicname=' + victimName + '">');
  288. })
  289. ;
  290. */
  291.  
  292. $actionCounts
  293. .each(function (i) {
  294. var $this = $(this);
  295. if ($this.text() === ' 3 / 3') {
  296. $this.css({
  297. 'color': 'red',
  298. 'font-weight': 'bold'
  299. }); // warning when tapped out on limited-per-turn actions
  300. if (i === 1) { // if it's the second one, i.e. recoveries. Kind of confusing and clunky.
  301. barButtonsData[0].isBlocked = true; // blocking both cleansing and meditation
  302. barButtonsData[1].isBlocked = true;
  303. }
  304. }
  305. })
  306. .css({
  307. 'font-size': 'larger'
  308. });
  309.  
  310. $allIcons
  311. .css({
  312. 'font-size': 'x-large'
  313. })
  314. .removeClass();
  315.  
  316. $attackIcon
  317. .html('⚔️');
  318.  
  319. $recoverIcon
  320. .html('🔮');
  321.  
  322. $moneyIcon
  323. .html('💰');
  324.  
  325. // preventing style rule freakout
  326. $containerInner
  327. .css({
  328. 'background': '#ebe8e0'
  329. });
  330.  
  331. // gross rearrangement of page layout -- this unfortunately causes a marked visual 'jump' once the page is reattached
  332. $siteTop
  333. .attr('class', 'container-fluid')
  334.  
  335. .children()
  336.  
  337. .wrapAll('<div class="col-lg-8">') // wrap the ENTIRE existing contents in new column
  338. .parent() // traverse to newly-made column
  339. .wrap('<div class="row">') // wrap the new column in a new row
  340. .append($specialBox)
  341. .append($siteFooter) // move footer to bottom of column
  342.  
  343. .after('<div class="col-lg-4">') // add another column to the row
  344. .next() // traverse to newly-made column
  345. .append($activityLogWide) // add side-log to the new column
  346.  
  347. ;
  348.  
  349. $moveGrid // direct alteration before moving to new spot
  350. .css({
  351. 'width': 'inherit',
  352. 'height': 'inherit',
  353. 'box-shadow': '-5px 5px 5px gray',
  354. 'table-layout': 'fixed'
  355. })
  356.  
  357. .find('td')
  358. .css({
  359. 'font-size': 12,
  360. })
  361. .end()
  362.  
  363. .find('tr')
  364. .css({
  365. 'height': '33%'
  366. })
  367.  
  368. .find('a')
  369. .css({
  370. 'display': 'table',
  371. 'width': '100%',
  372. 'height': '100%'
  373. }) // expands the anchor to fill the cell; this requires removing the display:table-cell property, which unfortunately messes up the text's vertical alignment
  374. .append(function () { // moves the anchor's text to a newly-created nested div element which itself has display:table-cell, fixing the vertical alignment
  375. var $myText = $(this).text();
  376. $(this).text('');
  377. return '<div style="display: table-cell; vertical-align: middle">' + $myText + '</div>';
  378. })
  379. .end()
  380.  
  381. .find('div:contains(' + wuffieTrail + ')') // if wuffieTrail is null, this finds nothing and nothing happens
  382. .each(function () {
  383. // alert('text is ' + $(this).text());
  384. if ($(this).text() === wuffieTrail) { // ensures a precise match, not just containing
  385. $(this).css({
  386. 'color': 'deeppink'
  387. });
  388. return false; // stops looping
  389. }
  390. })
  391.  
  392. .end()
  393.  
  394. .find('div:contains(' + lindellaTrail + ')') // if lindellaTrail is null, this finds nothing and nothing happens
  395. // will overwrite Wuffie's highlighting if they both coincide. A little wasteful
  396. .each(function () {
  397. // alert('text is ' + $(this).text());
  398. if ($(this).text() === lindellaTrail) { // ensures a precise match, not just containing
  399. $(this).css({
  400. 'color': 'cyan'
  401. });
  402. return false; // stops looping
  403. }
  404. })
  405. .end();
  406.  
  407. $moveGridCells // this part is a little repetitious, but not too much so, and the individual parts might change in the future
  408.  
  409. .eq(8) // ninth cell
  410. .css({
  411. 'background': '#A16969'
  412. })
  413.  
  414. .append('<a href="/pvp/EnchantLocation" style="font-weight: bold; font-size: larger; position: initial; color: white !important">Enchant</a>') // I don't have a better alternative than !important here unfortunately
  415. .find('a') // selects just-created anchor
  416. .hover(pseudoShoutClass)
  417. .end()
  418.  
  419. .end()
  420.  
  421. .eq(2) // third cell
  422. .css({
  423. 'background': '#A16969'
  424. })
  425.  
  426. .append('<a href="/pvp/shout" class="shout" style="font-weight: bold; font-size: larger; position: initial; color: white !important">Shout</a>')
  427. .find('a') // selects just-created anchor
  428. .hover(pseudoShoutClass)
  429. .end()
  430.  
  431. .end()
  432.  
  433. .eq(6) // seventh cell
  434. .css({
  435. 'background': '#A16969'
  436. })
  437.  
  438. .append($knownSpells.html()) // simply sticks the html for $knownSpells into the cell unmodified
  439. ;
  440.  
  441. if (busStop) {
  442. $moveGridCells
  443.  
  444. .eq(0)
  445. .css({
  446. 'background': '#A16969'
  447. })
  448.  
  449. .append('<a href="/pvp/Bus" style="font-weight: bold; font-size: larger; position: initial; color: white !important">Take Bus</a>')
  450. .find('a') // selects just-created anchor
  451. .hover(pseudoShoutClass);
  452. }
  453.  
  454. $avatarText // direct alteration before moving
  455. .css({
  456. 'margin-bottom': 10,
  457. 'display': 'inline-block'
  458. });
  459.  
  460. $innerRows.eq(0) // Row with player portrait and stats
  461. .css({
  462. 'margin-bottom': 20,
  463. 'height': 301
  464. })
  465.  
  466. .find($frontOuters) // the three columns
  467. .css({
  468. 'border': 0,
  469. 'height': 301
  470. })
  471.  
  472. .eq(0) // left column, original location of player name and form name, new location of moveGrid
  473. .css({
  474. 'width': 301
  475. })
  476. .prepend($moveGrid) // moves it from its original location, already has shadow set
  477. .end()
  478.  
  479. .eq(1) // center column, location of player portrait
  480. .css({
  481. 'width': 301
  482. })
  483.  
  484. .find('.portraitFront')
  485. .css({
  486. 'border': 0,
  487. 'width': 'inherit',
  488. 'height': 'inherit',
  489. 'box-shadow': '1px 5px 5px gray'
  490. })
  491. .wrap('<a href="' + selfLookURL + '" style="font-weight: normal; width: inherit; height: inherit"></a>')
  492. .end()
  493.  
  494. .end()
  495.  
  496. .eq(2) // right column, location of player stats
  497.  
  498. .find('.avatarBars')
  499. .css({
  500. 'background': '#d9d9d9',
  501. 'box-shadow': '5px 5px 5px gray'
  502. })
  503. .prepend($avatarText) // pulled over here from left column
  504.  
  505. .find('.barWrapper') // 'backgrounds' of the bars, empty bars
  506. .css({
  507. 'height': 35
  508. })
  509.  
  510. .find('.barText') // what it says on the tin
  511. .css({
  512. 'line-height': '35px'
  513. })
  514. .each(function (i) { // this will break if the order of the vital stat bars is changed
  515. var $this = $(this);
  516. $this
  517. .wrap('<a href="/PvP/' + barButtonsData[i].target + '" style="font-weight: normal"></a>') // inherits .barWrapper size
  518. .hover(
  519. function () {
  520. $this // mouseenter
  521. .data('initialText', $this.text())
  522. .text(barButtonsData[i].text)
  523. .css({
  524. 'color': '#e4e0d4',
  525. 'font-weight': 'bold'
  526. });
  527. if (barButtonsData[i].isBlocked && (secondsToUpdate > 0)) {
  528. $this
  529. .css({
  530. 'text-decoration': 'line-through'
  531. })
  532. .parent()
  533. .attr('href', null);
  534. }
  535. },
  536. function () {
  537. $this // mouseleave
  538. .text($this.data('initialText'))
  539. .css({
  540. 'color': '',
  541. 'font-weight': '',
  542. 'text-decoration': ''
  543. })
  544. .parent()
  545. .attr('href', '/PvP/' + barButtonsData[i].target);
  546. });
  547. })
  548. .end()
  549.  
  550. .find('.barData') // 'fullness' of bars, widths are percentages of bar wrappers
  551. .css({
  552. 'height': 'inherit'
  553. })
  554.  
  555. .filter('.barWP') // possibly I should be doing this with an array-like object instead?
  556. .css({
  557. 'background': 'linear-gradient(red, darkred)'
  558. })
  559. .end()
  560.  
  561. .filter('.barMP')
  562. .css({
  563. 'background': 'linear-gradient(blue, darkblue)'
  564. })
  565. .end()
  566.  
  567. .filter('.barAP')
  568. .css({
  569. 'background': 'linear-gradient(orange, darkorange)'
  570. })
  571. .end()
  572.  
  573. .end()
  574.  
  575. .end()
  576.  
  577. .find('.avatarXPAmt')
  578. .css({
  579. 'background': 'linear-gradient(violet, purple)'
  580. })
  581. .wrap('<a href="/PvP/MyPerks"></a>') // inherits size from .avatarXPWrapper
  582. ;
  583.  
  584. $innerRows.eq(1) // second row, with now-empty movegrid column and room description
  585.  
  586. .find('.col-md-4') // former movegrid column
  587. .remove()
  588. .end()
  589.  
  590. .find('.covenDescription') // room description and title
  591. .removeClass('col-md-8');
  592.  
  593. if (covSafeground) {
  594.  
  595. $covController
  596.  
  597. .css({
  598. 'color': '#a13d2d'
  599. })
  600.  
  601. .contents()
  602.  
  603. .first() // The text that says 'Enchanted by the '
  604. .replaceWith('SAFEGROUND for the ');
  605. }
  606.  
  607. // Finally update the real site with the changes
  608. $('head').after($tftSite);
  609.  
  610. // console.log(Date.now() - startTime);
  611.  
  612. });