瀏覽代碼

Add light theme toggle

Markus-Rost 4 年之前
父節點
當前提交
8a6b94bb86
共有 8 個文件被更改,包括 321 次插入115 次删除
  1. 7 3
      dashboard/guilds.js
  2. 2 0
      dashboard/i18n/en.json
  3. 31 0
      dashboard/index.html
  4. 13 6
      dashboard/index.js
  5. 31 0
      dashboard/login.html
  6. 5 1
      dashboard/oauth.js
  7. 211 105
      dashboard/src/index.css
  8. 21 0
      dashboard/src/lang.js

+ 7 - 3
dashboard/guilds.js

@@ -23,12 +23,13 @@ const file = require('fs').readFileSync('./dashboard/index.html');
  * Let a user view settings
  * Let a user view settings
  * @param {import('http').ServerResponse} res - The server response
  * @param {import('http').ServerResponse} res - The server response
  * @param {import('./i18n.js')} dashboardLang - The user language.
  * @param {import('./i18n.js')} dashboardLang - The user language.
+ * @param {String} theme - The display theme
  * @param {String} state - The user state
  * @param {String} state - The user state
  * @param {URL} reqURL - The used url
  * @param {URL} reqURL - The used url
  * @param {String} [action] - The action the user made
  * @param {String} [action] - The action the user made
  * @param {String[]} [actionArgs] - The arguments for the action
  * @param {String[]} [actionArgs] - The arguments for the action
  */
  */
-function dashboard_guilds(res, dashboardLang, state, reqURL, action, actionArgs) {
+function dashboard_guilds(res, dashboardLang, theme, state, reqURL, action, actionArgs) {
 	reqURL.pathname = reqURL.pathname.replace( /^(\/(?:guild\/\d+(?:\/(?:settings|verification|rcscript)(?:\/(?:\d+|new))?)?)?)(?:\/.*)?$/, '$1' );
 	reqURL.pathname = reqURL.pathname.replace( /^(\/(?:guild\/\d+(?:\/(?:settings|verification|rcscript)(?:\/(?:\d+|new))?)?)?)(?:\/.*)?$/, '$1' );
 	var args = reqURL.pathname.split('/');
 	var args = reqURL.pathname.split('/');
 	args = reqURL.pathname.split('/');
 	args = reqURL.pathname.split('/');
@@ -40,6 +41,7 @@ function dashboard_guilds(res, dashboardLang, state, reqURL, action, actionArgs)
 	res.setHeader('Content-Language', [dashboardLang.lang]);
 	res.setHeader('Content-Language', [dashboardLang.lang]);
 	var $ = cheerio.load(file);
 	var $ = cheerio.load(file);
 	$('html').attr('lang', dashboardLang.lang);
 	$('html').attr('lang', dashboardLang.lang);
+	if ( theme === 'light' ) $('html').addClass('theme-light');
 	$('<script>').text(`
 	$('<script>').text(`
 		const selectLanguage = '${dashboardLang.get('general.language').replace( /'/g, '\\$&' )}';
 		const selectLanguage = '${dashboardLang.get('general.language').replace( /'/g, '\\$&' )}';
 		const allLangs = ${JSON.stringify(allLangs)};
 		const allLangs = ${JSON.stringify(allLangs)};
@@ -48,11 +50,13 @@ function dashboard_guilds(res, dashboardLang, state, reqURL, action, actionArgs)
 	$('.channel#settings div').text(dashboardLang.get('general.settings'));
 	$('.channel#settings div').text(dashboardLang.get('general.settings'));
 	$('.channel#verification div').text(dashboardLang.get('general.verification'));
 	$('.channel#verification div').text(dashboardLang.get('general.verification'));
 	$('.channel#rcscript div').text(dashboardLang.get('general.rcscript'));
 	$('.channel#rcscript div').text(dashboardLang.get('general.rcscript'));
+	$('.guild#invite a').attr('alt', dashboardLang.get('general.invite'));
+	$('.guild#refresh a').attr('alt', dashboardLang.get('general.refresh'));
+	$('.guild#theme-dark a').attr('alt', dashboardLang.get('general.theme-dark'));
+	$('.guild#theme-light a').attr('alt', dashboardLang.get('general.theme-light'));
 	$('#selector span').text(dashboardLang.get('general.selector'));
 	$('#selector span').text(dashboardLang.get('general.selector'));
 	$('#support span').text(dashboardLang.get('general.support'));
 	$('#support span').text(dashboardLang.get('general.support'));
 	$('#logout').attr('alt', dashboardLang.get('general.logout'));
 	$('#logout').attr('alt', dashboardLang.get('general.logout'));
