feat: themes

This commit is contained in:
trafficlunar 2025-12-14 22:08:30 +00:00
parent b36ac9523c
commit 0942761cd8
14 changed files with 1133 additions and 50 deletions

View file

@ -1,5 +1,6 @@
# TBA - monochrome status page <h1 align="center">statsys</h1>
TODO: thumbnail TODO: thumbnail
<p style="text-align: center">The cutest lightweight monochrome status page</p> <p align="center">The cutest lightweight status page</p>
<h4 align="center"><a href="https://status.trafficlunar.net">view demo</a> | <a href="#install">install</a></h4>

View file

@ -9,16 +9,24 @@ link_url = "https://trafficlunar.net"
# Users can still switch views manually # Users can still switch views manually
# #
# Available options: # Available options:
# 'minutes' - past 30 minutes # "minutes" - past 30 minutes
# 'hours' - past 24 hours # "hours" - past 24 hours
# 'days' - past 30 days # "days" - past 30 days
default_view = "hours" default_view = "hours"
# Default theme for the status page
# Available themes: https://github.com/trafficlunar/statsys/tree/main/www/themes
# Defaults to "monochrome"
default_theme = "color"
# Set to true to allow users to switch the theme
# Defaults to true
enable_theme_switcher = true
# List of services to monitor # List of services to monitor
[[services]] [[services]]
name = "website" name = "website"
url = "http://localhost:9999/up" url = "http://localhost:9999/up"
# Response time (in milliseconds) after which the service is considered "Degraded" # Response time (milliseconds) after which the service is marked as "Degraded"
# Defaults to 1000ms # Defaults to 1000ms
latency_threshold = 1000 latency_threshold = 1000

View file

