Compare commits

..

No commits in common. "95f42bcfe1e76b3e67051a58e2bb13eacd22792c" and "8f35f448b6779c608028864c1cdeb02ee5d8ffdf" have entirely different histories.

19 changed files with 793 additions and 119 deletions

View File

@ -33,7 +33,7 @@ public sealed class MarkdownFormatter : IFormatter
return false; return false;
var pipeline = _serviceProvider.GetService<MarkdownPipeline>(); var pipeline = _serviceProvider.GetService<MarkdownPipeline>();
formattingInfo.Write(Markdown.ToHtml(value, pipeline)); formattingInfo.Write(Markdig.Markdown.ToHtml(value, pipeline));
return true; return true;
} }
} }

View File

@ -0,0 +1,22 @@
namespace OliverBooth.Data;
/// <summary>
/// An enumeration of site themes.
/// </summary>
public enum SiteTheme
{
/// <summary>
/// Dark mode.
/// </summary>
Dark,
/// <summary>
/// Light mode.
/// </summary>
Light,
/// <summary>
/// Follow system settings.
/// </summary>
Auto
}

View File

@ -2,6 +2,7 @@
@using OliverBooth.Common.Data.Blog @using OliverBooth.Common.Data.Blog
@using OliverBooth.Common.Data.Web @using OliverBooth.Common.Data.Web
@using OliverBooth.Common.Services @using OliverBooth.Common.Services
@using OliverBooth.Data
@using OliverBooth.Extensions @using OliverBooth.Extensions
@inject IBlogPostService BlogPostService @inject IBlogPostService BlogPostService
@inject ITutorialService TutorialService @inject ITutorialService TutorialService
@ -9,9 +10,15 @@
@{ @{
HttpRequest request = Context.Request; HttpRequest request = Context.Request;
var url = new Uri($"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}"); var url = new Uri($"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}");
SiteTheme siteTheme = request.Cookies["_theme"] switch
{
"dark" => SiteTheme.Dark,
"light" => SiteTheme.Light,
_ => SiteTheme.Auto
};
} }
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" data-bs-theme="dark"> <html lang="en" data-bs-theme="@(siteTheme switch { SiteTheme.Light => "light", _ => "dark" })">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -61,7 +68,21 @@
<link href="https://fonts.googleapis.com/css2?family=Gabarito:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Gabarito:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="~/css/prism.min.css" asp-append-version="true"> <link rel="stylesheet" href="~/css/prism.min.css" asp-append-version="true">
<link rel="stylesheet" href="~/css/prism.vs.min.css" asp-append-version="true"> @switch (siteTheme)
{
case SiteTheme.Dark:
<link rel="stylesheet" href="~/css/prism-vs-dark.min.css" asp-append-version="true">
break;
case SiteTheme.Light:
<link rel="stylesheet" href="~/css/prism-vs-light.min.css" asp-append-version="true">
break;
default:
<link rel="stylesheet" href="~/css/prism-vs-dark.min.css" asp-append-version="true" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="~/css/prism-vs-light.min.css" asp-append-version="true" media="(prefers-color-scheme: light)">
break;
}
<link rel="stylesheet" href="~/css/app.min.css" asp-append-version="true"> <link rel="stylesheet" href="~/css/app.min.css" asp-append-version="true">
<link rel="stylesheet" href="~/css/ribbon.min.css" asp-append-version="true"> <link rel="stylesheet" href="~/css/ribbon.min.css" asp-append-version="true">
</head> </head>
@ -69,7 +90,7 @@
<header class="container" style="margin-top: 20px;"> <header class="container" style="margin-top: 20px;">
<div id="site-title" class="text-center"> <div id="site-title" class="text-center">
<h1> <h1>
<a href="/"><img src="~/img/ob-256x256.png" alt="Oliver Booth" height="128"> Oliver Booth</a> <a href="/"><img id="site-logo" src="~/img/ob-256x256.png" alt="Oliver Booth" height="128"> Oliver Booth</a>
</h1> </h1>
</div> </div>
</header> </header>