-	$('.guild#invite a').attr('alt', dashboardLang.get('general.invite'));
-	$('.guild#refresh a').attr('alt', dashboardLang.get('general.refresh'));
 	if ( process.env.READONLY ) createNotice($, 'readonly', dashboardLang);
 	if ( process.env.READONLY ) createNotice($, 'readonly', dashboardLang);
 	if ( action ) createNotice($, action, dashboardLang, actionArgs);
 	if ( action ) createNotice($, action, dashboardLang, actionArgs);
 	$('head').append(
 	$('head').append(

+ 2 - 0
dashboard/i18n/en.json

@@ -18,6 +18,8 @@
         "selector": "Server Selector",
         "selector": "Server Selector",
         "settings": "Settings",
         "settings": "Settings",
         "support": "Support Server",
         "support": "Support Server",
+        "theme-dark": "Use dark theme",
+        "theme-light": "Use light theme",
         "title": "Wiki-Bot Settings",
         "title": "Wiki-Bot Settings",
         "verification": "Verifications",
         "verification": "Verifications",
         "welcome": "<h2>Welcome to the Wiki-Bot Dashboard.</h2>\n<p>Wiki-Bot is a Discord bot made to bring Discord servers and MediaWiki wikis together. It helps with linking wiki pages, verifying wiki users, informing about latest changes on the wiki and more. <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[More information]</a></p>\n<p>Here you can change different bot settings for servers you have Manage Server permission on. To begin, you will have to authenticate your Discord account which you can do with this button:</p>"
         "welcome": "<h2>Welcome to the Wiki-Bot Dashboard.</h2>\n<p>Wiki-Bot is a Discord bot made to bring Discord servers and MediaWiki wikis together. It helps with linking wiki pages, verifying wiki users, informing about latest changes on the wiki and more. <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[More information]</a></p>\n<p>Here you can change different bot settings for servers you have Manage Server permission on. To begin, you will have to authenticate your Discord account which you can do with this button:</p>"

+ 31 - 0
dashboard/index.html

@@ -72,6 +72,37 @@
 					</div>
 					</div>
 				</a>
 				</a>
 			</div>
 			</div>
+			<div class="guild" id="theme-separator" style="display: none;">
+				<div class="separator"></div>
+			</div>
+			<div class="guild" id="theme-light" style="display: none;">
+				<div class="bar"></div>
+				<a alt="Use light theme">
+					<div class="avatar noicon">
+						<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+							<circle cx="12" cy="12" r="5"/>
+							<line x1="12" y1="1" x2="12" y2="3"/>
+							<line x1="12" y1="21" x2="12" y2="23"/>
+							<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
+							<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
+							<line x1="1" y1="12" x2="3" y2="12"/>
+							<line x1="21" y1="12" x2="23" y2="12"/>
+							<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
+							<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
+						</svg>
+					</div>
+				</a>
+			</div>
+			<div class="guild" id="theme-dark" style="display: none;">
+				<div class="bar"></div>
+				<a alt="Use dark theme">
+					<div class="avatar noicon">
+						<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+							<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
+						</svg>
+					</div>
+				</a>
+			</div>
 		</div>
 		</div>
 	</div>
 	</div>
 </body>
 </body>

+ 13 - 6
dashboard/index.js

@@ -92,6 +92,9 @@ const server = http.createServer( (req, res) => {
 			 * @param {String[]} [actionArgs]
 			 * @param {String[]} [actionArgs]
 			 */
 			 */
 			function save_response(resURL = '/', action, ...actionArgs) {
 			function save_response(resURL = '/', action, ...actionArgs) {
+				var themeCookie = ( req.headers?.cookie?.split('; ')?.find( cookie => {
+					return cookie.split('=')[0] === 'theme' && /^"(?:light|dark)"$/.test(( cookie.split('=')[1] || '' ));
+				} ) || 'dark' ).replace( /^theme="(light|dark)"$/, '$1' );
 				var langCookie = ( req.headers?.cookie?.split('; ')?.filter( cookie => {
 				var langCookie = ( req.headers?.cookie?.split('; ')?.filter( cookie => {
 					return cookie.split('=')[0] === 'language' && /^"[a-z\-]+"$/.test(( cookie.split('=')[1] || '' ));
 					return cookie.split('=')[0] === 'language' && /^"[a-z\-]+"$/.test(( cookie.split('=')[1] || '' ));
 				} )?.map( cookie => cookie.replace( /^language="([a-z\-]+)"$/, '$1' ) ) || [] );
 				} )?.map( cookie => cookie.replace( /^language="([a-z\-]+)"$/, '$1' ) ) || [] );
@@ -105,7 +108,7 @@ const server = http.createServer( (req, res) => {
 					return '';
 					return '';
 				} ) || [] ));
 				} ) || [] ));
 				dashboardLang.fromCookie = langCookie;
 				dashboardLang.fromCookie = langCookie;