@ -14,11 +14,13 @@ type ServiceConfig struct {
} }
type Config struct { type Config struct {
Title string `toml:"title"` Title string `toml:"title"`
LinkText string `toml:"link_text"` LinkText string `toml:"link_text"`
LinkUrl string `toml:"link_url"` LinkUrl string `toml:"link_url"`
DefaultView string `toml:"default_view"` DefaultView string `toml:"default_view"`
Services []ServiceConfig `toml:"services"` DefaultTheme string `toml:"default_theme"`
EnableThemeSwitcher bool `toml:"enable_theme_switcher"`
Services []ServiceConfig `toml:"services"`
} }
var config Config var config Config
@ -36,6 +38,7 @@ func LoadConfig() {
templateData.Title = config.Title templateData.Title = config.Title
templateData.LinkText = config.LinkText templateData.LinkText = config.LinkText
templateData.LinkUrl = config.LinkUrl templateData.LinkUrl = config.LinkUrl
templateData.EnableThemeSwitcher = config.EnableThemeSwitcher
templateData.Services = make([]Service, len(config.Services)) templateData.Services = make([]Service, len(config.Services))
for index, ser := range config.Services { for index, ser := range config.Services {
// default to 1000ms // default to 1000ms

View file

@ -39,13 +39,15 @@ type Service struct {
} }
type TemplateData struct { type TemplateData struct {
Title string Title string
View string View string
LinkText string Theme string
LinkUrl string LinkText string
LastUpdated int64 LinkUrl string
IsOperational bool EnableThemeSwitcher bool
Services []Service LastUpdated int64
IsOperational bool
Services []Service
} }
type ErrorTemplateData struct { type ErrorTemplateData struct {
@ -58,7 +60,8 @@ func ToUpper(s string) string {
} }
var templateData = TemplateData{ var templateData = TemplateData{
LastUpdated: time.Now().UTC().UnixMilli(), EnableThemeSwitcher: true,
LastUpdated: time.Now().UTC().UnixMilli(),
} }
var tmpl *template.Template var tmpl *template.Template
var errorTmpl *template.Template var errorTmpl *template.Template
@ -85,6 +88,7 @@ func index(w http.ResponseWriter, req *http.Request) {
data := templateData data := templateData
data.View = req.URL.Query().Get("view") data.View = req.URL.Query().Get("view")
data.Theme = req.URL.Query().Get("theme")
switch data.View { switch data.View {
case "hours", "minutes", "days": case "hours", "minutes", "days":
@ -96,6 +100,10 @@ func index(w http.ResponseWriter, req *http.Request) {
return return
} }
if data.Theme == "" {
data.Theme = config.DefaultTheme
}
if err := tmpl.ExecuteTemplate(w, "index.html", data); err != nil { if err := tmpl.ExecuteTemplate(w, "index.html", data); err != nil {
log.Printf("template execute error: %v", err) log.Printf("template execute error: %v", err)
renderError(w, http.StatusInternalServerError, "Internal server error") renderError(w, http.StatusInternalServerError, "Internal server error")
@ -139,6 +147,7 @@ func StartHttpServer() {
mux.HandleFunc("/styles.css", styles) mux.HandleFunc("/styles.css", styles)
mux.HandleFunc("/favicon.ico", favicon) mux.HandleFunc("/favicon.ico", favicon)
mux.HandleFunc("/robots.txt", robots) mux.HandleFunc("/robots.txt", robots)
mux.Handle("/themes/", gzhttp.GzipHandler(http.StripPrefix("/themes/", http.FileServer(http.Dir("www/themes/")))))
mux.Handle("/fonts/", http.StripPrefix("/fonts/", http.FileServer(http.Dir("www/fonts/")))) mux.Handle("/fonts/", http.StripPrefix("/fonts/", http.FileServer(http.Dir("www/fonts/"))))
// wrap with gzip middleware // wrap with gzip middleware

View file

@ -13,6 +13,7 @@
} }
main { main {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
@ -103,7 +104,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2.5rem; gap: 2.5rem;
margin-bottom: 4rem; margin-bottom: 5rem;
} }
.info { .info {
@ -150,7 +151,6 @@
display: flex; display: flex;
justify-content: end; justify-content: end;
width: 100%; width: 100%;
/* gap: 0.185rem; */
} }
.bars > div { .bars > div {
@ -169,14 +169,12 @@
transition: transform 100ms ease-in-out; transition: transform 100ms ease-in-out;
position: relative; position: relative;
z-index: 0; z-index: 0;
border-radius: 3px;
} }
.bars > div:hover .status-bar { .bars > div:hover .status-bar {
transform: translateY(-3px); transform: translateY(-3px);
} filter: brightness(0.9);
.bars > div:hover .status-bar.Online {
background-color: rgba(0, 0, 0, 0.7);
} }
.bars > div:hover .status-bar::after { .bars > div:hover .status-bar::after {
@ -208,9 +206,9 @@
} }
.status-bar.Degraded { .status-bar.Degraded {
background-image: linear-gradient(to bottom, transparent 30%, white 30%), linear-gradient(to right, transparent 40%, white 40%); background-color: white;
background-size: 6px 7px; background-image: linear-gradient(45deg, black 25%, transparent 25%, transparent 50%, black 50%, black 75%, transparent 75%, transparent);
background-position: 3px 4px; background-size: 8px 8px;
} }
.status-bar.Offline { .status-bar.Offline {
@ -276,12 +274,12 @@
color: black; color: black;
} }
#combobox { .combobox {
position: relative; position: relative;
width: 8rem; width: 8rem;
} }
#combobox-trigger { .combobox-trigger {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@ -302,24 +300,23 @@
transition: background-color 100ms ease; transition: background-color 100ms ease;
} }
#combobox-trigger:hover { .combobox-trigger:hover {
background-color: rgba(0, 0, 0, 0.03); background-color: rgba(0, 0, 0, 0.03);
} }
#combobox-trigger div { .combobox-trigger div {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-left: 1px solid rgba(0, 0, 0, 0.5); border-left: 1px solid rgba(0, 0, 0, 0.5);
} }
#combobox-trigger.open div svg { .combobox-trigger.open div svg {
transform: rotate(180deg); transform: rotate(180deg);
} }
#combobox-dropdown { .combobox-dropdown {
position: absolute; position: absolute;
bottom: calc(100% + 0.25rem);
width: 100%; width: 100%;
background-color: white; background-color: white;
border: 1px solid black; border: 1px solid black;
@ -327,9 +324,10 @@
flex-direction: column; flex-direction: column;
z-index: 100; z-index: 100;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
} }
#combobox-dropdown.open { .combobox-dropdown.open {
display: flex; display: flex;
} }
@ -338,6 +336,7 @@
cursor: pointer; cursor: pointer;
color: black; color: black;
font-size: 0.825rem; font-size: 0.825rem;
text-decoration: none;
} }
.combobox-option.selected { .combobox-option.selected {
@ -435,6 +434,8 @@
} }
} }
</style> </style>
<link rel="stylesheet" href="/themes/{{ .Theme }}.css" />
</head> </head>
<body> <body>
<main> <main>
@ -443,6 +444,37 @@
</div> </div>
<h1>status — {{ .Title }}</h1> <h1>status — {{ .Title }}</h1>
{{ if .EnableThemeSwitcher }}
<div class="combobox" style="position: absolute; right: 2rem; top: 2rem">
<button class="combobox-trigger">
<span
>{{ if eq .Theme "colored-dark" }} colored dark {{ else if eq .Theme "ctp-latte" }} ctp latte {{ else if eq .Theme "ctp-frappe" }} ctp
frappé {{ else if eq .Theme "ctp-macchiato" }} ctp macchiato {{ else if eq .Theme "ctp-mocha" }} ctp mocha {{ else }} {{ .Theme }} {{ end
}}</span
>
<!-- arrow icon -->
<div>
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m7 10l5 5m0 0l5-5" />
</svg>
</div>
</button>
<div class="combobox-dropdown" style="top: calc(100% + 0.25rem)">
<a href="/?theme=monochrome&view={{ .View }}" class='combobox-option {{ if eq .Theme "monochrome" }}selected{{ end }}'>monochrome</a>
<a href="/?theme=inverted&view={{ .View }}" class='combobox-option {{ if eq .Theme "inverted" }}selected{{ end }}'>inverted</a>
<a href="/?theme=color&view={{ .View }}" class='combobox-option {{ if eq .Theme "color" }}selected{{ end }}'>color</a>
<a href="/?theme=colored-dark&view={{ .View }}" class='combobox-option {{ if eq .Theme "colored-dark" }}selected{{ end }}'>colored dark</a>
<a href="/?theme=ctp-latte&view={{ .View }}" class='combobox-option {{ if eq .Theme "ctp-latte" }}selected{{ end }}'>ctp latte</a>
<a href="/?theme=ctp-frappe&view={{ .View }}" class='combobox-option {{ if eq .Theme "ctp-frappe" }}selected{{ end }}'>ctp frappé</a>
<a href="/?theme=ctp-macchiato&view={{ .View }}" class='combobox-option {{ if eq .Theme "ctp-macchiato" }}selected{{ end }}'
>ctp macchiato</a
>
<a href="/?theme=ctp-mocha&view={{ .View }}" class='combobox-option {{ if eq .Theme "ctp-mocha" }}selected{{ end }}'>ctp mocha</a>
</div>
</div>
{{ end }}
<div style="display: flex"> <div style="display: flex">
<div id="overall-status"> <div id="overall-status">
<span style="font-weight: 500; margin-right: 0.15rem">{{ if .IsOperational }} ✔ {{ else }} ✖ {{ end }}</span> <span style="font-weight: 500; margin-right: 0.15rem">{{ if .IsOperational }} ✔ {{ else }} ✖ {{ end }}</span>
@ -465,7 +497,7 @@
<span>Degraded</span> <span>Degraded</span>
</div> </div>
<div class="legend-item"> <div class="legend-item">
<div class="status-bar"></div> <div class="status-bar Online"></div>
<span>Online</span> <span>Online</span>
</div> </div>
</div> </div>
@ -555,8 +587,8 @@
</div> </div>
<footer> <footer>
<div id="combobox"> <div class="combobox">
<button id="combobox-trigger"> <button class="combobox-trigger">
<span>{{ if eq .View "minutes" }} 30 minutes {{ else if eq .View "hours" }} 24 hours {{ else }} 30 days {{ end }}</span> <span>{{ if eq .View "minutes" }} 30 minutes {{ else if eq .View "hours" }} 24 hours {{ else }} 30 days {{ end }}</span>
<!-- arrow icon --> <!-- arrow icon -->
<div> <div>
@ -566,10 +598,10 @@
</div> </div>
</button> </button>
<div id="combobox-dropdown"> <div class="combobox-dropdown" style="bottom: calc(100% + 0.25rem)">
<a href="/?view=minutes" class='combobox-option {{ if eq .View "minutes" }}selected{{ end }}' data-value="minutes">30 minutes</a> <a href="/?view=minutes&theme={{ .Theme }}" class='combobox-option {{ if eq .View "minutes" }}selected{{ end }}'>30 minutes</a>
<a href="/?view=hours" class='combobox-option {{ if eq .View "hours" }}selected{{ end }}' data-value="hours">24 hours</a> <a href="/?view=hours&theme={{ .Theme }}" class='combobox-option {{ if eq .View "hours" }}selected{{ end }}'>24 hours</a>
<a href="/?view=days" class='combobox-option {{ if eq .View "days" }}selected{{ end }}' data-value="days">30 days</a> <a href="/?view=days&theme={{ .Theme }}" class='combobox-option {{ if eq .View "days" }}selected{{ end }}'>30 days</a>
</div> </div>
</div> </div>
@ -601,12 +633,16 @@
setInterval(update, 60000); setInterval(update, 60000);
// combobox // combobox
const trigger = document.querySelector("#combobox-trigger"); const triggers = document.querySelectorAll(".combobox-trigger");
const dropdown = document.querySelector("#combobox-dropdown");
trigger.addEventListener("click", (e) => { triggers.forEach((trigger) => {
trigger.classList.toggle("open"); trigger.addEventListener("click", () => {
dropdown.classList.toggle("open"); const parent = trigger.parentElement;
const dropdown = parent.querySelector(".combobox-dropdown");
trigger.classList.toggle("open");
dropdown.classList.toggle("open");
});
}); });
</script> </script>
</body> </body>

