| @@ -0,0 +1,132 @@ | |||
| 1 | + | <?php | |
| 2 | + | /** | |
| 3 | + | * Step 4 - import the ZnoteX schema. | |
| 4 | + | * | |
| 5 | + | * Only the znote_* tables. Every statement in znote_schema.sql is | |
| 6 | + | * CREATE TABLE IF NOT EXISTS, so running this against a database that already | |
| 7 | + | * has some of them adds the missing ones and leaves the rest alone. | |
| 8 | + | */ | |
| 9 | + | ||
| 10 | + | if (!defined('ZNOTE_INSTALL')) { http_response_code(403); die('Direct access denied.'); } | |
| 11 | + | ||
| 12 | + | $schemaFile = install_root() . '/SQL/znote_schema.sql'; | |
| 13 | + | $report = null; | |
| 14 | + | ||
| 15 | + | if ($_SERVER['REQUEST_METHOD'] === 'POST') { | |
| 16 | + | ||
| 17 | + | $link = install_connect($connectError); | |
| 18 | + | ||
| 19 | + | if ($link === null) { | |
| 20 | + | install_error('Lost the database connection: ' . ih((string)$connectError)); | |
| 21 | + | } elseif (!is_file($schemaFile)) { | |
| 22 | + | install_error('SQL/znote_schema.sql is missing.'); | |
| 23 | + | } else { | |
| 24 | + | ||
| 25 | + | $sql = (string)file_get_contents($schemaFile); | |
| 26 | + | $sql = str_replace(array("\r\n", "\r"), "\n", $sql); | |
| 27 | + | ||
| 28 | + | // Strip line comments, then split on ";" at end of line. The file is | |
| 29 | + | // generated by us and has no procedures or triggers, so this is safe - | |
| 30 | + | // a general-purpose SQL splitter would be overkill. | |
| 31 | + | $sql = preg_replace('/^--.*$/m', '', $sql); | |
| 32 | + | ||
| 33 | + | $created = 0; | |
| 34 | + | $skipped = 0; | |
| 35 | + | $errors = array(); | |
| 36 | + | ||
| 37 | + | foreach (array_filter(array_map('trim', explode(";\n", $sql))) as $statement) { | |
| 38 | + | $statement = trim($statement, "; \t\n\r"); | |
| 39 | + | if ($statement === '') { | |
| 40 | + | continue; | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // Seed rows must not be duplicated on a re-run. | |
| 44 | + | if (stripos($statement, 'INSERT INTO') === 0) { | |
| 45 | + | if (preg_match('/INSERT INTO `([a-z_]+)`/i', $statement, $m)) { | |
| 46 | + | $check = @$link->query('SELECT 1 FROM `' . $m[1] . '` LIMIT 1'); | |
| 47 | + | if ($check !== false && $check->num_rows > 0) { | |
| 48 | + | $skipped++; | |
| 49 | + | continue; | |
| 50 | + | } | |
| 51 | + | } | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | if (@$link->query($statement)) { | |
| 55 | + | $created++; | |
| 56 | + | } else { | |
| 57 | + | $errors[] = $link->error . "\n in: " | |
| 58 | + | . substr((string)preg_replace('/\s+/', ' ', $statement), 0, 90); | |
| 59 | + | } | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | // Record the version, which the schema's own INSERT may have skipped. | |
| 63 | + | @$link->query("UPDATE `znote` SET `version` = '2.0.1'"); | |
| 64 | + | ||
| 65 | + | $tablesNow = 0; | |
| 66 | + | $result = @$link->query("SHOW TABLES LIKE 'znote%'"); | |
| 67 | + | if ($result !== false) { | |
| 68 | + | $tablesNow = $result->num_rows; | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | $link->close(); | |
| 72 | + | ||
| 73 | + | $report = array( | |
| 74 | + | 'created' => $created, | |
| 75 | + | 'skipped' => $skipped, | |
| 76 | + | 'errors' => $errors, | |
| 77 | + | 'tables' => $tablesNow, | |
| 78 | + | ); | |
| 79 | + | ||
| 80 | + | if (!$errors) { | |
| 81 | + | install_state(array('schema_done' => true)); | |
| 82 | + | install_max_step(5); | |
| 83 | + | } else { | |
| 84 | + | install_error(count($errors) . ' statement(s) failed. The first was: ' | |
| 85 | + | . ih((string)$errors[0])); | |
| 86 | + | } | |
| 87 | + | } | |
| 88 | + | } | |
| 89 | + | ?> | |
| 90 | + | <h1>Schema</h1> | |
| 91 | + | <p class="lead"> | |
| 92 | + | Creates the <code>znote_*</code> tables in | |
| 93 | + | <code><?= ih(install_get('sqlDatabase')) ?></code>. Your server's own tables are not touched. | |
| 94 | + | </p> | |
| 95 | + | ||
| 96 | + | <?php if ($report === null): ?> | |
| 97 | + | ||
| 98 | + | <div class="info"> | |
| 99 | + | Every statement is <code>CREATE TABLE IF NOT EXISTS</code>, and seed rows are skipped for | |
| 100 | + | tables that already hold data — running this on a database that already has some | |
| 101 | + | ZnoteX tables adds only what is missing. | |
| 102 | + | </div> | |
| 103 | + | ||
| 104 | + | <form method="post"> | |
| 105 | + | <div class="actions"> | |
| 106 | + | <button class="btn" type="submit">Import the schema</button> | |
| 107 | + | <a class="btn ghost" href="<?= install_url(3) ?>">Back</a> | |
| 108 | + | </div> | |
| 109 | + | </form> | |
| 110 | + | ||
| 111 | + | <?php else: ?> | |
| 112 | + | ||
| 113 | + | <?php if (!$report['errors']): ?> | |
| 114 | + | <p class="good"> | |
| 115 | + | Done. <?= (int)$report['created'] ?> statement<?= $report['created'] === 1 ? '' : 's' ?> ran, | |
| 116 | + | <?= (int)$report['skipped'] ?> skipped as already populated. | |
| 117 | + | <?= (int)$report['tables'] ?> <code>znote_*</code> tables are now present. | |
| 118 | + | </p> | |
| 119 | + | <?php else: ?> | |
| 120 | + | <p class="bad"><?= count($report['errors']) ?> statement(s) failed:</p> | |
| 121 | + | <pre><?php foreach (array_slice($report['errors'], 0, 6) as $e) { echo ih($e), "\n"; } ?></pre> | |
| 122 | + | <?php endif; ?> | |
| 123 | + | ||
| 124 | + | <div class="actions"> | |
| 125 | + | <?php if (!$report['errors']): ?> | |
| 126 | + | <a class="btn" href="<?= install_url(5) ?>">Continue</a> | |
| 127 | + | <?php else: ?> | |
| 128 | + | <a class="btn ghost" href="<?= install_url(4) ?>">Try again</a> | |
| 129 | + | <?php endif; ?> | |
| 130 | + | </div> | |
| 131 | + | ||
| 132 | + | <?php endif; ?> |
| @@ -0,0 +1,187 @@ | |||
| 1 | + | <?php | |
| 2 | + | /** | |
| 3 | + | * Step 5 - the administrator. | |
| 4 | + | * | |
| 5 | + | * Creates an account, a character on it, and remembers the account name so the | |
| 6 | + | * last step can put it in page_admin_access. | |
| 7 | + | * | |
| 8 | + | * The account is created the way register.php does it, so the password hash | |
| 9 | + | * matches what login.php expects on this engine. | |
| 10 | + | */ | |
| 11 | + | ||
| 12 | + | if (!defined('ZNOTE_INSTALL')) { http_response_code(403); die('Direct access denied.'); } | |
| 13 | + | ||
| 14 | + | $engine = (string)install_get('ServerEngine', 'TFS_10'); | |
| 15 | + | $isOthire = ($engine === 'OTHIRE'); | |
| 16 | + | $done = (bool)install_get('admin_done', false); | |
| 17 | + | ||
| 18 | + | if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$done) { | |
| 19 | + | ||
| 20 | + | $accountName = trim((string)($_POST['account'] ?? '')); | |
| 21 | + | $password = (string)($_POST['password'] ?? ''); | |
| 22 | + | $password2 = (string)($_POST['password_again'] ?? ''); | |
| 23 | + | $email = trim((string)($_POST['email'] ?? '')); | |
| 24 | + | $character = trim((string)($_POST['character'] ?? '')); | |
| 25 | + | ||
| 26 | + | $problems = array(); | |
| 27 | + | ||
| 28 | + | if ($accountName === '' || strlen($accountName) > 30) { $problems[] = 'The account name is required, up to 30 characters.'; } | |
| 29 | + | if ($isOthire) { | |
| 30 | + | if (!preg_match('/^[0-9]+$/', $accountName)) { | |
| 31 | + | $problems[] = 'OTHire account numbers may only contain digits.'; | |
| 32 | + | } | |
| 33 | + | } elseif (!preg_match('/^[A-Za-z0-9]+$/', $accountName)) { | |
| 34 | + | $problems[] = 'The account name may only contain letters and numbers. Do not use @ or special characters.'; | |
| 35 | + | } | |
| 36 | + | if (strlen($password) < 6) { $problems[] = 'The password must be at least 6 characters.'; } | |
| 37 | + | if (strlen($password) > 29) { $problems[] = 'The password may not be longer than 29 characters.'; } | |
| 38 | + | if ($password !== $password2) { $problems[] = 'The passwords do not match.'; } | |
| 39 | + | if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { $problems[] = 'A valid e-mail address is required.'; } | |
| 40 | + | if ($character === '' || strlen($character) > 20) { $problems[] = 'The character name is required, up to 20 characters.'; } | |
| 41 | + | if (!preg_match('/^[A-Za-z ]+$/', $character)) { $problems[] = 'The character name may only contain letters and spaces.'; } | |
| 42 | + | ||
| 43 | + | if ($problems) { | |
| 44 | + | install_error(implode('<br>', array_map('ih', $problems))); | |
| 45 | + | } else { | |
| 46 | + | ||
| 47 | + | $link = install_connect($connectError); | |
| 48 | + | ||
| 49 | + | if ($link === null) { | |
| 50 | + | install_error('Lost the database connection: ' . ih((string)$connectError)); | |
| 51 | + | } else { | |
| 52 | + | ||
| 53 | + | $esc = static fn(string $v): string => $link->real_escape_string($v); | |
| 54 | + | ||
| 55 | + | // Refuse rather than silently attach to someone else's account. | |
| 56 | + | $taken = @$link->query("SELECT `id` FROM `players` WHERE `name` = '" . $esc($character) . "' LIMIT 1"); | |
| 57 | + | if ($taken !== false && $taken->num_rows > 0) { | |
| 58 | + | install_error('A character named <strong>' . ih($character) . '</strong> already exists.'); | |
| 59 | + | } else { | |
| 60 | + | ||
| 61 | + | $now = time(); | |
| 62 | + | $hash = sha1($password); | |
| 63 | + | ||
| 64 | + | if ($isOthire) { | |
| 65 | + | // OTHire identifies accounts by number, not by name. | |
| 66 | + | $accountId = (int)$accountName; | |
| 67 | + | @$link->query("INSERT INTO `accounts` (`id`, `password`, `email`) VALUES ({$accountId}, '{$hash}', '" . $esc($email) . "')"); | |
| 68 | + | } else { | |
| 69 | + | $creation = ($engine === 'TFS_10' || $engine === 'TFS_16' || $engine === 'CANARY') | |
| 70 | + | ? ", `creation`" : ''; | |
| 71 | + | $creationValue = $creation !== '' ? ", {$now}" : ''; | |
| 72 | + | ||
| 73 | + | @$link->query("INSERT INTO `accounts` (`name`, `password`, `email`{$creation}) | |
| 74 | + | VALUES ('" . $esc($accountName) . "', '{$hash}', '" . $esc($email) . "'{$creationValue})"); | |
| 75 | + | $accountId = (int)$link->insert_id; | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | if ($accountId <= 0) { | |
| 79 | + | install_error('Could not create the account: ' . ih($link->error)); | |
| 80 | + | } else { | |
| 81 | + | ||
| 82 | + | @$link->query("INSERT INTO `znote_accounts` (`account_id`, `ip`, `created`, `points`, `active`, `active_email`, `activekey`, `flag`) | |
| 83 | + | VALUES ({$accountId}, 0, {$now}, 0, 1, 1, 0, '')"); | |
| 84 | + | ||
| 85 | + | // A level 8 knight with the stock starting stats. The point | |
| 86 | + | // is to have a character whose name grants panel access; | |
| 87 | + | // tune it in game or from Admin Panel > Character Skills. | |
| 88 | + | $ok = @$link->query("INSERT INTO `players` | |
| 89 | + | (`name`, `group_id`, `account_id`, `level`, `vocation`, `health`, `healthmax`, | |
| 90 | + | `experience`, `maglevel`, `mana`, `manamax`, `town_id`, `cap`, `sex`, `looktype`) | |
| 91 | + | VALUES ('" . $esc($character) . "', 1, {$accountId}, 8, 4, 185, 185, 4200, 0, 90, 90, 1, 470, 1, 128)"); | |
| 92 | + | ||
| 93 | + | if (!$ok) { | |
| 94 | + | install_error('The account was created but the character was not: ' . ih($link->error) | |
| 95 | + | . '<br>Your server\'s <code>players</code> table may need columns this insert does not set.'); | |
| 96 | + | } else { | |
| 97 | + | $playerId = (int)$link->insert_id; | |
| 98 | + | @$link->query("INSERT INTO `znote_players` (`player_id`, `created`, `hide_char`, `comment`) | |
| 99 | + | VALUES ({$playerId}, {$now}, 0, '')"); | |
| 100 | + | ||
| 101 | + | install_state(array( | |
| 102 | + | 'admin_done' => true, | |
| 103 | + | 'admin_account' => $accountName, | |
| 104 | + | 'admin_character' => $character, | |
| 105 | + | )); | |
| 106 | + | install_max_step(6); | |
| 107 | + | ||
| 108 | + | $link->close(); | |
| 109 | + | header('Location: ' . install_url(6)); | |
| 110 | + | exit; | |
| 111 | + | } | |
| 112 | + | } | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | $link->close(); | |
| 116 | + | } | |
| 117 | + | } | |
| 118 | + | } | |
| 119 | + | ?> | |
| 120 | + | <h1>Administrator</h1> | |
| 121 | + | ||
| 122 | + | <?php if ($done): ?> | |
| 123 | + | ||
| 124 | + | <p class="good"> | |
| 125 | + | Account <strong><?= ih(install_get('admin_account')) ?></strong> and character | |
| 126 | + | <strong><?= ih(install_get('admin_character')) ?></strong> were created. The account | |
| 127 | + | is given panel access at the next step. | |
| 128 | + | </p> | |
| 129 | + | <div class="actions"> | |
| 130 | + | <a class="btn" href="<?= install_url(6) ?>">Continue</a> | |
| 131 | + | </div> | |
| 132 | + | ||
| 133 | + | <?php else: ?> | |
| 134 | + | ||
| 135 | + | <p class="lead"> | |
| 136 | + | An account to log in with, and a character on it. ZnoteX grants admin rights by | |
| 137 | + | <em>account name</em>, so the account name below is what unlocks the panel. | |
| 138 | + | </p> | |
| 139 | + | ||
| 140 | + | <form method="post"> | |
| 141 | + | <div class="row"> | |
| 142 | + | <div class="field"> | |
| 143 | + | <label class="lbl" for="account"><?= $isOthire ? 'Account number' : 'Account name' ?></label> | |
| 144 | + | <input type="text" id="account" name="account" maxlength="30" | |
| 145 | + | value="<?= ih(install_get('admin_account')) ?>" required> | |
| 146 | + | <?php if ($isOthire): ?> | |
| 147 | + | <p class="hint">OTHire identifies accounts by number.</p> | |
| 148 | + | <?php else: ?> | |
| 149 | + | <p class="hint">Letters and numbers only. Do not use @ or special characters.</p> | |
| 150 | + | <?php endif; ?> | |
| 151 | + | </div> | |
| 152 | + | <div class="field"> | |
| 153 | + | <label class="lbl" for="email">E-mail</label> | |
| 154 | + | <input type="email" id="email" name="email" required> | |
| 155 | + | </div> | |
| 156 | + | </div> | |
| 157 | + | ||
| 158 | + | <div class="row"> | |
| 159 | + | <div class="field"> | |
| 160 | + | <label class="lbl" for="password">Password</label> | |
| 161 | + | <input type="password" id="password" name="password" required> | |
| 162 | + | <p class="hint">At least 6 characters.</p> | |
| 163 | + | </div> | |
| 164 | + | <div class="field"> | |
| 165 | + | <label class="lbl" for="password_again">Password again</label> | |
| 166 | + | <input type="password" id="password_again" name="password_again" required> | |
| 167 | + | </div> | |
| 168 | + | </div> | |
| 169 | + | ||
| 170 | + | <div class="field"> | |
| 171 | + | <label class="lbl" for="character">Character name</label> | |
| 172 | + | <input type="text" id="character" name="character" maxlength="20" required> | |
| 173 | + | <p class="hint">Letters and spaces only. Your character in game — panel access comes from the account name above.</p> | |
| 174 | + | </div> | |
| 175 | + | ||
| 176 | + | <div class="info"> | |
| 177 | + | The character is created as a level 8 knight with the default starting stats. Change it | |
| 178 | + | in game, or from <strong>Admin Panel → Character Skills</strong>, once you are in. | |
| 179 | + | </div> | |
| 180 | + | ||
| 181 | + | <div class="actions"> | |
| 182 | + | <button class="btn" type="submit">Create the administrator</button> | |
| 183 | + | <a class="btn ghost" href="<?= install_url(4) ?>">Back</a> | |
| 184 | + | </div> | |
| 185 | + | </form> | |
| 186 | + | ||
| 187 | + | <?php endif; ?> |
| @@ -0,0 +1,157 @@ | |||
| 1 | + | <?php | |
| 2 | + | /** | |
| 3 | + | * Step 6 - write the configuration and lock the installer. | |
| 4 | + | * | |
| 5 | + | * The generated values go to config.local.php, which config.php includes at | |
| 6 | + | * the end. config.php is never rewritten: it stays the documented default file | |
| 7 | + | * you can update with a new ZnoteX release without losing your settings, and | |
| 8 | + | * a 1000-line file edited by regular expression is a file waiting to break. | |
| 9 | + | */ | |
| 10 | + | ||
| 11 | + | if (!defined('ZNOTE_INSTALL')) { http_response_code(403); die('Direct access denied.'); } | |
| 12 | + | ||
| 13 | + | /** | |
| 14 | + | * The file we are about to write, also shown for manual copying. | |
| 15 | + | */ | |
| 16 | + | function install_config_contents(): string { | |
| 17 | + | $quote = static function (string $value): string { | |
| 18 | + | return "'" . str_replace(array('\\', "'"), array('\\\\', "\\'"), $value) . "'"; | |
| 19 | + | }; | |
| 20 | + | ||
| 21 | + | $admin = (string)install_get('admin_account'); | |
| 22 | + | $nl = "\n"; | |
| 23 | + | ||
| 24 | + | $out = '<?php' . $nl; | |
| 25 | + | $out .= '/**' . $nl; | |
| 26 | + | $out .= ' * Written by the ZnoteX installer.' . $nl; | |
| 27 | + | $out .= ' *' . $nl; | |
| 28 | + | $out .= ' * config.php is included first and holds every default; this file overrides the' . $nl; | |
| 29 | + | $out .= ' * handful of values specific to this install. Updating ZnoteX therefore means' . $nl; | |
| 30 | + | $out .= ' * replacing config.php and leaving this file alone.' . $nl; | |
| 31 | + | $out .= ' *' . $nl; | |
| 32 | + | $out .= ' * Most other settings are editable from Admin Panel > Settings.' . $nl; | |
| 33 | + | $out .= ' */' . $nl . $nl; | |
| 34 | + | ||
| 35 | + | $out .= '$config[\'sqlHost\'] = ' . $quote((string)install_get('sqlHost')) . ';' . $nl; | |
| 36 | + | $out .= '$config[\'sqlUser\'] = ' . $quote((string)install_get('sqlUser')) . ';' . $nl; | |
| 37 | + | $out .= '$config[\'sqlPassword\'] = ' . $quote((string)install_get('sqlPassword')) . ';' . $nl; | |
| 38 | + | $out .= '$config[\'sqlDatabase\'] = ' . $quote((string)install_get('sqlDatabase')) . ';' . $nl . $nl; | |
| 39 | + | ||
| 40 | + | $out .= '$config[\'ServerEngine\'] = ' . $quote((string)install_get('ServerEngine', 'TFS_10')) . ';' . $nl; | |
| 41 | + | $out .= '$config[\'site_title\'] = ' . $quote((string)install_get('site_title')) . ';' . $nl; | |
| 42 | + | $out .= '$config[\'site_url\'] = ' . $quote((string)install_get('site_url')) . ';' . $nl; | |
| 43 | + | ||
| 44 | + | $path = (string)install_get('server_path'); | |
| 45 | + | if ($path !== '') { | |
| 46 | + | $out .= '$config[\'server_path\'] = ' . $quote($path) . ';' . $nl; | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | $out .= $nl . '// Admin access is granted by account name.' . $nl; | |
| 50 | + | $out .= '$config[\'page_admin_access\'] = array(' . $nl; | |
| 51 | + | $out .= "\t" . $quote($admin) . ',' . $nl; | |
| 52 | + | $out .= ');' . $nl; | |
| 53 | + | ||
| 54 | + | return $out; | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | $written = false; | |
| 58 | + | $writeErr = ''; | |
| 59 | + | ||
| 60 | + | $contents = install_config_contents(); | |
| 61 | + | ||
| 62 | + | if ($_SERVER['REQUEST_METHOD'] === 'POST') { | |
| 63 | + | $target = install_config_file(); | |
| 64 | + | ||
| 65 | + | if (@file_put_contents($target, $contents) === false) { | |
| 66 | + | $writeErr = 'Could not write ' . $target . '. Create it by hand with the contents below.'; | |
| 67 | + | } else { | |
| 68 | + | $written = true; | |
| 69 | + | ||
| 70 | + | // The lock. Written last, so a failed write above does not lock a site | |
| 71 | + | // that is not actually configured. | |
| 72 | + | @file_put_contents(install_lock_file(), | |
| 73 | + | "Installed on " . date('Y-m-d H:i:s') . "\n" | |
| 74 | + | . "Delete this file only if you mean to run the installer again.\n"); | |
| 75 | + | ||
| 76 | + | install_reset(); | |
| 77 | + | } | |
| 78 | + | } | |
| 79 | + | ?> | |
| 80 | + | <h1><?= $written ? 'Installed' : 'Finish' ?></h1> | |
| 81 | + | ||
| 82 | + | <?php if ($written): ?> | |
| 83 | + | ||
| 84 | + | <p class="good"> | |
| 85 | + | ZnoteX is installed. <code>config.local.php</code> is written and the installer is locked. | |
| 86 | + | </p> | |
| 87 | + | ||
| 88 | + | <h2>Before you open the site</h2> | |
| 89 | + | <p class="lead"> | |
| 90 | + | Vocations, starting towns and starting skills come pre-filled with generic defaults. | |
| 91 | + | Most servers change at least some of this before letting anyone create a character - | |
| 92 | + | it is much less painful to set it now than after players have already made characters. | |
| 93 | + | </p> | |
| 94 | + | <ul class="checks"> | |
| 95 | + | <li> | |
| 96 | + | <span class="state opt">1</span> | |
| 97 | + | <span class="what"> | |
| 98 | + | Configure vocations, towns & starting skills | |
| 99 | + | <span class="detail">Admin Panel → Settings → Character creation. Each one is an editable table now, no JSON to hand-edit.</span> | |
| 100 | + | </span> | |
| 101 | + | </li> | |
| 102 | + | <li> | |
| 103 | + | <span class="state opt">2</span> | |
| 104 | + | <span class="what"> | |
| 105 | + | Delete the <code>install/</code> folder | |
| 106 | + | <span class="detail">It refuses to run now, but there is no reason to leave it on a public server.</span> | |
| 107 | + | </span> | |
| 108 | + | </li> | |
| 109 | + | <li> | |
| 110 | + | <span class="state opt">3</span> | |
| 111 | + | <span class="what"> | |
| 112 | + | Look around the rest of the panel | |
| 113 | + | <span class="detail">Admin Panel → Settings covers almost everything else you would otherwise edit by hand.</span> | |
| 114 | + | </span> | |
| 115 | + | </li> | |
| 116 | + | </ul> | |
| 117 | + | ||
| 118 | + | <div class="actions"> | |
| 119 | + | <a class="btn green" href="../admin/index.php?p=settings#character-creation">Configure vocations & towns</a> | |
| 120 | + | <a class="btn ghost" href="../admin/index.php">Admin panel</a> | |
| 121 | + | <a class="btn ghost" href="../index.php">Open the site</a> | |
| 122 | + | </div> | |
| 123 | + | ||
| 124 | + | <?php else: ?> | |
| 125 | + | ||
| 126 | + | <p class="lead">Review what will be written, then finish.</p> | |
| 127 | + | ||
| 128 | + | <table> | |
| 129 | + | <tr><th>Setting</th><th>Value</th></tr> | |
| 130 | + | <tr><td>Database</td><td><code><?= ih(install_get('sqlUser')) ?>@<?= ih(install_get('sqlHost')) ?> / <?= ih(install_get('sqlDatabase')) ?></code></td></tr> | |
| 131 | + | <tr><td>Server engine</td><td><code><?= ih(install_get('ServerEngine')) ?></code></td></tr> | |
| 132 | + | <tr><td>Site name</td><td><?= ih(install_get('site_title')) ?></td></tr> | |
| 133 | + | <tr><td>Site URL</td><td><?= ih(install_get('site_url')) ?></td></tr> | |
| 134 | + | <tr><td>Administrator</td><td><?= ih(install_get('admin_account')) ?> <span class="detail">(account name — this is what grants panel access)</span></td></tr> | |
| 135 | + | <tr><td>Character</td><td><?= ih(install_get('admin_character')) ?></td></tr> | |
| 136 | + | </table> | |
| 137 | + | ||
| 138 | + | <?php if ($writeErr !== ''): ?> | |
| 139 | + | <p class="bad"><?= ih($writeErr) ?></p> | |
| 140 | + | <h2>Create config.local.php yourself</h2> | |
| 141 | + | <p>Put this in the ZnoteX root, next to <code>config.php</code>:</p> | |
| 142 | + | <pre><code><?= ih($contents) ?></code></pre> | |
| 143 | + | <?php endif; ?> | |
| 144 | + | ||
| 145 | + | <div class="info"> | |
| 146 | + | These go to <code>config.local.php</code>. Your <code>config.php</code> is left untouched, | |
| 147 | + | so a future ZnoteX update can replace it without losing anything. | |
| 148 | + | </div> | |
| 149 | + | ||
| 150 | + | <form method="post"> | |
| 151 | + | <div class="actions"> | |
| 152 | + | <button class="btn green" type="submit">Write the config and finish</button> | |
| 153 | + | <a class="btn ghost" href="<?= install_url(5) ?>">Back</a> | |
| 154 | + | </div> | |
| 155 | + | </form> | |
| 156 | + | ||
| 157 | + | <?php endif; ?> |
| @@ -0,0 +1,228 @@ | |||
| 1 | + | <?php | |
| 2 | + | /* 2021: Paypal hosts arent neccesarily notify.paypal.com any longer. | |
| 3 | + | if (gethostbyaddr($_SERVER['REMOTE_ADDR']) !== 'notify.paypal.com') { | |
| 4 | + | exit(); | |
| 5 | + | } | |
| 6 | + | */ | |
| 7 | + | ||
| 8 | + | function ip_in_range( $ip, $range ) { | |
| 9 | + | if ( strpos( $range, '/' ) === false ) { | |
| 10 | + | $range .= '/32'; | |
| 11 | + | } | |
| 12 | + | // $range is in IP/CIDR format eg 127.0.0.1/24 | |
| 13 | + | list( $range, $netmask ) = explode( '/', $range, 2 ); | |
| 14 | + | $range_decimal = ip2long( $range ); | |
| 15 | + | $ip_decimal = ip2long( $ip ); | |
| 16 | + | $wildcard_decimal = pow( 2, ( 32 - (int)$netmask ) ) - 1; | |
| 17 | + | $netmask_decimal = ~ $wildcard_decimal; | |
| 18 | + | return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) ); | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | $paypal_ip_ranges = array( | |
| 22 | + | "173.0.81.65", | |
| 23 | + | "173.0.81.140", | |
| 24 | + | "64.4.240.0/21", | |
| 25 | + | "64.4.248.0/22", | |
| 26 | + | "66.211.168.0/22", | |
| 27 | + | "173.0.80.0/20", | |
| 28 | + | "91.243.72.0/23" | |
| 29 | + | ); | |
| 30 | + | ||
| 31 | + | $verified = false; | |
| 32 | + | for($i = 0; $i < count($paypal_ip_ranges); $i++) { | |
| 33 | + | if(ip_in_range($_SERVER["REMOTE_ADDR"], $paypal_ip_ranges[$i])) { | |
| 34 | + | $verified = true; | |
| 35 | + | break; | |
| 36 | + | } | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | if(!$verified) { | |
| 40 | + | exit(); | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // Require the functions to connect to database and fetch config values | |
| 44 | + | require 'config.php'; | |
| 45 | + | require 'engine/database/connect.php'; | |
| 46 | + | ||
| 47 | + | // Fetch and sanitize POST and GET values | |
| 48 | + | function getValue($value) { | |
| 49 | + | return (!empty($value)) ? sanitize($value) : false; | |
| 50 | + | } | |
| 51 | + | function sanitize($data) { | |
| 52 | + | return htmlentities(strip_tags(mysql_znote_escape_string($data))); | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | require_once 'engine/function/translate.php'; | |
| 56 | + | require_once 'engine/function/settings.php'; | |
| 57 | + | require_once 'engine/function/users.php'; | |
| 58 | + | require_once 'engine/function/plugins.php'; | |
| 59 | + | znote_apply_settings(); | |
| 60 | + | znote_plugins_load(); | |
| 61 | + | ||
| 62 | + | function VerifyPaypalIPN(?array $IPN = null){ | |
| 63 | + | if(empty($IPN)){ | |
| 64 | + | $IPN = $_POST; | |
| 65 | + | } | |
| 66 | + | if(empty($IPN['verify_sign'])){ | |
| 67 | + | return null; | |
| 68 | + | } | |
| 69 | + | $IPN['cmd'] = '_notify-validate'; | |
| 70 | + | $PaypalHost = (empty($IPN['test_ipn']) ? 'www' : 'www.sandbox').'.paypal.com'; | |
| 71 | + | $cURL = curl_init(); | |
| 72 | + | curl_setopt($cURL, CURLOPT_SSL_VERIFYPEER, 1); | |
| 73 | + | curl_setopt($cURL, CURLOPT_SSL_VERIFYHOST, 2); | |
| 74 | + | curl_setopt($cURL, CURLOPT_SSLVERSION, 6); | |
| 75 | + | curl_setopt($cURL, CURLOPT_CAINFO, __DIR__ . '/engine/cert/cacert.pem'); | |
| 76 | + | curl_setopt($cURL, CURLOPT_URL, "https://{$PaypalHost}/cgi-bin/webscr"); | |
| 77 | + | curl_setopt($cURL, CURLOPT_ENCODING, 'gzip'); | |
| 78 | + | curl_setopt($cURL, CURLOPT_POST, true); // POST back | |
| 79 | + | curl_setopt($cURL, CURLOPT_POSTFIELDS, $IPN); // the $IPN | |
| 80 | + | curl_setopt($cURL, CURLOPT_HEADER, false); | |
| 81 | + | curl_setopt($cURL, CURLOPT_RETURNTRANSFER, true); | |
| 82 | + | curl_setopt($cURL, CURLOPT_FORBID_REUSE, true); | |
| 83 | + | curl_setopt($cURL, CURLOPT_FRESH_CONNECT, true); | |
| 84 | + | curl_setopt($cURL, CURLOPT_CONNECTTIMEOUT, 30); | |
| 85 | + | curl_setopt($cURL, CURLOPT_TIMEOUT, 60); | |
| 86 | + | curl_setopt($cURL, CURLINFO_HEADER_OUT, true); | |
| 87 | + | curl_setopt($cURL, CURLOPT_HTTPHEADER, array( | |
| 88 | + | 'Connection: close', | |
| 89 | + | 'Expect: ', | |
| 90 | + | )); | |
| 91 | + | $Response = curl_exec($cURL); | |
| 92 | + | $Status = (int)curl_getinfo($cURL, CURLINFO_HTTP_CODE); | |
| 93 | + | curl_close($cURL); | |
| 94 | + | if(empty($Response) or !preg_match('~^(VERIFIED|INVALID)$~i', $Response = trim($Response)) or !$Status){ | |
| 95 | + | return null; | |
| 96 | + | } | |
| 97 | + | if(intval($Status / 100) != 2){ | |
| 98 | + | return false; | |
| 99 | + | } | |
| 100 | + | return !strcasecmp($Response, 'VERIFIED'); | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // Fetch paypal configurations | |
| 104 | + | $paypal = $config['paypal']; | |
| 105 | + | $prices = $config['paypal_prices']; | |
| 106 | + | ||
| 107 | + | // Send an empty HTTP 204 OK response to acknowledge receipt of the notification | |
| 108 | + | http_response_code(204); | |
| 109 | + | ||
| 110 | + | // Build the required acknowledgement message out of the notification just received | |
| 111 | + | $postdata = 'cmd=_notify-validate'; | |
| 112 | + | if(!empty($_POST)){ | |
| 113 | + | $postdata.="&".http_build_query($_POST); | |
| 114 | + | } | |
| 115 | + | // Assign payment notification values to local variables | |
| 116 | + | $item_name = $_POST['item_name'] ?? null; | |
| 117 | + | $item_number = $_POST['item_number'] ?? null; | |
| 118 | + | $payment_status = $_POST['payment_status'] ?? null; | |
| 119 | + | $payment_amount = $_POST['mc_gross'] ?? null; | |
| 120 | + | $payment_currency = $_POST['mc_currency'] ?? null; | |
| 121 | + | $txn_id = getValue($_POST['txn_id'] ?? null); | |
| 122 | + | $receiver_email = getValue($_POST['receiver_email'] ?? null); | |
| 123 | + | $payer_email = getValue($_POST['payer_email'] ?? null); | |
| 124 | + | $custom_raw = (string)($_POST['custom'] ?? ''); | |
| 125 | + | $custom = (int)$custom_raw; | |
| 126 | + | ||
| 127 | + | $connectedIp = $_SERVER['REMOTE_ADDR']; | |
| 128 | + | db()->execute("INSERT INTO `znote_paypal` VALUES ('0', '0', ?, '0', '0', '0')", ["Connection from IP: $connectedIp"]); | |
| 129 | + | ||
| 130 | + | $status = VerifyPaypalIPN(); | |
| 131 | + | if ($status) { | |
| 132 | + | // Check that the payment_status is Completed | |
| 133 | + | if ($payment_status == 'Completed') { | |
| 134 | + | ||
| 135 | + | ||
| 136 | + | // Check that txn_id has not been previously processed | |
| 137 | + | $txn_id_check = db()->fetchOne("SELECT `txn_id` FROM `znote_paypal` WHERE `txn_id` = ?", [$txn_id]); | |
| 138 | + | if ($txn_id_check === false) { | |
| 139 | + | // Check that receiver_email is your Primary PayPal email | |
| 140 | + | if ($receiver_email == $paypal['email']) { | |
| 141 | + | ||
| 142 | + | $status = true; | |
| 143 | + | $paidMoney = 0; | |
| 144 | + | $paidPoints = 0; | |
| 145 | + | $payment = array( | |
| 146 | + | 'provider' => 'paypal', | |
| 147 | + | 'reference' => (string)$txn_id, | |
| 148 | + | 'custom' => $custom_raw, | |
| 149 | + | 'account_id' => $custom, | |
| 150 | + | 'price' => $payment_amount, | |
| 151 | + | 'currency' => $payment_currency, | |
| 152 | + | 'points' => 0, | |
| 153 | + | 'status' => 'Completed', | |
| 154 | + | 'raw' => $_POST, | |
| 155 | + | 'resolved' => false, | |
| 156 | + | ); | |
| 157 | + | if (function_exists('znote_hook_filter')) { | |
| 158 | + | $payment = znote_hook_filter('payment.resolve', $payment, array('provider' => 'paypal', 'raw' => $_POST)); | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | if (!empty($payment['resolved'])) { | |
| 162 | + | $custom = (int)($payment['account_id'] ?? 0); | |
| 163 | + | $paidMoney = $payment['price'] ?? 0; | |
| 164 | + | $paidPoints = (int)($payment['points'] ?? 0); | |
| 165 | + | } else { | |
| 166 | + | foreach ($prices as $priceValue => $pointsValue) { | |
| 167 | + | if ($priceValue == $payment_amount) { | |
| 168 | + | $paidMoney = $priceValue; | |
| 169 | + | $paidPoints = $pointsValue; | |
| 170 | + | } | |
| 171 | + | } | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | if ($paidMoney == 0 || number_format((float)$paidMoney, 2, '.', '') !== number_format((float)$payment_amount, 2, '.', '')) $status = false; // Wrong ammount of money | |
| 175 | + | if ($payment_currency != ($payment['currency'] ?? $paypal['currency'])) $status = false; // Wrong currency | |
| 176 | + | if ($custom <= 0) $status = false; | |
| 177 | + | ||
| 178 | + | // Verify that the user havent messed around with POST data | |
| 179 | + | if ($status) { | |
| 180 | + | // Re-check for a duplicate and credit inside one locked transaction, | |
| 181 | + | // so two concurrent IPN deliveries for the same txn_id cannot both credit points. | |
| 182 | + | $creditResult = db()->transaction(function ($db) use ($txn_id, $payer_email, $custom, $paidMoney, $paidPoints) { | |
| 183 | + | $dup = $db->fetchOne("SELECT `txn_id` FROM `znote_paypal` WHERE `txn_id` = ? LIMIT 1 FOR UPDATE;", [$txn_id]); | |
| 184 | + | if ($dup !== false) { | |
| 185 | + | return 'duplicate'; | |
| 186 | + | } | |
| 187 | + | ||
| 188 | + | $db->execute("INSERT INTO `znote_paypal` VALUES ('0', ?, ?, ?, ?, ?)", [$txn_id, $payer_email, $custom, $paidMoney, $paidPoints]); | |
| 189 | + | ||
| 190 | + | $data = $db->fetchOne("SELECT `points` AS `old_points` FROM `znote_accounts` WHERE `account_id` = ? LIMIT 1 FOR UPDATE;", [$custom]); | |
| 191 | + | if (!is_array($data)) { | |
| 192 | + | return 'no_account'; | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | $new_points = (int)$data['old_points'] + $paidPoints; | |
| 196 | + | $db->execute("UPDATE `znote_accounts` SET `points` = ? WHERE `account_id` = ?", [$new_points, $custom]); | |
| 197 | + | ||
| 198 | + | return 'credited'; | |
| 199 | + | }); | |
| 200 | + | ||
| 201 | + | if ($creditResult === 'credited') { | |
| 202 | + | if (function_exists('znote_hook')) { | |
| 203 | + | znote_hook('payment.completed', array_merge($payment, array( | |
| 204 | + | 'provider' => 'paypal', | |
| 205 | + | 'reference' => (string)$txn_id, | |
| 206 | + | 'custom' => $custom_raw, | |
| 207 | + | 'account_id' => $custom, | |
| 208 | + | 'price' => $paidMoney, | |
| 209 | + | 'currency' => $payment_currency, | |
| 210 | + | 'points' => $paidPoints, | |
| 211 | + | 'status' => 'Completed', | |
| 212 | + | ))); | |
| 213 | + | } | |
| 214 | + | } elseif ($creditResult === 'no_account') { | |
| 215 | + | db()->execute("INSERT INTO `znote_paypal` VALUES ('0', ?, ?, '0', '0', '0')", [$txn_id, "ERROR: No znote_accounts row for account_id $custom"]); | |
| 216 | + | } | |
| 217 | + | } | |
| 218 | + | } else { | |
| 219 | + | $pmail = $paypal['email']; | |
| 220 | + | db()->execute("INSERT INTO `znote_paypal` VALUES ('0', ?, ?, '0', '0', '0')", [$txn_id, "ERROR: Wrong mail. Received: $receiver_email, configured: $pmail"]); | |
| 221 | + | } | |
| 222 | + | } | |
| 223 | + | } | |
| 224 | + | } else { | |
| 225 | + | // Something is wrong | |
| 226 | + | db()->execute("INSERT INTO `znote_paypal` VALUES ('0', ?, ?, '0', '0', '0')", [$txn_id, "ERROR: Invalid data. $postdata"]); | |
| 227 | + | } | |
| 228 | + | ?> |
| @@ -0,0 +1,30 @@ | |||
| 1 | + | <?php require_once 'engine/init.php'; theme_open(); | |
| 2 | + | ||
| 3 | + | /** | |
| 4 | + | * Equipable items browser. | |
| 5 | + | * | |
| 6 | + | * items.xml is uploaded and parsed in the admin panel, under Server Info. This | |
| 7 | + | * page only reads the result: | |
| 8 | + | * $itemsEnabled the page is turned on in config.php | |
| 9 | + | * $items the item list, or false when nothing was published | |
| 10 | + | * $itemsAdmin, $itemsUpdated, $itemsFailed kept at false for older themes | |
| 11 | + | */ | |
| 12 | + | ||
| 13 | + | $itemsEnabled = ($config['items'] == true); | |
| 14 | + | $itemsAdmin = false; | |
| 15 | + | $itemsUpdated = false; | |
| 16 | + | $itemsFailed = false; | |
| 17 | + | $items = false; | |
| 18 | + | ||
| 19 | + | if ($itemsEnabled) { | |
| 20 | + | $items = serverdata_load('items'); | |
| 21 | + | ||
| 22 | + | if ($items === false && is_file(serverdata_file('items.xml'))) { | |
| 23 | + | serverdata_rebuild('items'); | |
| 24 | + | $items = serverdata_load('items'); | |
| 25 | + | } | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | view('items'); | |
| 29 | + | ||
| 30 | + | theme_close(); |
| @@ -0,0 +1,73 @@ | |||
| 1 | + | <?php | |
| 2 | + | require_once 'engine/init.php'; | |
| 3 | + | theme_open(); | |
| 4 | + | ||
| 5 | + | /** | |
| 6 | + | * Kill statistics. | |
| 7 | + | * | |
| 8 | + | * TFS 0.3 exposes a different set of tables from the 1.x line, so the two | |
| 9 | + | * engines produce different data. Which one applies is decided here and the | |
| 10 | + | * view only has to read $killersMode - it never touches the database. | |
| 11 | + | */ | |
| 12 | + | ||
| 13 | + | $engine = znote_server_adapter()->normalizedEngine(); | |
| 14 | + | ||
| 15 | + | if (in_array($engine, array('TFS_02', 'TFS_10', 'OTHIRE'), true)) { | |
| 16 | + | $killersMode = 'modern'; | |
| 17 | + | } elseif ($engine === 'TFS_03') { | |
| 18 | + | $killersMode = 'legacy'; | |
| 19 | + | } else { | |
| 20 | + | $killersMode = 'unsupported'; | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | $killers = false; | |
| 24 | + | $victims = false; | |
| 25 | + | $latests = false; | |
| 26 | + | $deaths = false; | |
| 27 | + | ||
| 28 | + | if ($killersMode === 'modern') { | |
| 29 | + | ||
| 30 | + | $cache = new Cache('engine/cache/killers'); | |
| 31 | + | if ($cache->hasExpired()) { | |
| 32 | + | $killers = fetchMurders(); | |
| 33 | + | $cache->setContent($killers); | |
| 34 | + | $cache->save(); | |
| 35 | + | } else { | |
| 36 | + | $killers = $cache->load(); | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | $cache = new Cache('engine/cache/victims'); | |
| 40 | + | if ($cache->hasExpired()) { | |
| 41 | + | $victims = fetchLoosers(); | |
| 42 | + | $cache->setContent($victims); | |
| 43 | + | $cache->save(); | |
| 44 | + | } else { | |
| 45 | + | $victims = $cache->load(); | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | $cache = new Cache('engine/cache/lastkillers'); | |
| 49 | + | if ($cache->hasExpired()) { | |
| 50 | + | $latests = db()->fetchAll("SELECT `p`.`name` AS `victim`, `d`.`killed_by` as `killed_by`, `d`.`time` as `time` FROM `player_deaths` as `d` INNER JOIN `players` as `p` ON d.player_id = p.id WHERE d.`is_player`='1' ORDER BY `time` DESC LIMIT 20;"); | |
| 51 | + | if ($latests !== false) { | |
| 52 | + | $cache->setContent($latests); | |
| 53 | + | $cache->save(); | |
| 54 | + | } | |
| 55 | + | } else { | |
| 56 | + | $latests = $cache->load(); | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | } elseif ($killersMode === 'legacy') { | |
| 60 | + | ||
| 61 | + | $cache = new Cache('engine/cache/killers'); | |
| 62 | + | if ($cache->hasExpired()) { | |
| 63 | + | $deaths = fetchLatestDeaths_03(30, true); | |
| 64 | + | $cache->setContent($deaths); | |
| 65 | + | $cache->save(); | |
| 66 | + | } else { | |
| 67 | + | $deaths = $cache->load(); | |
| 68 | + | } | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | view('killers'); | |
| 72 | + | ||
| 73 | + | theme_close(); |
| @@ -0,0 +1,30 @@ | |||
| 1 | + | :root { color-scheme: dark; } | |
| 2 | + | ||
| 3 | + | body { | |
| 4 | + | margin: 0; | |
| 5 | + | min-height: 100vh; | |
| 6 | + | display: grid; | |
| 7 | + | place-items: center; | |
| 8 | + | padding: 24px; | |
| 9 | + | background: #14181f; | |
| 10 | + | color: #dfe4ec; | |
| 11 | + | font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; | |
| 12 | + | } | |
| 13 | + | ||
| 14 | + | main { max-width: 560px; text-align: center; } | |
| 15 | + | h1 { font-size: 28px; margin: 0 0 16px; } | |
| 16 | + | p { color: #93a0b2; margin: 0 0 16px; } | |
| 17 | + | .note { font-size: 14px; } | |
| 18 | + | code { color: #d1a233; } | |
| 19 | + | ||
| 20 | + | .enter { | |
| 21 | + | display: inline-block; | |
| 22 | + | margin-top: 8px; | |
| 23 | + | padding: 12px 28px; | |
| 24 | + | color: #14181f; | |
| 25 | + | background: #d1a233; | |
| 26 | + | border-radius: 4px; | |
| 27 | + | font-weight: 600; | |
| 28 | + | text-decoration: none; | |
| 29 | + | } | |
| 30 | + | .enter:hover { background: #e2b854; } |
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <!doctype html> | |
| 2 | + | <html lang="en"> | |
| 3 | + | <head> | |
| 4 | + | <meta charset="utf-8"> | |
| 5 | + | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | + | <title>Welcome</title> | |
| 7 | + | <link rel="stylesheet" href="css/landing.css"> | |
| 8 | + | </head> | |
| 9 | + | <body> | |
| 10 | + | <main> | |
| 11 | + | <h1>Your landing page</h1> | |
| 12 | + | <p> | |
| 13 | + | This file is <code>landing/index.html</code>. Replace it with your own, and put its | |
| 14 | + | css, images and scripts in this folder — relative paths work as normal. | |
| 15 | + | Or simple create a new one in pure html. | |
| 16 | + | </p> | |
| 17 | + | <a class="enter" href="/index.php?site=1">Enter the site</a> | |
| 18 | + | </main> | |
| 19 | + | </body> | |
| 20 | + | </html> |
| @@ -0,0 +1,17 @@ | |||
| 1 | + | # Nothing inside a theme is meant to be requested directly: ZnoteX includes its | |
| 2 | + | # shells and views from the page that owns them. Reaching one on its own means | |
| 3 | + | # running it outside engine/init.php, with no $config and no session - | |
| 4 | + | # unpredictable at best, and a way to read a theme's source at worst. | |
| 5 | + | # | |
| 6 | + | # The theme's own css, js, images and fonts still work: they do not match the | |
| 7 | + | # list below. | |
| 8 | + | ||
| 9 | + | <FilesMatch "\.(php|phtml|php[0-9]|inc|sql|json|md|txt|ya?ml|ini|log|bak|dist|example)$"> | |
| 10 | + | <IfModule mod_authz_core.c> | |
| 11 | + | Require all denied | |
| 12 | + | </IfModule> | |
| 13 | + | <IfModule !mod_authz_core.c> | |
| 14 | + | Order deny,allow | |
| 15 | + | Deny from all | |
| 16 | + | </IfModule> | |
| 17 | + | </FilesMatch> |
| @@ -0,0 +1,64 @@ | |||
| 1 | + | <?php | |
| 2 | + | /** | |
| 3 | + | * Right column of the default theme. | |
| 4 | + | * | |
| 5 | + | * Included only because shells/default.php calls theme_sidebar(). A theme with | |
| 6 | + | * no sidebar simply does not call it, and does not need this file at all. | |
| 7 | + | * | |
| 8 | + | * Each widget() call loads widgets/<name>.php from this theme, falling back to | |
| 9 | + | * the default theme's copy. | |
| 10 | + | */ | |
| 11 | + | ?> | |
| 12 | + | <!-- RIGHT PANE --> | |
| 13 | + | <div class="pull-right rightPane"> | |
| 14 | + | <?php | |
| 15 | + | ||
| 16 | + | if (user_logged_in() === true) { | |
| 17 | + | ||
| 18 | + | widget('myaccount'); | |
| 19 | + | ||
| 20 | + | if (isset($user_data) && has_admin_panel_access($user_data)) { | |
| 21 | + | widget('admin'); | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | } else { | |
| 25 | + | widget('login'); | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | if (!empty($config['otservers_eu_voting']['enabled'])) { | |
| 29 | + | widget('vote'); | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | widget('charactersearch'); | |
| 33 | + | widget('topplayers'); | |
| 34 | + | widget('highscore'); | |
| 35 | + | ||
| 36 | + | if (!empty($config['powergamers']['enabled'])) { | |
| 37 | + | widget('powergamers'); | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | widget('serverinfo'); | |
| 41 | + | ||
| 42 | + | if (!empty($config['ServerEngine']) && znote_server_adapter()->normalizedEngine() !== 'TFS_02') { | |
| 43 | + | widget('houses'); | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | /* FOLLOW BLOCK */ | |
| 47 | + | if (!empty($follow['enabled'])): ?> | |
| 48 | + | <div class="well"> | |
| 49 | + | <div class="header"><?= t('widget.follow.title') ?></div> | |
| 50 | + | <div class="body"> | |
| 51 | + | <table class="smedia centralizeContent"> | |
| 52 | + | <tr> | |
| 53 | + | <td><a href="<?= htmlspecialchars($follow['facebook'] ?? '#') ?>" target="_blank" rel="noopener"><i class="fa fa-facebook"></i></a></td> | |
| 54 | + | <td><a href="<?= htmlspecialchars($follow['twitter'] ?? '#') ?>" target="_blank" rel="noopener"><i class="fa fa-twitter"></i></a></td> | |
| 55 | + | <td><a href="<?= htmlspecialchars($follow['youtube'] ?? '#') ?>" target="_blank" rel="noopener"><i class="fa fa-youtube"></i></a></td> | |
| 56 | + | <td><a href="<?= htmlspecialchars($follow['twitch'] ?? '#') ?>" target="_blank" rel="noopener"><i class="fa fa-twitch"></i></a></td> | |
| 57 | + | </tr> | |
| 58 | + | </table> | |
| 59 | + | </div> | |
| 60 | + | </div> | |
| 61 | + | <?php endif; ?> | |
| 62 | + | ||
| 63 | + | </div> | |
| 64 | + | <!-- RIGHT PANE END --> |
| @@ -0,0 +1,29 @@ | |||
| 1 | + | @media screen and (max-width:1300px){ | |
| 2 | + | .main { | |
| 3 | + | width: 1100px; | |
| 4 | + | } | |
| 5 | + | .banner { | |
| 6 | + | height: 214px; | |
| 7 | + | } | |
| 8 | + | } | |
| 9 | + | ||
| 10 | + | @media screen and (max-width:1200px){ | |
| 11 | + | .main { | |
| 12 | + | width: 1000px; | |
| 13 | + | } | |
| 14 | + | .banner { | |
| 15 | + | height: 192px; | |
| 16 | + | } | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | @media screen and (max-width:1100px){ | |
| 20 | + | .main { | |
| 21 | + | width: 900px; | |
| 22 | + | } | |
| 23 | + | nav .container > div > ul > li > a { | |
| 24 | + | padding: 20px 10px; | |
| 25 | + | } | |
| 26 | + | .banner { | |
| 27 | + | height: 170px; | |
| 28 | + | } | |
| 29 | + | } |
| @@ -0,0 +1,984 @@ | |||
| 1 | + | :root { | |
| 2 | + | /* backgrounds */ | |
| 3 | + | --primary: rgb(30,33,40); | |
| 4 | + | --secondary: rgb(25,28,33); | |
| 5 | + | --third: #ddd; | |
| 6 | + | --border: rgb(19,20,23); | |
| 7 | + | ||
| 8 | + | /* text */ | |
| 9 | + | --font-color: rgb(155,162,177); | |
| 10 | + | ||
| 11 | + | /* Links / anchors */ | |
| 12 | + | --anchor:#b39062; | |
| 13 | + | --anchor-hover:#e79424; | |
| 14 | + | ||
| 15 | + | /* buttons or alert boxes with different colors */ | |
| 16 | + | --bg-danger: #3c0e0e; | |
| 17 | + | --color-danger: #9e5858; | |
| 18 | + | --border-danger: #350505; | |
| 19 | + | ||
| 20 | + | --bg-warning: #905c00; | |
| 21 | + | --color-warning: #39280a; | |
| 22 | + | --border-warning: #322001; | |
| 23 | + | ||
| 24 | + | --bg-info: #005d90; | |
| 25 | + | --color-info: #00263e; | |
| 26 | + | --border-info: #022530; | |
| 27 | + | ||
| 28 | + | --bg-success: #009039; | |
| 29 | + | --color-success: #003f0c; | |
| 30 | + | --border-success: #00380c; | |
| 31 | + | ||
| 32 | + | --bg-default: rgb(15,17,20); | |
| 33 | + | --color-default: #968452; | |
| 34 | + | --border-default: #000; | |
| 35 | + | ||
| 36 | + | /* accent (gold) used by buttons, banners and highlights */ | |
| 37 | + | --accent: #d1a233; | |
| 38 | + | --accent-ink: #1a1510; | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | + | body { | |
| 44 | + | font-family: 'arial', sans-serif; | |
| 45 | + | color:var(--font-color); | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | ||
| 49 | + | body, ul { margin: 0; padding: 0; } | |
| 50 | + | li { list-style: none; } | |
| 51 | + | a { text-decoration: none;color:var(--anchor); } | |
| 52 | + | a:hover {color:var(--anchor-hover);} | |
| 53 | + | a:hover, button:hover, input[type="submit"]:hover { cursor: pointer; } | |
| 54 | + | * {-webkit-transition-duration: 0.2s;-moz-transition-duration: 0.2s;-o-transition-duration: 0.2s;transition-duration: 0.2s;} | |
| 55 | + | *:hover {-webkit-transition-duration: 0s;-moz-transition-duration: 0s;-o-transition-duration: 0s;transition-duration: 0s;} | |
| 56 | + | ||
| 57 | + | nav .container > div > ul > li > ul { | |
| 58 | + | position: absolute; | |
| 59 | + | border: 2px solid; | |
| 60 | + | border-top:none !important; | |
| 61 | + | border-color: var(--border); | |
| 62 | + | width: 200px; | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | nav { border: 10px solid var(--primary); } | |
| 66 | + | nav .container { padding-left: 10px; } | |
| 67 | + | nav .container > div > ul > li, | |
| 68 | + | .modIcon:hover > i:nth-child(2) { display: inline-block; } | |
| 69 | + | ||
| 70 | + | nav .container > div > ul > li > a, | |
| 71 | + | nav .container > div > ul > li > .menuCategory { padding: 20px; } | |
| 72 | + | ||
| 73 | + | /* A top-level category is a heading, not a link: same look, no navigation. */ | |
| 74 | + | nav .container > div > ul > li > .menuCategory { | |
| 75 | + | display: block; | |
| 76 | + | z-index: 1; | |
| 77 | + | color: var(--anchor); | |
| 78 | + | cursor: default; | |
| 79 | + | } | |
| 80 | + | nav .container > div > ul > li:hover > .menuCategory { color: var(--anchor-hover); } | |
| 81 | + | nav .container > div > ul > li:hover > ul, | |
| 82 | + | nav .container > div > ul > li > a, | |
| 83 | + | nav .container > div > ul > li > ul > li > a { display: block; z-index: 1; } | |
| 84 | + | nav .container > div > ul > li > ul > li > a { padding: 10px 20px; } | |
| 85 | + | nav .container, .preventCollapse, .ellipsis { overflow: hidden; } | |
| 86 | + | ||
| 87 | + | .topPane { margin-bottom: 10px; } | |
| 88 | + | .leftPane { width: 70%; } | |
| 89 | + | .rightPane { width: 29%; } | |
| 90 | + | .searchForm { width: 200px; } | |
| 91 | + | .body { padding: 10px 0px; } | |
| 92 | + | table { width: 100%; } | |
| 93 | + | td { padding: 10px 5px; } | |
| 94 | + | .header { color: var(--accent); } | |
| 95 | + | .feedContainer { margin:2rem 0; } | |
| 96 | + | .pull-left { float:left; } | |
| 97 | + | .pull-right { float:right; } | |
| 98 | + | .well, .header { width: auto; } | |
| 99 | + | .centralizeContent { text-align: center; } | |
| 100 | + | .smedia { font-size: 2em; } | |
| 101 | + | ||
| 102 | + | .banner { | |
| 103 | + | background: url("../img/header.png"); | |
| 104 | + | height: 240px; | |
| 105 | + | background-size: 100%; | |
| 106 | + | background-repeat: no-repeat; | |
| 107 | + | margin-top: 30px; | |
| 108 | + | border: 10px solid var(--primary); | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | ||
| 112 | + | ||
| 113 | + | .searchForm input { | |
| 114 | + | width: 100%; | |
| 115 | + | height: 25px; | |
| 116 | + | border: 1px solid var(--border); | |
| 117 | + | color: var(--font-color); | |
| 118 | + | font-size: 1em; | |
| 119 | + | } | |
| 120 | + | ||
| 121 | + | #countDownTimer { | |
| 122 | + | line-height: 2.3; | |
| 123 | + | padding: 0px 10px; | |
| 124 | + | color: #5390a8; | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | .modIcon > i:nth-child(2), | |
| 128 | + | .modIcon:hover > i:nth-child(1), | |
| 129 | + | /*.loginContainer, */ | |
| 130 | + | nav .container > div > ul > li > ul { | |
| 131 | + | display: none; | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | .loginForm { | |
| 135 | + | display: flex; | |
| 136 | + | flex-wrap: wrap; | |
| 137 | + | } | |
| 138 | + | .loginForm button:hover { | |
| 139 | + | background: #0000004d; | |
| 140 | + | } | |
| 141 | + | .loginForm button { | |
| 142 | + | width: 100%; | |
| 143 | + | margin-top: 10px; | |
| 144 | + | background: rgb(15,17,20); | |
| 145 | + | color: var(--font-color); | |
| 146 | + | height: 40px; | |
| 147 | + | border: 1px solid var(--border); | |
| 148 | + | font-size: 1em; | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | .loginForm input { | |
| 152 | + | width: 100%; | |
| 153 | + | height: 30px; | |
| 154 | + | border: none; | |
| 155 | + | color: #fff; | |
| 156 | + | font-size: 1em; | |
| 157 | + | border-bottom: 1px solid var(--border); | |
| 158 | + | } | |
| 159 | + | ||
| 160 | + | .main { | |
| 161 | + | width: 1220px; | |
| 162 | + | margin: 2rem auto; | |
| 163 | + | position: relative; | |
| 164 | + | z-index: 1; | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | .ellipsis { | |
| 168 | + | text-overflow: ellipsis; | |
| 169 | + | white-space: nowrap; | |
| 170 | + | } | |
| 171 | + | ||
| 172 | + | .alert-box { | |
| 173 | + | max-width: 500px; | |
| 174 | + | font-size: 14px; | |
| 175 | + | border-radius: 5px; | |
| 176 | + | border: 1px solid var(--third); | |
| 177 | + | margin: 0 auto; | |
| 178 | + | margin-bottom: 15px; | |
| 179 | + | text-align: center; | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | .alert-default { | |
| 183 | + | background: var(--bg-default); | |
| 184 | + | color: var(--color-default); | |
| 185 | + | border-color: var(--border-default); | |
| 186 | + | } | |
| 187 | + | .alert-info { | |
| 188 | + | background: var(--bg-info); | |
| 189 | + | color: var(--color-info); | |
| 190 | + | border-color: var(--border-info); | |
| 191 | + | } | |
| 192 | + | .alert-success { | |
| 193 | + | background: var(--bg-success); | |
| 194 | + | color: var(--color-success); | |
| 195 | + | border-color: var(--border-success); | |
| 196 | + | } | |
| 197 | + | .alert-warning { | |
| 198 | + | background: var(--bg-warning); | |
| 199 | + | color: var(--color-warning); | |
| 200 | + | border-color: var(--border-warning); | |
| 201 | + | } | |
| 202 | + | .alert-danger { | |
| 203 | + | background: var(--bg-danger); | |
| 204 | + | color: var(--color-danger); | |
| 205 | + | border-color: var(--border-danger); | |
| 206 | + | } | |
| 207 | + | .alert-collapse {display:inline-block;} | |
| 208 | + | .alert-size1 { font-size: 12px; } | |
| 209 | + | .alert-size2 { font-size: 15px; } | |
| 210 | + | .alert-size3 { font-size: 18px; } | |
| 211 | + | .alert-size4 { font-size: 20px; } | |
| 212 | + | ||
| 213 | + | ||
| 214 | + | nav .container > div > ul > li > ul > li > a:hover, | |
| 215 | + | .searchForm input, #countDownTimer, .loginForm input, | |
| 216 | + | nav, footer, table, .header, .feedContainer { | |
| 217 | + | background: var(--primary); | |
| 218 | + | } | |
| 219 | + | .loginForm .well { | |
| 220 | + | width: 100%; | |
| 221 | + | } | |
| 222 | + | ||
| 223 | + | body, nav .container, | |
| 224 | + | nav .container > div > ul > li > ul, | |
| 225 | + | .leftPane, .rightPane, .topPane, | |
| 226 | + | tr:nth-child(2n+1) { | |
| 227 | + | background: var(--secondary); | |
| 228 | + | } | |
| 229 | + | ||
| 230 | + | footer, .feedContainer, | |
| 231 | + | nav .container, .topPane { | |
| 232 | + | border-bottom: 2px solid var(--border) | |
| 233 | + | } | |
| 234 | + | ||
| 235 | + | table, .header, .well, .smedia a { | |
| 236 | + | padding: 10px; | |
| 237 | + | } | |
| 238 | + | ||
| 239 | + | .searchForm input, | |
| 240 | + | .loginForm input, | |
| 241 | + | .alert-box { | |
| 242 | + | padding: 5px; | |
| 243 | + | } | |
| 244 | + | ||
| 245 | + | /* Znote AAC */ | |
| 246 | + | .leftPane img { | |
| 247 | + | max-width: 100%; | |
| 248 | + | } | |
| 249 | + | /* adding button style to select */ | |
| 250 | + | select { | |
| 251 | + | background: rgb(15,17,20); | |
| 252 | + | color: var(--font-color); | |
| 253 | + | height: 40px; | |
| 254 | + | border: 1px solid var(--border); | |
| 255 | + | font-size: 1em; | |
| 256 | + | } | |
| 257 | + | input { | |
| 258 | + | background: rgb(15,17,20); | |
| 259 | + | color: var(--font-color); | |
| 260 | + | height: 40px; | |
| 261 | + | border: 1px solid var(--border); | |
| 262 | + | font-size: 1em; | |
| 263 | + | } | |
| 264 | + | #loginContainer li { | |
| 265 | + | text-align: right; | |
| 266 | + | } | |
| 267 | + | ||
| 268 | + | ul.linkbuttons { | |
| 269 | + | margin-top: 8px; | |
| 270 | + | padding: 0 8px; | |
| 271 | + | } | |
| 272 | + | ul.linkbuttons li { | |
| 273 | + | display: inline-block; | |
| 274 | + | border: 1px solid var(--accent); | |
| 275 | + | width: calc(50% - 10px); | |
| 276 | + | float: left; | |
| 277 | + | margin-bottom: 16px; | |
| 278 | + | text-align: center; | |
| 279 | + | } | |
| 280 | + | ul.linkbuttons li:nth-child(odd) { | |
| 281 | + | margin-right: 16px; | |
| 282 | + | } | |
| 283 | + | ul.linkbuttons li a { | |
| 284 | + | padding: 5px 0; | |
| 285 | + | display: inline-block; | |
| 286 | + | width: 100%; | |
| 287 | + | text-align: center; | |
| 288 | + | } | |
| 289 | + | ul.linkbuttons:after { | |
| 290 | + | content: ''; | |
| 291 | + | display: block; | |
| 292 | + | clear: both; | |
| 293 | + | } | |
| 294 | + | .widget, | |
| 295 | + | .widget .body, | |
| 296 | + | .search_widget, | |
| 297 | + | .search_widget .body { | |
| 298 | + | padding-bottom: 0; | |
| 299 | + | } | |
| 300 | + | .widget center { | |
| 301 | + | margin: auto; | |
| 302 | + | } | |
| 303 | + | .widget h3 { | |
| 304 | + | margin-bottom: 0; | |
| 305 | + | } | |
| 306 | + | .search_widget form { | |
| 307 | + | margin: auto; | |
| 308 | + | } | |
| 309 | + | .search_widget input { | |
| 310 | + | width: 50%; | |
| 311 | + | float: left; | |
| 312 | + | } | |
| 313 | + | .search_widget label { | |
| 314 | + | padding: 9px; | |
| 315 | + | float: left; | |
| 316 | + | } | |
| 317 | + | .search_widget form:after { | |
| 318 | + | display: block; | |
| 319 | + | content: ''; | |
| 320 | + | clear: both; | |
| 321 | + | } | |
| 322 | + | div.relative { | |
| 323 | + | position: relative; | |
| 324 | + | } | |
| 325 | + | .search_widget #name_suggestion { | |
| 326 | + | position: absolute; | |
| 327 | + | width: 280px; | |
| 328 | + | left: -290px; | |
| 329 | + | display: none; | |
| 330 | + | } | |
| 331 | + | .search_widget #name_suggestion.show { | |
| 332 | + | display: block; | |
| 333 | + | } | |
| 334 | + | .search_widget .sname { | |
| 335 | + | text-align: right; | |
| 336 | + | } | |
| 337 | + | .search_widget .sname a { | |
| 338 | + | display: inline-block; | |
| 339 | + | background-color: black; | |
| 340 | + | padding: 10px 20px; | |
| 341 | + | border-bottom: 1px solid rgb(30,33,40); | |
| 342 | + | } | |
| 343 | + | .page_credits .feedContainer .pull-left.leftPane { | |
| 344 | + | box-sizing: border-box; | |
| 345 | + | padding-left: 8px; | |
| 346 | + | padding-right: 8px; | |
| 347 | + | } | |
| 348 | + | ||
| 349 | + | .page_characterprofile #characterProfileTable thead th:first-of-type { | |
| 350 | + | position: relative; | |
| 351 | + | width: 28%; | |
| 352 | + | } | |
| 353 | + | .page_characterprofile #characterProfileTable thead th:last-of-type { | |
| 354 | + | text-align: left; | |
| 355 | + | padding-left: 16px; | |
| 356 | + | } | |
| 357 | + | .page_characterprofile .outfit { | |
| 358 | + | position: absolute; | |
| 359 | + | top: 0; | |
| 360 | + | left: 0; | |
| 361 | + | } | |
| 362 | + | .page_characterprofile .flag { | |
| 363 | + | position: absolute; | |
| 364 | + | top: 16px; | |
| 365 | + | right: 16px; | |
| 366 | + | } | |
| 367 | + | .postHolder iframe { | |
| 368 | + | display: block; | |
| 369 | + | margin: auto; | |
| 370 | + | } | |
| 371 | + | .znx-empty-box { | |
| 372 | + | margin: 0 0 18px; | |
| 373 | + | padding: 16px; | |
| 374 | + | border: 1px solid var(--border); | |
| 375 | + | border-radius: 6px; | |
| 376 | + | background: var(--secondary); | |
| 377 | + | color: var(--color-default); | |
| 378 | + | text-align: center; | |
| 379 | + | } | |
| 380 | + | .znx-section-head { | |
| 381 | + | margin: 0 0 12px; | |
| 382 | + | padding: 10px 14px; | |
| 383 | + | border: 1px solid var(--border); | |
| 384 | + | border-radius: 6px; | |
| 385 | + | background: var(--primary); | |
| 386 | + | color: var(--accent); | |
| 387 | + | font-weight: 800; | |
| 388 | + | font-size: 15px; | |
| 389 | + | } | |
| 390 | + | .sr-only { | |
| 391 | + | position: absolute; | |
| 392 | + | width: 1px; | |
| 393 | + | height: 1px; | |
| 394 | + | margin: -1px; | |
| 395 | + | padding: 0; | |
| 396 | + | overflow: hidden; | |
| 397 | + | clip-path: inset(50%); | |
| 398 | + | white-space: nowrap; | |
| 399 | + | border: 0; | |
| 400 | + | } | |
| 401 | + | .znx-acct-head { | |
| 402 | + | margin: 0 0 12px; | |
| 403 | + | padding: 10px 14px; | |
| 404 | + | border: 1px solid var(--border); | |
| 405 | + | border-radius: 6px; | |
| 406 | + | background: var(--primary); | |
| 407 | + | color: var(--accent); | |
| 408 | + | font-weight: 800; | |
| 409 | + | font-size: 15px; | |
| 410 | + | } | |
| 411 | + | .znx-acct-info { | |
| 412 | + | margin: 0 0 18px; | |
| 413 | + | padding: 16px; | |
| 414 | + | border: 1px solid var(--border); | |
| 415 | + | border-radius: 6px; | |
| 416 | + | background: var(--secondary); | |
| 417 | + | } | |
| 418 | + | .znx-acct-info p { | |
| 419 | + | margin: 0; | |
| 420 | + | line-height: 1.6; | |
| 421 | + | } | |
| 422 | + | .znx-acct-2fa { | |
| 423 | + | display: flex; | |
| 424 | + | flex-wrap: wrap; | |
| 425 | + | gap: 10px 24px; | |
| 426 | + | margin-top: 12px; | |
| 427 | + | padding-top: 12px; | |
| 428 | + | border-top: 1px solid var(--border); | |
| 429 | + | } | |
| 430 | + | .znx-acct-2fa__row { | |
| 431 | + | display: flex; | |
| 432 | + | align-items: center; | |
| 433 | + | gap: 8px; | |
| 434 | + | font-size: 13px; | |
| 435 | + | } | |
| 436 | + | .znx-acct-badge { | |
| 437 | + | display: inline-block; | |
| 438 | + | padding: 2px 10px; | |
| 439 | + | border-radius: 12px; | |
| 440 | + | font-size: 12px; | |
| 441 | + | font-weight: 700; | |
| 442 | + | text-decoration: none; | |
| 443 | + | } | |
| 444 | + | .znx-acct-badge.is-on { | |
| 445 | + | background: rgba(0,144,57,0.18); | |
| 446 | + | color: #3fbf6a; | |
| 447 | + | border: 1px solid #1f5c37; | |
| 448 | + | } | |
| 449 | + | .znx-acct-badge.is-off { | |
| 450 | + | background: rgba(60,14,14,0.35); | |
| 451 | + | color: #c47a7a; | |
| 452 | + | border: 1px solid #4a2020; | |
| 453 | + | } | |
| 454 | + | .znx-acct-badge:hover { | |
| 455 | + | color: var(--anchor-hover); | |
| 456 | + | } | |
| 457 | + | .znx-acct-table-wrap { | |
| 458 | + | border: 1px solid var(--border); | |
| 459 | + | border-radius: 6px; | |
| 460 | + | overflow: hidden; | |
| 461 | + | margin-bottom: 18px; | |
| 462 | + | } | |
| 463 | + | .znx-acct-table { | |
| 464 | + | width: 100%; | |
| 465 | + | border-collapse: collapse; | |
| 466 | + | } | |
| 467 | + | .znx-acct-table th { | |
| 468 | + | background: var(--primary); | |
| 469 | + | color: var(--accent); | |
| 470 | + | text-align: left; | |
| 471 | + | font-size: 12px; | |
| 472 | + | padding: 10px; | |
| 473 | + | } | |
| 474 | + | .znx-acct-table td { | |
| 475 | + | padding: 8px 10px; | |
| 476 | + | font-size: 13px; | |
| 477 | + | border-top: 1px solid var(--border); | |
| 478 | + | } | |
| 479 | + | .znx-acct-table tr.yellow td { | |
| 480 | + | background: var(--primary); | |
| 481 | + | color: var(--accent); | |
| 482 | + | text-align: left; | |
| 483 | + | font-size: 12px; | |
| 484 | + | border-top: none; | |
| 485 | + | } | |
| 486 | + | .znx-pill-nav { | |
| 487 | + | display: flex; | |
| 488 | + | flex-wrap: wrap; | |
| 489 | + | gap: 8px; | |
| 490 | + | margin: 0 0 18px; | |
| 491 | + | } | |
| 492 | + | .znx-pill-nav a { | |
| 493 | + | display: inline-block; | |
| 494 | + | padding: 6px 14px; | |
| 495 | + | border: 1px solid var(--border); | |
| 496 | + | border-radius: 999px; | |
| 497 | + | background: var(--secondary); | |
| 498 | + | color: var(--font-color); | |
| 499 | + | font-size: 12px; | |
| 500 | + | text-decoration: none; | |
| 501 | + | } | |
| 502 | + | .znx-pill-nav a:hover { | |
| 503 | + | border-color: var(--accent); | |
| 504 | + | color: var(--anchor-hover); | |
| 505 | + | } | |
| 506 | + | .znx-jump-top { | |
| 507 | + | margin: 0 0 10px; | |
| 508 | + | font-size: 12px; | |
| 509 | + | } | |
| 510 | + | .znx-acct-toolbar { | |
| 511 | + | display: flex; | |
| 512 | + | flex-wrap: wrap; | |
| 513 | + | gap: 10px; | |
| 514 | + | align-items: stretch; | |
| 515 | + | padding: 14px; | |
| 516 | + | border: 1px solid var(--border); | |
| 517 | + | border-radius: 6px; | |
| 518 | + | background: var(--secondary); | |
| 519 | + | margin-bottom: 18px; | |
| 520 | + | } | |
| 521 | + | .znx-acct-toolbar__field { | |
| 522 | + | flex: 1 1 200px; | |
| 523 | + | min-width: 160px; | |
| 524 | + | } | |
| 525 | + | .znx-acct-toolbar__submit { | |
| 526 | + | flex: 0 0 auto; | |
| 527 | + | min-width: 0; | |
| 528 | + | display: flex; | |
| 529 | + | } | |
| 530 | + | .znx-acct-select { | |
| 531 | + | width: 100%; | |
| 532 | + | height: 40px; | |
| 533 | + | padding: 0 10px; | |
| 534 | + | background: var(--bg-default); | |
| 535 | + | color: var(--font-color); | |
| 536 | + | border: 1px solid var(--border); | |
| 537 | + | border-radius: 6px; | |
| 538 | + | font-size: 13px; | |
| 539 | + | } | |
| 540 | + | .znx-acct-btn { | |
| 541 | + | height: 40px; | |
| 542 | + | padding: 0 22px; | |
| 543 | + | border: 1px solid var(--accent); | |
| 544 | + | border-radius: 6px; | |
| 545 | + | background: var(--accent); | |
| 546 | + | color: var(--accent-ink); | |
| 547 | + | font-weight: 700; | |
| 548 | + | font-size: 13px; | |
| 549 | + | } | |
| 550 | + | .znx-acct-btn:hover { | |
| 551 | + | background: var(--anchor-hover); | |
| 552 | + | border-color: var(--anchor-hover); | |
| 553 | + | } | |
| 554 | + | @media (max-width: 640px) { | |
| 555 | + | .znx-acct-toolbar { | |
| 556 | + | flex-direction: column; | |
| 557 | + | } | |
| 558 | + | .znx-acct-toolbar__field, | |
| 559 | + | .znx-acct-toolbar__submit { | |
| 560 | + | flex: 1 1 auto; | |
| 561 | + | width: 100%; | |
| 562 | + | } | |
| 563 | + | .znx-acct-btn { | |
| 564 | + | width: 100%; | |
| 565 | + | } | |
| 566 | + | } | |
| 567 | + | .znx-acct-edit-btn { | |
| 568 | + | display: inline-block; | |
| 569 | + | padding: 4px 12px; | |
| 570 | + | border: 1px solid var(--accent); | |
| 571 | + | border-radius: 5px; | |
| 572 | + | background: transparent; | |
| 573 | + | color: var(--accent); | |
| 574 | + | font-weight: 700; | |
| 575 | + | font-size: 12px; | |
| 576 | + | text-decoration: none; | |
| 577 | + | } | |
| 578 | + | .znx-acct-edit-btn:hover { | |
| 579 | + | background: var(--accent); | |
| 580 | + | color: var(--accent-ink); | |
| 581 | + | } | |
| 582 | + | .znx-editchar-back { | |
| 583 | + | margin: 0 0 14px; | |
| 584 | + | } | |
| 585 | + | .znx-editchar-back a { | |
| 586 | + | display: inline-flex; | |
| 587 | + | align-items: center; | |
| 588 | + | gap: 6px; | |
| 589 | + | height: 34px; | |
| 590 | + | padding: 0 16px; | |
| 591 | + | border: 1px solid var(--border); | |
| 592 | + | border-radius: 6px; | |
| 593 | + | background: var(--primary); | |
| 594 | + | color: var(--accent); | |
| 595 | + | font-weight: 700; | |
| 596 | + | font-size: 13px; | |
| 597 | + | text-decoration: none; | |
| 598 | + | } | |
| 599 | + | .znx-editchar-back a:hover { | |
| 600 | + | border-color: var(--accent); | |
| 601 | + | color: var(--anchor-hover); | |
| 602 | + | } | |
| 603 | + | .znx-editchar-notice { | |
| 604 | + | font-size: 13px; | |
| 605 | + | line-height: 1.6; | |
| 606 | + | } | |
| 607 | + | .znx-editchar-notice ul { | |
| 608 | + | margin: 0; | |
| 609 | + | padding-left: 18px; | |
| 610 | + | } | |
| 611 | + | .znx-editchar-notice--ok { | |
| 612 | + | border-color: #1f5c37; | |
| 613 | + | color: #3fbf6a; | |
| 614 | + | } | |
| 615 | + | .znx-editchar-notice--error { | |
| 616 | + | border-color: #4a2020; | |
| 617 | + | color: #c47a7a; | |
| 618 | + | } | |
| 619 | + | .znx-editchar-info { | |
| 620 | + | display: flex; | |
| 621 | + | flex-wrap: wrap; | |
| 622 | + | gap: 8px 28px; | |
| 623 | + | } | |
| 624 | + | .znx-editchar-info__row { | |
| 625 | + | display: flex; | |
| 626 | + | align-items: center; | |
| 627 | + | gap: 8px; | |
| 628 | + | font-size: 13px; | |
| 629 | + | } | |
| 630 | + | .znx-editchar-info__row span { | |
| 631 | + | color: var(--color-default); | |
| 632 | + | text-transform: uppercase; | |
| 633 | + | font-size: 11px; | |
| 634 | + | letter-spacing: .04em; | |
| 635 | + | } | |
| 636 | + | .znx-editchar-box { | |
| 637 | + | margin: 0 0 18px; | |
| 638 | + | padding: 14px; | |
| 639 | + | border: 1px solid var(--border); | |
| 640 | + | border-radius: 6px; | |
| 641 | + | background: var(--secondary); | |
| 642 | + | } | |
| 643 | + | .znx-editchar-box--muted { | |
| 644 | + | color: var(--color-default); | |
| 645 | + | font-size: 13px; | |
| 646 | + | } | |
| 647 | + | .znx-editchar-ticket-note { | |
| 648 | + | margin-top: 8px; | |
| 649 | + | font-size: 12px; | |
| 650 | + | color: var(--color-default); | |
| 651 | + | } | |
| 652 | + | .znx-settings-field { | |
| 653 | + | margin-bottom: 14px; | |
| 654 | + | } | |
| 655 | + | .znx-settings-field .znx-acct-select { | |
| 656 | + | max-width: 320px; | |
| 657 | + | } | |
| 658 | + | .znx-settings-field label { | |
| 659 | + | display: block; | |
| 660 | + | margin-bottom: 6px; | |
| 661 | + | font-size: 12px; | |
| 662 | + | color: var(--color-default); | |
| 663 | + | text-transform: uppercase; | |
| 664 | + | letter-spacing: .04em; | |
| 665 | + | } | |
| 666 | + | .znx-guild-create { | |
| 667 | + | margin: 0 0 18px; | |
| 668 | + | padding: 14px; | |
| 669 | + | border: 1px solid var(--border); | |
| 670 | + | border-radius: 6px; | |
| 671 | + | background: var(--secondary); | |
| 672 | + | } | |
| 673 | + | .znx-guild-create ul { | |
| 674 | + | margin: 0; | |
| 675 | + | padding: 0; | |
| 676 | + | } | |
| 677 | + | .znx-guild-create li { | |
| 678 | + | list-style: none; | |
| 679 | + | display: flex; | |
| 680 | + | flex-wrap: wrap; | |
| 681 | + | align-items: center; | |
| 682 | + | gap: 10px; | |
| 683 | + | font-size: 12px; | |
| 684 | + | color: var(--color-default); | |
| 685 | + | text-transform: uppercase; | |
| 686 | + | letter-spacing: .04em; | |
| 687 | + | } | |
| 688 | + | .znx-guild-create li br { | |
| 689 | + | display: none; | |
| 690 | + | } | |
| 691 | + | .znx-guild-create select, | |
| 692 | + | .znx-guild-create input[type="text"] { | |
| 693 | + | height: 40px; | |
| 694 | + | padding: 0 10px; | |
| 695 | + | background: var(--bg-default); | |
| 696 | + | color: var(--font-color); | |
| 697 | + | border: 1px solid var(--border); | |
| 698 | + | border-radius: 6px; | |
| 699 | + | font-size: 13px; | |
| 700 | + | text-transform: none; | |
| 701 | + | letter-spacing: normal; | |
| 702 | + | } | |
| 703 | + | .znx-guild-create input[type="submit"] { | |
| 704 | + | height: 40px; | |
| 705 | + | padding: 0 22px; | |
| 706 | + | border: 1px solid var(--accent); | |
| 707 | + | border-radius: 6px; | |
| 708 | + | background: var(--accent); | |
| 709 | + | color: var(--accent-ink); | |
| 710 | + | font-weight: 700; | |
| 711 | + | font-size: 13px; | |
| 712 | + | text-transform: none; | |
| 713 | + | letter-spacing: normal; | |
| 714 | + | } | |
| 715 | + | .znx-guild-create input[type="submit"]:hover { | |
| 716 | + | background: var(--anchor-hover); | |
| 717 | + | border-color: var(--anchor-hover); | |
| 718 | + | color: var(--accent-ink); | |
| 719 | + | } | |
| 720 | + | .znx-guild-overview #guildTitleDiv { | |
| 721 | + | display: flex; | |
| 722 | + | align-items: center; | |
| 723 | + | gap: 14px; | |
| 724 | + | margin: 0 0 18px; | |
| 725 | + | padding: 14px; | |
| 726 | + | border: 1px solid var(--border); | |
| 727 | + | border-radius: 6px; | |
| 728 | + | background: var(--primary); | |
| 729 | + | } | |
| 730 | + | .znx-guild-overview #guildImageDiv { | |
| 731 | + | flex: 0 0 auto; | |
| 732 | + | margin: 0 !important; | |
| 733 | + | } | |
| 734 | + | .znx-guild-overview #guildImageDiv img { | |
| 735 | + | display: block; | |
| 736 | + | border-radius: 6px; | |
| 737 | + | } | |
| 738 | + | .znx-guild-overview #guildDescription h1 { | |
| 739 | + | margin: 0 0 4px; | |
| 740 | + | font-size: 20px; | |
| 741 | + | color: var(--accent); | |
| 742 | + | } | |
| 743 | + | .znx-guild-overview #guildDescription p { | |
| 744 | + | margin: 0; | |
| 745 | + | font-size: 13px; | |
| 746 | + | color: var(--font-color); | |
| 747 | + | } | |
| 748 | + | .znx-guild-overview h1, | |
| 749 | + | .znx-guild-overview h2, | |
| 750 | + | .znx-guild-overview h3 { | |
| 751 | + | margin: 0 0 12px; | |
| 752 | + | padding: 10px 14px; | |
| 753 | + | border: 1px solid var(--border); | |
| 754 | + | border-radius: 6px; | |
| 755 | + | background: var(--primary); | |
| 756 | + | color: var(--accent); | |
| 757 | + | font-weight: 800; | |
| 758 | + | font-size: 15px; | |
| 759 | + | } | |
| 760 | + | .znx-guild-overview table { | |
| 761 | + | width: 100%; | |
| 762 | + | border-collapse: collapse; | |
| 763 | + | margin: 0 0 18px; | |
| 764 | + | border: 1px solid var(--border); | |
| 765 | + | border-radius: 6px; | |
| 766 | + | overflow: hidden; | |
| 767 | + | } | |
| 768 | + | .znx-guild-overview table th, | |
| 769 | + | .znx-guild-overview table td { | |
| 770 | + | padding: 8px 10px; | |
| 771 | + | font-size: 13px; | |
| 772 | + | border-top: 1px solid var(--border); | |
| 773 | + | } | |
| 774 | + | .znx-guild-overview table tr.yellow th, | |
| 775 | + | .znx-guild-overview table tr.yellow td { | |
| 776 | + | background: var(--primary); | |
| 777 | + | color: var(--accent); | |
| 778 | + | border-top: none; | |
| 779 | + | text-align: left; | |
| 780 | + | font-size: 12px; | |
| 781 | + | } | |
| 782 | + | .znx-guild-overview ul { | |
| 783 | + | margin: 0 0 14px; | |
| 784 | + | padding: 0; | |
| 785 | + | } | |
| 786 | + | .znx-guild-overview li { | |
| 787 | + | list-style: none; | |
| 788 | + | display: flex; | |
| 789 | + | flex-wrap: wrap; | |
| 790 | + | align-items: center; | |
| 791 | + | gap: 10px; | |
| 792 | + | margin-bottom: 8px; | |
| 793 | + | font-size: 12px; | |
| 794 | + | color: var(--color-default); | |
| 795 | + | text-transform: uppercase; | |
| 796 | + | letter-spacing: .04em; | |
| 797 | + | } | |
| 798 | + | .znx-guild-overview li:last-child { | |
| 799 | + | margin-bottom: 0; | |
| 800 | + | } | |
| 801 | + | .znx-guild-overview li br { | |
| 802 | + | display: none; | |
| 803 | + | } | |
| 804 | + | .znx-guild-overview form { | |
| 805 | + | margin: 0 0 18px; | |
| 806 | + | padding: 14px; | |
| 807 | + | border: 1px solid var(--border); | |
| 808 | + | border-radius: 6px; | |
| 809 | + | background: var(--secondary); | |
| 810 | + | } | |
| 811 | + | .znx-guild-overview select, | |
| 812 | + | .znx-guild-overview input[type="text"], | |
| 813 | + | .znx-guild-overview input[type="number"], | |
| 814 | + | .znx-guild-overview textarea { | |
| 815 | + | height: 40px; | |
| 816 | + | padding: 0 10px; | |
| 817 | + | background: var(--bg-default); | |
| 818 | + | color: var(--font-color); | |
| 819 | + | border: 1px solid var(--border); | |
| 820 | + | border-radius: 6px; | |
| 821 | + | font-size: 13px; | |
| 822 | + | text-transform: none; | |
| 823 | + | letter-spacing: normal; | |
| 824 | + | } | |
| 825 | + | .znx-guild-overview textarea { | |
| 826 | + | height: auto; | |
| 827 | + | min-height: 70px; | |
| 828 | + | padding: 10px; | |
| 829 | + | width: 100%; | |
| 830 | + | box-sizing: border-box; | |
| 831 | + | font-family: inherit; | |
| 832 | + | } | |
| 833 | + | .znx-guild-overview input[type="submit"] { | |
| 834 | + | height: 40px; | |
| 835 | + | padding: 0 22px; | |
| 836 | + | border: 1px solid var(--accent); | |
| 837 | + | border-radius: 6px; | |
| 838 | + | background: var(--accent); | |
| 839 | + | color: var(--accent-ink); | |
| 840 | + | font-weight: 700; | |
| 841 | + | font-size: 13px; | |
| 842 | + | text-transform: none; | |
| 843 | + | letter-spacing: normal; | |
| 844 | + | cursor: pointer; | |
| 845 | + | } | |
| 846 | + | .znx-guild-overview input[type="submit"]:hover { | |
| 847 | + | background: var(--anchor-hover); | |
| 848 | + | border-color: var(--anchor-hover); | |
| 849 | + | color: var(--accent-ink); | |
| 850 | + | } | |
| 851 | + | .znx-dl-grid { | |
| 852 | + | display: grid; | |
| 853 | + | grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); | |
| 854 | + | gap: 12px; | |
| 855 | + | margin: 0 0 18px; | |
| 856 | + | } | |
| 857 | + | .znx-dl-card { | |
| 858 | + | display: flex; | |
| 859 | + | flex-direction: column; | |
| 860 | + | align-items: center; | |
| 861 | + | gap: 8px; | |
| 862 | + | padding: 14px; | |
| 863 | + | border: 1px solid var(--border); | |
| 864 | + | border-radius: 6px; | |
| 865 | + | background: var(--secondary); | |
| 866 | + | text-align: center; | |
| 867 | + | } | |
| 868 | + | .znx-dl-card img { | |
| 869 | + | max-width: 64px; | |
| 870 | + | max-height: 64px; | |
| 871 | + | } | |
| 872 | + | .znx-dl-card__label { | |
| 873 | + | font-weight: 700; | |
| 874 | + | color: var(--font-color); | |
| 875 | + | } | |
| 876 | + | .znx-dl-card__desc { | |
| 877 | + | font-size: 12px; | |
| 878 | + | color: var(--color-default); | |
| 879 | + | } | |
| 880 | + | .znx-dl-card__btn { | |
| 881 | + | display: inline-block; | |
| 882 | + | margin-top: auto; | |
| 883 | + | padding: 6px 18px; | |
| 884 | + | border: 1px solid var(--accent); | |
| 885 | + | border-radius: 6px; | |
| 886 | + | background: var(--accent); | |
| 887 | + | color: var(--accent-ink); | |
| 888 | + | font-weight: 700; | |
| 889 | + | font-size: 12px; | |
| 890 | + | text-decoration: none; | |
| 891 | + | } | |
| 892 | + | .znx-dl-card__btn:hover { | |
| 893 | + | background: var(--anchor-hover); | |
| 894 | + | border-color: var(--anchor-hover); | |
| 895 | + | color: var(--accent-ink); | |
| 896 | + | } | |
| 897 | + | .znx-dl-register { | |
| 898 | + | margin-top: 14px; | |
| 899 | + | padding-top: 14px; | |
| 900 | + | border-top: 1px solid var(--border); | |
| 901 | + | display: flex; | |
| 902 | + | flex-direction: column; | |
| 903 | + | align-items: flex-start; | |
| 904 | + | gap: 10px; | |
| 905 | + | font-size: 13px; | |
| 906 | + | } | |
| 907 | + | .znx-dl-steps { | |
| 908 | + | margin: 0; | |
| 909 | + | padding: 0; | |
| 910 | + | list-style: none; | |
| 911 | + | counter-reset: znx-dl-step; | |
| 912 | + | } | |
| 913 | + | .znx-dl-steps li { | |
| 914 | + | position: relative; | |
| 915 | + | margin-bottom: 14px; | |
| 916 | + | padding-left: 34px; | |
| 917 | + | line-height: 1.6; | |
| 918 | + | font-size: 13px; | |
| 919 | + | } | |
| 920 | + | .znx-dl-steps li:last-child { | |
| 921 | + | margin-bottom: 0; | |
| 922 | + | } | |
| 923 | + | .znx-dl-steps li::before { | |
| 924 | + | counter-increment: znx-dl-step; | |
| 925 | + | content: counter(znx-dl-step); | |
| 926 | + | position: absolute; | |
| 927 | + | left: 0; | |
| 928 | + | top: 0; | |
| 929 | + | width: 22px; | |
| 930 | + | height: 22px; | |
| 931 | + | border-radius: 50%; | |
| 932 | + | background: var(--primary); | |
| 933 | + | color: var(--accent); | |
| 934 | + | font-size: 12px; | |
| 935 | + | font-weight: 800; | |
| 936 | + | display: flex; | |
| 937 | + | align-items: center; | |
| 938 | + | justify-content: center; | |
| 939 | + | } | |
| 940 | + | .znx-editchar-inline { | |
| 941 | + | display: flex; | |
| 942 | + | flex-wrap: wrap; | |
| 943 | + | align-items: center; | |
| 944 | + | justify-content: space-between; | |
| 945 | + | gap: 10px; | |
| 946 | + | font-size: 13px; | |
| 947 | + | } | |
| 948 | + | .znx-editchar-textarea { | |
| 949 | + | width: 100%; | |
| 950 | + | min-height: 120px; | |
| 951 | + | margin-bottom: 10px; | |
| 952 | + | padding: 10px; | |
| 953 | + | background: var(--bg-default); | |
| 954 | + | color: var(--font-color); | |
| 955 | + | border: 1px solid var(--border); | |
| 956 | + | border-radius: 6px; | |
| 957 | + | font-size: 13px; | |
| 958 | + | font-family: inherit; | |
| 959 | + | resize: vertical; | |
| 960 | + | box-sizing: border-box; | |
| 961 | + | } | |
| 962 | + | .znx-char-action { | |
| 963 | + | height: 36px; | |
| 964 | + | padding: 0 16px; | |
| 965 | + | border: 1px solid var(--border); | |
| 966 | + | border-radius: 6px; | |
| 967 | + | background: var(--primary); | |
| 968 | + | color: var(--font-color); | |
| 969 | + | font-weight: 700; | |
| 970 | + | font-size: 12px; | |
| 971 | + | } | |
| 972 | + | .znx-char-action:hover { | |
| 973 | + | border-color: var(--accent); | |
| 974 | + | color: var(--anchor-hover); | |
| 975 | + | } | |
| 976 | + | .znx-char-action--danger { | |
| 977 | + | border-color: #4a2020; | |
| 978 | + | background: #3c0e0e; | |
| 979 | + | color: #c47a7a; | |
| 980 | + | } | |
| 981 | + | .znx-char-action--danger:hover { | |
| 982 | + | border-color: #9e5858; | |
| 983 | + | color: #ff8a8a; | |
| 984 | + | } |
| @@ -0,0 +1,50 @@ | |||
| 1 | + | function countDown(elid, seconds, msg){ | |
| 2 | + | // Set the date we're counting down to | |
| 3 | + | var countDownDate = new Date(); | |
| 4 | + | countDownDate.setSeconds(countDownDate.getSeconds() + seconds); | |
| 5 | + | var countDownDate = countDownDate.getTime(); | |
| 6 | + | ||
| 7 | + | // Update the count down every 1 second | |
| 8 | + | window.countDownInterval = setInterval(function() { | |
| 9 | + | ||
| 10 | + | // Get todays date and time | |
| 11 | + | var now = new Date().getTime(); | |
| 12 | + | ||
| 13 | + | // Find the distance between now and the count down date | |
| 14 | + | var distance = countDownDate - now; | |
| 15 | + | ||
| 16 | + | // Time calculations for days, hours, minutes and seconds | |
| 17 | + | var days = Math.floor(distance / (1000 * 60 * 60 * 24)); | |
| 18 | + | var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); | |
| 19 | + | var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); | |
| 20 | + | var seconds = Math.floor((distance % (1000 * 60)) / 1000); | |
| 21 | + | ||
| 22 | + | // Display the result in the element with id="demo" | |
| 23 | + | document.getElementById(elid).innerHTML = "<b>Server starts in:</b> "+ days + "d " + hours + "h " + minutes + "m " + seconds + "s "; | |
| 24 | + | ||
| 25 | + | // If the count down is finished, write some text | |
| 26 | + | if (distance < 0) { | |
| 27 | + | clearInterval(window.countDownInterval); | |
| 28 | + | document.getElementById(elid).innerHTML = msg; | |
| 29 | + | } | |
| 30 | + | }, 1000); | |
| 31 | + | ||
| 32 | + | // Get todays date and time | |
| 33 | + | var now = new Date().getTime(); | |
| 34 | + | ||
| 35 | + | // Find the distance between now and the count down date | |
| 36 | + | var distance = countDownDate - now; | |
| 37 | + | ||
| 38 | + | // Time calculations for days, hours, minutes and seconds | |
| 39 | + | var days = Math.floor(distance / (1000 * 60 * 60 * 24)); | |
| 40 | + | var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); | |
| 41 | + | var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); | |
| 42 | + | var seconds = Math.floor((distance % (1000 * 60)) / 1000); | |
| 43 | + | ||
| 44 | + | // Display the result in the element with id="demo" | |
| 45 | + | document.getElementById(elid).innerHTML = "<b>Server starts in:</b> "+ days + "d " + hours + "h " + minutes + "m " + seconds + "s "; | |
| 46 | + | ||
| 47 | + | if (distance < 0) { | |
| 48 | + | document.getElementById(elid).innerHTML = msg; | |
| 49 | + | } | |
| 50 | + | } |
| @@ -0,0 +1,19 @@ | |||
| 1 | + | <?php | |
| 2 | + | // Set enabled to false to remove the widget | |
| 3 | + | $follow = array( | |
| 4 | + | "enabled" => false, | |
| 5 | + | "twitter" => "https://www.twitter.com/", | |
| 6 | + | "facebook" => "https://www.facebook.com/", | |
| 7 | + | "youtube" => "https://www.youtube.com/", | |
| 8 | + | "twitch" => "https://www.twitch.tv/" | |
| 9 | + | ); | |
| 10 | + | ||
| 11 | + | // Use same date format when changing: yyyy-mm-dd hh:mm | |
| 12 | + | $countDown = "2020-06-10 01:00"; | |
| 13 | + | ||
| 14 | + | // Hide countdown after 1 day (24 hours) after countDown | |
| 15 | + | $countDown_hide = 1 * 24 * 60 * 60; | |
| 16 | + | ||
| 17 | + | // Say this after countdown, and before the row is hidden | |
| 18 | + | $countDown_complete = "<span style='color: green;'>ONLINE</span>"; | |
| 19 | + | ?> |