-				return dashboard(res, dashboardLang, state, new URL(resURL, process.env.dashboard), action, actionArgs);
+				return dashboard(res, dashboardLang, themeCookie, state, new URL(resURL, process.env.dashboard), action, actionArgs);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -131,6 +134,10 @@ const server = http.createServer( (req, res) => {
 
 
 	res.setHeader('Content-Type', 'text/html');
 	res.setHeader('Content-Type', 'text/html');
 
 
+	var themeCookie = ( req.headers?.cookie?.split('; ')?.find( cookie => {
+		return cookie.split('=')[0] === 'theme' && /^"(?:light|dark)"$/.test(( cookie.split('=')[1] || '' ));
+	} ) || 'dark' ).replace( /^theme="(light|dark)"$/, '$1' );
+
 	var langCookie = ( req.headers?.cookie?.split('; ')?.filter( cookie => {
 	var langCookie = ( req.headers?.cookie?.split('; ')?.filter( cookie => {
 		return cookie.split('=')[0] === 'language' && /^"[a-z\-]+"$/.test(( cookie.split('=')[1] || '' ));
 		return cookie.split('=')[0] === 'language' && /^"[a-z\-]+"$/.test(( cookie.split('=')[1] || '' ));
 	} )?.map( cookie => cookie.replace( /^language="([a-z\-]+)"$/, '$1' ) ) || [] );
 	} )?.map( cookie => cookie.replace( /^language="([a-z\-]+)"$/, '$1' ) ) || [] );
@@ -158,7 +165,7 @@ const server = http.createServer( (req, res) => {
 	if ( reqURL.pathname === '/login' ) {
 	if ( reqURL.pathname === '/login' ) {
 		let action = '';
 		let action = '';
 		if ( reqURL.searchParams.get('action') === 'failed' ) action = 'loginfail';
 		if ( reqURL.searchParams.get('action') === 'failed' ) action = 'loginfail';
-		return pages.login(res, dashboardLang, state, action);
+		return pages.login(res, dashboardLang, themeCookie, state, action);
 	}
 	}
 
 
 	if ( reqURL.pathname === '/logout' ) {
 	if ( reqURL.pathname === '/logout' ) {
@@ -167,7 +174,7 @@ const server = http.createServer( (req, res) => {
 			...( res.getHeader('Set-Cookie') || [] ),
 			...( res.getHeader('Set-Cookie') || [] ),
 			'wikibot=""; HttpOnly; Path=/; Max-Age=0'
 			'wikibot=""; HttpOnly; Path=/; Max-Age=0'
 		]);
 		]);
-		return pages.login(res, dashboardLang, state, 'logout');
+		return pages.login(res, dashboardLang, themeCookie, state, 'logout');
 	}
 	}
 
 
 	if ( !state ) {
 	if ( !state ) {
@@ -177,7 +184,7 @@ const server = http.createServer( (req, res) => {
 				res.setHeader('Set-Cookie', [`guild="${pathGuild}"; HttpOnly; Path=/`]);
 				res.setHeader('Set-Cookie', [`guild="${pathGuild}"; HttpOnly; Path=/`]);
 			}
 			}
 		}
 		}
-		return pages.login(res, dashboardLang, state, ( reqURL.pathname === '/' ? '' : 'unauthorized' ));
+		return pages.login(res, dashboardLang, themeCookie, state, ( reqURL.pathname === '/' ? '' : 'unauthorized' ));
 	}
 	}
 
 
 	if ( reqURL.pathname === '/oauth' ) {
 	if ( reqURL.pathname === '/oauth' ) {
@@ -191,7 +198,7 @@ const server = http.createServer( (req, res) => {
 				res.setHeader('Set-Cookie', [`guild="${pathGuild}"; HttpOnly; Path=/`]);
 				res.setHeader('Set-Cookie', [`guild="${pathGuild}"; HttpOnly; Path=/`]);
 			}
 			}
 		}
 		}
