Add theme toggle for templates

This commit is contained in:
thiloho
2024-10-03 18:51:30 +02:00
parent 6c314970bd
commit f2d114dac4
22 changed files with 366 additions and 123 deletions

View File

@@ -23,7 +23,10 @@
in
{
api = pkgs.mkShell {
packages = with pkgs; [ postgresql_16 ];
packages = with pkgs; [
postgresql_16
postgrest
];
shellHook = ''
alias dbmate="${pkgs.dbmate}/bin/dbmate --no-dump-schema --url postgres://postgres@localhost:15432/archtika?sslmode=disable"
alias formatsql="${pkgs.pgformatter}/bin/pg_format -s 2 -f 2 -U 2 -i db/migrations/*.sql"

View File

@@ -24,6 +24,8 @@
virtualisation = {
graphics = false;
memorySize = 2048;
cores = 2;
sharedDirectories = {
websites = {
source = "/var/www/archtika-websites";
@@ -59,6 +61,11 @@
};
nginx = {
enable = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
recommendedZstdSettings = true;
recommendedOptimisation = true;
virtualHosts."_" = {
listen = [
{
@@ -67,13 +74,15 @@
}
];
locations = {
"/previews/" = {
alias = "/var/www/archtika-websites/previews/";
index = "index.html";
tryFiles = "$uri $uri/ $uri.html =404";
};
"/" = {
root = "/var/www/archtika-websites";
index = "index.html";
tryFiles = "$uri $uri/ $uri.html =404";
extraConfig = ''
autoindex on;
'';
};
};
};

View File

@@ -105,6 +105,7 @@ in
User = cfg.user;
Group = cfg.group;
Restart = "always";
WorkingDirectory = "${cfg.package}/rest-api";
};
script = ''

View File

@@ -37,8 +37,7 @@ CREATE TABLE internal.website (
is_published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user (id) ON DELETE SET NULL,
title_search TSVECTOR GENERATED ALWAYS AS (TO_TSVECTOR('english', title)) STORED
last_modified_by UUID REFERENCES internal.user (id) ON DELETE SET NULL
);
CREATE TABLE internal.media (
@@ -107,7 +106,6 @@ CREATE TABLE internal.article (
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user (id) ON DELETE SET NULL,
title_description_search TSVECTOR GENERATED ALWAYS AS (TO_TSVECTOR('english', COALESCE(title, '') || ' ' || COALESCE(meta_description, ''))) STORED,
UNIQUE (website_id, category, article_weight)
);

View File

@@ -1,5 +1,5 @@
-- migrate:up
CREATE FUNCTION pgrst_watch ()
CREATE FUNCTION internal.pgrst_watch ()
RETURNS EVENT_TRIGGER
AS $$
BEGIN
@@ -10,10 +10,10 @@ $$
LANGUAGE plpgsql;
CREATE EVENT TRIGGER pgrst_watch ON ddl_command_end
EXECUTE FUNCTION pgrst_watch ();
EXECUTE FUNCTION internal.pgrst_watch ();
-- migrate:down
DROP EVENT TRIGGER pgrst_watch;
DROP FUNCTION pgrst_watch ();
DROP FUNCTION internal.pgrst_watch ();

View File

@@ -48,7 +48,7 @@ CREATE FUNCTION internal.user_role (username TEXT, pass TEXT, OUT role_name NAME
AS $$
BEGIN
SELECT
ROLE INTO role_name
u.role INTO role_name
FROM
internal.user AS u
WHERE
@@ -111,7 +111,7 @@ AS $$
DECLARE
_role NAME;
_user_id UUID;
_exp INTEGER;
_exp INTEGER := EXTRACT(EPOCH FROM CLOCK_TIMESTAMP())::INTEGER + 86400;
BEGIN
SELECT
internal.user_role (login.username, login.pass) INTO _role;
@@ -120,12 +120,11 @@ BEGIN
USING message = 'Invalid username or password';
ELSE
SELECT
id INTO _user_id
u.id INTO _user_id
FROM
internal.user AS u
WHERE
u.username = login.username;
_exp := EXTRACT(EPOCH FROM CLOCK_TIMESTAMP())::INTEGER + 86400;
SELECT
SIGN(JSON_BUILD_OBJECT('role', _role, 'user_id', _user_id, 'username', login.username, 'exp', _exp), CURRENT_SETTING('app.jwt_secret')) INTO token;
END IF;

View File

@@ -99,17 +99,7 @@ BEGIN
INSERT INTO internal.home (website_id, main_content)
VALUES (_website_id, '## About
archtika is a FLOSS, modern, performant and lightweight CMS (Content Mangement System) in the form of a web application. It allows you to easily create, manage and publish minimal, responsive and SEO friendly blogging and documentation websites with official, professionally designed templates.
It is also possible to add contributors to your sites, which is very useful for larger projects where, for example, several people are constantly working on the documentation.
## How it works
For the backend, PostgreSQL is used in combination with PostgREST to create a RESTful API. JSON web tokens along with row-level security control authentication and authorisation flows.
The web application uses SvelteKit with SSR (Server Side Rendering) and Svelte version 5, currently in beta.
NGINX is used to deploy the websites, serving the static site files from the `/var/www/archtika-websites` directory. The static files can be found in this directory via the path `<user_id>/<website_id>`, which is dynamically created by the web application.');
archtika is a FLOSS, modern, performant and lightweight CMS (Content Mangement System) in the form of a web application. It allows you to easily create, manage and publish minimal, responsive and SEO friendly blogging and documentation websites with official, professionally designed templates. It is also possible to add contributors to your sites, which is very useful for larger projects where, for example, several people are constantly working on the documentation.');
INSERT INTO internal.footer (website_id, additional_text)
VALUES (_website_id, 'archtika is a free, open, modern, performant and lightweight CMS');
website_id := _website_id;

View File

@@ -7,11 +7,11 @@ DECLARE
BEGIN
IF (NOT EXISTS (
SELECT
id
u.id
FROM
internal.user
internal.user AS u
WHERE
id = _user_id)) THEN
u.id = _user_id)) THEN
RETURN COALESCE(NEW, OLD);
END IF;
IF TG_OP != 'DELETE' THEN

View File

@@ -21,7 +21,7 @@ BEGIN
SELECT
UNNEST(_allowed_mimetypes))) THEN
RAISE invalid_parameter_value
USING message = 'Invalid MIME type. Allowed types are: png, jpg, webp';
USING message = 'Invalid MIME type. Allowed types are: png, jpg, webp, avif, gif, svg';
ELSIF OCTET_LENGTH($1) > _max_file_size THEN
RAISE program_limit_exceeded
USING message = FORMAT('File size exceeds the maximum limit of %s MB', _max_file_size / (1024 * 1024));
@@ -56,7 +56,7 @@ BEGIN
SELECT
m.blob
FROM
internal.media m
internal.media AS m
WHERE
m.id = retrieve_file.id INTO _blob;
IF FOUND THEN

View File

@@ -22,11 +22,11 @@ DECLARE
BEGIN
IF (NOT EXISTS (
SELECT
id
u.id
FROM
internal.user
internal.user AS u
WHERE
id = _user_id) OR (to_jsonb (OLD.*) - 'last_modified_at' - 'last_modified_by') = (to_jsonb (NEW.*) - 'last_modified_at' - 'last_modified_by')) THEN
u.id = _user_id) OR (to_jsonb (OLD.*) - 'last_modified_at' - 'last_modified_by') = (to_jsonb (NEW.*) - 'last_modified_at' - 'last_modified_by')) THEN
RETURN NULL;
END IF;
IF TG_TABLE_NAME = 'website' THEN
@@ -40,21 +40,21 @@ BEGIN
ELSIF (TG_OP = 'UPDATE'
AND EXISTS (
SELECT
id
w.id
FROM
internal.website
internal.website AS w
WHERE
id = _website_id)) THEN
w.id = _website_id)) THEN
INSERT INTO internal.change_log (website_id, table_name, operation, old_value, new_value)
VALUES (_website_id, TG_TABLE_NAME, TG_OP, HSTORE (OLD) - HSTORE (NEW), HSTORE (NEW) - HSTORE (OLD));
ELSIF (TG_OP = 'DELETE'
AND EXISTS (
SELECT
id
w.id
FROM
internal.website
internal.website AS w
WHERE
id = _website_id)) THEN
w.id = _website_id)) THEN
INSERT INTO internal.change_log (website_id, table_name, operation, old_value)
VALUES (_website_id, TG_TABLE_NAME, TG_OP, HSTORE (OLD));
END IF;

