feat: full JS-less theme transition, better transition animation between themes, better light theme colors

This commit is contained in:
Oleksandr 2026-02-13 07:46:00 +02:00
parent da6dd0100b
commit c0e0266d45
Signed by: Xanazf
GPG key ID: 821EEC32761AC17C
46 changed files with 4031 additions and 2536 deletions

1280
.pnp.cjs generated

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@
},
"plugins": [],
"linter": {
"enabled": false,
"enabled": true,
"rules": {
"recommended": true,
"style": {
@ -53,10 +53,7 @@
},
"overrides": [
{
"includes": [
"**/*.ts",
"**/*.tsx"
],
"includes": ["**/*.ts", "**/*.tsx"],
"linter": {
"rules": {
"complexity": {
@ -80,11 +77,13 @@
}
},
{
"includes": [
"*.astro"
],
"includes": ["*.astro"],
"linter": {
"rules": {
"correctness": {
"noUnusedImports": "off",
"noUnusedVariables": "off"
},
"style": {
"useConst": "off",
"useImportType": "off"

2807
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@
"@fontsource-variable/rubik": "^5.2.8",
"@hbsnow/rehype-sectionize": "^1.0.7",
"@pagefind/default-ui": "^1.4.0",
"@shikijs/rehype": "^3.20.0",
"@shikijs/rehype": "^3.22.0",
"astro": "5.17.2",
"astro-breadcrumbs": "^3.3.3",
"astro-icon": "^1.1.5",
@ -26,26 +26,27 @@
"hastscript": "^9.0.1",
"rehype": "^13.0.2",
"remark-github-blockquote-alert": "^2.0.1",
"solid-js": "^1.9.10",
"solid-js": "^1.9.11",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0"
"unist-util-visit": "^5.1.0"
},
"devDependencies": {
"@astrojs/ts-plugin": "1.10.6",
"@babel/core": "^7.28.5",
"@babel/plugin-syntax-typescript": "^7.27.1",
"@biomejs/biome": "^2.3.10",
"@babel/core": "^7.29.0",
"@babel/plugin-syntax-typescript": "^7.28.6",
"@biomejs/biome": "^2.3.15",
"@types/babel__core": "^7.20.5",
"@types/hast": "^3.0.4",
"@types/mdast": "^4.0.4",
"@types/node": "^25.0.3",
"@types/node": "^25.2.3",
"@types/unist": "^3.0.3",
"baseline-browser-mapping": "^2.9.19",
"jsonc-parser": "^3.3.1",
"pagefind": "^1.4.0",
"shiki": "^3.20.0",
"shiki": "^3.22.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"vite": "^7.3.0"
"vite": "^7.3.1"
},
"packageManager": "yarn@4.12.0"
}

View file

@ -12,15 +12,11 @@ export default function pagefind(): AstroIntegration {
const cwd = dirname(fileURLToPath(import.meta.url));
const relativeDir = relative(cwd, targetDir);
return new Promise<void>(resolve => {
spawn(
"yarn",
["pagefind", "--site", relativeDir],
{
stdio: "inherit",
shell: true,
cwd,
}
).on("close", () => resolve());
spawn("yarn", ["pagefind", "--site", relativeDir], {
stdio: "inherit",
shell: true,
cwd,
}).on("close", () => resolve());
});
},
},

View file

@ -2,7 +2,6 @@
import matrixLogo from "@icons/matrix-logo.svg?raw";
import discordLogo from "@icons/discord-logo.svg?raw";
import gitLogo from "@icons/git-logo.svg?raw";
import { ThemeSelect } from "./hooks/ThemeSwitch";
import ThemeToggle from "./ThemeToggle.astro";
interface Props {

View file

@ -1,6 +1,6 @@
---
import { ThemeSelect } from "@components/hooks/ThemeSwitch";
import type { TypeData } from "@config/io/types";
import ThemeToggle from "./ThemeToggle.astro";
import type { TypeData } from "@config/_types";
import Nav from "@components/navigation/sidebars/nav/index.astro";
import TOC from "@components/navigation/sidebars/TOC.astro";
import type { ConfigHeading } from "@components/navigation/sidebars/types";
@ -23,7 +23,7 @@ const { title, headings, type } = Astro.props;
</div>
<div class="header-item header-right">
<Search/>
<ThemeSelect client:load />
<ThemeToggle />
<TOC title={title} headings={headings} type={type} mobile={true}/>
</div>
</div>

View file

@ -2,21 +2,19 @@
import { Icon } from "astro-icon/components";
---
<label class="theme-toggle icon-button standard" title="Toggle theme">
<input
type="checkbox"
id="theme-manual-toggle"
class="theme-toggle-input"
aria-label="Toggle theme (light/dark)"
/>
<label
class="theme-toggle icon-button standard"
title="Toggle theme"
for="theme-manual-toggle"
>
<Icon
name="sun"
name="moon"
class="light-icon"
style="width: 24px; height: 24px;"
aria-hidden="true"
/>
<Icon
name="moon"
name="sun"
class="dark-icon"
style="width: 24px; height: 24px;"
aria-hidden="true"
@ -30,13 +28,6 @@ import { Icon } from "astro-icon/components";
user-select: none;
}
.theme-toggle-input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.light-icon {
display: block;
}
@ -44,11 +35,11 @@ import { Icon } from "astro-icon/components";
display: none;
}
.theme-toggle:has(.theme-toggle-input:checked) .light-icon {
:global(html:has(input#theme-manual-toggle:checked)) .light-icon {
display: none;
}
.theme-toggle:has(.theme-toggle-input:checked) .dark-icon {
:global(html:has(input#theme-manual-toggle:checked)) .dark-icon {
display: block;
}

View file

@ -1,30 +0,0 @@
---
// NOTE: to be migrated to @config/styling/animations_helper.ts
---
<script>
window.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const heading = entry.target.querySelector('h1, h2, h3, h4, h5, h6')
if(heading) {
const id = heading.id
if (entry.intersectionRatio > 0) {
const desktopElement = document.querySelector(`.toc-wrapper li a[href="#${id}"]`);
const mobileElement = document.querySelector(`.toc-wrapper-mobile li a[href="#${id}"]`);
const element = mobileElement?.checkVisibility() ? mobileElement : desktopElement;
element?.parentElement?.classList.add('active')
} else {
const desktopElement = document.querySelector(`.toc-wrapper li a[href="#${id}"]`);
const mobileElement = document.querySelector(`.toc-wrapper-mobile li a[href="#${id}"]`);
const element = mobileElement?.checkVisibility() ? mobileElement : desktopElement;
element?.parentElement?.classList.remove('active')
}
}
});
});
document.querySelectorAll('section[data-heading-rank]').forEach((section) => {
observer.observe(section);
});
});
</script>

View file

@ -1,113 +0,0 @@
// NOTE: to be replaced by @components/ThemeToggle.astro
import {
createSignal,
createEffect,
onCleanup,
onMount,
type VoidComponent,
} from "solid-js";
import Sun from "@icons/sun.svg?raw";
import Moon from "@icons/moon.svg?raw";
export interface ThemeProps {
theme: "light" | "dark";
system: "light" | "dark";
}
export const getCurrentTheme = (): ThemeProps => {
if (
typeof localStorage !== "undefined" &&
(localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)")
.matches))
) {
return {
theme: "dark",
system: window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light",
};
}
return {
theme: "light",
system: window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light",
};
};
const updateTheme = () => {
document.documentElement.classList.add("changing-theme");
if (
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
requestAnimationFrame(() => {
requestAnimationFrame(() => {
document.documentElement.classList.remove("changing-theme");
});
});
};
export const ThemeSelect: VoidComponent = () => {
const [currentTheme, setCurrentTheme] =
createSignal<ThemeProps>({
theme: "dark",
system: "dark",
});
const [mounted, setMounted] = createSignal(false);
const toggleTheme = () => {
if (!mounted()) return;
setCurrentTheme(getCurrentTheme());
if (currentTheme()!.theme !== currentTheme()!.system) {
localStorage.removeItem("theme");
} else {
localStorage.theme =
currentTheme()!.theme === "dark" ? "light" : "dark";
}
updateTheme();
setCurrentTheme(getCurrentTheme());
};
onMount(() => {
setMounted(true);
setCurrentTheme(getCurrentTheme());
});
createEffect(() => {
const mediaQuery = window.matchMedia(
"(prefers-color-scheme: dark)"
);
mediaQuery.addEventListener("change", updateTheme);
window.addEventListener("storage", updateTheme);
onCleanup(() => {
mediaQuery.removeEventListener("change", updateTheme);
window.removeEventListener("storage", updateTheme);
setMounted(false);
});
});
return (
<div
onclick={toggleTheme}
class="theme-toggle"
innerHTML={
(mounted() && currentTheme().theme === "light") ||
currentTheme().system === "light"
? Sun
: Moon
}
/>
);
};

View file

@ -1,6 +1,6 @@
---
import "@pagefind/default-ui/css/ui.css";
import magnifierIcon from "@icons/magnifier.svg?raw"
import magnifierIcon from "@icons/magnifier.svg?raw";
---
<site-search class="search-wrapper">
<button

View file

@ -12,12 +12,14 @@ export interface Props {
const { title, headings, type, mobile } = Astro.props;
const types: TypeTOC | null = type ? {
properties: Object.keys(type.properties ?? {}),
functions: (type.functions ?? []).map(f => f.name),
signals: Object.keys(type.signals ?? {}),
variants: Object.keys(type.variants ?? {}),
} : null;
const types: TypeTOC | null = type
? {
properties: Object.keys(type.properties ?? {}),
functions: (type.functions ?? []).map(f => f.name),
signals: Object.keys(type.signals ?? {}),
variants: Object.keys(type.variants ?? {}),
}
: null;
---
{((headings?.length ?? 0) != 0 || types) &&
<div id="toc" aria-mobile={mobile} class=`toc-wrapper${mobile ? "-mobile":""}`>

View file

@ -13,32 +13,46 @@ import Link from "./Link.astro";
const versions = await getVersionsData();
const versionName = Astro.params.version;
const modules = versions.versions.find(version => version.name === versionName)?.modules;
const modules = versions.versions.find(
version => version.name === versionName
)?.modules;
const currentPath = Astro.url.pathname.split('/').filter(s => s !== "");
const currentPath = Astro.url.pathname
.split("/")
.filter(s => s !== "");
const guidePages = await getGuideCollection(versionName ?? "");
interface NavTree {
title: string,
slug: string,
entries?: NavTree[],
title: string;
slug: string;
entries?: NavTree[];
}
function mkTree(mount: string, pathIdx: number, { title, slug, entries }: NavTree): TreeEntry {
function mkTree(
mount: string,
pathIdx: number,
{ title, slug, entries }: NavTree
): TreeEntry {
const link = `${mount}/${slug}`;
return {
title,
link,
current: currentPath[pathIdx] === slug,
entries: entries?.map(entry => mkTree(link, pathIdx + 1, entry)),
entries: entries?.map(entry =>
mkTree(link, pathIdx + 1, entry)
),
};
}
function genGuideNav(base: string): NavTree[] | undefined {
const pages = guidePages
.filter(page => page.id.match(`^${base}[^/]*$`) !== null && page.id !== "index")
.filter(
page =>
page.id.match(`^${base}[^/]*$`) !== null &&
page.id !== "index"
)
.sort((a, b) => a.data.index - b.data.index)
.map(page => ({
title: page.data.title,
@ -68,8 +82,8 @@ if (versionName) {
entries: module.types.map(type => ({
title: type.name,
slug: type.name,
}))
}))
})),
})),
}),
};