-		return pages.login(res, dashboardLang, state, ( reqURL.pathname === '/' ? '' : 'unauthorized' ));
+		return pages.login(res, dashboardLang, themeCookie, state, ( reqURL.pathname === '/' ? '' : 'unauthorized' ));
 	}
 	}
 
 
 	if ( reqURL.pathname === '/refresh' ) {
 	if ( reqURL.pathname === '/refresh' ) {
@@ -210,7 +217,7 @@ const server = http.createServer( (req, res) => {
 	let action = '';
 	let action = '';
 	if ( reqURL.searchParams.get('refresh') === 'success' ) action = 'refresh';
 	if ( reqURL.searchParams.get('refresh') === 'success' ) action = 'refresh';
 	if ( reqURL.searchParams.get('refresh') === 'failed' ) action = 'refreshfail';
 	if ( reqURL.searchParams.get('refresh') === 'failed' ) action = 'refreshfail';
-	return dashboard(res, dashboardLang, state, reqURL, action);
+	return dashboard(res, dashboardLang, themeCookie, state, reqURL, action);
 } );
 } );
 
 
 server.listen( 8080, 'localhost', () => {
 server.listen( 8080, 'localhost', () => {

+ 31 - 0
dashboard/login.html

@@ -65,6 +65,37 @@
 					</div>
 					</div>
 				</a>
 				</a>
 			</div>
 			</div>
+			<div class="guild" id="theme-separator" style="display: none;">
+				<div class="separator"></div>
+			</div>
+			<div class="guild" id="theme-light" style="display: none;">
+				<div class="bar"></div>
+				<a alt="Use light theme">
+					<div class="avatar noicon">
+						<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+							<circle cx="12" cy="12" r="5"/>
+							<line x1="12" y1="1" x2="12" y2="3"/>
+							<line x1="12" y1="21" x2="12" y2="23"/>
+							<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
+							<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
+							<line x1="1" y1="12" x2="3" y2="12"/>
+							<line x1="21" y1="12" x2="23" y2="12"/>
+							<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
+							<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
+						</svg>
+					</div>
+				</a>
+			</div>
+			<div class="guild" id="theme-dark" style="display: none;">
+				<div class="bar"></div>
+				<a alt="Use dark theme">
+					<div class="avatar noicon">
+						<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+							<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
+						</svg>
+					</div>
+				</a>
+			</div>
 		</div>
 		</div>
 	</div>
 	</div>
 </body>
 </body>

+ 5 - 1
dashboard/oauth.js

@@ -18,10 +18,11 @@ const file = require('fs').readFileSync('./dashboard/login.html');
  * Let a user login
  * Let a user login
  * @param {import('http').ServerResponse} res - The server response
  * @param {import('http').ServerResponse} res - The server response
  * @param {import('./i18n.js')} dashboardLang - The user language.
  * @param {import('./i18n.js')} dashboardLang - The user language.
+ * @param {String} theme - The display theme
  * @param {String} [state] - The user state
  * @param {String} [state] - The user state
  * @param {String} [action] - The action the user made
  * @param {String} [action] - The action the user made
  */
  */
-function dashboard_login(res, dashboardLang, state, action) {
+function dashboard_login(res, dashboardLang, theme, state, action) {
 	if ( state && settingsData.has(state) ) {
 	if ( state && settingsData.has(state) ) {
 		if ( !action ) {
 		if ( !action ) {
 			res.writeHead(302, {Location: '/'});
 			res.writeHead(302, {Location: '/'});
@@ -31,6 +32,7 @@ function dashboard_login(res, dashboardLang, state, action) {
 	}
 	}
 	var $ = cheerio.load(file);
 	var $ = cheerio.load(file);
 	$('html').attr('lang', dashboardLang.lang);
 	$('html').attr('lang', dashboardLang.lang);
+	if ( theme === 'light' ) $('html').addClass('theme-light');
 	$('<script>').text(`
 	$('<script>').text(`
 		const selectLanguage = '${dashboardLang.get('general.language').replace( /'/g, '\\$&' )}';
 		const selectLanguage = '${dashboardLang.get('general.language').replace( /'/g, '\\$&' )}';
 		const allLangs = ${JSON.stringify(allLangs)};
 		const allLangs = ${JSON.stringify(allLangs)};
@@ -39,6 +41,8 @@ function dashboard_login(res, dashboardLang, state, action) {
 	$('#login-button span, .channel#login div').text(dashboardLang.get('general.login'));
 	$('#login-button span, .channel#login div').text(dashboardLang.get('general.login'));
 	$('.channel#invite-wikibot div').text(dashboardLang.get('general.invite'));
 	$('.channel#invite-wikibot div').text(dashboardLang.get('general.invite'));
 	$('.guild#invite a').attr('alt', dashboardLang.get('general.invite'));
 	$('.guild#invite a').attr('alt', dashboardLang.get('general.invite'));
+	$('.guild#theme-dark a').attr('alt', dashboardLang.get('general.theme-dark'));
+	$('.guild#theme-light a').attr('alt', dashboardLang.get('general.theme-light'));
 	$('#support span').text(dashboardLang.get('general.support'));
 	$('#support span').text(dashboardLang.get('general.support'));
 	$('#text .description #welcome').html(dashboardLang.get('general.welcome'));
 	$('#text .description #welcome').html(dashboardLang.get('general.welcome'));
 	let responseCode = 200;
 	let responseCode = 200;

+ 211 - 105
dashboard/src/index.css

@@ -10,6 +10,10 @@ body {
 	min-height: 100%;
 	min-height: 100%;
 	margin: 0;
 	margin: 0;
 }
 }
+.theme-light body {
+	background-color: #ffffff;
+	color: #2e3338;
+}
 a {
 a {
 	text-decoration: none;
 	text-decoration: none;
 	color: inherit;
 	color: inherit;
@@ -17,96 +21,19 @@ a {
 a[alt]:hover:after {
 a[alt]:hover:after {
 	content: attr(alt);
 	content: attr(alt);
 	position: absolute;
 	position: absolute;
-	background: #000000;
+	background: #18191c;
 	color: #dcddde;
 	color: #dcddde;
 	font-weight: bold;
 	font-weight: bold;
 	font-size: 90%;
 	font-size: 90%;
 	word-break: break-word;
 	word-break: break-word;
 	border-radius: 4px;
 	border-radius: 4px;
 	padding: 8px;
 	padding: 8px;
+	box-shadow: 0 8px 16px rgba(0,0,0,0.24);
 }
 }
-.description a,
-a .description,
-.notice a {
-	color: #00b0f4;
-}
-.description a:hover,
-a:hover .description,
-.notice a:hover {
-	text-decoration: underline;
-}
-code {
-	background: #2f3136;
-	padding: 1px 3px;
-	margin: -2px 0;
-	border: 1px solid #202225;
-	border-radius: 3px;
-	white-space: pre-wrap;
-	font-size: 120%;
-}
-#text {
-	position: relative;
-	padding: 8px;
-	width: calc(100% - 328px);
-	top: 48px;
-	left: 312px;
-}
-.user-select {
-	-webkit-user-select: all;
-	-moz-user-select: all;
-	-ms-user-select: all;
-	user-select: all;
-}
-.notice {
-	padding: 5px 10px;
-	line-height: 1.6;
-	text-align: center;
-	margin: 0 auto 1em;
-	width: -moz-fit-content;
-	width: fit-content;
-	border: 2px solid;
-}
-.notice-success {
-	background-color: #020;
-	border-color: #050;
-}
-.notice-info {
-	background-color: #220;
-	border-color: #550;
-}
-.notice-error {
-	background-color: #200;
-	border-color: #500;
-}
-.server-selector {
-	display: flex;
-	flex-wrap: wrap;
-}
-.server {
-	background-color: rgba(0,0,0,0.5);
-	text-align: center;
-	border-radius: 10%;
-	width: 200px;
-	margin: 5px;
-}
-.server:hover {
-	background: rgba(0,0,0,0.3);
-	filter: brightness(1.2);
-}
-.server .avatar {
-	border-radius: 10%;
-	width: 200px;
-	height: 200px;
-}
-.server .noicon {
-	font-size: 416%;
-	background-color: unset;
-}
-.server .server-name {
-	padding: 8px 12px;
-	word-break: break-word;
-	font-weight: bold;
-	font-size: 90%;
+.theme-light a[alt]:hover:after {
+	background-color: #ffffff;
+	color: #2e3338;
+	box-shadow: 0 8px 16px rgba(0,0,0,0.16);
 }
 }
 #navbar {
 #navbar {
 	background: #2f3136;
 	background: #2f3136;
@@ -125,6 +52,12 @@ code {
 				0 1.5px 0 rgba(6,6,7,0.05),
 				0 1.5px 0 rgba(6,6,7,0.05),
 				0 2px 0 rgba(4,4,5,0.05);
 				0 2px 0 rgba(4,4,5,0.05);
 }
 }
+.theme-light #navbar {
+	background-color: #f2f3f5;
+	box-shadow: 0 1px 0 rgba(6,6,7,0.1),
+				0 1.5px 0 rgba(6,6,7,0.025),
+				0 2px 0 rgba(6,6,7,0.025);
+}
 :target::before {
 :target::before {
 	content: "";
 	content: "";
 	display: block;
 	display: block;
@@ -141,6 +74,9 @@ code {
 #navbar a:hover {
 #navbar a:hover {
 	background: #202225;
 	background: #202225;
 }
 }
+.theme-light #navbar a:hover {
+	background-color: #e3e5e8;
+}
 #navbar a[alt]:hover:after {
 #navbar a[alt]:hover:after {
 	top: 48px;
 	top: 48px;
 }
 }
@@ -175,6 +111,9 @@ code {
 	min-height: calc(100% - 24px);
 	min-height: calc(100% - 24px);
 	width: 72px;
 	width: 72px;
 }
 }
+.theme-light #guildlist {
+	background-color: #e3e5e8;
+}
 .guild {
 .guild {
 	margin: 0 0 8px;
 	margin: 0 0 8px;
 	position: relative;
 	position: relative;
@@ -194,6 +133,9 @@ code {
 	color: #dcddde;
 	color: #dcddde;
 	font-weight: bold;
 	font-weight: bold;
 }
 }
+.theme-light .avatar {
+	color: #2e3338;
+}
 #navbar a:hover .avatar,
 #navbar a:hover .avatar,
 .guild.selected .avatar,
 .guild.selected .avatar,
 .guild:hover .avatar {
 .guild:hover .avatar {
@@ -205,14 +147,22 @@ code {
 	height: 48px;
 	height: 48px;
 	background: #36393f;
 	background: #36393f;
 }
 }
+.theme-light .noicon {
+	background-color: #ffffff;
+}
 .guild.selected .noicon,
 .guild.selected .noicon,
 .guild:hover .noicon {
 .guild:hover .noicon {
+	color: #ffffff;
 	background-color: #7289da;
 	background-color: #7289da;
 }
 }
 .svg-avatar {
 .svg-avatar {
 	color: #43b581;
 	color: #43b581;
 	background: #36393f;
 	background: #36393f;
 }
 }
+.theme-light .svg-avatar {
+	color: #43b581;
+	background-color: #ffffff;
+}
 .guild:hover .svg-avatar {
 .guild:hover .svg-avatar {
 	color: #ffffff;
 	color: #ffffff;
 	background-color: #43b581;
 	background-color: #43b581;
@@ -223,6 +173,9 @@ code {
 	border-radius: 1px;
 	border-radius: 1px;
 	background-color: rgba(255,255,255,0.06);
 	background-color: rgba(255,255,255,0.06);
 }
 }
+.theme-light .separator {
+	background-color: rgba(6,6,7,0.08);
+}
 .bar {
 .bar {
 	position: absolute;
 	position: absolute;
 	left: 0;
 	left: 0;
@@ -233,6 +186,9 @@ code {
 	margin-left: -4px;
 	margin-left: -4px;
 	background-color: #ffffff;
 	background-color: #ffffff;
 }
 }
+.theme-light .bar {
+	background-color: #060607;
+}
 .guild:hover .bar {
 .guild:hover .bar {
 	margin-top: 14px;
 	margin-top: 14px;
 	height: 20px;
 	height: 20px;
@@ -257,6 +213,9 @@ code {
 	left: 72px;
 	left: 72px;
 	bottom: 0;
 	bottom: 0;
 }
 }
+.theme-light #channellist {
+	background-color: #f2f3f5;
+}
 .channel {
 .channel {
 	padding: 0 8px;
 	padding: 0 8px;
 	margin: 0 8px 2px 12px;
 	margin: 0 8px 2px 12px;
@@ -266,6 +225,9 @@ code {
 	align-items: center;
 	align-items: center;
 	color: #8e9297;
 	color: #8e9297;
 }
 }
+.theme-light .channel {
+	color: #6a7480;
+}
 .channel img {
 .channel img {
 	margin-right: 6px;
 	margin-right: 6px;
 	width: 20px;
 	width: 20px;
@@ -284,15 +246,27 @@ code {
 .channel:hover {
 .channel:hover {
 	background: rgba(79,84,92,0.16);
 	background: rgba(79,84,92,0.16);
 }
 }
+.theme-light .channel:hover {
+	background-color: rgba(116,127,141,0.08);
+}
 .channel.selected {
 .channel.selected {
 	background: rgba(79,84,92,0.32);
 	background: rgba(79,84,92,0.32);
 }
 }
+.theme-light .channel.selected {
+	background-color: rgba(116,127,141,0.24);
+}
 .channel:hover div {
 .channel:hover div {
 	color: #dcddde;
 	color: #dcddde;
 }
 }
+.theme-light .channel:hover div {
+	color: #2e3338;
+}
 .channel.selected div {
 .channel.selected div {
 	color: #ffffff;
 	color: #ffffff;
 }
 }
+.theme-light .channel.selected div {
+	color: #060607;
+}
 .channel-header {
 .channel-header {
 	margin-left: 8px;
 	margin-left: 8px;
 	height: 44px;
 	height: 44px;
@@ -320,6 +294,11 @@ code {
 	align-items: center;
 	align-items: center;
 	color: #8e9297;
 	color: #8e9297;
 }
 }
+.theme-light #lang-selector {
+	background-color: #f2f3f5;
+	border-top-color: #e3e5e8;
+	color: #6a7480;
+}
 #lang-selector img {
 #lang-selector img {
 	padding-right: 2px;
 	padding-right: 2px;
 }
 }
@@ -335,6 +314,9 @@ code {
 	bottom: 32px;
 	bottom: 32px;
 	overflow: overlay;
 	overflow: overlay;
 }
 }
+.theme-light #lang-dropdown {
+	background-color: #e3e5e8;
+}
 #lang-dropdown div {
 #lang-dropdown div {
 	cursor: pointer;
 	cursor: pointer;
 	margin: 2px;
 	margin: 2px;
@@ -342,9 +324,154 @@ code {
 	font-size: 90%;
 	font-size: 90%;
 	border-radius: 2px;
 	border-radius: 2px;
 }
 }
-#lang-dropdown div:hover, #lang-dropdown div.current {
+#lang-dropdown div.current,
+#lang-dropdown div:hover {
 	background: #2f3136;
 	background: #2f3136;
 }
 }
+.theme-light #lang-dropdown div.current,
+.theme-light #lang-dropdown div:hover {
+	background-color: #f2f3f5;
+}
+.description a,
+a .description,
+.notice a {
+	color: #00b0f4;
+}
+.theme-light .description a,
+.theme-light a .description,
+.theme-light .notice a {
+	color: #0067e0;
+}
+.description a:hover,
+a:hover .description,
+.notice a:hover {
+	text-decoration: underline;
+}
+code {
+	background: #2f3136;
+	padding: 1px 3px;
+	margin: -2px 0;
+	border: 1px solid #202225;
+	border-radius: 3px;
+	white-space: pre-wrap;
+	font-size: 120%;
+}
+.theme-light code {
+	background-color: #f2f3f5;
+	border-color: #e3e5e8;
+}
+#text {
+	position: relative;
+	padding: 8px;
+	width: calc(100% - 328px);
+	top: 48px;
+	left: 312px;
+}
+.user-select {
+	-webkit-user-select: all;
+	-moz-user-select: all;
+	-ms-user-select: all;
+	user-select: all;
+}
+.notice {
+	padding: 5px 10px;
+	line-height: 1.6;
+	text-align: center;
+	margin: 0 auto 1em;
+	width: -moz-fit-content;
+	width: fit-content;
+	border: 2px solid;
+}
+.notice-success {
+	background-color: #020;
+	border-color: #050;
+}
+.theme-light .notice-success {
+	background-color: #cfc;
+	border-color: #afa;
+}
+.notice-info {
+	background-color: #220;
+	border-color: #550;
+}
+.theme-light .notice-info {
+	background-color: #ffc;
+	border-color: #ffa;
+}
+.notice-error {
+	background-color: #200;
+	border-color: #500;
+}
+.theme-light .notice-error {
+	background-color: #fcc;
+	border-color: #faa;
+}
+#login-button {
+	display: flex;
+	margin: 20px auto;
+	padding: 20px 50px;
+	width: -moz-fit-content;
+	width: fit-content;
+	justify-content: center;
+	align-items: center;
+	font-size: 300%;
+	background: #2f3136;
+	border: 5px solid #202225;
+	border-radius: 30px;
+}
+.theme-light #login-button {
+	background-color: #f2f3f5;
+	border-color: #e3e5e8;
+}
+#login-button:hover {
+	background-color: #36393f;
+}
+.theme-light #login-button:hover {
+	background-color: #fff;
+}
+#login-button img {
+	width: 60px;
+	height: 60px;
+	padding-right: 10px;
+}
+.server-selector {
+	display: flex;
+	flex-wrap: wrap;
+}
+.server {
+	background-color: #202225;
+	text-align: center;
+	border-radius: 10%;
+	width: 200px;
+	margin: 5px;
+}
+.theme-light .server {
+	background-color: #e3e5e8;
+}
+.server:hover {
+	background-color: #2f3136;
+}
+.theme-light .server:hover {
+	background-color: #f2f3f5;
+}
+.server:hover .avatar {
+	filter: brightness(1.2);
+}
+.server .avatar {
+	border-radius: 10%;
+	width: 200px;
+	height: 200px;
+}
+.server .noicon {
+	font-size: 416%;
+	background-color: unset;
+}
+.server .server-name {
+	padding: 8px 12px;
+	word-break: break-word;
+	font-weight: bold;
+	font-size: 90%;
+}
 fieldset > div {
 fieldset > div {
 	margin: 10px 0;
 	margin: 10px 0;
 }
 }