View File

@@ -24,6 +24,16 @@
const scrollHeight = previewElement.scrollHeight - previewElement.clientHeight;
previewElement.scrollTop = (textareaScrollTop.value / 100) * scrollHeight;
});
const tabs = [
"settings",
"articles",
"categories",
"collaborators",
"legal-information",
"publish",
"logs"
];
</script>
<input type="checkbox" id="toggle-mobile-preview" hidden />
@@ -34,27 +44,17 @@
<nav class="operations__nav">
<ul class="unpadded">
<li>
<a href="/website/{id}">Settings</a>
</li>
<li>
<a href="/website/{id}/articles">Articles</a>
</li>
{#if contentType === "Docs"}
<a href="/website/{id}/categories">Categories</a>
{/if}
<li>
<a href="/website/{id}/collaborators">Collaborators</a>
</li>
<li>
<a href="/website/{id}/legal-information">Legal information</a>
</li>
<li>
<a href="/website/{id}/publish">Publish</a>
</li>
<li>
<a href="/website/{id}/logs">Logs</a>
</li>
{#each tabs.filter((tab) => (tab !== "categories" && contentType === "Blog") || contentType === "Docs") as tab}
<li>
<a
href="/website/{id}{tab === 'settings' ? '' : `/${tab}`}"
class:active={tab === "settings"
? $page.url.pathname === `/website/${id}`
: $page.url.pathname.includes(tab)}
>{(tab.charAt(0).toUpperCase() + tab.slice(1)).replace("-", " ") || "Settings"}</a
>
</li>
{/each}
</ul>
</nav>
@@ -117,6 +117,11 @@
gap: var(--space-s);
}
.active {
text-underline-offset: 0.375rem;
text-decoration-thickness: 0.25rem;
}
@media (min-width: 640px) {
label[for="toggle-mobile-preview"] {
display: none;

View File

@@ -27,7 +27,6 @@ export interface Article {
created_at: Date;
last_modified_at: Date;
last_modified_by: string | null;
title_description_search: any | null;
}
export interface ArticleInput {
id?: string;
@@ -44,7 +43,6 @@ export interface ArticleInput {
created_at?: Date;
last_modified_at?: Date;
last_modified_by?: string | null;
title_description_search?: any | null;
}
const article = {
tableName: "article",
@@ -62,8 +60,7 @@ const article = {
"article_weight",
"created_at",
"last_modified_at",
"last_modified_by",
"title_description_search"
"last_modified_by"
],
requiredForInsert: ["website_id", "title"],
primaryKey: "id",
@@ -463,7 +460,6 @@ export interface Website {
created_at: Date;
last_modified_at: Date;
last_modified_by: string | null;
title_search: any | null;
}
export interface WebsiteInput {
id?: string;
@@ -474,7 +470,6 @@ export interface WebsiteInput {
created_at?: Date;
last_modified_at?: Date;
last_modified_by?: string | null;
title_search?: any | null;
}
const website = {
tableName: "website",
@@ -486,8 +481,7 @@ const website = {
"is_published",
"created_at",
"last_modified_at",
"last_modified_by",
"title_search"
"last_modified_by"
],
requiredForInsert: ["content_type", "title"],
primaryKey: "id",

View File

@@ -19,7 +19,7 @@
<svelte:head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<title>{websiteOverview.title === title ? title : `${websiteOverview.title} | ${title}`}</title>
<meta name="description" content={metaDescription ?? title} />
<link rel="stylesheet" href={`${"../".repeat(nestingLevel)}styles.css`} />
{#if websiteOverview.settings.favicon_image}

View File

@@ -82,5 +82,32 @@
/>
{/if}
</a>
<label style="margin-inline-start: auto;" for="toggle-theme">
<input type="checkbox" id="toggle-theme" hidden />
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
width="20"
height="20"
>
<path
d="M10 2a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 2ZM10 15a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 15ZM10 7a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM15.657 5.404a.75.75 0 1 0-1.06-1.06l-1.061 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM6.464 14.596a.75.75 0 1 0-1.06-1.06l-1.06 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM18 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 18 10ZM5 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 5 10ZM14.596 15.657a.75.75 0 0 0 1.06-1.06l-1.06-1.061a.75.75 0 1 0-1.06 1.06l1.06 1.06ZM5.404 6.464a.75.75 0 0 0 1.06-1.06l-1.06-1.06a.75.75 0 1 0-1.061 1.06l1.06 1.06Z"
></path>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
width="20"
height="20"
>
<path
fill-rule="evenodd"
d="M7.455 2.004a.75.75 0 0 1 .26.77 7 7 0 0 0 9.958 7.967.75.75 0 0 1 1.067.853A8.5 8.5 0 1 1 6.647 1.921a.75.75 0 0 1 .808.083Z"
clip-rule="evenodd"
></path>
</svg>
</label>
</div>
</nav>

View File

@@ -3,7 +3,6 @@ import type { Renderer, Token } from "marked";
import { markedHighlight } from "marked-highlight";
import hljs from "highlight.js";
import DOMPurify from "isomorphic-dompurify";
import { applyAction, deserialize } from "$app/forms";
import type {
Website,
Settings,

View File

@@ -18,7 +18,7 @@ export const actions: Actions = {
return response;
}
cookies.set("session_token", response.data.token, { path: "/" });
cookies.set("session_token", response.data.token, { path: "/", maxAge: 86400 });
return response;
}
};

View File

@@ -14,7 +14,7 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
const baseFetchUrl = `${API_BASE_PREFIX}/website?order=last_modified_at.desc,created_at.desc`;
if (searchQuery) {
params.append("title_search", `wfts(english).${searchQuery}`);
params.append("title", `wfts.${searchQuery}`);
}
switch (filterBy) {

View File

@@ -21,7 +21,7 @@ export const load: PageServerLoad = async ({ params, fetch, url, parent, locals
const parameters = new URLSearchParams();
if (searchQuery) {
parameters.append("title_description_search", `wfts(english).${searchQuery}`);
parameters.append("title", `wfts.${searchQuery}`);
}
switch (filterBy) {

View File

@@ -7,7 +7,7 @@
import { page } from "$app/stores";
import { tables } from "$lib/db-schema";
import { previewContent } from "$lib/runes.svelte";
import { sanitize } from "isomorphic-dompurify";
import DOMPurify from "isomorphic-dompurify";
const { data }: { data: PageServerData } = $props();
@@ -156,9 +156,12 @@
<p>{table_name} &mdash; {operation}</p>
</hgroup>
<pre style="white-space: pre-wrap">{@html sanitize(htmlDiff(oldValue, newValue), {
ALLOWED_TAGS: ["ins", "del"]
})}</pre>
<pre style="white-space: pre-wrap">{@html DOMPurify.sanitize(
htmlDiff(oldValue, newValue),
{
ALLOWED_TAGS: ["ins", "del"]
}
)}</pre>
</Modal>
</td>
</tr>

View File

@@ -13,6 +13,12 @@ nav {
border-block-end: var(--border-primary);
}
nav > .container {
display: flex;
align-items: center;
gap: var(--space-2xs);
}
header > .container {
display: flex;
flex-direction: column;

View File

@@ -1,41 +1,4 @@
@import url("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/github.min.css")
screen and (prefers-color-scheme: light);
@import url("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/github-dark.min.css")
screen and (prefers-color-scheme: dark);
@font-face {
font-family: "JetBrains Mono";
font-style: normal;
font-display: swap;
font-weight: 400;
src:
url(https://cdn.jsdelivr.net/fontsource/fonts/jetbrains-mono@latest/latin-400-normal.woff2)
format("woff2"),
url(https://cdn.jsdelivr.net/fontsource/fonts/jetbrains-mono@latest/latin-400-normal.woff)
format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
}
:root {
--bg-primary-h: /* BACKGROUND_COLOR_LIGHT_THEME_H */ 0;
--bg-primary-s: /* BACKGROUND_COLOR_LIGHT_THEME_S */ 0%;
--bg-primary-l: /* BACKGROUND_COLOR_LIGHT_THEME_L */ 100%;
--bg-primary: hsl(var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l));
--bg-secondary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 5%));
--bg-tertiary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 10%));
--bg-blurred: hsla(
var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) - 20%)
);
--color-text: hsl(var(--bg-primary-h) var(--bg-primary-s) 0%);
--color-text-invert: var(--bg-primary);
--color-border: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 50%));
--color-accent: /* ACCENT_COLOR_LIGHT_THEME */ hsl(210 100% 30%);
--color-success: hsl(105 100% 30%);
--color-error: hsl(0 100% 30%);
html {
--border-primary: 0.0625rem solid var(--color-border);
--border-radius: 0.125rem;
@@ -72,12 +35,92 @@
--space-2xl: clamp(4rem, 3.7368rem + 1.3158cqi, 5rem);
/* Space 3xl: 96px → 120px */
--space-3xl: clamp(6rem, 5.6053rem + 1.9737cqi, 7.5rem);
}
html {
--bg-primary-h: /* BACKGROUND_COLOR_LIGHT_THEME_H */ 0;
--bg-primary-s: /* BACKGROUND_COLOR_LIGHT_THEME_S */ 0%;
--bg-primary-l: /* BACKGROUND_COLOR_LIGHT_THEME_L */ 100%;
--bg-primary: hsl(var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l));
--bg-secondary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 5%));
--bg-tertiary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 10%));
--bg-blurred: hsla(
var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) - 20%)
);
--color-text: hsl(var(--bg-primary-h) var(--bg-primary-s) 0%);
--color-text-invert: var(--bg-primary);
--color-border: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 50%));
--color-accent: /* ACCENT_COLOR_LIGHT_THEME */ hsl(210 100% 30%);
--color-success: hsl(105 100% 30%);
--color-error: hsl(0 100% 30%);
--display-light: none;
--display-dark: initial;
--hl-bg: #fff;
--hl-color: #24292e;
--hl-keyword: #d73a49;
--hl-title: #6f42c1;
--hl-attr: #005cc5;
--hl-string: #032f62;
--hl-built-in: #e36209;
--hl-comment: #6a737d;
--hl-tag: #22863a;
--hl-section: #005cc5;
--hl-bullet: #735c0f;
--hl-emphasis: #24292e;
--hl-addition-bg: #f0fff4;
--hl-addition-text: #22863a;
--hl-deletion-bg: #ffeef0;
--hl-deletion-text: #b31d28;
color-scheme: light;
}
html:has(#toggle-theme:checked) {
--bg-primary-h: /* BACKGROUND_COLOR_DARK_THEME_H */ 0;
--bg-primary-s: /* BACKGROUND_COLOR_DARK_THEME_S */ 0%;
--bg-primary-l: /* BACKGROUND_COLOR_DARK_THEME_L */ 15%;
--bg-primary: hsl(var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l));
--bg-secondary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) + 5%));
--bg-tertiary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) + 10%));
--bg-blurred: hsla(
var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) + 20%)
);
--color-text: hsl(var(--bg-primary-h) var(--bg-primary-s) 100%);
--color-text-invert: var(--bg-primary);
--color-border: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) + 50%));
--color-accent: /* ACCENT_COLOR_DARK_THEME */ hsl(210 100% 80%);
--color-success: hsl(105 100% 80%);
--color-error: hsl(0 100% 80%);
--display-light: initial;
--display-dark: none;
--hl-bg: #0d1117;
--hl-color: #c9d1d9;
--hl-keyword: #ff7b72;
--hl-title: #d2a8ff;
--hl-attr: #79c0ff;
--hl-string: #a5d6ff;
--hl-built-in: #ffa657;
--hl-comment: #8b949e;
--hl-tag: #7ee787;
--hl-section: #1f6feb;
--hl-bullet: #f2cc60;
--hl-emphasis: #c9d1d9;
--hl-addition-bg: #033a16;
--hl-addition-text: #aff5b4;
--hl-deletion-bg: #67060c;
--hl-deletion-text: #ffdcd7;
color-scheme: dark;
}
@media (prefers-color-scheme: dark) {
:root {
html {
--bg-primary-h: /* BACKGROUND_COLOR_DARK_THEME_H */ 0;
--bg-primary-s: /* BACKGROUND_COLOR_DARK_THEME_S */ 0%;
--bg-primary-l: /* BACKGROUND_COLOR_DARK_THEME_L */ 15%;
@@ -95,8 +138,69 @@
--color-success: hsl(105 100% 80%);
--color-error: hsl(0 100% 80%);
--display-light: initial;
--display-dark: none;
--hl-bg: #0d1117;
--hl-color: #c9d1d9;
--hl-keyword: #ff7b72;
--hl-title: #d2a8ff;
--hl-attr: #79c0ff;
--hl-string: #a5d6ff;
--hl-built-in: #ffa657;
--hl-comment: #8b949e;
--hl-tag: #7ee787;
--hl-section: #1f6feb;
--hl-bullet: #f2cc60;
--hl-emphasis: #c9d1d9;
--hl-addition-bg: #033a16;
--hl-addition-text: #aff5b4;
--hl-deletion-bg: #67060c;
--hl-deletion-text: #ffdcd7;
color-scheme: dark;
}
html:has(#toggle-theme:checked) {
--bg-primary-h: /* BACKGROUND_COLOR_LIGHT_THEME_H */ 0;
--bg-primary-s: /* BACKGROUND_COLOR_LIGHT_THEME_S */ 0%;
--bg-primary-l: /* BACKGROUND_COLOR_LIGHT_THEME_L */ 100%;
--bg-primary: hsl(var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l));
--bg-secondary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 5%));
--bg-tertiary: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 10%));
--bg-blurred: hsla(
var(--bg-primary-h) var(--bg-primary-s) var(--bg-primary-l) / calc(var(--bg-primary-l) - 20%)
);
--color-text: hsl(var(--bg-primary-h) var(--bg-primary-s) 0%);
--color-text-invert: var(--bg-primary);
--color-border: hsl(var(--bg-primary-h) var(--bg-primary-s) calc(var(--bg-primary-l) - 50%));
--color-accent: /* ACCENT_COLOR_LIGHT_THEME */ hsl(210 100% 30%);
--color-success: hsl(105 100% 30%);
--color-error: hsl(0 100% 30%);
--display-light: none;
--display-dark: initial;
--hl-bg: #fff;
--hl-color: #24292e;
--hl-keyword: #d73a49;
--hl-title: #6f42c1;
--hl-attr: #005cc5;
--hl-string: #032f62;
--hl-built-in: #e36209;
--hl-comment: #6a737d;
--hl-tag: #22863a;
--hl-section: #005cc5;
--hl-bullet: #735c0f;
--hl-emphasis: #24292e;
--hl-addition-bg: #f0fff4;
--hl-addition-text: #22863a;
--hl-deletion-bg: #ffeef0;
--hl-deletion-text: #b31d28;
color-scheme: light;
}
}
*,
@@ -109,11 +213,12 @@
body {
line-height: 1.5;
font-family: system-ui, sans-serif;
font-family: system-ui;
background-color: var(--bg-primary);
display: flex;
flex-direction: column;
min-block-size: 100vh;
color: var(--color-text);
}
button,
@@ -123,6 +228,7 @@ select,
[role="option"],
label[for="toggle-mobile-preview"],
label[for="toggle-sidebar"],
label[for="toggle-theme"],
summary {
cursor: pointer;
}
@@ -134,6 +240,7 @@ select,
a[role="button"],
label[for="toggle-mobile-preview"],
label[for="toggle-sidebar"],
label[for="toggle-theme"],
summary {
font: inherit;
color: inherit;
@@ -174,15 +281,30 @@ button,
a[role="button"],
label[for="toggle-mobile-preview"],
label[for="toggle-sidebar"],
label[for="toggle-theme"],
summary {
background-color: var(--bg-secondary);
}
label:has(svg) {
display: inline-grid;
place-content: center;
}
label[for="toggle-theme"] svg:first-of-type {
display: var(--display-light);
}
label[for="toggle-theme"] svg:last-of-type {
display: var(--display-dark);
}
:is(
button,
a[role="button"],
label[for="toggle-mobile-preview"],
label[for="toggle-sidebar"],
label[for="toggle-theme"],
summary
):hover {
background-color: var(--bg-tertiary);
@@ -304,7 +426,7 @@ pre {
}
code {
font-family: "JetBrains Mono", monospace;
font-family: monospace;
font-size: var(--font-size--1);
}
@@ -354,3 +476,95 @@ del {
background-color: var(--color-error);
color: var(--color-text-invert);
}
.hljs {
color: var(--hl-color);
background: var(--hl-bg);
}
.hljs-doctag,
.hljs-keyword,
.hljs-meta .hljs-keyword,
.hljs-template-tag,
.hljs-template-variable,
.hljs-type,
.hljs-variable.language_ {
color: var(--hl-keyword);
}
.hljs-title,
.hljs-title.class_,
.hljs-title.class_.inherited__,
.hljs-title.function_ {
color: var(--hl-title);
}
.hljs-attr,
.hljs-attribute,
.hljs-literal,
.hljs-meta,
.hljs-number,
.hljs-operator,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-id,
.hljs-variable {
color: var(--hl-attr);
}
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: var(--hl-string);
}
.hljs-built_in,
.hljs-symbol {
color: var(--hl-built-in);
}
.hljs-code,
.hljs-comment,
.hljs-formula {
color: var(--hl-comment);
}
.hljs-name,
.hljs-quote,
.hljs-selector-pseudo,
.hljs-selector-tag {
color: var(--hl-tag);
}
.hljs-subst {
color: var(--hl-color);
}
.hljs-section {
color: var(--hl-section);
font-weight: bold;
}
.hljs-bullet {
color: var(--hl-bullet);
}
.hljs-emphasis {
color: var(--hl-emphasis);
font-style: italic;
}
.hljs-strong {
color: var(--hl-emphasis);
font-weight: bold;
}
.hljs-addition {
color: var(--hl-addition-text);
background-color: var(--hl-addition-bg);
}
.hljs-deletion {
color: var(--hl-deletion-text);
background-color: var(--hl-deletion-bg);
}

View File

@@ -41,11 +41,6 @@ section {
scroll-margin-block-start: var(--space-xl);
}
label[for="toggle-sidebar"] {
display: inline-grid;
place-content: center;
}
.docs-navigation {
display: none;
position: fixed;