View File

@ -4,7 +4,7 @@
"windowsAuthentication": false, "windowsAuthentication": false,
"anonymousAuthentication": true, "anonymousAuthentication": true,
"iisExpress": { "iisExpress": {
"applicationUrl": "http://localhost:5000", "applicationUrl": "http://*:5000",
"sslPort": 2845 "sslPort": 2845
} }
}, },
@ -14,7 +14,7 @@
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": false,
"workingDirectory": "bin/Debug/net8.0/", "workingDirectory": "bin/Debug/net8.0/",
"applicationUrl": "https://localhost:2845", "applicationUrl": "https://*:2845",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

BIN
src/img/ob-256x256-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

88
src/scss/_prism-vs.scss Normal file
View File

@ -0,0 +1,88 @@
code[class*="language-"] {
font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
background: $background;
color: $foreground;
}
.token {
&.punctuation {
color: $foreground;
}
&.comment {
color: $comment;
}
&.string {
color: $string;
}
&.function {
color: $function;
}
&.class-name {
color: $class-name;
}
&.doctype, &.prolog {
color: $doctype;
}
&.keyword {
color: $keyword;
}
&.preprocessor.property {
color: $preprocessor-property;
.keyword {
color: $preprocessor-property;
}
}
&.selector {
color: $css-selector;
}
&.atrule {
color: $atrule;
}
&.tag {
color: $markup-tag;
}
&.attr-name {
color: $attr-name;
}
&.attr-value {
color: $markup-attr-value;
}
}
// CSS
code[class*="language-css"] {
color: $css-foreground;
.token {
&.property {
color: $css-property;
}
&.string {
color: $css-foreground;
}
}
}
// Markup
code[class*="language-markup"] {
.token {
&.punctuation {
color: $markup-punctuation;
}
}
}

132
src/scss/app.dark.scss Normal file
View File