@@ -388,25 +515,4 @@ button.addmore:not([hidden]) {
 #wb-settings-lang-widget {
 #wb-settings-lang-widget {
 	vertical-align: top;
 	vertical-align: top;
 	margin-left: 5px;
 	margin-left: 5px;
-}
-#login-button {
-	display: flex;
-	margin: 20px auto;
-	padding: 20px 50px;
-	width: -moz-fit-content;
-	width: fit-content;
-	justify-content: center;
-	align-items: center;
-	font-size: 300%;
-	background: #2f3136;
-	border: 5px solid #202225;
-	border-radius: 30px;
-}
-#login-button:hover {
-	background-color:#36393f;
-}
-#login-button img {
-	width: 60px;
-	height: 60px;
-	padding-right: 10px;
 }
 }

+ 21 - 0
dashboard/src/lang.js

@@ -1,3 +1,24 @@
+var currentTheme = ( document.cookie.split('; ').find( cookie => {
+	return cookie.split('=')[0] === 'theme' && /^"(?:light|dark)"$/.test(( cookie.split('=')[1] || '' ));
+} ) || 'dark' ).replace( /^theme="(light|dark)"$/, '$1' );
+var lightTheme = document.getElementById('theme-light');
+var darkTheme = document.getElementById('theme-dark');
+lightTheme.onclick = function() {
+	document.cookie = 'theme="light"; Path=/; Max-Age=31536000';
+	document.documentElement.classList.add('theme-light');
+	lightTheme.setAttribute('style', 'display: none;');
+	darkTheme.removeAttribute('style');
+};
+darkTheme.onclick = function() {
+	document.cookie = 'theme="dark"; Path=/; Max-Age=31536000';
+	document.documentElement.classList.remove('theme-light');
+	darkTheme.setAttribute('style', 'display: none;');
+	lightTheme.removeAttribute('style');
+};
+document.getElementById('theme-separator').removeAttribute('style');
+if ( currentTheme === 'light' ) darkTheme.removeAttribute('style');
+else lightTheme.removeAttribute('style');
+
 var currentLang = ( document.cookie.split('; ').find( cookie => {
 var currentLang = ( document.cookie.split('; ').find( cookie => {
 	return cookie.split('=')[0] === 'language' && /^"[a-z\-]+"$/.test(( cookie.split('=')[1] || '' ));
 	return cookie.split('=')[0] === 'language' && /^"[a-z\-]+"$/.test(( cookie.split('=')[1] || '' ));
 } ) || 'en' ).replace( /^language="([a-z\-]+)"$/, '$1' );
 } ) || 'en' ).replace( /^language="([a-z\-]+)"$/, '$1' );