View file

@ -62,7 +62,7 @@
} }
body { body {
background-color: #fcfcfc; background-color: #fafafa;
font-family: "Inter", sans-serif; font-family: "Inter", sans-serif;
height: 100vh; height: 100vh;
margin: 0; margin: 0;
@ -74,7 +74,7 @@ body::before {
content: ""; content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.04) 1px, transparent 1px), linear-gradient(rgba(0, 0, 0, 0.04) 1px, transparent 1px); background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.05) 1px, transparent 1px), linear-gradient(rgba(0, 0, 0, 0.05) 1px, transparent 1px);
background-size: 48px 48px; background-size: 48px 48px;
background-position: center; background-position: center;
pointer-events: none; pointer-events: none;

146
www/themes/color-dark.css Normal file
View file

@ -0,0 +1,146 @@
/* Colored Dark Mode Theme */
body {
background-color: #0a0f1a;
color: #e0e7ff;
}
/* Grid */
body::before {
background-image: linear-gradient(90deg, rgba(59, 130, 246, 0.08) 1px, transparent 1px),
linear-gradient(rgba(59, 130, 246, 0.08) 1px, transparent 1px);
}
main {
background-color: #111827;
outline: 1px solid rgba(59, 130, 246, 0.15);
}
hr {
border-bottom: 2px dotted rgba(224, 231, 255, 0.2);
}
#emoticon {
color: #60a5fa;
}
#overall-status {
background-color: #1e40af;
color: #e0e7ff;
}
#pattern {
background-image: repeating-linear-gradient(-45deg, #1e40af, #1e40af 10px, transparent 0px, transparent 20px);
}
#last-updated {
color: rgba(224, 231, 255, 0.7);
}
.service-name {
color: #e2e8f0;
}
.service-name svg {
color: rgba(59, 130, 246, 0.4);
}
.service-name:hover svg {
color: rgba(96, 165, 250, 0.9);
}
/* Tooltip */
.bars > div:hover .status-bar::after {
background-color: #1e293b;
color: #f1f5f9;
border: 1px solid rgba(38, 89, 172, 0.2);
}
.status-bar.Online {
background-color: #10b981;
border-color: #059669;
}
.status-bar.Unknown {
background: radial-gradient(circle at center, #475569 3px, transparent 3px);
}
.status-bar.Degraded {
background-color: #f59e0b;
border-color: #d97706;
background-image: linear-gradient(
45deg,
rgba(0, 0, 0, 0.2) 25%,
transparent 25%,
transparent 50%,
rgba(0, 0, 0, 0.2) 50%,
rgba(0, 0, 0, 0.2) 75%,
transparent 75%,
transparent
);
background-size: 8px 8px;
}
.status-bar.Offline {
background-color: #dc2626;
border-color: #b91c1c;
}
.status-bar.Offline::before {
color: #fef2f2;
}
.bars-footer {
color: rgba(224, 231, 255, 0.6);
}
.bars-footer hr {
border-color: rgba(224, 231, 255, 0.15);
}
.uptime-percentage {
background-color: #111827;
color: rgba(96, 165, 250, 0.8);
}
footer a {
color: #60a5fa;
}
footer a:hover {
color: #93c5fd;
}
/* Combobox */
.combobox-trigger {
color: #e0e7ff;
background-color: #1e293b;
border: 1px solid rgba(71, 85, 105, 0.6);
}
.combobox-trigger:hover {
background-color: #334155;
border-color: rgba(96, 165, 250, 0.5);
}
.combobox-trigger div {
border-left: 1px solid rgba(71, 85, 105, 0.6);
}
.combobox-dropdown {
background-color: #1e293b;
border: 1px solid rgba(59, 130, 246, 0.4);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
}
.combobox-option {
color: #e2e8f0;
}
.combobox-option.selected {
background-color: rgba(59, 130, 246, 0.15);
}
.combobox-option:hover {
background-color: #2563eb;
color: white;
}

142
www/themes/color.css Normal file
View file

@ -0,0 +1,142 @@
/* Colored Theme */
body {
background-color: rgba(37, 99, 235, 0.04);
}
body::before {
background-image: linear-gradient(90deg, rgba(37, 99, 235, 0.1) 1px, transparent 1px), linear-gradient(rgba(37, 99, 235, 0.1) 1px, transparent 1px);
}
main {
background-color: #f8fafc;
outline: 1px solid rgba(37, 99, 235, 0.1);
}
hr {
border-bottom: 2px dotted rgba(15, 23, 42, 0.15);
}
#emoticon {
color: #2563eb;
}
#overall-status {
background-color: #2563eb;
color: white;
}
#pattern {
background-image: repeating-linear-gradient(-45deg, #2563eb, #2563eb 10px, transparent 0px, transparent 20px);
}
#last-updated {
color: rgba(15, 23, 42, 0.6);
}
.service-name {
color: #0f172a;
}
.service-name svg {
color: rgba(37, 99, 235, 0.4);
}
.service-name:hover svg {
color: rgba(37, 99, 235, 0.9);
}
.bars > div:hover .status-bar::after {
background-color: #0f172a;
color: white;
}
.status-bar.Online {
background-color: #22c55e;
border-color: #16a34a;
}
.bars > div:hover .status-bar.Online {
background-color: #16a34a;
}
.status-bar.Degraded {
background-color: #facc15;
border-color: #eab308;
background-image: linear-gradient(
45deg,
rgba(255, 255, 255, 0.4) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.4) 50%,
rgba(255, 255, 255, 0.4) 75%,
transparent 75%,
transparent
);
background-size: 8px 8px;
}
.status-bar.Offline {
background-color: #ef4444;
border-color: #dc2626;
}
.status-bar.Offline::before {
color: white;
}
.status-bar.Unknown {
background: radial-gradient(circle at center, #94a3b8 3px, transparent 3px);
}
.bars-footer {
color: rgba(15, 23, 42, 0.45);
}
.bars-footer hr {
border-color: rgba(15, 23, 42, 0.15);
}
.uptime-percentage {
background-color: #f8fafc;
color: rgba(37, 99, 235, 0.75);
}
footer a {
color: #2563eb;
}
footer a:hover {
color: #1e40af;
}
.combobox-trigger {
background-color: white;
border: 1px solid rgba(15, 23, 42, 0.4);
}
.combobox-trigger:hover {
background-color: rgba(37, 99, 235, 0.05);
}
.combobox-trigger div {
border-left: 1px solid rgba(15, 23, 42, 0.4);
}
.combobox-dropdown {
background-color: white;
border: 1px solid rgba(15, 23, 42, 0.4);
}
.combobox-option {
color: #0f172a;
}
.combobox-option.selected {
background-color: rgba(37, 99, 235, 0.12);
}
.combobox-option:hover {
background-color: #2563eb;
color: white;
}

151
www/themes/ctp-frappe.css Normal file
View file

@ -0,0 +1,151 @@
/* Catppuccin Frappé Theme */
body {
background-color: #303446; /* Base */
color: #c6d0f5; /* Text */
}
/* Grid */
body::before {
background-image: linear-gradient(90deg, rgba(140, 170, 238, 0.08) 1px, transparent 1px),
linear-gradient(rgba(140, 170, 238, 0.08) 1px, transparent 1px);
}
main {
background-color: #292c3c; /* Mantle */
outline: 1px solid rgba(140, 170, 238, 0.12);
}
hr {
border-bottom: 2px dotted rgba(140, 170, 238, 0.4); /* Blue */
}
#emoticon {
color: #8caaee; /* Blue */
}
#overall-status {
background-color: #8caaee; /* Blue */
color: #292c3c; /* Mantle */
}
#pattern {
background-image: repeating-linear-gradient(-45deg, #8caaee, #8caaee 10px, transparent 0px, transparent 20px);
}
#last-updated {
color: rgba(165, 173, 206, 0.6); /* Overlay2 */
}
.service-name {
color: #c6d0f5; /* Text */
}
.service-name svg {
color: rgba(140, 170, 238, 0.4); /* Blue faded */
}
.service-name:hover svg {
color: #8caaee; /* Blue */
}
/* Tooltip */
.bars > div:hover .status-bar::after {
background-color: #292c3c; /* Crust */
color: #c6d0f5; /* Text */
border: 1px solid rgba(140, 170, 238, 0.3);
}
.status-bar.Online {
background-color: #a6d189; /* Green */
border-color: #a6d189; /* Green */
}
.bars > div:hover .status-bar.Online {
background-color: #81c563;
}
.status-bar.Unknown {
background: radial-gradient(circle at center, #414559 3px, transparent 3px); /* Surface0 */
}
.status-bar.Degraded {
background-color: #e5c890; /* Yellow */
border-color: #e5c890; /* Yellow */
background-image: linear-gradient(
45deg,
rgba(35, 38, 52, 0.2) 25%,
transparent 25%,
transparent 50%,
rgba(35, 38, 52, 0.2) 50%,
rgba(35, 38, 52, 0.2) 75%,
transparent 75%,
transparent
);
background-size: 8px 8px;
}
.status-bar.Offline {
background-color: #e78284; /* Red */
border-color: #e78284; /* Red */
}
.status-bar.Offline::before {
color: #292c3c; /* Mantle */
}
.bars-footer {
color: rgba(140, 170, 238, 0.5); /* Blue */
}
.bars-footer hr {
border-color: rgba(140, 170, 238, 0.2); /* Blue */
}
.uptime-percentage {
background-color: #292c3c; /* Mantle */
color: rgba(140, 170, 238, 0.85); /* Blue */
}
footer a {
color: #8caaee; /* Blue */
}
footer a:hover {
color: #8caaee; /* Blue */
filter: brightness(0.8);
}
/* Combobox */
.combobox-trigger {
background-color: #232634; /* Crust */
color: #c6d0f5; /* Text */
border: 1px solid rgba(98, 104, 128, 0.6); /* Overlay1 */
}
.combobox-trigger:hover {
background-color: #292c3c; /* Mantle */
border-color: rgba(140, 170, 238, 0.5);
}
.combobox-trigger div {
border-left: 1px solid rgba(98, 104, 128, 0.6);
}
.combobox-dropdown {
background-color: #232634; /* Crust */
border: 1px solid rgba(140, 170, 238, 0.4);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
}
.combobox-option {
color: #c6d0f5; /* Text */
}
.combobox-option.selected {
background-color: rgba(140, 170, 238, 0.15);
}
.combobox-option:hover {
background-color: #8caaee; /* Blue */
color: #292c3c; /* Mantle */
}

151
www/themes/ctp-latte.css Normal file
View file

@ -0,0 +1,151 @@
/* Catppuccin Latte Theme */
body {
background-color: #eff1f5; /* Base */
color: #4c4f69; /* Text */
}
/* Grid */
body::before {
background-image: linear-gradient(90deg, rgba(30, 102, 245, 0.08) 1px, transparent 1px),
linear-gradient(rgba(30, 102, 245, 0.08) 1px, transparent 1px);
}
main {
background-color: #e6e9ef; /* Mantle */
outline: 1px solid rgba(30, 102, 245, 0.12);
}
hr {
border-bottom: 2px dotted rgba(30, 102, 245, 0.4); /* Blue */
}
#emoticon {
color: #1e66f5; /* Blue */
}
#overall-status {
background-color: #1e66f5; /* Blue */
color: #e6e9ef; /* Mantle */
}
#pattern {
background-image: repeating-linear-gradient(-45deg, #1e66f5, #1e66f5 10px, transparent 0px, transparent 20px);
}
#last-updated {
color: rgba(140, 143, 161, 1); /* Overlay2 */
}
.service-name {
color: #4c4f69; /* Text */
}
.service-name svg {
color: rgba(30, 102, 245, 0.4); /* Blue faded */
}
.service-name:hover svg {
color: #1e66f5; /* Blue */
}
/* Tooltip */
.bars > div:hover .status-bar::after {
background-color: #e6e9ef; /* Mantle */
color: #4c4f69; /* Text */
border: 1px solid rgba(30, 102, 245, 0.3);
}
.status-bar.Online {
background-color: #40a02b; /* Green */
border-color: #40a02b; /* Green */
}
.bars > div:hover .status-bar.Online {
background-color: #357a22;
}
.status-bar.Unknown {
background: radial-gradient(circle at center, #ccd0da 3px, transparent 3px); /* Surface0 */
}
.status-bar.Degraded {
background-color: #df8e1d; /* Yellow */
border-color: #df8e1d; /* Yellow */
background-image: linear-gradient(
45deg,
rgba(230, 233, 239, 0.3) 25%,
transparent 25%,
transparent 50%,
rgba(230, 233, 239, 0.3) 50%,
rgba(230, 233, 239, 0.3) 75%,
transparent 75%,
transparent
);
background-size: 8px 8px;
}
.status-bar.Offline {
background-color: #d20f39; /* Red */
border-color: #d20f39; /* Red */
}
.status-bar.Offline::before {
color: #e6e9ef; /* Mantle */
}
.bars-footer {
color: rgba(30, 102, 245, 0.6); /* Blue */
}
.bars-footer hr {
border-color: rgba(30, 102, 245, 0.2); /* Blue */
}
.uptime-percentage {
background-color: #e6e9ef; /* Mantle */
color: rgba(30, 102, 245, 0.85); /* Blue */
}
footer a {
color: #1e66f5; /* Blue */
}
footer a:hover {
color: #1e66f5; /* Blue */
filter: brightness(0.8);
}
/* Combobox */
.combobox-trigger {
background-color: #dce0e8; /* Crust */
color: #4c4f69; /* Text */
border: 1px solid rgba(156, 160, 176, 0.6); /* Overlay1 */
}
.combobox-trigger:hover {
background-color: #d0d4db;
border-color: rgba(30, 102, 245, 0.5);
}
.combobox-trigger div {
border-left: 1px solid rgba(156, 160, 176, 0.6);
}
.combobox-dropdown {
background-color: #dce0e8; /* Crust */
border: 1px solid rgba(30, 102, 245, 0.4);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.combobox-option {
color: #4c4f69; /* Text */
}
.combobox-option.selected {
background-color: rgba(30, 102, 245, 0.15);
}
.combobox-option:hover {
background-color: #1e66f5; /* Blue */
color: white;
}

View file

@ -0,0 +1,151 @@
/* Catppuccin Macchiato Theme */
body {
background-color: #24273a; /* Base */
color: #cad3f5; /* Text */
}
/* Grid */
body::before {
background-image: linear-gradient(90deg, rgba(138, 173, 244, 0.08) 1px, transparent 1px),
linear-gradient(rgba(138, 173, 244, 0.08) 1px, transparent 1px);
}
main {
background-color: #1e2030; /* Mantle */
outline: 1px solid rgba(138, 173, 244, 0.12);
}
hr {
border-bottom: 2px dotted rgba(138, 173, 244, 0.4); /* Blue */
}
#emoticon {
color: #8aadf4; /* Blue */
}
#overall-status {
background-color: #8aadf4; /* Blue */
color: #1e2030; /* Mantle */
}
#pattern {
background-image: repeating-linear-gradient(-45deg, #8aadf4, #8aadf4 10px, transparent 0px, transparent 20px);
}
#last-updated {
color: rgba(165, 173, 203, 0.6); /* Overlay2 */
}
.service-name {
color: #cad3f5; /* Text */
}
.service-name svg {
color: rgba(138, 173, 244, 0.4); /* Blue faded */
}
.service-name:hover svg {
color: #8aadf4; /* Blue */
}
/* Tooltip */
.bars > div:hover .status-bar::after {
background-color: #1e2030; /* Mantle */
color: #cad3f5; /* Text */
border: 1px solid rgba(138, 173, 244, 0.3);
}
.status-bar.Online {
background-color: #a6da95; /* Green */
border-color: #a6da95; /* Green */
}
.bars > div:hover .status-bar.Online {
background-color: #8bd074;
}
.status-bar.Unknown {
background: radial-gradient(circle at center, #363a4f 3px, transparent 3px); /* Surface0 */
}
.status-bar.Degraded {
background-color: #eed49f; /* Yellow */
border-color: #eed49f; /* Yellow */
background-image: linear-gradient(
45deg,
rgba(30, 32, 48, 0.2) 25%,
transparent 25%,
transparent 50%,
rgba(30, 32, 48, 0.2) 50%,
rgba(30, 32, 48, 0.2) 75%,
transparent 75%,
transparent
);
background-size: 8px 8px;
}
.status-bar.Offline {
background-color: #ed8796; /* Red */
border-color: #ed8796; /* Red */
}
.status-bar.Offline::before {
color: #1e2030; /* Mantle */
}
.bars-footer {
color: rgba(138, 173, 244, 0.5); /* Blue */
}
.bars-footer hr {
border-color: rgba(138, 173, 244, 0.2); /* Blue */
}
.uptime-percentage {
background-color: #1e2030; /* Mantle */
color: rgba(138, 173, 244, 0.85); /* Blue */
}
footer a {
color: #8aadf4; /* Blue */
}
footer a:hover {
color: #8aadf4; /* Blue */
filter: brightness(0.8);
}
/* Combobox */
.combobox-trigger {
background-color: #181926; /* Crust */
color: #cad3f5; /* Text */
border: 1px solid rgba(91, 96, 120, 0.6); /* Overlay1 */
}
.combobox-trigger:hover {
background-color: #1e2030; /* Mantle */
border-color: rgba(138, 173, 244, 0.5);
}
.combobox-trigger div {
border-left: 1px solid rgba(91, 96, 120, 0.6);
}
.combobox-dropdown {
background-color: #181926; /* Crust */
border: 1px solid rgba(138, 173, 244, 0.4);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
}
.combobox-option {
color: #cad3f5; /* Text */
}
.combobox-option.selected {
background-color: rgba(138, 173, 244, 0.15);
}
.combobox-option:hover {
background-color: #8aadf4; /* Blue */
color: #1e2030; /* Mantle */
}

151
www/themes/ctp-mocha.css Normal file
View file

@ -0,0 +1,151 @@
/* Catppuccin Mocha Theme */
body {
background-color: #1e1e2e; /* Base */
color: #cdd6f4; /* Text */
}
/* Grid */
body::before {
background-image: linear-gradient(90deg, rgba(137, 180, 250, 0.08) 1px, transparent 1px),
linear-gradient(rgba(137, 180, 250, 0.08) 1px, transparent 1px);
}
main {
background-color: #181825; /* Mantle */
outline: 1px solid rgba(137, 180, 250, 0.12);
}
hr {
border-bottom: 2px dotted rgba(137, 180, 250, 0.4); /* Blue */
}
#emoticon {
color: #89b4fa; /* Blue */
}
#overall-status {
background-color: #89b4fa; /* Blue */
color: #181825; /* Mantle */
}
#pattern {
background-image: repeating-linear-gradient(-45deg, #89b4fa, #89b4fa 10px, transparent 0px, transparent 20px);
}
#last-updated {
color: rgba(166, 173, 200, 0.6); /* Overlay2 */
}
.service-name {
color: #cdd6f4; /* Text */
}
.service-name svg {
color: rgba(137, 180, 250, 0.4); /* Blue faded */
}
.service-name:hover svg {
color: #89b4fa; /* Blue */
}
/* Tooltip */
.bars > div:hover .status-bar::after {
background-color: #181825; /* Mantle */
color: #cdd6f4; /* Text */
border: 1px solid rgba(137, 180, 250, 0.3);
}
.status-bar.Online {
background-color: #a6e3a1; /* Green */
border-color: #a6e3a1; /* Green */
}
.bars > div:hover .status-bar.Online {
background-color: #8ad882;
}
.status-bar.Unknown {
background: radial-gradient(circle at center, #313244 3px, transparent 3px); /* Surface0 */
}
.status-bar.Degraded {
background-color: #f9e2af; /* Yellow */
border-color: #f9e2af; /* Yellow */
background-image: linear-gradient(
45deg,
rgba(24, 24, 37, 0.2) 25%,
transparent 25%,
transparent 50%,
rgba(24, 24, 37, 0.2) 50%,
rgba(24, 24, 37, 0.2) 75%,
transparent 75%,
transparent
);
background-size: 8px 8px;
}
.status-bar.Offline {
background-color: #f38ba8; /* Red */
border-color: #f38ba8; /* Red */
}
.status-bar.Offline::before {
color: #181825; /* Mantle */
}
.bars-footer {
color: rgba(137, 180, 250, 0.5); /* Blue */
}
.bars-footer hr {
border-color: rgba(137, 180, 250, 0.2); /* Blue */
}
.uptime-percentage {
background-color: #181825; /* Mantle */
color: rgba(137, 180, 250, 0.85); /* Blue */
}
footer a {
color: #89b4fa; /* Blue */
}
footer a:hover {
color: #89b4fa; /* Blue */
filter: brightness(0.8);
}
/* Combobox */
.combobox-trigger {
background-color: #11111b; /* Crust */
color: #cdd6f4; /* Text */
border: 1px solid rgba(88, 91, 112, 0.6); /* Overlay1 */
}
.combobox-trigger:hover {
background-color: #181825; /* Mantle */
border-color: rgba(137, 180, 250, 0.5);
}
.combobox-trigger div {
border-left: 1px solid rgba(88, 91, 112, 0.6);
}
.combobox-dropdown {
background-color: #11111b; /* Crust */
border: 1px solid rgba(137, 180, 250, 0.4);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
}
.combobox-option {
color: #cdd6f4; /* Text */
}
.combobox-option.selected {
background-color: rgba(137, 180, 250, 0.15);
}
.combobox-option:hover {
background-color: #89b4fa; /* Blue */
color: #181825; /* Mantle */
}

4
www/themes/inverted.css Normal file
View file

@ -0,0 +1,4 @@
/* Dark mode (inverted) theme */
html {
filter: invert();
}

130
www/themes/monochrome.css Normal file
View file

@ -0,0 +1,130 @@
/* Default Theme */
body {
background-color: #fcfcfc;
}
/* Grid */
body::before {
background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.04) 1px, transparent 1px), linear-gradient(rgba(0, 0, 0, 0.04) 1px, transparent 1px);
}
main {
background-color: white;
outline: 1px solid rgba(0, 0, 0, 0.04);
}
hr {
border-bottom: 2px dotted rgba(0, 0, 0, 0.1);
}
#overall-status {
background-color: black;
color: white;
}
#pattern {
background-image: repeating-linear-gradient(-45deg, #000, #000 10px, transparent 0px, transparent 20px);
}
#last-updated {
color: rgba(0, 0, 0, 0.6);
}
.service-name {
color: black;
}
.service-name svg {
color: rgba(0, 0, 0, 0.2);
}
.service-name:hover svg {
color: rgba(0, 0, 0, 0.8);
}
.status-bar {
background-color: black;
border: 1px solid black;
}
/* Tooltip */
.bars > div:hover .status-bar::after {
background-color: black;
color: white;
}
.bars > div:hover .status-bar.Online {
background-color: rgba(0, 0, 0, 0.7);
}
.status-bar.Unknown {
background-color: transparent;
background: radial-gradient(circle at center, rgb(235, 235, 235) 3px, transparent 3px);
}
.status-bar.Degraded {
background-image: linear-gradient(to bottom, transparent 30%, white 30%), linear-gradient(to right, transparent 40%, white 40%);
background-size: 6px 7px;
background-position: 3px 4px;
}
.status-bar.Offline {
background-color: white;
}
.status-bar.Offline::before {
color: black;
}
.bars-footer {
color: rgba(0, 0, 0, 0.4);
}
.bars-footer hr {
border-color: rgba(0, 0, 0, 0.1);
}
.uptime-percentage {
background-color: white;
color: rgba(0, 0, 0, 0.5);
}
footer a {
color: rgba(0, 0, 0, 0.3);
}
footer a:hover {
color: black;
}
/* Combobox */
.combobox-trigger {
background-color: white;
border: 1px solid rgba(0, 0, 0, 0.5);
}
.combobox-trigger:hover {
background-color: rgba(0, 0, 0, 0.03);
}
.combobox-trigger div {
border-left: 1px solid rgba(0, 0, 0, 0.5);
}
.combobox-dropdown {
background-color: white;
border: 1px solid black;
}
.combobox-option {
color: black;
}
.combobox-option.selected {
background-color: rgba(0, 0, 0, 0.08);
}
.combobox-option:hover {
background-color: black;
color: white;
}