@ -0,0 +1,132 @@
@use "sass:color";
@import "colors";
html, body {
background: $background-dark;
color: $foreground-dark;
}
#site-title {
a {
&:link, &:visited, &:hover, &:active {
color: $ui-foreground-dark;
* {
color: $ui-foreground-dark;
}
}
}
}
nav ul.site-nav li a {
&:link, &:visited, &:hover, &:active {
color: $ui-foreground-dark;
}
}
ul.contact-reasons li a {
&:link, &:visited, &:hover, &:active {
color: $ui-foreground-dark;
}
}
.card {
background: $card-background-dark;
color: $ui-foreground-dark;
a.btn, button {
color: $ui-foreground-dark;
}
}
article {
background: $content-background-dark;
}
main.container {
background: $content-background-dark;
}
a {
&:link, &:visited, &:hover, &:active {
color: $link-text-dark;
}
&:hover {
color: $link-hover-dark;
}
}
a.brand-mastodon {
&:hover {
color: $ui-foreground-dark;
}
}
hr.page-separator {
border-top: 5px dashed $ui-foreground-dark;
}
article blockquote {
border-left: 2px solid $blockquote-dark;
}
.project-card {
background: $project-background-dark;
a {
&:link, &:visited, &:hover &:active {
color: $ui-foreground-dark;
}
}
p.project-title {
background-color: rgba($project-background-dark, 50%);
}
}
code:not([class*="language-"]) {
background: #1e1e1e !important;
color: #dcdcda !important;
}
a:link, a:visited, a:active {
code:not([class*="language-"]) {
color: $link-text-dark !important;
}
}
a:hover {
code:not([class*="language-"]) {
color: $link-hover-dark !important;
}
}
a.brand-linkedin {
&:hover {
color: $ui-foreground-dark;
}
}
pre {
background: #1e1e1e !important;
code mark, code mark span {
background: #d8ba76 !important;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem $ui-foreground-dark, 0 0 0 0.25rem #258cfb;
}
.keystroke {
background: color.adjust($foreground-dark, $lightness: -20%);
color: $background-dark;
border: color.adjust($foreground-dark, $lightness: -30%);
}
.mastodon-update-card.card {
background-color: desaturate(darken(#6364FF, 50%), 50%);
color: #fff;
}

140
src/scss/app.light.scss Normal file
View File

@ -0,0 +1,140 @@
@use "sass:color";
@import "colors";
html, body {
background: $background-light;
color: $foreground-light;
}
#site-title {
a {
&:link, &:visited, &:hover, &:active {
color: $ui-foreground-light;
* {
color: $ui-foreground-light;
}
}
}
}
nav ul.site-nav li a {
&:link, &:visited, &:hover, &:active {
color: $ui-foreground-light;
}
}
ul.contact-reasons li a {
&:link, &:visited, &:hover, &:active {
color: $ui-foreground-light;
}
}
.card {
background: $card-background-light;
color: $ui-foreground-light;
a.btn, button {
color: $ui-foreground-light;
}
}
article {
background: $content-background-light;
}
main.container {
background: $content-background-light;
}
a.brand-mastodon {
&:hover {
color: $ui-foreground-light;
}
}
a {
&:link, &:visited, &:hover, &:active {
color: $link-text-light;
}
&:hover {
color: $link-hover-light;
}
}
hr.page-separator {
border-top: 5px dashed $ui-foreground-light;
}
article blockquote {
border-left: 2px solid $blockquote-light;
}
.project-card {
background: $project-background-light;
a {
&:link, &:visited, &:hover &:active {
color: $ui-foreground-light;
}
}
p.project-title {
background-color: rgba($project-background-light, 50%);
}
}
code:not([class*="language-"]) {
background: #dcdcda !important;
color: #1e1e1e !important;
}
a:link, a:visited, a:hover, a:active {
code:not([class*="language-"]) {
color: $link-text-light !important;
}
}
a:hover {
code:not([class*="language-"]) {
color: $link-hover-light !important;
}
}
a.brand-linkedin {
&:hover {
color: $ui-foreground-light;
}
}
pre {
background: #ffffff !important;
code mark, code mark span {
background: #ffff00 !important;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem $ui-foreground-light, 0 0 0 0.25rem #258cfb;
}
.keystroke {
background: color.adjust($foreground-light, $lightness: -20%);
color: $background-light;
border: color.adjust($foreground-light, $lightness: -30%);
}
.mastodon-update-card.card {
background-color: #6364FF;
color: $ui-foreground-dark;
.text-muted abbr {
color: $ui-foreground-dark;
}
a:link, a:visited, a:hover, a:active {
color: $ui-foreground-dark;
}
}

View File

@ -4,11 +4,17 @@
@import "blog"; @import "blog";
html, body { html, body {
background: $background;
color: $foreground;
font-size: 16px; font-size: 16px;
} }
@media (prefers-color-scheme: dark) {
@import "app.dark";
}
@media (prefers-color-scheme: light) {
@import "app.light";
}
@media (min-width: 768px) { @media (min-width: 768px) {
html { html {
font-size: 16px; font-size: 16px;
@ -16,19 +22,12 @@ html, body {
} }
.keystroke { .keystroke {
background: color.adjust($foreground, $lightness: -20%);
color: $background;
border-radius: 2px; border-radius: 2px;
border: color.adjust($foreground, $lightness: -30%);
box-shadow: #2b2b2b 2px 2px; box-shadow: #2b2b2b 2px 2px;
font-size: 12px; font-size: 12px;
padding: 2px 4px; padding: 2px 4px;
} }
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem $ui-foreground, 0 0 0 0.25rem #258cfb;
}
html { html {
position: relative; position: relative;
min-height: 100%; min-height: 100%;
@ -39,7 +38,6 @@ body {
} }
main.container { main.container {
background: #333;
padding: 20px; padding: 20px;
border-radius: 5px; border-radius: 5px;
} }
@ -47,11 +45,6 @@ main.container {
a { a {
&:link, &:visited, &:hover, &:active { &:link, &:visited, &:hover, &:active {
text-decoration: none; text-decoration: none;
color: $link-text;
}
&:hover {
color: $link-hover;
} }
} }
@ -59,19 +52,16 @@ a {
a { a {
&:link, &:visited, &:hover, &:active { &:link, &:visited, &:hover, &:active {
text-decoration: none; text-decoration: none;
color: $ui-foreground;
* { * {
font-family: 'Roboto Mono', monospace; font-family: 'Roboto Mono', monospace;
text-decoration: none; text-decoration: none;
color: $ui-foreground;
} }
} }
} }
} }
hr.page-separator { hr.page-separator {
border-top: 5px dashed $ui-foreground;
opacity: 1; opacity: 1;
margin: 50px 0; margin: 50px 0;
} }
@ -98,7 +88,6 @@ ul.contact-reasons {
a { a {
&:link, &:visited, &:hover, &:active { &:link, &:visited, &:hover, &:active {
text-decoration: none; text-decoration: none;
color: $ui-foreground;
} }
&:hover { &:hover {
@ -108,15 +97,6 @@ ul.contact-reasons {
} }
} }
.card {
background: #212121;
color: $ui-foreground;
a.btn, button {
color: $ui-foreground;
}
}
nav { nav {
margin: 0 auto; margin: 0 auto;
max-width: 960px; max-width: 960px;
@ -140,7 +120,6 @@ nav {
&:link, &:visited, &:hover, &:active { &:link, &:visited, &:hover, &:active {
text-decoration: none; text-decoration: none;
color: $ui-foreground;
} }
&:hover { &:hover {
@ -156,7 +135,6 @@ nav {
} }
article { article {
background: $content-background;
padding: 20px; padding: 20px;
border-radius: 5px; border-radius: 5px;
@ -165,7 +143,6 @@ article {
} }
blockquote { blockquote {
border-left: 2px solid $blockquote;
padding-left: 10px; padding-left: 10px;
} }
@ -185,7 +162,7 @@ article {
abbr { abbr {
text-decoration: none; text-decoration: none;
border-bottom: 1px dotted $link-hover; border-bottom: 1px dotted $link-hover-dark;
} }
span.timestamp { span.timestamp {
@ -197,7 +174,6 @@ article {
.project-card { .project-card {
position: relative; position: relative;
background: $project-background;
box-shadow: 0 0 15px rgba(0, 0, 0, .1); box-shadow: 0 0 15px rgba(0, 0, 0, .1);
&:hover { &:hover {
@ -223,12 +199,6 @@ article {
transition: transform 500ms; transition: transform 500ms;
} }
a {
&:link, &:visited, &:hover &:active {
color: $ui-foreground;
}
}
p.project-title { p.project-title {
font-size: 1em; font-size: 1em;
transition: font-size 500ms, margin 500ms; transition: font-size 500ms, margin 500ms;
@ -237,37 +207,20 @@ article {
width: 100%; width: 100%;
margin: -34px 0 0; margin: -34px 0 0;
padding: 5px; padding: 5px;
background-color: rgba($project-background, 50%);
} }
} }
} }
code:not([class*="language-"]) { code:not([class*="language-"]) {
background: #1e1e1e !important;
color: #dcdcda !important;
font-size: inherit !important; font-size: inherit !important;
padding: 3px; padding: 3px;
} }
a:link, a:visited, a:hover, a:active {
code:not([class*="language-"]) {
color: #03a9f4 !important;
}
}
a:hover {
code:not([class*="language-"]) {
color: #fff !important;
}
}
pre { pre {
background: #1e1e1e !important;
border-radius: 5px; border-radius: 5px;
font-size: 0.9em !important; font-size: 0.9em !important;
code mark, code mark span { code mark, code mark span {
background: #d8ba76 !important;
color: #000 !important; color: #000 !important;
} }
} }
@ -351,22 +304,12 @@ a.bmc-btn {
a.brand-mastodon { a.brand-mastodon {
&:link, &:visited, &:hover, &:active { &:link, &:visited, &:hover, &:active {
text-decoration: none; text-decoration: none;
color: #5a48dd;
}
&:hover {
color: #fff;
} }
} }
a.brand-linkedin { a.brand-linkedin {
&:link, &:visited, &:hover, &:active { &:link, &:visited, &:hover, &:active {
text-decoration: none; text-decoration: none;
color: #0077b5;
}
&:hover {
color: #fff;
} }
} }
@ -385,38 +328,6 @@ td.trim-p p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
#usa-countdown {
background-image: url('/img/us-flag-cover_512x166.png');
background-position: center;
background-repeat: no-repeat;
background-size: cover;
border-radius: 10px;
cursor: pointer;
* {
cursor: pointer;
}
.usa-countdown-element {
margin: 10px 0;
padding: 5px;
font-family: "Gabarito", sans-serif;
font-weight: 500;
text-align: center;
font-size: 3em;
border-right: 2px solid #fff;
border-left: 2px solid #fff;
&:first-child {
border-left: none;
}
&:last-child {
border-right: none;
}
}
}
.post-tags a.badge { .post-tags a.badge {
transition: color 250ms, background-color 250ms; transition: color 250ms, background-color 250ms;
@ -426,7 +337,7 @@ td.trim-p p:last-child {
} }
&:hover { &:hover {
color: $accent; color: $accent-dark;
background-color: #1E1E1E !important; background-color: #1E1E1E !important;
} }
} }
@ -463,8 +374,13 @@ td.trim-p p:last-child {
} }
} }
@media (prefers-color-scheme: dark) {
}
@media (prefers-color-scheme: light) {
}
.mastodon-update-card.card { .mastodon-update-card.card {
background-color: desaturate(darken(#6364FF, 50%), 50%);
margin-bottom: 20px; margin-bottom: 20px;
border-radius: 3px; border-radius: 3px;
border: none; border: none;
@ -543,6 +459,11 @@ td.trim-p p:last-child {
} }
} }
button#theme-switch {
width: 42pt;
height: 42pt;
}
@keyframes headshot-spin-start { @keyframes headshot-spin-start {
0% { 0% {
transform: rotateY(0) scale(1.0); transform: rotateY(0) scale(1.0);

View File

@ -1,9 +1,21 @@
$blog-card-bg: #333333; $blog-card-bg-dark: #333333;
$blog-card-bg-light: #ffffff;
$blog-card-gutter: 20px; $blog-card-gutter: 20px;
$border-radius: 3px; $border-radius: 3px;
@media (prefers-color-scheme: dark) {
div.blog-card {
background-color: $blog-card-bg-dark;
}
}
@media (prefers-color-scheme: light) {
div.blog-card {
background-color: $blog-card-bg-light;
}
}
div.blog-card { div.blog-card {
background: $blog-card-bg;
margin-bottom: $blog-card-gutter; margin-bottom: $blog-card-gutter;
padding: $blog-card-gutter; padding: $blog-card-gutter;
border-radius: $border-radius; border-radius: $border-radius;

View File

@ -1,12 +1,23 @@
@use "sass:color"; @use "sass:color";
$background: #121212; $background-dark: #121212;
$foreground: #f5f5f5; $foreground-dark: #f5f5f5;
$accent: #03a9f4; $accent-dark: #03a9f4;
$ui-foreground: #ffffff; $ui-foreground-dark: #ffffff;
$content-background: #333333; $content-background-dark: #333333;
$blockquote: #ff0033; $blockquote-dark: #ff0033;
$project-background: #000000; $project-background-dark: #000000;
$card-background-dark: #212121;
$link-text-dark: $accent-dark;
$link-hover-dark: lighten($link-text-dark, 50%);
$link-text: $accent; $background-light: #dddddd;
$link-hover: lighten($link-text, 50%); $foreground-light: #0a0a0a;
$accent-light: #007ab1;
$ui-foreground-light: #000000;
$content-background-light: #ffffff;
$blockquote-light: #ff0033;
$project-background-light: #ffffff;
$card-background-light: #dedede;
$link-text-light: $accent-light;
$link-hover-light: lighten($link-text-light, 50%);

View File

@ -0,0 +1,183 @@
$callout-bg-blue: #e6f0fc;
$callout-bg-cyan: #e5f8f8;
$callout-bg-green: #e6f8ed;
$callout-bg-orange: #fdf1e5;
$callout-bg-red: #fdeaec;
$callout-bg-purple: #f1edfd;
$callout-bg-grey: #f5f5f5;
$callout-fg-blue: #157aff;
$callout-fg-cyan: #53dfdd;
$callout-fg-green: #44cf6e;
$callout-fg-orange: #e9973f;
$callout-fg-red: #fb464c;
$callout-fg-purple: #a882ff;
$callout-fg-grey: #9e9e9e;
.callout {
font-size: 16px;
border-radius: 5px;
padding: 20px;
margin-bottom: 1rem;
.callout-title {
font-weight: bold;
margin-bottom: 1rem;
}
p:last-child {
margin-bottom: 0;
}
&.collapsible {
.callout-fold {
transform: rotate(180deg);
transition: transform 500ms;
margin-left: 0.5em;
svg {
transform: rotate(180deg);
transition: transform 500ms;
}
}
.callout-title {
cursor: pointer;
transition: margin-bottom 500ms;
}
&.collapsed {
.callout-title {
margin-bottom: 0;
}
.callout-fold svg {
transform: rotate(0deg);
}
}
}
&[data-callout="note"] {
background-color: $callout-bg-blue;
.callout-title {
color: $callout-fg-blue;
}
}
&[data-callout="abstract"] {
background-color: $callout-bg-cyan;
.callout-title {
color: $callout-fg-cyan;
}
}
&[data-callout="info"] {
background-color: $callout-bg-blue;
.callout-title {
color: $callout-fg-blue;
}
}
&[data-callout="todo"] {
background-color: $callout-bg-blue;
.callout-title {
color: $callout-fg-blue;
}
}
&[data-callout="tip"] {
background-color: $callout-bg-cyan;
.callout-title {
color: $callout-fg-cyan;
}
}
&[data-callout="important"] {
background-color: $callout-bg-cyan;
.callout-title {
color: $callout-fg-cyan;
}
}
&[data-callout="success"] {
background-color: $callout-bg-green;
.callout-title {
color: $callout-fg-green;
}
}
&[data-callout="question"] {
background-color: $callout-bg-orange;
.callout-title {
color: $callout-fg-orange;
}
}
&[data-callout="warning"] {
background-color: $callout-bg-orange;
.callout-title {
color: $callout-fg-orange;
}
}
&[data-callout="failure"] {
background-color: $callout-bg-red;
.callout-title {
color: $callout-fg-red;
}
}
&[data-callout="danger"] {
background-color: $callout-bg-red;
.callout-title {
color: $callout-fg-red;
}
}
&[data-callout="bug"] {
background-color: $callout-bg-red;
.callout-title {
color: $callout-fg-red;
}
}
&[data-callout="example"] {
background-color: $callout-bg-purple;
.callout-title {
color: $callout-fg-purple;
}
}
&[data-callout="cite"] {
background-color: $callout-bg-grey;
.callout-title {
color: $callout-fg-grey;
}
}
&[data-callout="update"] {
background-color: $callout-bg-blue;
.callout-title {
color: $callout-fg-blue;
}
}
}
svg.lucide {
width: 16px;
}

View File

@ -1 +1,7 @@
@import "markdown-callouts"; @media (prefers-color-scheme: dark) {
@import "markdown-callouts-dark";
}
@media (prefers-color-scheme: light) {
@import "markdown-callouts-light";
}

View File

@ -0,0 +1,25 @@
$background: #1E1E1E;
$foreground: #DCDCDA;
// C#
$comment: #23A658;
$string: #EA9D78;
$function: #A0D7A7;
$class-name: #13C9C6;
$keyword: #439CE2;
$preprocessor-property: #9B9B99;
// Css
$atrule: $keyword;
$css-foreground: #C8C8C6;
$css-selector: #D8BA76;
$css-property: #67DCFF;
// Markup (HTML, XML)
$doctype: $class-name;
$markup-tag: $keyword;
$attr-name: #87DCFF;
$markup-punctuation: #80807F;
$markup-attr-value: $css-foreground;
@import "_prism-vs";

View File

@ -0,0 +1,25 @@
$background: #FFFFFC;
$foreground:#000;
// C#
$comment: #008016;
$string: #E11500;
$function: #785318;
$class-name: #0491C1;
$keyword: #3200FF;
$preprocessor-property: #80807F;
// Css
$atrule:$keyword;
$css-foreground: #3200FF;
$css-selector: #B80000;
$css-property: $css-selector;
// Markup (HTML, XML)
$doctype: $class-name ;
$markup-tag: $css-selector;
$attr-name: #FF0000;
$markup-punctuation: $keyword;
$markup-attr-value:$keyword;
@import "_prism-vs";

21
src/ts/SiteTheme.ts Normal file
View File

@ -0,0 +1,21 @@
/**
* An enumeration if site themes.
*/
enum SiteTheme {
/**
* Dark mode.
*/
DARK,
/**
* Light mode.
*/
LIGHT,
/**
* Follow system settings.
*/
AUTO
}
export default SiteTheme;

View File

@ -1,4 +1,5 @@
import TimeUtility from "./TimeUtility"; import TimeUtility from "./TimeUtility";
import SiteTheme from "./SiteTheme";
declare const bootstrap: any; declare const bootstrap: any;
declare const katex: any; declare const katex: any;
@ -16,6 +17,68 @@ class UI {
return script; return script;
} }
/**
* Gets the user's current-requested site theme.
*/
public static getSiteTheme(): SiteTheme {
const theme = getCookie("_theme");
switch (theme) {
case "dark":
return SiteTheme.DARK;
case "light":
return SiteTheme.LIGHT;
default:
return SiteTheme.AUTO;
}
function getCookie(name: string): string {
name = `${name}=`;
const decodedCookie = decodeURIComponent(document.cookie);
const cookies = decodedCookie.split(';');
for (let index = 0; index < cookies.length; index++) {
let current = cookies[index];
while (current.charAt(0) == ' ') {
current = current.substring(1);
}
if (current.indexOf(name) == 0) {
return current.substring(name.length, current.length);
}
}
return "";
}
}
/**
* Gets the user's current-requested site theme.
*/
public static setSiteTheme(theme: SiteTheme) {
const cookieName = "_theme";
const expiryDays = 30;
switch (theme) {
case SiteTheme.DARK:
setCookie(cookieName, "dark", expiryDays);
break;
case SiteTheme.LIGHT:
setCookie(cookieName, "light", expiryDays);
break;
case SiteTheme.AUTO:
setCookie(cookieName, "auto", expiryDays);
break;
}
function setCookie(name: string, value: any, expiryDays: number) {
const date = new Date();
date.setTime(date.getTime() + (expiryDays * 24 * 60 * 60 * 1000));
const expires = "expires=" + date.toUTCString();
document.cookie = `${name}=${value};${expires};path=/`;
}
}
/** /**
* Forces all UI elements under the given element to update their rendering. * Forces all UI elements under the given element to update their rendering.
* @param element The element to search for UI elements in. * @param element The element to search for UI elements in.

View File

@ -75,6 +75,10 @@ declare const lucide: any;
const favicon = document.querySelector("link[rel~=\"icon\"]"); const favicon = document.querySelector("link[rel~=\"icon\"]");
// @ts-ignore // @ts-ignore
favicon.href = `/img/${darkMode ? "favicon.png" : "favicon-dark.png"}`; favicon.href = `/img/${darkMode ? "favicon.png" : "favicon-dark.png"}`;
document.querySelector("html").dataset.bsTheme = darkMode ? "dark" : "light";
const siteLogo = document.getElementById("site-logo") as HTMLImageElement;
siteLogo.src = darkMode ? "/img/ob-256x256.png" : "/img/ob-256x256-dark.png";
} }
setFavicon(); setFavicon();