View file

@ -20,10 +20,12 @@ export const Table: Component<{
if (configTOC) {
return (
<div class="toc-content">
{title && <>
<p>{title}</p>
<hr/>
</>}
{title && (
<>
<p>{title}</p>
<hr />
</>
)}
<For each={configTOC}>
{heading => (
<Heading

View file

@ -6,7 +6,7 @@ import {
type Component,
} from "solid-js";
import { Article } from "@icons";
import { Article, MenuToX } from "@icons";
import { Table } from "./Table";
import type { TOCProps } from "../types";
import { buildHierarchy } from "@config/io/helpers";
@ -27,7 +27,10 @@ const TableOfContents: Component<TOCProps> = props => {
return type ? (
<Table typeTOC={type} />
) : (
<Table title={title} configTOC={buildHierarchy(config!)} />
<Table
title={title}
configTOC={buildHierarchy(config!)}
/>
);
}
@ -92,13 +95,17 @@ const TableOfContents: Component<TOCProps> = props => {
id="toc-toggle"
>
<div onclick={e => toggle(e)}>
<Article />
<Article class={`toc-icon ${!open() ? "active" : ""}`} />
<MenuToX class={`toc-icon ${open() ? "active" : ""}`} />
</div>
<div class={`toc-mobile ${open() ? "shown" : ""}`}>
{type ? (
<Table typeTOC={type} />
) : (
<Table title={title} configTOC={buildHierarchy(config!)} />
<Table
title={title}
configTOC={buildHierarchy(config!)}
/>
)}
</div>
</div>

View file

@ -8,7 +8,9 @@ export interface Props {
const { markdown } = Astro.props;
const { version } = Astro.params;
const html = markdown ? await processMarkdown(version!, markdown) : null;
const html = markdown
? await processMarkdown(version!, markdown)
: null;
---
<section class="typedata-details">
{html ? <div class="typedata-detailsdata" set:html={html} /> : <em>No details provided</em>}

View file

@ -1,23 +0,0 @@
---
---
<script is:inline>
function updateTheme() {
if (
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}
// Run on initial load
updateTheme();
// Run on view transitions
document.addEventListener("astro:after-swap", updateTheme);
</script>

View file

@ -27,118 +27,118 @@ let currentVersion = "NOVERSION";
const remarkParseAtTypes: RemarkPlugin<[]> =
() =>
(root: Md.Root): Md.Root => {
visit(root as Unist.Parent, (rawNode: Unist.Node) => {
if (
rawNode.type === "text" ||
(rawNode.type === "code" &&
(rawNode as Md.Code).lang === "qml")
) {
const node = rawNode as Md.Literal;
(root: Md.Root): Md.Root => {
visit(root as Unist.Parent, (rawNode: Unist.Node) => {
if (
rawNode.type === "text" ||
(rawNode.type === "code" &&
(rawNode as Md.Code).lang === "qml")
) {
const node = rawNode as Md.Literal;
node.value = node.value.replace(
/@@((?<module>([A-Z]\w*\.)*)(?<type>([A-Z]\w*))(\.(?!\s|$))?)?((?<member>[a-z]\w*)((?<function>\(\))|(?<signal>\(s\)))?)?(?=[$.,;:)\s]|$)/g,
(_full, ...args) => {
type Capture = {
module: string | undefined;
type: string | undefined;
member: string | undefined;
function: string | undefined;
signal: string | undefined;
};
node.value = node.value.replace(
/@@((?<module>([A-Z]\w*\.)*)(?<type>([A-Z]\w*))(\.(?!\s|$))?)?((?<member>[a-z]\w*)((?<function>\(\))|(?<signal>\(s\)))?)?(?=[$.,;:)\s]|$)/g,
(_full, ...args) => {
type Capture = {
module: string | undefined;
type: string | undefined;
member: string | undefined;
function: string | undefined;
signal: string | undefined;
};
const groups = args.pop() as Capture;
const groups = args.pop() as Capture;
if (groups.module) {
groups.module = groups.module.substring(
0,
groups.module.length - 1
);
const isQs = groups.module.startsWith("Quickshell");
groups.module = `99M${isQs ? "QS" : "QT_qml"}_${groups.module.replace(".", "_")}`;
} else groups.module = ""; // WARNING: rehype parser can't currently handle intra-module links
if (groups.module) {
groups.module = groups.module.substring(
0,
groups.module.length - 1
);
const isQs = groups.module.startsWith("Quickshell");
groups.module = `99M${isQs ? "QS" : "QT_qml"}_${groups.module.replace(".", "_")}`;
} else groups.module = ""; // WARNING: rehype parser can't currently handle intra-module links
groups.type = groups.type ? `99N${groups.type}` : "";
groups.member = groups.member
? `99V${groups.member}`
: "";
const type = groups.member
? `99T${groups.function ? "func" : groups.signal ? "signal" : "prop"}`
: "";
return `TYPE${groups.module}${groups.type}${groups.member}${type}99TYPE`;
}
);
}
});
return root;
};
groups.type = groups.type ? `99N${groups.type}` : "";
groups.member = groups.member
? `99V${groups.member}`
: "";
const type = groups.member
? `99T${groups.function ? "func" : groups.signal ? "signal" : "prop"}`
: "";
return `TYPE${groups.module}${groups.type}${groups.member}${type}99TYPE`;
}
);
}
});
return root;
};
const rehypeRewriteTypelinks: RehypePlugin<[]> =
() =>
(root: Html.Root): Html.Root => {
visit(
root as Unist.Parent,
"text",
(node: Html.Text, index: number, parent: Html.Parent) => {
let changed = false;
(root: Html.Root): Html.Root => {
visit(
root as Unist.Parent,
"text",
(node: Html.Text, index: number, parent: Html.Parent) => {
let changed = false;
node.value = node.value.replace(
/TYPE99(\w+.)99TYPE/g,
(_full: string, match: string) => {
changed = true;
node.value = node.value.replace(
/TYPE99(\w+.)99TYPE/g,
(_full: string, match: string) => {
changed = true;
const linkObject = getQMLTypeLinkObject(match);
const link = getQMLTypeLink(
currentVersion,
linkObject
);
const icon =
linkObject.mtype && linkObject.mtype !== "func"
? getIconForLink(linkObject.mtype, false)
: null;
const hasParens =
linkObject.mtype === "func" ||
linkObject.mtype === "signal";
const hasDot = linkObject.name && linkObject.mname;
const linkObject = getQMLTypeLinkObject(match);
const link = getQMLTypeLink(
currentVersion,
linkObject
);
const icon =
linkObject.mtype && linkObject.mtype !== "func"
? getIconForLink(linkObject.mtype, false)
: null;
const hasParens =
linkObject.mtype === "func" ||
linkObject.mtype === "signal";
const hasDot = linkObject.name && linkObject.mname;
return `<a class="type${linkObject.mtype}-link typedata-link" href=${link}>${icon ?? ""}${linkObject.name ?? ""}${hasDot ? "." : ""}${linkObject.mname ?? ""}${hasParens ? "()" : ""}</a>`;
}
);
if (changed) {
const fragment = fromHtml(node.value, {
fragment: true,
});
parent.children.splice(index, 1, ...fragment.children);
return SKIP;
return `<a class="type${linkObject.mtype}-link typedata-link" href=${link}>${icon ?? ""}${linkObject.name ?? ""}${hasDot ? "." : ""}${linkObject.mname ?? ""}${hasParens ? "()" : ""}</a>`;
}
);
return CONTINUE;
if (changed) {
const fragment = fromHtml(node.value, {
fragment: true,
});
parent.children.splice(index, 1, ...fragment.children);
return SKIP;
}
);
return root;
};
return CONTINUE;
}
);
return root;
};
const rehypeRewriteVersionedDoclinks: RehypePlugin<[]> =
() =>
(root: Html.Root): Html.Root => {
visit(
root as Unist.Parent,
"element",
({ tagName, properties }: Html.Element) => {
if (tagName !== "a") return CONTINUE;
if (
!((properties.href as string) ?? "").startsWith("@docs")
)
return CONTINUE;
properties.href = `/docs/${currentVersion}/${(properties.href as string).slice(6)}`;
(root: Html.Root): Html.Root => {
visit(
root as Unist.Parent,
"element",
({ tagName, properties }: Html.Element) => {
if (tagName !== "a") return CONTINUE;
if (
!((properties.href as string) ?? "").startsWith("@docs")
)
return CONTINUE;
}
);
properties.href = `/docs/${currentVersion}/${(properties.href as string).slice(6)}`;
return CONTINUE;
}
);
return root;
};
return root;
};
const shikiRewriteTypelinks: ShikiTransformer = {
name: "rewrite-typelinks",
@ -170,7 +170,7 @@ const shikiCopyButton: ShikiTransformer = {
role: "button",
"aria-label": "Copy to clipboard",
"alia-live": "polite",
// "data-code": removeCodeAnnotations(this.source),
"data-code": this.source,
onclick: `
navigator.clipboard.writeText(this.dataset.code);
this.classList.add('copied');
@ -179,8 +179,6 @@ const shikiCopyButton: ShikiTransformer = {
`,
},
[
h("span", { class: "ready" }),
h("span", { class: "success" }),
h(
"svg",
{
@ -198,6 +196,23 @@ const shikiCopyButton: ShikiTransformer = {
}),
]
),
h(
"svg",
{
class: "check-icon",
role: "icon",
xmlns: "http://www.w3.org/2000/svg",
width: "1em",
height: "1em",
viewBox: "0 0 256 256",
},
[
h("path", {
fill: "currentColor",
d: "M229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69L218.34 66.34a8 8 0 0 1 11.32 11.32Z",
}),
]
),
]
);
node.children.splice(0, 0, button);

View file

@ -23,7 +23,47 @@ export function initAnimations() {
animatedElements.forEach(el => observer.observe(el));
}
export function initTOCHighlighting() {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const heading = entry.target.querySelector(
"h1, h2, h3, h4, h5, h6"
);
if (heading) {
const id = heading.id;
const desktopElement = document.querySelector(
`.toc-wrapper li a[href="#${id}"]`
);
const mobileElement = document.querySelector(
`.toc-wrapper-mobile li a[href="#${id}"]`
);
if (entry.isIntersecting) {
desktopElement?.parentElement?.classList.add("active");
mobileElement?.parentElement?.classList.add("active");
} else {
desktopElement?.parentElement?.classList.remove(
"active"
);
mobileElement?.parentElement?.classList.remove(
"active"
);
}
}
});
});
document
.querySelectorAll("section[data-heading-rank]")
.forEach(section => {
observer.observe(section);
});
}
// auto-init on DOMContentLoaded
if (typeof document !== "undefined") {
document.addEventListener("DOMContentLoaded", initAnimations);
document.addEventListener("DOMContentLoaded", () => {
initAnimations();
initTOCHighlighting();
});
}

View file

@ -1,8 +1,14 @@
document.addEventListener("DOMContentLoaded", () => {
const container = document.querySelector(".marquee") as HTMLDivElement;
const scroller = document.querySelector(".marquee-content") as HTMLDivElement;
const container = document.querySelector(
".marquee"
) as HTMLDivElement;
const scroller = document.querySelector(
".marquee-content"
) as HTMLDivElement;
const btnLeft = document.getElementById("marquee-scroll-left");
const btnRight = document.getElementById("marquee-scroll-right");
const btnRight = document.getElementById(
"marquee-scroll-right"
);
if (!container || !scroller) return;
@ -28,13 +34,15 @@ document.addEventListener("DOMContentLoaded", () => {
// setup clones
const setupClones = () => {
// remove existing clones
scroller.querySelectorAll(".clone").forEach((c) => c.remove());
scroller.querySelectorAll(".clone").forEach(c => c.remove());
const originals = Array.from(scroller.querySelectorAll(".marquee-item")) as HTMLDivElement[];
const originals = Array.from(
scroller.querySelectorAll(".marquee-item")
) as HTMLDivElement[];
// add clones after
for (let i = 0; i < bufferSize; i++) {
originals.forEach((item) => {
originals.forEach(item => {
const clone = item.cloneNode(true) as HTMLDivElement;
clone.classList.add("clone");
scroller.appendChild(clone);
@ -44,7 +52,7 @@ document.addEventListener("DOMContentLoaded", () => {
// add clones before
const beforeContainer = document.createDocumentFragment();
for (let i = 0; i < bufferSize; i++) {
originals.forEach((item) => {
originals.forEach(item => {
const clone = item.cloneNode(true) as HTMLDivElement;
clone.classList.add("clone");
beforeContainer.appendChild(clone);
@ -52,7 +60,9 @@ document.addEventListener("DOMContentLoaded", () => {
}
scroller.insertBefore(beforeContainer, scroller.firstChild);
items = Array.from(scroller.querySelectorAll(".marquee-item")) as HTMLDivElement[];
items = Array.from(
scroller.querySelectorAll(".marquee-item")
) as HTMLDivElement[];
};
const updateDimensions = () => {
@ -63,13 +73,15 @@ document.addEventListener("DOMContentLoaded", () => {
// standardize width
scroller.style.width = `${items.length * itemWidth}px`;
items.forEach((item) => {
items.forEach(item => {
item.style.width = `${itemWidth}px`;
item.style.flex = `0 0 ${itemWidth}px`;
item.style.maxWidth = `${itemWidth}px`;
});
targetScrollX = bufferSize * sequenceWidth + (targetScrollX % sequenceWidth);
targetScrollX =
bufferSize * sequenceWidth +
(targetScrollX % sequenceWidth);
currentScrollX = targetScrollX;
scroller.style.transform = `translateX(-${currentScrollX}px)`;
};
@ -79,20 +91,35 @@ document.addEventListener("DOMContentLoaded", () => {
const animate = () => {
if (!isDown && Math.abs(touchVelocity) < 0.1) {
// snap to nearest item if not interacting and close to one
const nearestItemScroll = Math.round(targetScrollX / itemWidth) * itemWidth;
if (Math.abs(targetScrollX - nearestItemScroll) < itemWidth * 0.5) {
targetScrollX = lerp(targetScrollX, nearestItemScroll, 0.1);
}
// snap to nearest item if not interacting and close to one
const nearestItemScroll =
Math.round(targetScrollX / itemWidth) * itemWidth;
if (
Math.abs(targetScrollX - nearestItemScroll) <
itemWidth * 0.5
) {
targetScrollX = lerp(
targetScrollX,
nearestItemScroll,
0.1
);
}
}
currentScrollX = lerp(currentScrollX, targetScrollX, smoothFactor);
currentScrollX = lerp(
currentScrollX,
targetScrollX,
smoothFactor
);
// boundary reset
if (currentScrollX > (bufferSize + 1) * sequenceWidth) {
currentScrollX -= sequenceWidth;
targetScrollX -= sequenceWidth;
} else if (currentScrollX < (bufferSize - 1) * sequenceWidth) {
} else if (
currentScrollX <
(bufferSize - 1) * sequenceWidth
) {
currentScrollX += sequenceWidth;
targetScrollX += sequenceWidth;
}
@ -112,7 +139,9 @@ document.addEventListener("DOMContentLoaded", () => {
item.style.opacity = opacity.toString();
// NOTE: apply transform to the video container specifically
// to keep layout stable
const content = item.querySelector(".marquee-item-content") as HTMLElement;
const content = item.querySelector(
".marquee-item-content"
) as HTMLElement;
if (content) {
content.style.transform = `scale(${scale}) translateY(${yOffset}px)`;
}
@ -144,8 +173,8 @@ document.addEventListener("DOMContentLoaded", () => {
threshold: 0.5,
};
const videoObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const videoObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
const video = entry.target as HTMLVideoElement;
if (entry.isIntersecting) {
video.play().catch(() => {}); // Handle potential autoplay blocks
@ -155,7 +184,7 @@ document.addEventListener("DOMContentLoaded", () => {
});
}, observerOptions);
videos.forEach((v) => {
videos.forEach(v => {
videoObserver.observe(v);
v.addEventListener("ended", () => {
targetScrollX += itemWidth;
@ -174,20 +203,24 @@ document.addEventListener("DOMContentLoaded", () => {
startAnimation();
});
container.addEventListener("wheel", (e) => {
e.preventDefault();
targetScrollX += e.deltaY;
startAnimation();
}, { passive: false });
container.addEventListener(
"wheel",
e => {
e.preventDefault();
targetScrollX += e.deltaY;
startAnimation();
},
{ passive: false }
);
container.addEventListener("touchstart", (e) => {
container.addEventListener("touchstart", e => {
isDown = true;
lastTouchX = e.touches[0].clientX;
lastTouchTime = Date.now();
touchVelocity = 0;
});
container.addEventListener("touchmove", (e) => {
container.addEventListener("touchmove", e => {
if (!isDown) return;
const currentTouchX = e.touches[0].clientX;
const deltaX = lastTouchX - currentTouchX;
@ -221,6 +254,7 @@ document.addEventListener("DOMContentLoaded", () => {
setupClones();
setTimeout(() => {
updateDimensions();
container.classList.add("initialized");
startAnimation();
}, 50);
});

View file

@ -1,52 +0,0 @@
document.addEventListener("DOMContentLoaded", () => {
let currentProgressScale = 0;
let targetProgressScale = 0;
let lastPercentage = 0;
const progressCounter = document.querySelector(
".progress-counter h1"
);
const progressBar = document.querySelector(
".progress-bar"
) as HTMLDivElement;
const updateProgress = (
sequenceWidth: number,
forceReset = false
) => {
const basePosition = sequenceWidth * bufferSize;
const currentPosition =
(currentScrollX - basePosition) % sequenceWidth;
let percentage = (currentPosition / sequenceWidth) * 100;
if (percentage < 0) {
percentage = 100 + percentage;
}
const isWrapping =
(lastPercentage > 80 && percentage < 20) ||
(lastPercentage < 20 && percentage > 80) ||
forceReset;
progressCounter.textContent = `${Math.round(percentage)}`;
targetProgressScale = percentage / 100;
if (isWrapping) {
currentProgressScale = targetProgressScale;
progressBar.style.transform = `scaleX(${currentProgressScale})`;
}
lastPercentage = percentage;
};
updateProgress(sequenceWidth, true);
progressBar.style.transform = `scaleX(${currentProgressScale})`;
updateProgress(sequenceWidth, forceProgressReset);
if (!forceProgressReset) {
currentProgressScale = lerp(
currentProgressScale,
targetProgressScale,
smoothFactor
);
}
progressBar.style.transform = `scaleX(${currentProgressScale})`;
});

View file

@ -0,0 +1,75 @@
interface ThemeProps {
theme: "light" | "dark";
system: "light" | "dark";
}
export const getCurrentTheme = (): ThemeProps => {
const isDarkSystem = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
const systemTheme = isDarkSystem ? "dark" : "light";
if (typeof localStorage !== "undefined") {
if (localStorage.theme === "dark") {
return { theme: "dark", system: systemTheme };
}
if (localStorage.theme === "light") {
return { theme: "light", system: systemTheme };
}
}
return { theme: systemTheme, system: systemTheme };
};
export const updateTheme = () => {
const theme = getCurrentTheme();
const toggle = document.getElementById(
"theme-manual-toggle"
) as HTMLInputElement;
document.documentElement.classList.add("changing-theme");
if (theme.theme === "dark") {
document.documentElement.classList.add("dark");
if (toggle) toggle.checked = true;
} else {
document.documentElement.classList.remove("dark");
if (toggle) toggle.checked = false;
}
requestAnimationFrame(() => {
requestAnimationFrame(() => {
document.documentElement.classList.remove("changing-theme");
});
});
};
export const initTheme = () => {
const toggle = document.getElementById(
"theme-manual-toggle"
) as HTMLInputElement;
if (toggle) {
toggle.addEventListener("change", () => {
localStorage.theme = toggle.checked ? "dark" : "light";
updateTheme();
});
}
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", updateTheme);
window.addEventListener("storage", updateTheme);
// initial sync
updateTheme();
};
// auto-init on client
if (typeof document !== "undefined") {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initTheme);
} else {
initTheme();
}
}

View file

@ -2,11 +2,11 @@ import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
const guide = defineCollection({
loader: glob({ pattern: "**/*.md", base: "src/guide" }),
schema: z.object({
title: z.string(),
loader: glob({ pattern: "**/*.md", base: "src/guide" }),
schema: z.object({
title: z.string(),
index: z.number(),
}),
}),
});
export const collections = { guide };

View file

@ -1,7 +1,6 @@
---
//import Header from "@components/Header.astro";
import Head from "@config/Head.astro";
import PreTheme from "@config/PreTheme.astro";
interface Props {
title: string;
@ -14,13 +13,20 @@ const { title, description } = Astro.props;
<html lang="en" class="dark">
<head>
<Head description={description} title={title} />
<PreTheme />
</head>
<body class="baselayout">
<input
type="checkbox"
id="theme-manual-toggle"
class="theme-toggle-input"
style="display: none;"
aria-label="Toggle theme (light/dark)"
/>
<!--<Header />-->
<slot />
<script>
import "@config/styling/animations_helper.ts";
import "@config/styling/theme_persistence.ts";
</script>
</body>
</html>

View file

@ -2,13 +2,12 @@
import { Breadcrumbs } from "astro-breadcrumbs";
import "astro-breadcrumbs/breadcrumbs.css";
import PreTheme from "@config/PreTheme.astro";
import Header from "@components/Header.astro";
import Head from "@config/Head.astro";
import Nav from "@components/navigation/sidebars/nav/index.astro";
import type { ConfigHeading } from "@src/components/navigation/sidebars/types";
import Footer from "@src/components/Footer.astro";
import type { TypeData } from "@config/io/types";
import type { TypeData } from "@config/_types";
interface Props {
title: string;
@ -50,9 +49,15 @@ for (const segment of url) {
<html lang="en" class="dark">
<head>
<Head description={description} title={title} />
<PreTheme />
</head>
<body class="docslayout">
<input
type="checkbox"
id="theme-manual-toggle"
class="theme-toggle-input"
aria-label="Toggle theme (light/dark)"
style="display: none;"
/>
<Header title={title} headings={headings} type={type}/>
<div class="docslayout-root">
<Nav mobile={false}/>
@ -88,6 +93,10 @@ for (const segment of url) {
<slot name="alongside-content"/>
</div>
<Footer/>
<script>
import "@config/styling/animations_helper.ts";
import "@config/styling/theme_persistence.ts";
</script>
</body>
</html>
<script>

View file

@ -1,7 +1,6 @@
---
import DocsLayout from "@layouts/DocsLayout.astro";
import TOC from "@components/navigation/sidebars/TOC.astro";
import TOCIntersectionObserver from "@src/components/hooks/TOCIntersectionObserver.astro";
import type { ConfigHeading } from "@src/components/navigation/sidebars/types";
export interface Props {
@ -22,5 +21,3 @@ const { title, description, headings } = Astro.props;
</div>
<TOC slot="alongside-content" mobile={false} title={title} headings={headings} data-pagefind-ignore/>
</DocsLayout>
<TOCIntersectionObserver/>

View file

@ -70,13 +70,13 @@ const details = type.details
<Properties props={type.properties!}/>
)}
{ (type.functions?.length ?? 0) != 0 && (
<h2>Functions <a href={`/docs/${version.name}guide/qml-language#functions`}>[?]</a></h2>
<h2>Functions <a href={`/docs/${version.name}/guide/qml-language#functions`}>[?]</a></h2>
<Functions
funcData={type.functions!}
/>
)}
{ Object.keys(type.signals ?? {}).length != 0 && (
<h2>Signals <a href={`/docs/${version.name}guide/qml-language#signals`}>[?]</a></h2>
<h2>Signals <a href={`/docs/${version.name}/guide/qml-language#signals`}>[?]</a></h2>
<Signals
signals={type.signals!}
/>

View file

@ -1,7 +1,7 @@
.accordion {
& summary {
list-style: none;
transition: background-color 0.15s ease-out;
transition: background-color var(--theme-transition);
}
& .accordion-container {
@ -12,7 +12,9 @@
& .accordion-container.animate {
/* this somehow breaks if both min AND max aren't animated */
transition: min-height 0.3s ease, max-height 0.3s ease;
transition:
min-height 0.3s ease,
max-height 0.3s ease;
min-height: var(--height);
max-height: var(--height);
}

View file

@ -20,25 +20,33 @@
background-color: hsl(var(--blue) 60% 98%);
padding: 0.618rem;
border: 1px solid hsl(var(--blue) 9% 75%);
transition:
background-color var(--theme-transition),
border-color var(--theme-transition);
&::before {
content: "";
position: absolute;
inset: var(--xs);
background-image: radial-gradient(hsl(var(--blue) 9% 75%) 1px,
transparent 1px);
background-image: radial-gradient(
hsl(var(--blue) 9% 75%) 1px,
transparent 1px
);
background-position: 50% 50%;
background-size: 1.1rem 1.1rem;
}
}
html.dark .featurelist-item {
html.dark .featurelist-item,
html:has(input#theme-manual-toggle:checked) .featurelist-item {
background-color: hsl(var(--blue) 100% 81% / 0.05);
border-color: hsl(0deg 0% 100% / 0.05);
&::before {
background-image: radial-gradient(hsl(0deg 0% 100% / 0.1) 1px,
transparent 1px);
background-image: radial-gradient(
hsl(0deg 0% 100% / 0.1) 1px,
transparent 1px
);
}
}
@ -53,10 +61,12 @@ html.dark .featurelist-item {
& .feature-subtitle {
color: #303030;
transition: color var(--theme-transition);
}
}
html.dark .feature-text {
html.dark .feature-text,
html:has(input#theme-manual-toggle:checked) .feature-text {
& .feature-subtitle {
color: #afafaf;
}
@ -99,8 +109,13 @@ html.dark .feature-text {
}
}
html:not(.dark) .feature-showcase .shiki,
html:not(.dark) .feature-showcase .shiki span {
html:not(.dark):not(:has(input#theme-manual-toggle:checked))
.feature-showcase
.shiki,
html:not(.dark):not(:has(input#theme-manual-toggle:checked))
.feature-showcase
.shiki
span {
background-color: #ffffff;
}
@ -139,7 +154,7 @@ html:not(.dark) .feature-showcase .shiki span {
transform-origin: center bottom;
transform: translateX(-50%);
&>div {
& > div {
transform: rotate(0deg);
animation: counter-spin 40s linear infinite;

View file

@ -33,6 +33,13 @@
justify-content: flex-start;
align-items: flex-start;
overflow: hidden;
opacity: 0;
transition: opacity 0.6s ease;
min-height: 200px; /* placeholder height */
&.initialized {
opacity: 1;
}
}
.marquee-content {
@ -44,6 +51,11 @@
padding: 0;
will-change: transform;
transform: translateX(0);
visibility: hidden;
.initialized & {
visibility: visible;
}
}
.marquee-item {
@ -56,7 +68,7 @@
box-sizing: border-box;
will-change: opacity;
&>* {
& > * {
z-index: 11;
}
@ -89,8 +101,8 @@
justify-content: space-between;
align-items: stretch;
transition:
background-color 0.3s,
opacity 0.3s;
background-color var(--theme-transition),
opacity var(--theme-transition);
z-index: 20;
user-select: none;
pointer-events: none;
@ -106,7 +118,7 @@
align-items: center;
height: 100%;
&>div {
& > div {
width: 2.5rem;
aspect-ratio: 1 / 1;
display: flex;
@ -120,7 +132,7 @@
&:hover {
cursor: pointer;
&>div {
& > div {
opacity: 0.9;
}
}
@ -154,10 +166,9 @@
.marquee-scroll-arrow {
height: unset;
&>div {
& > div {
background-color: #55555580;
border-radius: var(--radius-xs);
}
}
}

View file

@ -7,8 +7,8 @@ html {
font-weight: 400;
height: 100svh;
width: 100svw;
max-width: 100svw;
/* width: 100svw; causes horizontal overflow due to the scrollbar*/
width: 100%;
font-synthesis: none;
text-rendering: optimizeLegibility;

View file

@ -16,7 +16,9 @@ pre.shiki {
}
html.dark .shiki,
html.dark .shiki span {
html.dark .shiki span,
html:has(input#theme-manual-toggle:checked) .shiki,
html:has(input#theme-manual-toggle:checked) .shiki span {
color: var(--shiki-dark);
background-color: var(--shiki-dark-bg);
}
@ -27,8 +29,8 @@ pre {
overflow: hidden;
text-wrap: wrap;
transition:
background-color 0.3s var(--ease-in-out),
color 0.3s var(--ease-in-out);
background-color var(--theme-transition),
color var(--theme-transition);
& .copy-button {
all: unset;
@ -48,17 +50,38 @@ pre {
background-color: hsla(var(--blue) 85 35 / 0.01);
cursor: pointer;
transition:
background-color 0.3s var(--ease-in-out),
color 0.3s var(--ease-in-out);
background-color var(--theme-transition),
color var(--theme-transition);
z-index: 10;
& svg {
position: absolute;
transition:
transform 0.3s var(--ease-in-out),
opacity 0.3s var(--ease-in-out);
}
& .check-icon {
opacity: 0;
transform: scale(0.5);
color: hsl(var(--green) 100 69);
}
&:hover {
color: hsla(var(--blue) 100 75 / 0.75);
background-color: hsla(var(--blue) 85 35 / 0.1);
}
&.copied {
animation: var(--animate-bounce);
& .copy-icon {
opacity: 0;
transform: scale(0.5);
}
& .check-icon {
opacity: 1;
transform: scale(1);
}
}
}

View file

@ -1,11 +1,11 @@
:root {
html:not(.dark):not(:has(input#theme-manual-toggle:checked)) {
color-scheme: light dark;
/* accent */
--green: 141deg;
--accent-400: var(--green) 90% 57%;
--accent-500: var(--green) 90% 47%;
--accent-600: var(--green) 88% 40%;
--accent-700: var(--green) 70% 40%;
--accent-700: var(--green) 70% 35%;
/* secondary */
--blue: 224deg;
@ -18,44 +18,45 @@
/* primary */
--white: 194deg;
--bg-400: var(--white) 10% 95%;
--bg-500: var(--white) 5% 90%;
--bg-600: var(--white) 5% 76%;
--bg-700: var(--white) 5% 56%;
--bg-800: var(--white) 5% 36%;
--bg-400: var(--white) 10% 98%;
--bg-500: var(--white) 10% 95%;
--bg-600: var(--white) 8% 88%;
--bg-700: var(--white) 8% 78%;
--bg-800: var(--white) 5% 56%;
--bg-900: var(--white) 5% 16%;
/* docs */
--background: var(--bg-500);
--text: var(--white) 0% 0%;
--text-dark: var(--white) 0% 18%;
--text-darker: var(--white) 0% 30%;
--link: var(--green) 48% 40%;
--toc-link: var(--green) 74% 30%;
--toc-link-active: var(--green) 80% 38%;
--prop-color: 350deg 78% 70%;
--prop-link-color: 350deg 78% 60%;
--func-color: 50deg 68% 50%;
--func-link-color: 50deg 58% 55%;
--signal-color: 270deg 78% 70%;
--signal-link-color: 270deg 85% 60%;
--var-color: 190deg 78% 70%;
--var-link-color: 190deg 85% 60%;
--background: var(--bg-400);
--text: var(--white) 0% 10%;
--text-dark: var(--white) 0% 25%;
--text-darker: var(--white) 0% 40%;
--link: var(--green) 60% 35%;
--toc-link: var(--white) 0% 40%;
--toc-link-active: var(--green) 60% 35%;
--prop-color: 350deg 78% 65%;
--prop-link-color: 350deg 78% 45%;
--func-color: 50deg 78% 45%;
--func-link-color: 50deg 85% 30%;
--signal-color: 270deg 60% 65%;
--signal-link-color: 270deg 75% 45%;
--var-color: 190deg 78% 65%;
--var-link-color: 190deg 85% 40%;
--inner-param-color: 215deg 80% 27%;
--inner-param-border-color: 215deg 50% 50%;
--nav-hovered-bkg: var(--blue) 100% 87%;
--nav-hovered-weak-bkg: var(--blue) 100% 91%;
--nav-selected-bkg: var(--blue) 100% 90%;
--nav-selected-hovered-bkg: var(--blue) 100% 85%;
--nav-selected-text: var(--blue) 60% 60%;
--nav-hovered-bkg: var(--blue) 100% 94%;
--nav-hovered-weak-bkg: var(--blue) 100% 96%;
--nav-selected-bkg: var(--blue) 100% 92%;
--nav-selected-hovered-bkg: var(--blue) 100% 88%;
--nav-selected-text: var(--blue) 70% 45%;
--nav-indicator-bkg: var(--blue) 45% 80%;
--toc-hovered-bkg: 0deg 0% 0% / 0.1;
--overlay-bkg: var(--blue) 25% 93%;
--overlay-bkg-border: var(--blue) 10% 75%;
--footer-bkg: var(--blue) 8% 87%;
--footer-bkg-border: var(--blue) 32% 84%;
--toc-hovered-bkg: 0deg 0% 0% / 0.05;
--overlay-bkg: var(--white) 10% 98%;
--overlay-bkg-border: var(--white) 10% 85%;
--footer-bkg: var(--white) 10% 95%;
--footer-bkg-border: var(--white) 10% 88%;
}
html.dark,
html:has(input#theme-manual-toggle:checked) {
/* accent */
--green: 141deg;

View file

@ -32,15 +32,15 @@ body {
/* aside, */
details,
/* figcaption, */
/* figure, */
/* footer, */
/* header, */
/* hgroup, */
/* main, */
/* menu, */
/* nav, */
/* section, */
summary {
/* figure, */
/* footer, */
/* header, */
/* hgroup, */
/* main, */
/* menu, */
/* nav, */
/* section, */
summary {
display: block;
}

View file

@ -77,4 +77,6 @@ html {
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--theme-transition: 0.3s var(--ease-in-out);
}

View file

@ -7,7 +7,7 @@
[data-scope="collapsible"][data-part="content"] {
padding: 0;
margin: 0;
transition: all 300ms;
transition: all var(--theme-transition);
}
[data-scope="collapsible"][data-part="content"][data-state="open"] {

View file

@ -68,7 +68,9 @@
margin-bottom: 0.618rem;
border-radius: 12px;
padding: 0.8rem;
transition: border 0.3s;
transition:
background-color var(--theme-transition),
border-color var(--theme-transition);
}
& .typedata-details {
@ -164,6 +166,9 @@
.typeprops {
& .typeprop-root {
border: 1px solid hsl(var(--prop-color) / 0.6);
transition:
background-color var(--theme-transition),
border-color var(--theme-transition);
&:hover {
border: 1px solid hsl(var(--prop-color));
@ -175,11 +180,13 @@
& .typeprop-name {
color: hsl(var(--prop-link-color));
transition: color var(--theme-transition);
}
}
}
html.dark .typeprops {
html.dark .typeprops,
html:has(input#theme-manual-toggle:checked) .typeprops {
& .typeprop-root {
border: 1px solid hsl(var(--prop-color) / 0.3);
@ -193,6 +200,9 @@ html.dark .typeprops {
.typefuncs {
& .typefunc-root {
border: 1px solid hsl(var(--func-color) / 0.6);
transition:
background-color var(--theme-transition),
border-color var(--theme-transition);
&:hover {
border: 1px solid hsl(var(--func-color));
@ -204,6 +214,7 @@ html.dark .typeprops {
& .typefunc-name {
color: hsl(var(--func-link-color));
transition: color var(--theme-transition);
}
& .typefunc-params {
@ -222,7 +233,8 @@ html.dark .typeprops {
}
}
html.dark .typefuncs {
html.dark .typefuncs,
html:has(input#theme-manual-toggle:checked) .typefuncs {
& .typefunc-root {
border: 1px solid hsl(var(--func-color) / 0.3);
@ -236,6 +248,9 @@ html.dark .typefuncs {
.typesignals {
& .typesignal-root {
border: 1px solid hsl(var(--signal-color) / 0.6);
transition:
background-color var(--theme-transition),
border-color var(--theme-transition);
&:hover {
border: 1px solid hsl(var(--signal-color));
@ -249,6 +264,7 @@ html.dark .typefuncs {
position: relative;
width: max-content;
color: hsl(var(--signal-link-color));
transition: color var(--theme-transition);
& .typesignal-doclink {
top: -12px;
@ -274,7 +290,8 @@ html.dark .typefuncs {
}
}
html.dark .typesignals {
html.dark .typesignals,
html:has(input#theme-manual-toggle:checked) .typesignals {
& .typesignal-root {
border: 1px solid hsl(var(--signal-color) / 0.3);
@ -288,6 +305,9 @@ html.dark .typesignals {
.typevariants {
& .typevariant-root {
border: 1px solid hsl(var(--var-color) / 0.6);
transition:
background-color var(--theme-transition),
border-color var(--theme-transition);
&:hover {
border: 1px solid hsl(var(--var-color));
@ -301,6 +321,7 @@ html.dark .typesignals {
position: relative;
width: max-content;
color: hsl(var(--var-link-color));
transition: color var(--theme-transition);
& .typevariant-doclink {
position: absolute;
@ -313,7 +334,8 @@ html.dark .typesignals {
}
}
html.dark .typevariants {
html.dark .typevariants,
html:has(input#theme-manual-toggle:checked) .typevariants {
& .typevariant-root {
border: 1px solid hsl(var(--var-color) / 0.3);

View file

@ -2,7 +2,7 @@
@import "./docs-types.css";
.docslayout {
transition: background-color 0.3s;
transition: background-color var(--theme-transition);
}
.docslayout-root {
@ -12,7 +12,7 @@
justify-content: center;
flex-direction: row;
flex-grow: 1;
transition: filter 0.3s;
transition: filter var(--theme-transition);
}
.docslayout-inner {
@ -31,9 +31,12 @@
pointer-events: none;
}
:not(html.dark) > .dim-content-toc,
:not(html.dark) > .dim-content-nav {
html:not(.dark):not(:has(input#theme-manual-toggle:checked)) > .dim-content-toc,
html:not(.dark):not(:has(input#theme-manual-toggle:checked)) > .dim-content-nav {
background-color: #909090;
}
.docs-content {
@ -66,7 +69,7 @@
--color-link-breadcrumbs: hsl(var(--link));
margin-top: 0.5rem;
margin-bottom: 0.318rem;
max-width: 100svw;
max-width: 100%;
}
.heading {

View file

@ -20,7 +20,9 @@
.nav-item {
display: block;
border-radius: 6px;
transition: background-color 0.2s ease;
transition:
background-color var(--theme-transition),
color var(--theme-transition);
padding: 0.4em;
font-size: 1rem;
@ -39,6 +41,7 @@
}
.fade {
mask-image: linear-gradient(to right, #000 80%, transparent);
-webkit-mask-image: linear-gradient(
to right,
#000 80%,
@ -55,7 +58,9 @@
& > div {
& > .nav-collapse-marker,
a {
transition: background-color 0.2s ease;
transition:
background-color var(--theme-transition),
color var(--theme-transition);
}
& > .nav-collapse-marker {

View file

@ -7,12 +7,16 @@
.nav-icon {
opacity: 0;
transform: scale(0.5);
position: absolute;
transition: opacity 0.6s;
transition:
opacity var(--theme-transition),
transform var(--theme-transition);
}
.nav-icon.active {
opacity: 1;
transform: scale(1);
position: relative;
}
@ -50,7 +54,9 @@
display: none;
}
transition: left 0.3s ease, padding 0.3s ease;
transition:
left 0.3s ease,
padding 0.3s ease;
&.shown {
display: flex;

View file

@ -4,20 +4,48 @@
}
#qs_search {
--search-result-spacing: calc(1.25rem * var(--pagefind-ui-scale));
--search-result-pad-inline-start: calc(3.75rem * var(--pagefind-ui-scale));
--search-result-pad-inline-end: calc(1.25rem * var(--pagefind-ui-scale));
--search-result-pad-block: calc(0.9375rem * var(--pagefind-ui-scale));
--search-result-nested-pad-block: calc(0.625rem * var(--pagefind-ui-scale));
--search-corners: calc(0.3125rem * var(--pagefind-ui-scale));
--search-page-icon-size: calc(1.875rem * var(--pagefind-ui-scale));
--search-page-icon-inline-start: calc(
(var(--search-result-pad-inline-start) - var(--search-page-icon-size)) / 2
--search-result-spacing: calc(
1.25rem *
var(--pagefind-ui-scale)
);
--search-result-pad-inline-start: calc(
3.75rem *
var(--pagefind-ui-scale)
);
--search-result-pad-inline-end: calc(
1.25rem *
var(--pagefind-ui-scale)
);
--search-result-pad-block: calc(
0.9375rem *
var(--pagefind-ui-scale)
);
--search-result-nested-pad-block: calc(
0.625rem *
var(--pagefind-ui-scale)
);
--search-corners: calc(0.3125rem * var(--pagefind-ui-scale));
--search-page-icon-size: calc(
1.875rem *
var(--pagefind-ui-scale)
);
--search-page-icon-inline-start: calc(
(
var(--search-result-pad-inline-start) -
var(--search-page-icon-size)
) /
2
);
--search-tree-diagram-size: calc(
2.5rem *
var(--pagefind-ui-scale)
);
--search-tree-diagram-size: calc(2.5rem * var(--pagefind-ui-scale));
--search-tree-diagram-inline-start: calc(
(var(--search-result-pad-inline-start) - var(--search-tree-diagram-size)) /
2
(
var(--search-result-pad-inline-start) -
var(--search-tree-diagram-size)
) /
2
);
}
@ -29,7 +57,12 @@
#qs_search
.pagefind-ui--reset
*:where(:not(html, iframe, canvas, img, svg, video):not(svg *, symbol *)) {
*:where(
:not(html, iframe, canvas, img, svg, video):not(
svg *,
symbol *
)
) {
outline: unset;
}
@ -57,9 +90,11 @@
#qs_search .pagefind-ui__search-clear::before {
content: "";
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E")
-webkit-mask:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E")
center / 50% no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E")
mask:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E")
center / 50% no-repeat;
background-color: hsl(0deg 25% 45%);
display: block;
@ -84,14 +119,18 @@
}
#qs_search
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)),
.pagefind-ui__result-title:not(
:where(.pagefind-ui__result-nested *)
),
#qs_search .pagefind-ui__result-nested {
position: relative;
background-color: hsl(0deg 0% 10%);
}
#qs_search
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):hover,
.pagefind-ui__result-title:not(
:where(.pagefind-ui__result-nested *)
):hover,
#qs_search
.pagefind-ui__result-title:not(
:where(.pagefind-ui__result-nested *)
@ -122,12 +161,17 @@
border-radius: 0 0 var(--search-corners) var(--search-corners);
}
#qs_search .pagefind-ui__result-inner > .pagefind-ui__result-title {
padding: var(--search-result-pad-block) var(--search-result-pad-inline-end);
#qs_search
.pagefind-ui__result-inner
> .pagefind-ui__result-title {
padding: var(--search-result-pad-block)
var(--search-result-pad-inline-end);
padding-inline-start: var(--search-result-pad-inline-start);
}
#qs_search .pagefind-ui__result-inner > .pagefind-ui__result-title::before {
#qs_search
.pagefind-ui__result-inner
> .pagefind-ui__result-title::before {
content: "";
position: absolute;
inset-block: 0;
@ -155,7 +199,9 @@
text-decoration: none;
}
#qs_search .pagefind-ui__result-nested .pagefind-ui__result-link::before {
#qs_search
.pagefind-ui__result-nested
.pagefind-ui__result-link::before {
content: unset;
}
@ -166,9 +212,11 @@
inset-inline-start: var(--search-tree-diagram-inline-start);
width: var(--search-tree-diagram-size);
background: hsl(var(--blue) 10% 30%);
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E")
-webkit-mask:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E")
0% 0% / 100% no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E")
mask:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E")
0% 0% / 100% no-repeat;
}
@ -188,7 +236,9 @@
overflow-wrap: anywhere;
}
#qs_search .pagefind-ui__result-inner > .pagefind-ui__result-excerpt {
#qs_search
.pagefind-ui__result-inner
> .pagefind-ui__result-excerpt {
display: inline-block;
position: relative;
background: hsl(0deg 0% 10%);
@ -204,9 +254,11 @@
inset-inline-start: var(--search-tree-diagram-inline-start);
width: var(--search-tree-diagram-size);
background: hsl(var(--blue) 10% 30%);
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m'/%3E%3C/svg%3E")
-webkit-mask:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m'/%3E%3C/svg%3E")
0% 0% / 100% no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m'/%3E%3C/svg%3E")
mask:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m'/%3E%3C/svg%3E")
0% 0% / 100% no-repeat;
}
}
@ -219,9 +271,20 @@
/* default styles */
site-search {
--shadow-lg: 0px 25px 7px hsl(0deg, 0%, 0%, 0.03),
0px 16px 6px hsl(0deg, 0%, 0%, 0.1), 0px 9px 5px hsl(223deg, 13%, 10%, 0.33),
0px 4px 4px hsl(0deg, 0%, 0%, 0.75), 0px 4px 2px hsl(0deg, 0%, 0%, 0.25);
--shadow-lg:
0px 25px 7px hsl(0deg, 0%, 0%, 0.03),
0px 16px 6px hsl(0deg, 0%, 0%, 0.1), 0px 9px 5px hsl(
223deg,
13%,
10%,
0.33
),
0px 4px 4px hsl(0deg, 0%, 0%, 0.75), 0px 4px 2px hsl(
0deg,
0%,
0%,
0.25
);
display: contents;
}
@ -275,7 +338,8 @@ button > kbd {
background-color: hsl(var(--blue) 15% 80%);
}
html.dark button > kbd {
html.dark button > kbd,
html:has(input#theme-manual-toggle:checked) button > kbd {
background-color: hsl(var(--blue) 5% 20% / 0.5);
}
@ -368,7 +432,9 @@ button[data-close-modal] {
}
}
html.dark button[data-open-modal] {
html.dark button[data-open-modal],
html:has(input#theme-manual-toggle:checked)
button[data-open-modal] {
background-color: hsla(var(--blue) 15% 15% / 0.5);
color: hsl(var(--blue) 40% 65%);

View file

@ -30,6 +30,28 @@
font-size: 1.614rem;
max-height: 500px;
& > div {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
& .toc-icon {
opacity: 0;
transform: scale(0.5);
position: absolute;
transition:
opacity var(--theme-transition),
transform var(--theme-transition);
&.active {
opacity: 1;
transform: scale(1);
position: relative;
}
}
& > svg {
height: 100%;
width: 24px;
@ -61,7 +83,9 @@
display: none;
}
transition: width 0.3s ease, padding 0.3s ease;
transition:
width 0.3s ease,
padding 0.3s ease;
&.shown {
& .toc-content {
@ -115,7 +139,7 @@
margin-right: 1.272rem;
& .toc_a {
transition: color 0.33s;
transition: color var(--theme-transition);
color: hsl(var(--toc-link));
}

View file

@ -12,10 +12,6 @@
@import "./components/featurelist.css";
@import "./components/marquee.css";
.changing-theme * {
transition: none !important;
}
.theme-toggle {
height: 24px;
font-size: 1.614rem;
@ -31,17 +27,25 @@
.header {
background-color: hsl(var(--bg-400));
box-shadow: 0 1px 1px 1px hsla(var(--white) 100 0 / 0.1);
transition:
background-color var(--theme-transition),
color var(--theme-transition),
box-shadow var(--theme-transition);
}
.baselayout,
.docslayout {
background-color: hsl(var(--background));
color: hsl(var(--secondary-900));
transition:
background-color var(--theme-transition),
color var(--theme-transition);
}
a {
color: hsl(var(--link));
text-decoration: none;
transition: color var(--theme-transition);
&:hover {
text-decoration: underline;
@ -50,12 +54,15 @@ a {
}
html.dark .baselayout,
html.dark .docslayout {
html.dark .docslayout,
html:has(input#theme-manual-toggle:checked) .baselayout,
html:has(input#theme-manual-toggle:checked) .docslayout {
background-color: hsl(var(--bg-900));
color: hsl(var(--secondary-400));
}
html.dark {
html.dark,
html:has(input#theme-manual-toggle:checked) {
& .header {
background-color: hsl(var(--secondary-900));
color: hsl(var(--secondary-500));
@ -153,6 +160,7 @@ body.overflow-toc {
footer {
position: relative;
margin-top: var(--2xl);
width: 100%;
font-size: 0.9rem;
display: flex;
@ -160,15 +168,17 @@ footer {
padding: 1rem 2rem;
flex-shrink: 0;
background: hsl(var(--footer-bkg));
transition:
background-color var(--theme-transition),
color var(--theme-transition);
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 54%;
right: calc(50% + 1.25rem);
height: 1px;
width: calc(48%);
background: linear-gradient(
90deg,
transparent 0%,
@ -181,9 +191,8 @@ footer {
position: absolute;
top: 0;
right: 0;
left: 54%;
left: calc(50% + 1.25rem);
height: 1px;
width: calc(48% - 1rem);
background: linear-gradient(
90deg,
hsl(var(--footer-bkg-border)) 0%,
@ -195,16 +204,17 @@ footer {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
width: max-content;
height: max-content;
}
& a {
color: hsl(var(--text-dark));
transition: color 0.3s ease;
transition: color var(--theme-transition);
& .hint {
transition: color 0.3s ease;
transition: color var(--theme-transition);
}
&:nth-child(2) .hint {

View file

@ -40,15 +40,20 @@
}
h1.gradient-text {
background: linear-gradient(30deg,
hsl(var(--green) 80% 42%),
hsl(var(--blue) 80% 49%));
background: linear-gradient(
30deg,
hsl(var(--green) 80% 42%),
hsl(var(--blue) 80% 49%)
);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
html.dark h1.gradient-text {
html.dark h1.gradient-text,
html:has(input#theme-manual-toggle:checked) h1.gradient-text {
background: linear-gradient(30deg, #42b96b, #4281b9);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
@ -70,7 +75,8 @@ html.dark h1.gradient-text {
}
}
html.dark .main-page_hero-text {
html.dark .main-page_hero-text,
html:has(input#theme-manual-toggle:checked) .main-page_hero-text {
& h2 {
color: hsl(var(--blue) 100% 83%);
}
@ -130,9 +136,9 @@ html.dark .main-page_hero-text {
overflow: hidden;
box-shadow: var(--shadow-md);
transition:
background-color 0.3s,
box-shadow 0.3s,
border-color 0.3s;
background-color var(--theme-transition),
box-shadow var(--theme-transition),
border-color var(--theme-transition);
background-color: hsl(var(--green) 38% 30%);
color: hsl(194deg 0% 100%);
@ -164,7 +170,8 @@ html.dark .main-page_hero-text {
}
}
html.dark .main-page_link-card {
html.dark .main-page_link-card,
html:has(input#theme-manual-toggle:checked) .main-page_link-card {
background-color: hsl(var(--green) 38% 25%);
color: hsl(194deg 0% 100%);

View file

@ -1,9 +1,7 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"lib": [
"es2016"
],
"lib": ["es2016"],
"plugins": [
{
"name": "@astrojs/ts-plugin"
@ -14,36 +12,16 @@
"verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": {
"@*": [
"./*"
],
"@/*": [
"./src/*"
],
"@config/*": [
"./src/config/*"
],
"@icons": [
"./src/components/icons.tsx"
],
"@icons/*": [
"./src/icons/*"
],
"@components/*": [
"./src/components/*"
],
"@layouts/*": [
"./src/layouts/*"
],
"@styles/*": [
"./src/styles/*"
],
"@_types": [
"./src/config/_types/index.ts"
],
"@_types/*": [
"./src/config/_types/*"
]
"@*": ["./*"],
"@/*": ["./src/*"],
"@config/*": ["./src/config/*"],
"@icons": ["./src/components/icons.tsx"],
"@icons/*": ["./src/icons/*"],
"@components/*": ["./src/components/*"],
"@layouts/*": ["./src/layouts/*"],
"@styles/*": ["./src/styles/*"],
"@_types": ["./src/config/_types/index.ts"],
"@_types/*": ["./src/config/_types/*"]
}
}
}

1134
yarn.lock

File diff suppressed because it is too large Load diff