squash and nuke dev

This commit is contained in:
outfoxxed 2026-02-18 02:40:40 -08:00
parent b2d43ad425
commit f26e76c114
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
93 changed files with 33827 additions and 7831 deletions

2
.env.development Normal file
View file

@ -0,0 +1,2 @@
VERSION_FILE_PATH="./versions.json"
BASE_URL="localhost:4321"

5
.gitignore vendored
View file

@ -27,4 +27,7 @@ modules/
modules_old/ modules_old/
# env # env
.env* .env.production
# module archive
*.tar.gz

19608
.pnp.cjs generated Executable file

File diff suppressed because one or more lines are too long

3022
.pnp.loader.mjs generated Normal file

File diff suppressed because it is too large Load diff

4
.vim/coc-settings.json Normal file
View file

@ -0,0 +1,4 @@
{
"workspace.workspaceFolderCheckCwd": false,
"tsserver.tsdk": ".yarn/sdks/typescript/lib"
}

1
.yarnrc.yml Normal file
View file

@ -0,0 +1 @@
nodeLinker: pnp

View file

@ -1,18 +1,62 @@
# Quickshell Docs Quickshell Docs
===============
Documentation for [quickshell](https://git.outfoxxed.me/outfoxxed/quickshell) Documentation for [quickshell](https://git.outfoxxed.me/outfoxxed/quickshell) Hosted version at [quickshell.org](https://quickshell.outfoxxed.me)
Hosted version at [quickshell.outfoxxed.me](https://quickshell.outfoxxed.me)
Frontend rewritten by [Xanazf](https://github.com/Xanazf) Frontend rewritten by [Xanazf](https://github.com/Xanazf)
## Notes for future updates ---
Development
-----------
### Install language server attribution
> [!INFO] Yarn
>
> ```bash
> yarn dlx @yarnpkg/sdks base
> ```
======
> [!INFO] NPM
>
> ```bash
> npx @yarnpkg/sdks base
> ```
---
### Enable the language server to use yarn sdks
> [!NOTE] Example for Neovim
>
> ```lua
> {
> -- ...
> typescript = {
> -- ...
> tsdk = "./.yarn/sdks/typescript/lib",
> -- ...
> },
> -- or whatever language server you're using
> vtsls = {
> autoUseWorkspaceTsdk = true,
> }
> }
> ```
Notes for future updates
------------------------
~- improve Head~ ~- improve Head~
- improve light theme - improve light theme
- QtQML docs search - QtQML docs search
- page metadata: - page metadata:
- lastUpdatedAt - `min_version`
- prevUpdate? - `max_version`
- editURL - `edit_URL`
- typedocs clearer borders between layout parts - typedocs clearer borders between layout parts
- better front page styling

View file

@ -1,4 +1,4 @@
import { defineConfig } from "astro/config"; import { defineConfig, envField } from "astro/config";
import solidJs from "@astrojs/solid-js"; import solidJs from "@astrojs/solid-js";
import mdx from "@astrojs/mdx"; import mdx from "@astrojs/mdx";
import icon from "astro-icon"; import icon from "astro-icon";
@ -10,14 +10,32 @@ import { markdownConfig } from "./src/config/io/markdown.ts";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
markdown: markdownConfig, markdown: markdownConfig,
site: "https://quickshell.outfoxxed.me", site: "https://quickshell.org",
integrations: [ integrations: [
solidJs({ solidJs({
devtools: true, devtools: false,
}), }),
mdx(), mdx(),
pagefind(), pagefind(),
icon(), icon(),
sitemap(), sitemap(),
], ],
env: {
schema: {
VERSION_FILE_PATH: envField.string({
context: "server",
access: "secret",
default: "./versions.json",
}),
BASE_URL: envField.number({
context: "server",
access: "public",
}),
PRODUCTION: envField.string({
context: "server",
access: "secret",
optional: true,
}),
},
},
}); });

View file

@ -1,13 +1,16 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json", "$schema": "https://biomejs.dev/schemas/2.4.1/schema.json",
"files": {
"includes": ["**", "!!**/dist", "!**/node_modules", "!**/.yarn"]
},
"formatter": { "formatter": {
"enabled": true, "enabled": true,
"formatWithErrors": true, "formatWithErrors": true,
"indentStyle": "space", "indentStyle": "space",
"indentWidth": 2, "indentWidth": 2,
"lineEnding": "lf", "lineEnding": "lf",
"lineWidth": 66, "lineWidth": 80,
"attributePosition": "multiline" "attributePosition": "auto"
}, },
"plugins": [], "plugins": [],
"linter": { "linter": {
@ -25,6 +28,14 @@
} }
} }
}, },
"html": {
"formatter": {
"enabled": true,
"indentScriptAndStyle": true,
"lineWidth": 80
},
"experimentalFullSupportEnabled": true
},
"javascript": { "javascript": {
"formatter": { "formatter": {
"jsxQuoteStyle": "double", "jsxQuoteStyle": "double",
@ -51,12 +62,18 @@
"cssModules": true "cssModules": true
} }
}, },
"json": {
"formatter": {
"enabled": true,
"lineWidth": 80,
"lineEnding": "lf",
"indentStyle": "space",
"indentWidth": 2
}
},
"overrides": [ "overrides": [
{ {
"includes": [ "includes": ["**/*.ts", "**/*.tsx"],
"**/*.ts",
"**/*.tsx"
],
"linter": { "linter": {
"rules": { "rules": {
"complexity": { "complexity": {
@ -78,19 +95,6 @@
} }
} }
} }
},
{
"includes": [
"*.astro"
],
"linter": {
"rules": {
"style": {
"useConst": "off",
"useImportType": "off"
}
}
}
} }
] ]
} }

8446
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{ {
"name": "quickshell-docs", "name": "quickshell-docs",
"type": "module", "type": "module",
"version": "0.0.1", "version": "0.1.1",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"start": "astro dev", "start": "astro dev",
@ -10,33 +10,45 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@ark-ui/solid": "^3.5.0", "@astrojs/check": "0.9.6",
"@astrojs/check": "^0.9.4", "@astrojs/markdown-remark": "6.3.10",
"@astrojs/markdown-remark": "^6.3.1", "@astrojs/mdx": "4.3.13",
"@astrojs/mdx": "^4.2.6", "@astrojs/sitemap": "3.7.0",
"@astrojs/sitemap": "^3.4.0", "@astrojs/solid-js": "^5.1.3",
"@astrojs/solid-js": "^5.0.10", "@fontsource-variable/rubik": "^5.2.8",
"@fontsource-variable/rubik": "^5.1.0",
"@hbsnow/rehype-sectionize": "^1.0.7", "@hbsnow/rehype-sectionize": "^1.0.7",
"@pagefind/default-ui": "^1.1.1", "@pagefind/default-ui": "^1.4.0",
"@shikijs/rehype": "^3.4.2", "@shikijs/rehype": "^3.22.0",
"@types/node": "^20.14.11", "astro": "5.17.2",
"astro": "^5.7.13", "astro-breadcrumbs": "^3.3.3",
"astro-breadcrumbs": "^3.3.1",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"hast": "^1.0.0",
"hast-util-from-html": "^2.0.3", "hast-util-from-html": "^2.0.3",
"remark-github-blockquote-alert": "^1.2.1", "hastscript": "^9.0.1",
"solid-devtools": "^0.30.1", "jsdom": "^28.1.0",
"solid-js": "^1.8.18", "rehype": "^13.0.2",
"typescript": "^5.5.3", "remark-github-blockquote-alert": "^2.0.1",
"solid-js": "^1.9.11",
"unified": "^11.0.5", "unified": "^11.0.5",
"unist-util-visit": "^5.0.0" "unist-util-visit": "^5.1.0"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/ts-plugin": "^1.10.3", "@astrojs/ts-plugin": "1.10.6",
"@biomejs/biome": "^1.8.3", "@babel/core": "^7.29.0",
"pagefind": "^1.1.1" "@babel/plugin-syntax-typescript": "^7.28.6",
"@biomejs/biome": "^2.3.15",
"@types/babel__core": "^7.20.5",
"@types/hast": "^3.0.4",
"@types/jsdom": "^27.0.0",
"@types/mdast": "^4.0.4",
"@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.22.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"vite": "^7.3.1"
}, },
"packageManager": "yarn@4.5.0" "packageManager": "yarn@4.12.0"
} }

View file

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

View file

@ -1,14 +1,11 @@
--- ---
import "@styles/components/accordion.css"; import "@styles/components/accordion.css";
--- ---
<details class=`accordion ${Astro.props.class ?? ""}` {...Astro.props}>
<summary> <details class={`accordion ${Astro.props.class ?? ""}`} {...Astro.props}>
<slot name="header"/> <summary><slot name="header" /></summary>
</summary>
<div class="accordion-container"> <div class="accordion-container">
<div> <div><slot /></div>
<slot/>
</div>
</div> </div>
</details> </details>
<script> <script>
@ -16,7 +13,9 @@ import "@styles/components/accordion.css";
document.querySelectorAll(".accordion").forEach(element => { document.querySelectorAll(".accordion").forEach(element => {
const accordion = element as HTMLDetailsElement; const accordion = element as HTMLDetailsElement;
const summary = accordion.querySelector("summary")!; const summary = accordion.querySelector("summary")!;
const body = accordion.querySelector(".accordion-container") as HTMLDivElement; const body = accordion.querySelector(
".accordion-container"
) as HTMLDivElement;
summary.addEventListener("click", event => { summary.addEventListener("click", event => {
if ((event.target as Element).tagName === "A") return; if ((event.target as Element).tagName === "A") return;

View file

@ -1,4 +1,5 @@
--- ---
const production = import.meta.env.PRODUCTION; const production = import.meta.env.PRODUCTION;
--- ---
{production && <script is:inline defer data-domain="quickshell.outfoxxed.me" src="https://z.outfoxxed.me/z.js"></script>} {production && <script is:inline defer data-domain="quickshell.outfoxxed.me" src="https://z.outfoxxed.me/z.js"></script>}

View file

@ -9,13 +9,13 @@ export interface Props {
const { badgeText, withIcon = true, badgeIconName } = Astro.props; const { badgeText, withIcon = true, badgeIconName } = Astro.props;
--- ---
<span class="badge"> <span class="badge">
{withIcon && {withIcon &&
( (
badgeIconName ? badgeIconName ?
<Icon name={badgeIconName}/> <Icon name={badgeIconName}/>
: <Icon name={"flag"}/> : <Icon name={"flag"}/>
) )}
}
<span class="badge-text">{badgeText}</span> <span class="badge-text">{badgeText}</span>
</span> </span>

View file

@ -7,10 +7,11 @@ interface Props {
} }
const { title } = Astro.props; const { title } = Astro.props;
--- ---
<Accordion class="docs-collapsible"> <Accordion class="docs-collapsible">
<div slot="header"> <div slot="header">
<Fragment set:html={collapsibleMarker}/> <Fragment set :html={collapsibleMarker} />
{title} {title}
</div> </div>
<slot> <slot />
</Accordion> </Accordion>

View file

@ -2,6 +2,7 @@
import matrixLogo from "@icons/matrix-logo.svg?raw"; import matrixLogo from "@icons/matrix-logo.svg?raw";
import discordLogo from "@icons/discord-logo.svg?raw"; import discordLogo from "@icons/discord-logo.svg?raw";
import gitLogo from "@icons/git-logo.svg?raw"; import gitLogo from "@icons/git-logo.svg?raw";
import ThemeToggle from "./ThemeToggle.astro";
interface Props { interface Props {
class?: string; class?: string;
@ -9,6 +10,7 @@ interface Props {
const props = Astro.props; const props = Astro.props;
--- ---
<footer class=`${props.class ?? ""}`> <footer class=`${props.class ?? ""}`>
<div class="credits"> <div class="credits">
<p class="hint">Brought to you by:</p> <p class="hint">Brought to you by:</p>
@ -18,6 +20,7 @@ const props = Astro.props;
and our contributors and our contributors
</a> </a>
</div> </div>
<ThemeToggle />
<div class="socials-changelog"> <div class="socials-changelog">
<section class="socials"> <section class="socials">
<a href="https://matrix.to/#/#quickshell:outfoxxed.me" target="_blank" aria-label="Join our matrix space"> <a href="https://matrix.to/#/#quickshell:outfoxxed.me" target="_blank" aria-label="Join our matrix space">

View file

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

View file

@ -0,0 +1,50 @@
---
import { Icon } from "astro-icon/components";
---
<label
class="theme-toggle icon-button standard"
title="Toggle theme"
for="theme-manual-toggle"
>
<Icon
name="moon"
class="light-icon"
style="width: 24px; height: 24px;"
aria-hidden="true"
/>
<Icon
name="sun"
class="dark-icon"
style="width: 24px; height: 24px;"
aria-hidden="true"
/>
<div class="state-layer"></div>
</label>
<style>
.theme-toggle {
cursor: pointer;
user-select: none;
}
.light-icon {
display: block;
}
.dark-icon {
display: none;
}
:global(html:has(input#theme-manual-toggle:checked)) .light-icon {
display: none;
}
:global(html:has(input#theme-manual-toggle:checked)) .dark-icon {
display: block;
}
.theme-toggle:focus-within {
outline: 2px solid var(--accent-600);
border-radius: 50%;
}
</style>

View file

@ -1,7 +1,9 @@
--- ---
import { processMarkdown } from "@config/io/markdown"; import { processMarkdown } from "@config/io/markdown";
const codeDesktop = await processMarkdown("N/A", `\`\`\`qml const codeDesktop = await processMarkdown(
"N/A",
`\`\`\`qml
// a standard desktop window // a standard desktop window
FloatingWindow { FloatingWindow {
Timer { Timer {
@ -19,9 +21,12 @@ FloatingWindow {
// change the window's color when timer.invert changes // change the window's color when timer.invert changes
color: timer.invert ? "purple" : "green" color: timer.invert ? "purple" : "green"
} }
\`\`\``); \`\`\``
);
const codeMobile = await processMarkdown("N/A", `\`\`\`qml const codeMobile = await processMarkdown(
"N/A",
`\`\`\`qml
// a standard desktop window // a standard desktop window
FloatingWindow { FloatingWindow {
Timer { Timer {
@ -47,19 +52,22 @@ FloatingWindow {
? "purple" ? "purple"
: "green" : "green"
} }
\`\`\``); \`\`\``
);
--- ---
<ul class="featurelist"> <ul class="featurelist">
<li class="featurelist-item hot-reloading left"> <li class="featurelist-item hot-reloading left">
<section class="feature-text"> <section class="feature-text">
<h3 class="feature-title">See your changes in real time</h3> <h3 class="feature-title">See your changes in real time</h3>
<span class="feature-subtitle"> <span class="feature-subtitle">
Quickshell loads changes as soon as they're saved, letting you iterate as fast as you can type. Quickshell loads changes as soon as they're saved, letting you iterate
as fast as you can type.
</span> </span>
</section> </section>
<section class="feature-showcase"> <section class="feature-showcase">
<video preload="metadata" controls={false} autoplay loop> <video preload="metadata" controls={false} autoplay loop>
<source src="/assets/simple-shell-livereload.mp4" type="video/mp4"/> <source src="/assets/simple-shell-livereload.mp4" type="video/mp4">
</video> </video>
</section> </section>
</li> </li>
@ -67,46 +75,69 @@ FloatingWindow {
<section class="feature-text"> <section class="feature-text">
<h3 class="feature-title">Easy to use language</h3> <h3 class="feature-title">Easy to use language</h3>
<span class="feature-subtitle"> <span class="feature-subtitle">
Quickshell is configured in QML, a simple language designed for creating flexible user interfaces. Quickshell is configured in QML, a simple language designed for creating
It also has LSP support. flexible user interfaces. It also has LSP support.
</span> </span>
</section> </section>
<section class="feature-showcase" id="qml-showcase"> <section class="feature-showcase" id="qml-showcase">
<div class="showcase-desktop"> <div class="showcase-desktop"><Fragment set :html={codeDesktop} /></div>
<Fragment set:html={codeDesktop}/> <div class="showcase-mobile"><Fragment set :html={codeMobile} /></div>
</div>
<div class="showcase-mobile">
<Fragment set:html={codeMobile}/>
</div>
</section> </section>
</li> </li>
<li class="featurelist-item cloud-li left"> <li class="featurelist-item cloud-li left">
<section class="feature-text"> <section class="feature-text">
<h3 class="feature-title">Extensive integrations</h3> <h3 class="feature-title">Extensive integrations</h3>
<span class="feature-subtitle"> <span class="feature-subtitle">
Quickshell comes with a large set of integrations, with new ones arriving all the time. Quickshell comes with a large set of integrations, with new ones
arriving all the time.
</span> </span>
</section> </section>
<section class="feature-showcase cloud"> <section class="feature-showcase cloud">
<section class="feature-cloud"> <section class="feature-cloud">
<div class="cloud-center"> <div class="cloud-center">
<img src="/favicon.svg" alt="Quickshell" /> <img src="/favicon.svg" alt="Quickshell">
</div> </div>
<div class="cloud-items-wrapper"> <div class="cloud-items-wrapper">
<span class="cloud-item wayland"> <span class="cloud-item wayland">
<div><img class="feature-icon" src="/assets/logos/wayland.svg" alt="Wayland" /></div> <div>
<img
class="feature-icon"
src="/assets/logos/wayland.svg"
alt="Wayland"
>
</div>
</span> </span>
<span class="cloud-item hyprland"> <span class="cloud-item hyprland">
<div><img class="feature-icon" src="/assets/logos/hyprland.svg" alt="Hyprland" /></div> <div>
<img
class="feature-icon"
src="/assets/logos/hyprland.svg"
alt="Hyprland"
>
</div>
</span> </span>
<span class="cloud-item pipewire"> <span class="cloud-item pipewire">
<div><img class="feature-icon" src="/assets/logos/pipewire.svg" alt="Pipewire" /></div> <div>
<img
class="feature-icon"
src="/assets/logos/pipewire.svg"
alt="Pipewire"
>
</div>
</span> </span>
<span class="cloud-item x-org"> <span class="cloud-item x-org">
<div><img class="feature-icon" src="/assets/logos/xorg.svg" alt="X.Org" /></div> <div>
<img
class="feature-icon"
src="/assets/logos/xorg.svg"
alt="X.Org"
>
</div>
</span> </span>
<span class="cloud-item sway"> <span class="cloud-item sway">
<div><img class="feature-icon" src="/assets/logos/sway.svg" alt="Sway" /></div> <div>
<img class="feature-icon" src="/assets/logos/sway.svg" alt="Sway">
</div>
</span> </span>
</div> </div>
</section> </section>

View file

@ -1,53 +0,0 @@
---
---
<script>
setTimeout(() => {
// code copy
let blocks = document.querySelectorAll("pre");
if (blocks.length > 0) {
blocks.forEach((block) => {
let button = document.createElement("button");
button.className = "copy-button";
button.innerHTML = `<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
>
<path
fill="currentColor"
d="M200 32h-36.26a47.92 47.92 0 0 0-71.48 0H56a16 16 0 0 0-16 16v168a16 16 0 0 0 16 16h144a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16m-72 0a32 32 0 0 1 32 32H96a32 32 0 0 1 32-32m72 184H56V48h26.75A47.9 47.9 0 0 0 80 64v8a8 8 0 0 0 8 8h80a8 8 0 0 0 8-8v-8a47.9 47.9 0 0 0-2.75-16H200Z"
/>
</svg>
`;
button.onclick = () => {
let snippet = block.innerText ?? "";
navigator.clipboard.writeText(snippet);
button.classList.toggle("copied");
setTimeout(() => button.classList.remove("copied"), 1000);
};
block.appendChild(button);
});
}
}, 3001)
// heading copy
let headings = document.getElementsByClassName("heading")
if (headings.length > 0) {
for (const heading of headings) {
let button = heading.querySelector("span")
if (button) {
button.onclick = () => {
let link = window.location.href.split("#")[0];
link += `#-${heading.textContent?.slice(10).trimEnd().replaceAll(" ", "-").toLowerCase()}`;
window.location.href = link
navigator.clipboard.writeText(link);
heading.classList.toggle("copied")
setTimeout(() => heading.classList.remove("copied"), 1000);
}
}
}
}
</script>

View file

@ -1,29 +0,0 @@
---
---
<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,112 +0,0 @@
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,145 +1,18 @@
--- ---
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
import MarqueeContent from "./MarqueeContent.astro";
const videos = [
{
author: '<a href="https://github.com/soramanew">soramane</a>',
source: "https://github.com/caelestia-dots/shell",
path: "/assets/showcase/soramane.mp4",
installable: true,
},
{
author: '<a href="https://github.com/end-4">end_4</a>',
source: "https://github.com/end-4/dots-hyprland",
path: "/assets/showcase/end4.mp4",
installable: true,
},
{
author: '<a href="https://outfoxxed.me">outfoxxed</a>',
source: "https://git.outfoxxed.me/outfoxxed/nixnew/src/branch/master/modules/user/modules/quickshell",
path: "/assets/showcase/outfoxxed.mp4",
},
{
author: '<a href="https://github.com/pfaj/">pfaj</a> and <a href="https://github.com/bdebiase">bdebiase</a>',
path: "/assets/showcase/pfaj-bdeblase.mp4",
},
{
author: '<a href="https://github.com/flickowoa">flicko</a>',
source: "https://github.com/flickowoa/zephyr",
path: "/assets/showcase/flicko.mp4",
},
{
author: '<a href="https://vaxry.net">vaxry</a>',
path: "/assets/showcase/vaxry.mp4",
},
];
--- ---
<div class="marquee"> <div class="marquee">
<div class="marquee-scroll"> <div class="marquee-scroll">
<div id="marquee-scroll-left" class="marquee-scroll-arrow"> <div id="marquee-scroll-left" class="marquee-scroll-arrow">
<div><Icon name="caret-left"/></div> <div><Icon name="caret-left" /></div>
</div> </div>
<div id="marquee-scroll-right" class="marquee-scroll-arrow"> <div id="marquee-scroll-right" class="marquee-scroll-arrow">
<div><Icon name="caret-right"/></div> <div><Icon name="caret-right" /></div>
</div> </div>
</div> </div>
<div id="marquee-content" class="marquee-content" data-scroll="0" data-media-index="0"> <MarqueeContent />
{videos.map(({ author, source, installable, path }, index) => <div class="marquee-item">
<div>
<video
data-media-index={index}
data-media-author={author}
id="showcase-video"
class="marquee-item-spacing marquee-item-content"
muted
controls
playsinline
preload="metadata"
>
<source src={path} type="video/mp4"/>
</video>
<p>
Configuration by <Fragment set:html={author}/>
{source && !installable && <>(<a href={source}>source code</a>)</>}
{source && installable && <>(<a href={source}>install</a>)</>}
</p>
</div>
</div>)}
</div>
</div> </div>
<script> <script src="@config/styling/marquee.ts" />
const videoCount = 6; // last array index
const marquee = document.getElementById("marquee-content")!;
marquee.style.setProperty("--scroll", "0")
window.addEventListener("load", autoplayInit, false);
const videos = document.getElementsByClassName("marquee-item-content") as HTMLCollectionOf<HTMLVideoElement>;
let currentVideoIndex = 0;
let currentVideo: HTMLVideoElement | null = null;
function autoplayInit() {
setActiveVideo(0);
currentVideo!.play();
}
function setActiveVideo(index: number) {
currentVideo?.pause();
currentVideoIndex = index;
currentVideo = videos[currentVideoIndex];
currentVideo.currentTime = 0;
marquee.style.setProperty("--scroll", `-${currentVideoIndex*100}%`)
}
function offsetCarousel(offset: number) {
let nextIndex = currentVideoIndex + offset;
if (nextIndex < 0) nextIndex += videoCount;
nextIndex = nextIndex % videoCount;
setActiveVideo(nextIndex);
}
const intersectionOptions = {
root: marquee,
rootMargin: "0px",
threshold: 0.0,
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const video = entry.target as HTMLVideoElement;
if (!entry.isIntersecting) {
video.pause();
} else if (video === currentVideo) {
video.play();
}
});
}, intersectionOptions);
for (const video of videos) {
observer.observe(video);
video.addEventListener("ended", () => {
// The "ended" event might just mean its buffering.
if (video == currentVideo && video.duration !== 0 && video.currentTime === video.duration) {
offsetCarousel(1);
}
});
}
let wasPaused = false;
document.addEventListener("visibilitychange", () => {
if (currentVideo) {
if (document.hidden) {
wasPaused = currentVideo.paused;
currentVideo.pause();
} else if (!wasPaused) {
currentVideo.play();
}
}
});
// left-right buttons
document.getElementById("marquee-scroll-left")!.addEventListener("mousedown", () => offsetCarousel(-1));
document.getElementById("marquee-scroll-right")!.addEventListener("mousedown", () => offsetCarousel(1));
</script>

View file

@ -0,0 +1,66 @@
---
const videos = [
{
author: '<a href="https://github.com/soramanew">soramane</a>',
source: "https://github.com/caelestia-dots/shell",
path: "/assets/showcase/soramane.mp4",
installable: true,
},
{
author: '<a href="https://github.com/end-4">end_4</a>',
source: "https://github.com/end-4/dots-hyprland",
path: "/assets/showcase/end4.mp4",
installable: true,
},
{
author: '<a href="https://outfoxxed.me">outfoxxed</a>',
source:
"https://git.outfoxxed.me/outfoxxed/nixnew/src/branch/master/modules/user/modules/quickshell",
path: "/assets/showcase/outfoxxed.mp4",
},
{
author:
'<a href="https://github.com/pfaj/">pfaj</a> and <a href="https://github.com/bdebiase">bdebiase</a>',
path: "/assets/showcase/pfaj-bdeblase.mp4",
},
{
author: '<a href="https://github.com/flickowoa">flicko</a>',
source: "https://github.com/flickowoa/zephyr",
path: "/assets/showcase/flicko.mp4",
},
{
author: '<a href="https://vaxry.net">vaxry</a>',
path: "/assets/showcase/vaxry.mp4",
},
];
---
<div
id="marquee-content"
class="marquee-content"
data-scroll="0"
data-media-index="0"
>
{videos.map(({ author, source, installable, path }, index) => {
return (
<div class=`marquee-item`>
<video
data-media-index={index}
data-media-author={author}
id="showcase-video"
class="marquee-item-spacing marquee-item-content"
muted
controls
playsinline
preload="metadata"
>
<source src={path} type="video/mp4"/>
</video>
<p>
Configuration by <Fragment set:html={author}/>
{source && !installable && <>(<a href={source}>source code</a>)</>}
{source && installable && <>(<a href={source}>install</a>)</>}
</p>
</div>
)})}
</div>

View file

@ -1,7 +1,8 @@
--- ---
import "@pagefind/default-ui/css/ui.css"; 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"> <site-search class="search-wrapper">
<button <button
data-open-modal data-open-modal
@ -10,58 +11,60 @@ import magnifierIcon from "@icons/magnifier.svg?raw"
aria-keyshortcuts="Control+K" aria-keyshortcuts="Control+K"
class="search-button" class="search-button"
> >
<Fragment set:html={magnifierIcon}/> <Fragment set :html={magnifierIcon} />
<span class="search-label" aria-hidden="true">Search</span> <span class="search-label" aria-hidden="true">Search</span>
<kbd class="search-kbd"> <kbd class="search-kbd"> <kbd>Ctrl</kbd><kbd>K</kbd> </kbd>
<kbd>Ctrl</kbd><kbd>K</kbd>
</kbd>
</button> </button>
<dialog aria-label="Search" class="search-dialog"> <dialog aria-label="Search" class="search-dialog">
<div class="dialog-frame"> <div class="dialog-frame">
<button data-close-modal class="search-cancel"> <button data-close-modal class="search-cancel">Cancel</button>
Cancel
</button>
<div class="search-container"> <div class="search-container">
<div id="qs_search"/> <div id="qs_search" />
</div> </div>
</div> </div>
</dialog> </dialog>
</site-search> </site-search>
{ {/**
/**
* NOTE: YOINKED FROM STARLIGHT * NOTE: YOINKED FROM STARLIGHT
* This is intentionally inlined to avoid briefly showing an invalid shortcut. * This is intentionally inlined to avoid briefly showing an invalid shortcut.
* Purposely using the deprecated `navigator.platform` property to detect Apple devices, as the * Purposely using the deprecated `navigator.platform` property to detect Apple devices, as the
* user agent is spoofed by some browsers when opening the devtools. * user agent is spoofed by some browsers when opening the devtools.
*/ */}
}
<script is:inline> <script is:inline>
(() => { (() => {
const openBtn = document.querySelector('button[data-open-modal]'); const openBtn = document.querySelector("button[data-open-modal]");
const shortcut = openBtn?.querySelector('kbd'); const shortcut = openBtn?.querySelector("kbd");
if (!openBtn || !(shortcut instanceof HTMLElement)) return; if (!openBtn || !(shortcut instanceof HTMLElement)) return;
const platformKey = shortcut.querySelector('kbd'); const platformKey = shortcut.querySelector("kbd");
if (platformKey && /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)) { if (platformKey && /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)) {
platformKey.textContent = '⌘'; platformKey.textContent = "⌘";
openBtn.setAttribute('aria-keyshortcuts', 'Meta+K'); openBtn.setAttribute("aria-keyshortcuts", "Meta+K");
} }
shortcut.style.display = ''; shortcut.style.display = "";
})(); })();
</script> </script>
<script> <script>
import { getQMLTypeLinkObject, getQMLTypeLink, getIconForLink } from '@src/config/io/helpers'; import {
getQMLTypeLinkObject,
getQMLTypeLink,
getIconForLink,
} from "@src/config/io/helpers";
class SiteSearch extends HTMLElement { class SiteSearch extends HTMLElement {
constructor() { constructor() {
super(); super();
const openBtn = this.querySelector<HTMLButtonElement>('button[data-open-modal]')!; const openBtn = this.querySelector<HTMLButtonElement>(
const closeBtn = this.querySelector<HTMLButtonElement>('button[data-close-modal]')!; "button[data-open-modal]"
const dialog = this.querySelector('dialog')!; )!;
const dialogFrame = this.querySelector('.dialog-frame')!; const closeBtn = this.querySelector<HTMLButtonElement>(
"button[data-close-modal]"
)!;
const dialog = this.querySelector("dialog")!;
const dialogFrame = this.querySelector(".dialog-frame")!;
/** Close the modal if a user clicks on a link or outside of the modal. */ /** Close the modal if a user clicks on a link or outside of the modal. */
const onClick = (event: MouseEvent) => { const onClick = (event: MouseEvent) => {
const isLink = 'href' in (event.target || {}); const isLink = "href" in (event.target || {});
if ( if (
isLink || isLink ||
(document.body.contains(event.target as Node) && (document.body.contains(event.target as Node) &&
@ -73,71 +76,89 @@ import { getQMLTypeLinkObject, getQMLTypeLink, getIconForLink } from '@src/confi
const openModal = (event?: MouseEvent) => { const openModal = (event?: MouseEvent) => {
dialog.showModal(); dialog.showModal();
document.body.toggleAttribute('data-search-modal-open', true); document.body.toggleAttribute("data-search-modal-open", true);
this.querySelector('input')?.focus(); this.querySelector("input")?.focus();
event?.stopPropagation(); event?.stopPropagation();
window.addEventListener('click', onClick); window.addEventListener("click", onClick);
}; };
const closeModal = () => dialog.close(); const closeModal = () => dialog.close();
openBtn.addEventListener('click', openModal); openBtn.addEventListener("click", openModal);
openBtn.disabled = false; openBtn.disabled = false;
closeBtn.addEventListener('click', closeModal); closeBtn.addEventListener("click", closeModal);
dialog.addEventListener('close', () => { dialog.addEventListener("close", () => {
document.body.toggleAttribute('data-search-modal-open', false); document.body.toggleAttribute("data-search-modal-open", false);
window.removeEventListener('click', onClick); window.removeEventListener("click", onClick);
}); });
// Listen for `ctrl + k` and `cmd + k` keyboard shortcuts. // Listen for `ctrl + k` and `cmd + k` keyboard shortcuts.
window.addEventListener('keydown', (e) => { window.addEventListener("keydown", e => {
if ((e.metaKey === true || e.ctrlKey === true) && e.key === 'k') { if ((e.metaKey === true || e.ctrlKey === true) && e.key === "k") {
dialog.open ? closeModal() : openModal(); dialog.open ? closeModal() : openModal();
e.preventDefault(); e.preventDefault();
} }
}); });
const processExcerpt = (sub_resultExcerpt:string):string => { const processExcerpt = (sub_resultExcerpt: string): string => {
const linkRegex = /TYPE99(\w+.)99TYPE/g; const linkRegex = /TYPE99(\w+.)99TYPE/g;
let excerpt = sub_resultExcerpt; let excerpt = sub_resultExcerpt;
const match = [...excerpt.matchAll(linkRegex)]; const match = [...excerpt.matchAll(linkRegex)];
if (match.length > 0){ if (match.length > 0) {
for (const matching of match) { for (const matching of match) {
const linkObject = getQMLTypeLinkObject(matching[1]); const linkObject = getQMLTypeLinkObject(matching[1]);
const link = getQMLTypeLink("NOVERSION", linkObject); const link = getQMLTypeLink("NOVERSION", linkObject);
const icon = linkObject.mtype ? getIconForLink(linkObject.mtype, false) : null; const icon = linkObject.mtype
? getIconForLink(linkObject.mtype, false)
: null;
// for signal // for signal
const bracketString = getIconForLink("func", false) const bracketString = getIconForLink("func", false);
const newLink = `<span class="type${linkObject.mtype}-link typedata-link">${icon ? icon : ""}<a href=${link}>${linkObject.mname || linkObject.name}</a>${linkObject.mtype === "signal" ? bracketString : ""}</span>`; const newLink = `<span class="type${linkObject.mtype}-link typedata-link">${icon ? icon : ""}<a href=${link}>${linkObject.mname || linkObject.name}</a>${linkObject.mtype === "signal" ? bracketString : ""}</span>`;
excerpt = excerpt.replace(matching[0], newLink) excerpt = excerpt.replace(matching[0], newLink);
} }
} }
return excerpt return excerpt;
} };
const formatURL = (path: string) => path; const formatURL = (path: string) => path;
window.addEventListener('DOMContentLoaded', () => { window.addEventListener("DOMContentLoaded", () => {
const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1)); const onIdle = window.requestIdleCallback || (cb => setTimeout(cb, 1));
onIdle(async () => { onIdle(async () => {
// @ts-expect-error — Missing types for @pagefind/default-ui package. const { PagefindUI } = await import(
const { PagefindUI } = await import('@pagefind/default-ui'); //@ts-expect-error — Missing types for @pagefind/default-ui package.
"@pagefind/default-ui"
);
new PagefindUI({ new PagefindUI({
element: '#qs_search', element: "#qs_search",
baseUrl: import.meta.env.BASE_URL, baseUrl: import.meta.env.BASE_URL,
bundlePath: import.meta.env.BASE_URL.replace(/\/$/, '') + '/pagefind/', bundlePath:
import.meta.env.BASE_URL.replace(/\/$/, "") + "/pagefind/",
showImages: false, showImages: false,
showSubResults: true, showSubResults: true,
processResult: (result: { url: string; excerpt:string; sub_results: Array<{ url: string, excerpt:string }> }) => { processResult: (result: {
url: string;
excerpt: string;
meta: {
source: string;
};
extra_class: string;
sub_results: Array<{
url: string;
excerpt: string;
}>;
}) => {
if (result.meta.source === "Qt Framework") {
result.extra_class = "qt-result-badge";
}
result.url = formatURL(result.url); result.url = formatURL(result.url);
result.excerpt = processExcerpt(result.excerpt) result.excerpt = processExcerpt(result.excerpt);
result.sub_results = result.sub_results.map((sub_result) => { result.sub_results = result.sub_results.map(sub_result => {
sub_result.url = formatURL(sub_result.url); sub_result.url = formatURL(sub_result.url);
sub_result.excerpt = processExcerpt(sub_result.excerpt) sub_result.excerpt = processExcerpt(sub_result.excerpt);
return sub_result; return sub_result;
}); });
}, },
@ -146,5 +167,5 @@ import { getQMLTypeLinkObject, getQMLTypeLink, getIconForLink } from '@src/confi
}); });
} }
} }
customElements.define('site-search', SiteSearch); customElements.define("site-search", SiteSearch);
</script> </script>

View file

@ -1,7 +1,13 @@
--- ---
import TableOfContents from "./toc"; import TableOfContents from "./toc";
import type { ConfigHeading, TypeTOC } from "./types.d.ts"; import type { ConfigHeading, TypeTOC } from "./types.d.ts";
import type { TypeData } from "@config/io/types"; import type {
TypeData,
QuickshellFunction,
// QuickshellSignal,
// QuickshellVariant,
// QuickshellProps,
} from "@config/_types";
export interface Props { export interface Props {
title?: string; title?: string;
@ -12,13 +18,16 @@ export interface Props {
const { title, headings, type, mobile } = Astro.props; const { title, headings, type, mobile } = Astro.props;
const types: TypeTOC | null = type ? { const types: TypeTOC | null = type
? {
properties: Object.keys(type.properties ?? {}), properties: Object.keys(type.properties ?? {}),
functions: (type.functions ?? []).map(f => f.name), functions: (type.functions ?? []).map((f: QuickshellFunction) => f.name),
signals: Object.keys(type.signals ?? {}), signals: Object.keys(type.signals ?? {}),
variants: Object.keys(type.variants ?? {}), variants: Object.keys(type.variants ?? {}),
} : null; }
: null;
--- ---
{((headings?.length ?? 0) != 0 || types) && {((headings?.length ?? 0) != 0 || types) &&
<div id="toc" aria-mobile={mobile} class=`toc-wrapper${mobile ? "-mobile":""}`> <div id="toc" aria-mobile={mobile} class=`toc-wrapper${mobile ? "-mobile":""}`>
<TableOfContents <TableOfContents
@ -28,5 +37,4 @@ const types: TypeTOC | null = type ? {
mobile={mobile} mobile={mobile}
client:idle client:idle
/> />
</div> </div>}
}

View file

@ -10,6 +10,7 @@ interface Props {
const { title, link, current, showIcon } = Astro.props; const { title, link, current, showIcon } = Astro.props;
--- ---
<a class=`nav-component nav-item nav-link ${current ? "nav-current" : ""}` href={link}> <a class=`nav-component nav-item nav-link ${current ? "nav-current" : ""}` href={link}>
{ showIcon ? ( { showIcon ? (
<div> <div>

View file

@ -9,6 +9,7 @@ interface Props {
} }
const { title, link, current } = Astro.props; const { title, link, current } = Astro.props;
--- ---
<Accordion class=`nav-component nav-collapsible ${current ? "nav-current" : ""}` {...(current ? { open: "_" } : {})}> <Accordion class=`nav-component nav-collapsible ${current ? "nav-current" : ""}` {...(current ? { open: "_" } : {})}>
<div slot="header"> <div slot="header">
<a class=`nav-link ${current ? "nav-current" : ""}` href={link}>{title}</a> <a class=`nav-link ${current ? "nav-current" : ""}` href={link}>{title}</a>
@ -16,5 +17,5 @@ const { title, link, current } = Astro.props;
<Fragment set:html={navMarker}/> <Fragment set:html={navMarker}/>
</div> </div>
</div> </div>
<slot> <slot />
</Accordion> </Accordion>

View file

@ -13,19 +13,25 @@ import Link from "./Link.astro";
const versions = await getVersionsData(); const versions = await getVersionsData();
const versionName = Astro.params.version; 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 ?? ""); const guidePages = await getGuideCollection(versionName ?? "");
interface NavTree { interface NavTree {
title: string, title: string;
slug: string, slug: string;
entries?: NavTree[], 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}`; const link = `${mount}/${slug}`;
return { return {
@ -38,7 +44,9 @@ function mkTree(mount: string, pathIdx: number, { title, slug, entries }: NavTre
function genGuideNav(base: string): NavTree[] | undefined { function genGuideNav(base: string): NavTree[] | undefined {
const pages = guidePages 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) .sort((a, b) => a.data.index - b.data.index)
.map(page => ({ .map(page => ({
title: page.data.title, title: page.data.title,
@ -68,8 +76,8 @@ if (versionName) {
entries: module.types.map(type => ({ entries: module.types.map(type => ({
title: type.name, title: type.name,
slug: type.name, slug: type.name,
})) })),
})) })),
}), }),
}; };
@ -84,6 +92,7 @@ if (versionName) {
}; };
} }
--- ---
<nav class="navtree"> <nav class="navtree">
<Link <Link
title="About" title="About"
@ -95,9 +104,9 @@ if (versionName) {
link="/changelog" link="/changelog"
current={currentPath.length === 1 && currentPath[0] === "changelog"} current={currentPath.length === 1 && currentPath[0] === "changelog"}
/> />
{ versionedEntries && <Tree {...versionsTree as TreeEntry}/>} {versionedEntries && <Tree {...versionsTree as TreeEntry}/>}
<hr/> <hr>
{ versionedEntries && ( {versionedEntries && (
<Tree {...versionedEntries.guide}/> <Tree {...versionedEntries.guide}/>
<Tree {...versionedEntries.types}/> <Tree {...versionedEntries.types}/>
<Link <Link
@ -111,7 +120,7 @@ if (versionName) {
showIcon={true} showIcon={true}
/> />
)} )}
{ !versionedEntries && versions.versions.map(version => ( {!versionedEntries && versions.versions.map(version => (
<Link <Link
title={`Quickshell Documentation (${version.name})`} title={`Quickshell Documentation (${version.name})`}
link={`/docs/${version.name}/guide`} link={`/docs/${version.name}/guide`}

View file

@ -32,6 +32,7 @@ const NavComponent: Component<SidebarContent> = props => {
if ( if (
isLink || isLink ||
!isInBody || !isInBody ||
//@ts-expect-error
(isInBody && !navRef.contains(event.target as Node)) (isInBody && !navRef.contains(event.target as Node))
) { ) {
setOpen(false); setOpen(false);

View file

@ -14,6 +14,7 @@ interface Props extends TreeEntry {}
const { title, link, entries, current } = Astro.props; const { title, link, entries, current } = Astro.props;
--- ---
<NavCollapsible title={title} link={link} current={current ?? false}> <NavCollapsible title={title} link={link} current={current ?? false}>
{entries?.map(entry => entry.entries ? ( {entries?.map(entry => entry.entries ? (
<Self {...entry}/> <Self {...entry}/>

View file

@ -8,6 +8,7 @@ export interface Props {
const { mobile } = Astro.props; const { mobile } = Astro.props;
--- ---
<aside class=`nav-wrapper${mobile ? "-mobile" : ""} id="nav"`> <aside class=`nav-wrapper${mobile ? "-mobile" : ""} id="nav"`>
{ mobile ? ( { mobile ? (
<SidebarWrapper client:load> <SidebarWrapper client:load>

View file

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

View file

@ -1,52 +0,0 @@
import { createSignal, type Component } from "solid-js";
import { Article } from "@icons";
import { Table } from "./Table";
import type {
TOCProps,
TypeTOC,
ConfigHeading,
} from "../types";
import { buildHierarchy } from "@config/io/helpers";
const TableOfContents: Component<TOCProps> = props => {
const [open, setOpen] = createSignal<boolean>(false);
const [typeProps] = createSignal<TypeTOC | undefined>(
props.type
);
const [configProps] = createSignal<
ConfigHeading[] | undefined
>(props.config);
function toggle(e: MouseEvent) {
e.preventDefault();
setOpen(!open());
}
if (!props.mobile) {
return typeProps() ? (
<Table typeTOC={typeProps()} />
) : (
<Table configTOC={buildHierarchy(configProps()!)} />
);
}
return (
<div class="menu-toggle">
<div onclick={e => toggle(e)}>
<Article />
</div>
<div class={`menu-items ${open() ? "shown" : ""}`}>
{typeProps() ? (
<Table typeTOC={typeProps()} />
) : (
<Table
configTOC={buildHierarchy(configProps()!)}
/>
)}
</div>
</div>
);
};
export default TableOfContents;

View file

@ -6,7 +6,7 @@ import {
type Component, type Component,
} from "solid-js"; } from "solid-js";
import { Article } from "@icons"; import { Article, MenuToX } from "@icons";
import { Table } from "./Table"; import { Table } from "./Table";
import type { TOCProps } from "../types"; import type { TOCProps } from "../types";
import { buildHierarchy } from "@config/io/helpers"; import { buildHierarchy } from "@config/io/helpers";
@ -27,7 +27,10 @@ const TableOfContents: Component<TOCProps> = props => {
return type ? ( return type ? (
<Table typeTOC={type} /> <Table typeTOC={type} />
) : ( ) : (
<Table title={title} configTOC={buildHierarchy(config!)} /> <Table
title={title}
configTOC={buildHierarchy(config!)}
/>
); );
} }
@ -37,6 +40,7 @@ const TableOfContents: Component<TOCProps> = props => {
if ( if (
isLink || isLink ||
!isInBody || !isInBody ||
//@ts-expect-error
(isInBody && !tocRef.contains(event.target as Node)) (isInBody && !tocRef.contains(event.target as Node))
) { ) {
setOpen(false); setOpen(false);
@ -92,13 +96,17 @@ const TableOfContents: Component<TOCProps> = props => {
id="toc-toggle" id="toc-toggle"
> >
<div onclick={e => toggle(e)}> <div onclick={e => toggle(e)}>
<Article /> <Article class={`toc-icon ${!open() ? "active" : ""}`} />
<MenuToX class={`toc-icon ${open() ? "active" : ""}`} />
</div> </div>
<div class={`toc-mobile ${open() ? "shown" : ""}`}> <div class={`toc-mobile ${open() ? "shown" : ""}`}>
{type ? ( {type ? (
<Table typeTOC={type} /> <Table typeTOC={type} />
) : ( ) : (
<Table title={title} configTOC={buildHierarchy(config!)} /> <Table
title={title}
configTOC={buildHierarchy(config!)}
/>
)} )}
</div> </div>
</div> </div>

View file

@ -1,8 +1,5 @@
--- ---
import type { import type { QMLTypeLinkObject, QuickshellFunction } from "@config/_types";
QMLTypeLinkObject,
QuickshellFunction,
} from "@config/io/types";
import { getQMLTypeLink } from "@config/io/helpers"; import { getQMLTypeLink } from "@config/io/helpers";
import { Tag } from "@icons"; import { Tag } from "@icons";
import TypeDetails from "./TypeDetails.astro"; import TypeDetails from "./TypeDetails.astro";
@ -15,9 +12,9 @@ export interface Props {
const { funcData } = Astro.props; const { funcData } = Astro.props;
const { version } = Astro.params; const { version } = Astro.params;
--- ---
<ul class="typedata typefuncs"> <ul class="typedata typefuncs">
{ {funcData.map((item:QuickshellFunction) => {
funcData.map(item => {
const functionParams = item.params.length > 0 ? item.params.map((funcparam,index) => `${funcparam.name}${index !== item.params.length -1 ? ", ":""}`) : undefined const functionParams = item.params.length > 0 ? item.params.map((funcparam,index) => `${funcparam.name}${index !== item.params.length -1 ? ", ":""}`) : undefined
const retTypeLink = getQMLTypeLink(version!, item.ret as unknown as QMLTypeLinkObject) const retTypeLink = getQMLTypeLink(version!, item.ret as unknown as QMLTypeLinkObject)
let genericType:string|undefined; let genericType:string|undefined;
@ -57,6 +54,5 @@ const { version } = Astro.params;
<TypeDetails markdown={item.details} /> <TypeDetails markdown={item.details} />
</li> </li>
) )
}) })}
}
</ul> </ul>

View file

@ -3,7 +3,8 @@ import { getQMLTypeLink } from "@config/io/helpers";
import type { import type {
QMLTypeLinkObject, QMLTypeLinkObject,
QuickshellProps, QuickshellProps,
} from "@config/io/types"; QuickshellInstance,
} from "@config/_types";
import { Tag } from "@icons"; import { Tag } from "@icons";
import TypeTitle from "./TypeTitle.astro"; import TypeTitle from "./TypeTitle.astro";
@ -16,13 +17,14 @@ export interface Props {
const { props } = Astro.props; const { props } = Astro.props;
const { version } = Astro.params; const { version } = Astro.params;
--- ---
<ul class="typedata typeprops"> <ul class="typedata typeprops">
{ {Object.keys(props).map((name) => {
Object.entries(props).map(([name, propData]) => { const propData:QuickshellInstance = props[name];
let typeLink:string; let typeLink: string;
let linkText:string; let linkText: string;
let genericType:string|undefined; let genericType: string|undefined;
let genericTypeLink:string|undefined; let genericTypeLink: string|undefined;
const gadget = propData.type.gadget; const gadget = propData.type.gadget;
if (gadget) { if (gadget) {
typeLink = "#" typeLink = "#"
@ -39,7 +41,7 @@ const { version } = Astro.params;
<li id={ name } class="typedata-root typeprop-root"> <li id={ name } class="typedata-root typeprop-root">
<TypeTitle <TypeTitle
typekind="prop" typekind="prop"
typename={name} typename={ name }
typelink={typeLink} typelink={typeLink}
typelink_text={linkText} typelink_text={linkText}
typename_generic={genericType} typename_generic={genericType}
@ -66,6 +68,5 @@ const { version } = Astro.params;
<TypeDetails markdown={propData.details} /> <TypeDetails markdown={propData.details} />
</li> </li>
) )
}) })}
}
</ul> </ul>

View file

@ -1,6 +1,6 @@
--- ---
import { getQMLTypeLink } from "@config/io/helpers"; import { getQMLTypeLink } from "@config/io/helpers";
import type { QuickshellSignal } from "@config/io/types"; import type { QuickshellSignal } from "@config/_types";
import { Tag } from "@icons"; import { Tag } from "@icons";
import TypeDetails from "./TypeDetails.astro"; import TypeDetails from "./TypeDetails.astro";
import TypeTitle from "./TypeTitle.astro"; import TypeTitle from "./TypeTitle.astro";
@ -12,17 +12,25 @@ export interface Props {
const { signals } = Astro.props; const { signals } = Astro.props;
const { version } = Astro.params; const { version } = Astro.params;
--- ---
<ul class="typedata typesignals"> <ul class="typedata typesignals">
{ {(Object.entries(signals) as [
Object.entries(signals).map(([name, signalData]) => { keyof QuickshellSignal,
const paramKeys = signalData.params.length > 0 ? signalData.params.map((param,index) => `${param.name}${index !== signalData.params.length -1 ? ", ":""}`) : [] QuickshellSignal[keyof QuickshellSignal],
][]).map(([name, signalData]) => {
const paramKeys = signalData.params.length > 0
? signalData.params.map((param,index) => `${param.name}${
index !== signalData.params.length -1
? ", "
:""
}`): []
let genericType:string|undefined; let genericType:string|undefined;
let genericTypeLink:string|undefined; let genericTypeLink:string|undefined;
return ( return (
<li id={ name } class="typedata-root typesignal-root"> <li id={ name.toString() } class="typedata-root typesignal-root">
<TypeTitle <TypeTitle
typekind="signal" typekind="signal"
typename={name} typename={ name.toString() }
typelink="/docs/configuration/qml-overview#-signals" typelink="/docs/configuration/qml-overview#-signals"
typelink_text="" typelink_text=""
typename_generic={genericType} typename_generic={genericType}
@ -50,6 +58,5 @@ const { version } = Astro.params;
<TypeDetails markdown={signalData.details} /> <TypeDetails markdown={signalData.details} />
</li> </li>
) )
}) })}
}
</ul> </ul>

View file

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

View file

@ -31,14 +31,17 @@ const iconSelector: { [key: string]: string } = {
variant: "fourdiamonds", variant: "fourdiamonds",
}; };
--- ---
<div class={`typedata-title type${typekind}-title`}> <div class={`typedata-title type${typekind}-title`}>
<section class={`typedata-name type${typekind}-name`}> <section class={`typedata-name type${typekind}-name`}>
{typekind !== "func" && <Icon name={iconSelector[typekind]}/>} {typekind !== "func" && <Icon name={iconSelector[typekind]}/>}
<span>{ typename }{ (typekind === "func" || typekind === "signal") ? <span
>{typename}
{(typekind === "func" || typekind === "signal") ?
(<span>(</span><span class="typedata-param">{typedata_params}</span><span>)</span>) (<span>(</span><span class="typedata-param">{typedata_params}</span><span>)</span>)
:""} :""}
</span> </span>
{ typekind !== "variant" && {typekind !== "variant" &&
<span class=`type-datatype ${typekind === "signal" && "typesignal-doclink"}`>{typekind !== "signal" &&":"}&nbsp; <span class=`type-datatype ${typekind === "signal" && "typesignal-doclink"}`>{typekind !== "signal" &&":"}&nbsp;
<a href={typelink}>{ typelink_text }</a> <a href={typelink}>{ typelink_text }</a>
{typename_generic && {typename_generic &&
@ -46,12 +49,11 @@ const iconSelector: { [key: string]: string } = {
<span class="type-generic"><span class="type-datatype">&lt;</span><a href={typelink_generic}>{typename_generic}</a><span class="type-datatype">&gt;</span></span> <span class="type-generic"><span class="type-datatype">&lt;</span><a href={typelink_generic}>{typename_generic}</a><span class="type-datatype">&gt;</span></span>
) )
} }
</span> </span>}
}
</section> </section>
<section class="type-badges"> <section class="type-badges">
{badges && badges.length > 0 ? ( {badges && badges.length > 0 ? (
badges.map(badgeText => <Badge badgeText={badgeText}/>) badges.map((badgeText:string) => <Badge badgeText={badgeText}/>)
) : null} ) : null}
</section> </section>
</div> </div>

View file

@ -1,5 +1,5 @@
--- ---
import type { QuickshellVariant } from "@config/io/types"; import type { QuickshellVariant } from "@config/_types";
import TypeDetails from "./TypeDetails.astro"; import TypeDetails from "./TypeDetails.astro";
import TypeTitle from "./TypeTitle.astro"; import TypeTitle from "./TypeTitle.astro";
@ -9,17 +9,20 @@ export interface Props {
const { variants } = Astro.props; const { variants } = Astro.props;
--- ---
<ul class="typedata typevariants"> <ul class="typedata typevariants">
{ {(Object.entries(variants) as [
Object.entries(variants).map(([name, variantData]) => { keyof QuickshellVariant,
QuickshellVariant[keyof QuickshellVariant],
][]).map(([name, variantData]) => {
const paramKeys = variantData.params && variantData.params.length > 0 const paramKeys = variantData.params && variantData.params.length > 0
? variantData.params.map(param => param.name) ? variantData.params.map(param => param.name)
: []; : [];
return ( return (
<li id={ name } class="typedata-root typevariant-root"> <li id={ name.toString() } class="typedata-root typevariant-root">
<TypeTitle <TypeTitle
typekind="variant" typekind="variant"
typename={name} typename={ name.toString() }
typelink="" typelink=""
typelink_text="" typelink_text=""
/> />
@ -36,7 +39,5 @@ const { variants } = Astro.props;
<TypeDetails markdown={variantData.details} /> <TypeDetails markdown={variantData.details} />
</li> </li>
) )
}) })}
}
</ul> </ul>

View file

@ -12,30 +12,48 @@ interface Props {
const { title, description } = Astro.props; const { title, description } = Astro.props;
--- ---
<meta charset="UTF-8" /> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator}>
<link rel="canonical" href={Astro.url} /> <link rel="canonical" href={Astro.url}>
<link rel="sitemap" href="/sitemap-index.xml" /> <link rel="sitemap" href="/sitemap-index.xml">
<title>{title}</title> <title>{title}</title>
<meta name="description" content={description} /> <meta name="description" content={description}>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg">
<script is:inline>
const theme = (() => {
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
return localStorage.getItem("theme");
}
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
return "dark";
}
return "light";
})();
if (theme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
</script>
<!-- Open Graph Meta Tags --> <!-- Open Graph Meta Tags -->
<meta name="og:type" content="website" /> <meta name="og:type" content="website">
<meta name="og:site_name" content="quickshell" /> <meta name="og:site_name" content="quickshell">
<meta name="og:url" content={Astro.url} /> <meta name="og:url" content={Astro.url}>
<meta name="og:title" content={title} /> <meta name="og:title" content={title}>
<meta name="og:description" content={description} /> <meta name="og:description" content={description}>
<!-- <meta name="og:image" content={image} /> --> <!-- <meta name="og:image" content={image} /> -->
<!-- Twitter Meta Tags --> <!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:domain" content="quickshell.outfoxxed.me" /> <meta name="twitter:domain" content="quickshell.outfoxxed.me">
<meta name="twitter:url" content={Astro.url} /> <meta name="twitter:url" content={Astro.url}>
<meta name="twitter:title" content={title} /> <meta name="twitter:title" content={title}>
<meta name="twitter:description" content={description} /> <meta name="twitter:description" content={description}>
<!-- <meta name="twitter:image" content={image} /> --> <!-- <meta name="twitter:image" content={image} /> -->
<Analytics/> <Analytics />

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

@ -0,0 +1,9 @@
interface CopyButtonOptions {
duration?: number;
copyIcon?: string;
successIcon?: string;
display?: "hover" | "ready";
cssVariables?: string;
}
export type { CopyButtonOptions };

View file

@ -0,0 +1,9 @@
interface QMLTypeLinkObject {
type: string;
module?: string;
name?: string;
mtype?: string;
mname?: string;
}
export type { QMLTypeLinkObject };

View file

@ -0,0 +1,33 @@
import type { QMLTypeLinkObject } from "./helper";
import type {
ModuleData,
QuickshellBase,
QuickshellFunction,
QuickshellGadget,
QuickshellInstance,
QuickshellProps,
QuickshellSignal,
QuickshellVariant,
TypeData,
VersionData,
VersionsData,
} from "./module";
import type { SearchLists } from "./search";
import type { CopyButtonOptions } from "./codeblock";
export type {
QMLTypeLinkObject,
QuickshellBase,
QuickshellInstance,
QuickshellGadget,
QuickshellProps,
QuickshellFunction,
QuickshellSignal,
QuickshellVariant,
TypeData,
ModuleData,
VersionData,
VersionsData,
SearchLists,
CopyButtonOptions,
};

View file

@ -1,15 +1,3 @@
//#FIXME fuseConfig.ts
// --
// generateSearchLists.ts
interface SearchLists {
slug: string;
link: string;
summary: string;
}
// --
// generateTypeData.ts
interface QuickshellBase { interface QuickshellBase {
type: string; type: string;
module: string; module: string;
@ -53,7 +41,7 @@ interface QuickshellSignal {
}; };
} }
export interface QuickshellVariant { interface QuickshellVariant {
[key: string]: { [key: string]: {
name?: string; name?: string;
details: string; details: string;
@ -61,7 +49,7 @@ export interface QuickshellVariant {
}; };
} }
export interface TypeData { interface TypeData {
name: string; name: string;
description: string; description: string;
details: string; details: string;
@ -72,37 +60,27 @@ export interface TypeData {
functions?: QuickshellFunction[]; functions?: QuickshellFunction[];
signals?: QuickshellSignal; signals?: QuickshellSignal;
variants?: QuickshellVariant; variants?: QuickshellVariant;
subtypes?: QuickshellData[]; // FIXME: QuickshellData[]
subtypes?: any[];
} }
export interface ModuleData { interface ModuleData {
name: string; name: string;
description: string; description: string;
details: string; details: string;
types: TypeData[]; types: TypeData[];
} }
export interface VersionData { interface VersionData {
name: string; name: string;
changelog?: string; changelog?: string;
modules: ModuleData[]; modules: ModuleData[];
} }
export interface VersionsData { interface VersionsData {
default: string; default: string;
versions: VersionData[]; versions: VersionData[];
} }
// --
// helpers.ts
interface QMLTypeLinkObject {
type: string;
module?: string;
name?: string;
mtype?: string;
mname?: string;
}
// --
export type { export type {
QuickshellBase, QuickshellBase,
@ -116,5 +94,4 @@ export type {
ModuleData, ModuleData,
VersionData, VersionData,
VersionsData, VersionsData,
QMLTypeLinkObject,
}; };

View file

@ -0,0 +1,7 @@
interface SearchLists {
slug: string;
link: string;
summary: string;
}
export type { SearchLists };

View file

@ -1,12 +1,15 @@
import { promises as fs } from "node:fs"; import { promises as fs } from "node:fs";
import path from "node:path"; import path from "node:path";
import type { VersionsData, ModuleData } from "./types"; import type { VersionsData, ModuleData } from "@_types";
async function readModulesData(basePath: string): Promise<ModuleData[]> { async function readModulesData(
basePath: string
): Promise<ModuleData[]> {
const moduleDirs = await fs.readdir(basePath); const moduleDirs = await fs.readdir(basePath);
const modules = await Promise.all(moduleDirs.map(async moduleDir => { const modules = await Promise.all(
moduleDirs.map(async moduleDir => {
const modulePath = path.join(basePath, moduleDir); const modulePath = path.join(basePath, moduleDir);
const indexPromise = async () => { const indexPromise = async () => {
@ -15,22 +18,28 @@ async function readModulesData(basePath: string): Promise<ModuleData[]> {
return JSON.parse(indexContent); return JSON.parse(indexContent);
}; };
const typeNames = (await fs.readdir(modulePath)).filter(name => name !== "index.json"); const typeNames = (await fs.readdir(modulePath)).filter(
name => name !== "index.json"
);
const typePromises = typeNames.map(async fileName => { const typePromises = typeNames.map(async fileName => {
const typePath = path.join(modulePath, fileName); const typePath = path.join(modulePath, fileName);
const fileContent = await fs.readFile(typePath, "utf8"); const fileContent = await fs.readFile(typePath, "utf8");
return JSON.parse(fileContent); return JSON.parse(fileContent);
}); });
const [index, ...types] = await Promise.all([indexPromise(), ...typePromises]); const [index, ...types] = await Promise.all([
indexPromise(),
...typePromises,
]);
return { return {
name: index.name, name: index.name,
description: index.description, description: index.description,
details: index.details, details: index.details,
types, types,
} };
})); })
);
return modules; return modules;
} }
@ -44,19 +53,31 @@ async function readVersionsData(): Promise<VersionsData> {
); );
} }
const resolvedPath = path.join(process.cwd(), versionsPath);
console.log(resolvedPath);
const content = await fs.readFile(versionsPath, "utf8"); const content = await fs.readFile(versionsPath, "utf8");
const data = JSON.parse(content); const data = JSON.parse(content);
const versions = await Promise.all(data.versions.map(async (d: { name: string, changelog?: string, types: any }) => ({ const versions = await Promise.all(
data.versions.map(
async (d: {
name: string;
changelog?: string;
types: any;
}) => ({
name: d.name, name: d.name,
changelog: d.changelog ? await fs.readFile(d.changelog, "utf8") : undefined, changelog: d.changelog
? await fs.readFile(d.changelog, "utf8")
: undefined,
modules: await readModulesData(d.types), modules: await readModulesData(d.types),
}))); })
)
);
return { return {
versions, versions,
default: data.default, default: data.default,
} };
} }
let globalVersionsData: Promise<VersionsData>; let globalVersionsData: Promise<VersionsData>;
@ -71,5 +92,6 @@ export function getVersionsData(): Promise<VersionsData> {
export async function getModulesData(): Promise<ModuleData[]> { export async function getModulesData(): Promise<ModuleData[]> {
const versions = await getVersionsData(); const versions = await getVersionsData();
return versions.versions.find(v => v.name === versions.default)!.modules; return versions.versions.find(v => v.name === versions.default)!
.modules;
} }

View file

@ -1,17 +1,22 @@
import { type CollectionEntry, getCollection } from "astro:content"; import {
type CollectionEntry,
getCollection,
} from "astro:content";
import { getVersionsData } from "@config/io/generateTypeData"; import { getVersionsData } from "@config/io/generateTypeData";
// load latest version of each page for version // load latest version of each page for version
async function buildGuideCollection(version: string): Promise<CollectionEntry<'guide'>[]> { async function buildGuideCollection(
version: string
): Promise<CollectionEntry<"guide">[]> {
const { versions } = await getVersionsData(); const { versions } = await getVersionsData();
const guidePages = await getCollection("guide"); const guidePages = await getCollection("guide");
const pages: { [key: string]: CollectionEntry<'guide'> } = {}; const pages: { [key: string]: CollectionEntry<"guide"> } = {};
for (const currentVersion of versions.toReversed()) { for (const currentVersion of versions.toReversed()) {
for (const page of guidePages) { for (const page of guidePages) {
let [guideVersion, id] = page.id.split('/'); let [guideVersion, id] = page.id.split("/");
guideVersion = guideVersion.replaceAll('_', '.'); guideVersion = guideVersion.replaceAll("_", ".");
id = id ?? "index"; id = id ?? "index";
if (guideVersion !== currentVersion.name) continue; if (guideVersion !== currentVersion.name) continue;
@ -24,12 +29,18 @@ async function buildGuideCollection(version: string): Promise<CollectionEntry<'g
return Object.values(pages); return Object.values(pages);
} }
let guideCollections: { [key: string]: Promise<CollectionEntry<'guide'>[]> } = {}; let guideCollections: {
[key: string]: Promise<CollectionEntry<"guide">[]>;
} = {};
export async function getGuideCollection(version: string): Promise<CollectionEntry<'guide'>[]> { async function getGuideCollection(
version: string
): Promise<CollectionEntry<"guide">[]> {
if (!(version in guideCollections)) { if (!(version in guideCollections)) {
guideCollections[version] = buildGuideCollection(version); guideCollections[version] = buildGuideCollection(version);
} }
return guideCollections[version]; return guideCollections[version];
} }
export { getGuideCollection };

View file

@ -10,7 +10,7 @@ import type {
ConfigHeading, ConfigHeading,
ConfigTOC, ConfigTOC,
} from "@components/navigation/sidebars/types"; } from "@components/navigation/sidebars/types";
import type { QMLTypeLinkObject } from "./types"; import type { QMLTypeLinkObject } from "@_types";
export function buildHierarchy(headings: ConfigHeading[]) { export function buildHierarchy(headings: ConfigHeading[]) {
const toc: ConfigTOC[] = []; const toc: ConfigTOC[] = [];
@ -30,7 +30,7 @@ export function buildHierarchy(headings: ConfigHeading[]) {
let depth = heading.depth - 1; let depth = heading.depth - 1;
let parent = null; let parent = null;
while (!parent && depth != 0) { while (!parent && depth !== 0) {
parent = parentHeadings.get(depth); parent = parentHeadings.get(depth);
depth -= 1; depth -= 1;
} }
@ -96,13 +96,10 @@ export function getQMLTypeLinkObject(unparsed: string) {
return hashMap[index](); return hashMap[index]();
} }
export function getQMLTypeLink(version: string, { export function getQMLTypeLink(
type, version: string,
module, { type, module, name, mtype, mname }: QMLTypeLinkObject
name, ) {
mtype,
mname,
}: QMLTypeLinkObject) {
if (type === "unknown") { if (type === "unknown") {
return "#unknown"; return "#unknown";
} }
@ -116,7 +113,9 @@ export function getQMLTypeLink(version: string, {
return localLink; return localLink;
}, },
qt: () => { qt: () => {
const isSpecific = mname ? `#${mname}-${mtype === "func" ? "method" : mtype}` : ""; const isSpecific = mname
? `#${mname}-${mtype === "func" ? "method" : mtype}`
: "";
const qtLink = `${qtStart}${module!.toLowerCase().replace(".", "-")}-${name!.toLowerCase()}.html${isSpecific}`; const qtLink = `${qtStart}${module!.toLowerCase().replace(".", "-")}-${name!.toLowerCase()}.html${isSpecific}`;
return qtLink; return qtLink;
}, },

View file

@ -14,16 +14,20 @@ import { remarkAlert } from "remark-github-blockquote-alert";
import rehypeShiki from "@shikijs/rehype"; import rehypeShiki from "@shikijs/rehype";
import sectionize from "@hbsnow/rehype-sectionize"; import sectionize from "@hbsnow/rehype-sectionize";
import type { ShikiTransformer } from "shiki"; import type { ShikiTransformer } from "shiki";
import { h } from "hastscript";
import { import {
getQMLTypeLinkObject, getQMLTypeLinkObject,
getQMLTypeLink, getQMLTypeLink,
getIconForLink, getIconForLink,
} from "./helpers.ts"; } from "./helpers.ts";
import type { CopyButtonOptions } from "@_types";
let currentVersion = "NOVERSION"; let currentVersion = "NOVERSION";
const remarkParseAtTypes: RemarkPlugin<[]> = () => (root: Md.Root): Md.Root => { const remarkParseAtTypes: RemarkPlugin<[]> =
() =>
(root: Md.Root): Md.Root => {
visit(root as Unist.Parent, (rawNode: Unist.Node) => { visit(root as Unist.Parent, (rawNode: Unist.Node) => {
if ( if (
rawNode.type === "text" || rawNode.type === "text" ||
@ -31,7 +35,6 @@ const remarkParseAtTypes: RemarkPlugin<[]> = () => (root: Md.Root): Md.Root => {
(rawNode as Md.Code).lang === "qml") (rawNode as Md.Code).lang === "qml")
) { ) {
const node = rawNode as Md.Literal; const node = rawNode as Md.Literal;
node.value = node.value.replace( node.value = node.value.replace(
/@@(?<path>([A-Z]\w*\.)*([A-Z]\w*))?((?<member>[a-z]\w*)((?<function>\(\))|(?<signal>\(s\)))?)?(?=[$.,;:)\s]|$)/g, /@@(?<path>([A-Z]\w*\.)*([A-Z]\w*))?((?<member>[a-z]\w*)((?<function>\(\))|(?<signal>\(s\)))?)?(?=[$.,;:)\s]|$)/g,
(_full, ...args) => { (_full, ...args) => {
@ -43,9 +46,11 @@ const remarkParseAtTypes: RemarkPlugin<[]> = () => (root: Md.Root): Md.Root => {
}; };
const groups = args.pop() as Capture; const groups = args.pop() as Capture;
const pathp = (groups.path ?? "").split('.').filter(Boolean); const pathp = (groups.path ?? "")
let type = (pathp.length >= 1 ? pathp.pop() : ""); .split(".")
let module = pathp.join('_'); .filter(Boolean);
let type = pathp.length >= 1 ? pathp.pop() : "";
let module = pathp.join("_");
if (module) { if (module) {
const isQs = module.startsWith("Quickshell"); const isQs = module.startsWith("Quickshell");
@ -65,9 +70,11 @@ const remarkParseAtTypes: RemarkPlugin<[]> = () => (root: Md.Root): Md.Root => {
} }
}); });
return root; return root;
}; };
const rehypeRewriteTypelinks: RehypePlugin<[]> = () => (root: Html.Root): Html.Root => { const rehypeRewriteTypelinks: RehypePlugin<[]> =
() =>
(root: Html.Root): Html.Root => {
visit( visit(
root as Unist.Parent, root as Unist.Parent,
"text", "text",
@ -80,7 +87,10 @@ const rehypeRewriteTypelinks: RehypePlugin<[]> = () => (root: Html.Root): Html.R
changed = true; changed = true;
const linkObject = getQMLTypeLinkObject(match); const linkObject = getQMLTypeLinkObject(match);
const link = getQMLTypeLink(currentVersion, linkObject); const link = getQMLTypeLink(
currentVersion,
linkObject
);
const icon = const icon =
linkObject.mtype && linkObject.mtype !== "func" linkObject.mtype && linkObject.mtype !== "func"
? getIconForLink(linkObject.mtype, false) ? getIconForLink(linkObject.mtype, false)
@ -101,28 +111,30 @@ const rehypeRewriteTypelinks: RehypePlugin<[]> = () => (root: Html.Root): Html.R
parent.children.splice(index, 1, ...fragment.children); parent.children.splice(index, 1, ...fragment.children);
return SKIP; return SKIP;
} }
return CONTINUE;
} }
); );
return root; return root;
}; };
const rehypeRewriteVersionedDoclinks: RehypePlugin<[]> = () => (root: Html.Root): Html.Root => { const rehypeRewriteVersionedDoclinks: RehypePlugin<[]> =
() =>
(root: Html.Root): Html.Root => {
visit( visit(
root as Unist.Parent, root as Unist.Parent,
"element", "element",
({ tagName, properties }: Html.Element) => { ({ tagName, properties }: Html.Element) => {
if (tagName !== "a") return CONTINUE; if (tagName !== "a") return CONTINUE;
if (!(properties.href as string ?? "").startsWith("@docs")) return CONTINUE; if (
!((properties.href as string) ?? "").startsWith("@docs")
)
return CONTINUE;
properties.href = `/docs/${currentVersion}/${(properties.href as string).slice(6)}`; properties.href = `/docs/${currentVersion}/${(properties.href as string).slice(6)}`;
return CONTINUE; return CONTINUE;
} }
); );
return root; return root;
}; };
const shikiRewriteTypelinks: ShikiTransformer = { const shikiRewriteTypelinks: ShikiTransformer = {
name: "rewrite-typelinks", name: "rewrite-typelinks",
@ -141,7 +153,69 @@ const shikiRewriteTypelinks: ShikiTransformer = {
}, },
}; };
export const markdownConfig: AstroMarkdownOptions = { const shikiCopyButton: ShikiTransformer = {
name: "copy-button",
pre(node) {
const options: CopyButtonOptions = {
duration: 3000,
};
const button = h(
"button",
{
class: "copy-button",
role: "button",
"aria-label": "Copy to clipboard",
"alia-live": "polite",
"data-code": this.source,
onclick: `
navigator.clipboard.writeText(this.dataset.code);
this.classList.add('copied');
this.setAttribute('aria-pressed', 'true');
setTimeout(() => { this.classList.remove('copied'); this.setAttribute('aria-pressed', 'false');}, ${options.duration})
`,
},
[
h(
"svg",
{
class: "copy-icon",
role: "icon",
xmlns: "http://www.w3.org/2000/svg",
width: "1em",
height: "1em",
viewBox: "0 0 256 256",
},
[
h("path", {
fill: "currentColor",
d: "M200 32h-36.26a47.92 47.92 0 0 0-71.48 0H56a16 16 0 0 0-16 16v168a16 16 0 0 0 16 16h144a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16m-72 0a32 32 0 0 1 32 32H96a32 32 0 0 1 32-32m72 184H56V48h26.75A47.9 47.9 0 0 0 80 64v8a8 8 0 0 0 8 8h80a8 8 0 0 0 8-8v-8a47.9 47.9 0 0 0-2.75-16H200Z",
}),
]
),
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);
},
};
const markdownConfig: AstroMarkdownOptions = {
syntaxHighlight: false, syntaxHighlight: false,
remarkPlugins: [ remarkPlugins: [
remarkParseAtTypes, remarkParseAtTypes,
@ -168,7 +242,7 @@ export const markdownConfig: AstroMarkdownOptions = {
}, },
defaultColor: false, defaultColor: false,
wrap: true, wrap: true,
transformers: [shikiRewriteTypelinks], transformers: [shikiRewriteTypelinks, shikiCopyButton],
}, },
], ],
// FIXME: incompatible types between unified/Plugin and Astro/RehypePlugin // FIXME: incompatible types between unified/Plugin and Astro/RehypePlugin
@ -185,18 +259,23 @@ let globalMarkdownProcessor: Promise<MarkdownProcessor>;
async function getMarkdownProcessor(): Promise<MarkdownProcessor> { async function getMarkdownProcessor(): Promise<MarkdownProcessor> {
if (!globalMarkdownProcessor) { if (!globalMarkdownProcessor) {
globalMarkdownProcessor = createMarkdownProcessor(markdownConfig); globalMarkdownProcessor =
createMarkdownProcessor(markdownConfig);
} }
return globalMarkdownProcessor; return globalMarkdownProcessor;
} }
export async function processMarkdown( async function processMarkdown(
version: string, version: string,
markdown: string, markdown: string
): Promise<string> { ): Promise<string> {
currentVersion = version; currentVersion = version;
const r = (await (await getMarkdownProcessor()).render(markdown)).code; const r = (
await (await getMarkdownProcessor()).render(markdown)
).code;
currentVersion = "NOVERSION"; currentVersion = "NOVERSION";
return r; return r;
} }
export { markdownConfig, processMarkdown };

View file

@ -0,0 +1,69 @@
export function initAnimations() {
const observerOptions = {
root: null,
rootMargin: "0px",
threshold: 0.1,
};
const observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
observer.unobserve(entry.target);
}
});
},
observerOptions
);
const animatedElements = document.querySelectorAll(
".animate-fade-up, .stagger-parent"
);
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();
initTOCHighlighting();
});
}

View file

@ -0,0 +1,260 @@
document.addEventListener("DOMContentLoaded", () => {
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"
);
if (!container || !scroller) return;
const bufferSize = 2;
let items = Array.from(
scroller.querySelectorAll(".marquee-item")
) as HTMLDivElement[];
const originalCount = items.length;
if (originalCount === 0) return;
let itemWidth = 0;
let sequenceWidth = 0;
let targetScrollX = 0;
let currentScrollX = 0;
let isAnimating = false;
let isDown = false;
let lastTouchX = 0;
let touchVelocity = 0;
let lastTouchTime = 0;
const smoothFactor = 0.1;
const snapThreshold = 0.1;
// setup clones
const setupClones = () => {
// remove existing clones
scroller.querySelectorAll(".clone").forEach(c => c.remove());
const originals = Array.from(
scroller.querySelectorAll(".marquee-item")
) as HTMLDivElement[];
// add clones after
for (let i = 0; i < bufferSize; i++) {
originals.forEach(item => {
const clone = item.cloneNode(true) as HTMLDivElement;
clone.classList.add("clone");
scroller.appendChild(clone);
});
}
// add clones before
const beforeContainer = document.createDocumentFragment();
for (let i = 0; i < bufferSize; i++) {
originals.forEach(item => {
const clone = item.cloneNode(true) as HTMLDivElement;
clone.classList.add("clone");
beforeContainer.appendChild(clone);
});
}
scroller.insertBefore(beforeContainer, scroller.firstChild);
items = Array.from(
scroller.querySelectorAll(".marquee-item")
) as HTMLDivElement[];
};
const updateDimensions = () => {
itemWidth = container.clientWidth;
if (itemWidth === 0) return;
sequenceWidth = originalCount * itemWidth;
// standardize width
scroller.style.width = `${items.length * itemWidth}px`;
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);
currentScrollX = targetScrollX;
scroller.style.transform = `translateX(-${currentScrollX}px)`;
};
const lerp = (start: number, end: number, factor: number) =>
start + (end - start) * factor;
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
);
}
}
currentScrollX = lerp(
currentScrollX,
targetScrollX,
smoothFactor
);
// boundary reset
if (currentScrollX > (bufferSize + 1) * sequenceWidth) {
currentScrollX -= sequenceWidth;
targetScrollX -= sequenceWidth;
} else if (
currentScrollX <
(bufferSize - 1) * sequenceWidth
) {
currentScrollX += sequenceWidth;
targetScrollX += sequenceWidth;
}
scroller.style.transform = `translateX(-${currentScrollX}px)`;
// fade in-out and scale items based on distance from center
items.forEach((item, index) => {
const itemCenter = index * itemWidth;
const distance = Math.abs(currentScrollX - itemCenter);
const progress = Math.min(distance / itemWidth, 1); // 0 at center, 1 at edge
const opacity = 1 - progress;
const scale = 1 - progress * 0.1; // scale down as it leaves
const yOffset = progress * 20; // slide down as it leaves
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;
if (content) {
content.style.transform = `scale(${scale}) translateY(${yOffset}px)`;
}
});
const diff = Math.abs(targetScrollX - currentScrollX);
const interaction = isDown || Math.abs(touchVelocity) > 0.1;
if (diff > snapThreshold || interaction) {
requestAnimationFrame(animate);
} else {
isAnimating = false;
currentScrollX = targetScrollX;
scroller.style.transform = `translateX(-${currentScrollX}px)`;
}
};
const startAnimation = () => {
if (!isAnimating) {
isAnimating = true;
requestAnimationFrame(animate);
}
};
// video handling
const videos = scroller.querySelectorAll("video");
const observerOptions = {
root: container,
threshold: 0.5,
};
const videoObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
const video = entry.target as HTMLVideoElement;
if (entry.isIntersecting) {
video.play().catch(() => {}); // Handle potential autoplay blocks
} else {
video.pause();
}
});
}, observerOptions);
videos.forEach(v => {
videoObserver.observe(v);
v.addEventListener("ended", () => {
targetScrollX += itemWidth;
startAnimation();
});
});
// events
btnLeft?.addEventListener("click", () => {
targetScrollX -= itemWidth;
startAnimation();
});
btnRight?.addEventListener("click", () => {
targetScrollX += itemWidth;
startAnimation();
});
container.addEventListener(
"wheel",
e => {
e.preventDefault();
targetScrollX += e.deltaY;
startAnimation();
},
{ passive: false }
);
container.addEventListener("touchstart", e => {
isDown = true;
lastTouchX = e.touches[0].clientX;
lastTouchTime = Date.now();
touchVelocity = 0;
});
container.addEventListener("touchmove", e => {
if (!isDown) return;
const currentTouchX = e.touches[0].clientX;
const deltaX = lastTouchX - currentTouchX;
targetScrollX += deltaX * 1.5;
const now = Date.now();
const dt = now - lastTouchTime;
if (dt > 0) touchVelocity = deltaX / dt;
lastTouchX = currentTouchX;
lastTouchTime = now;
startAnimation();
});
container.addEventListener("touchend", () => {
isDown = false;
targetScrollX += touchVelocity * 100; // Momentum
touchVelocity = 0;
startAnimation();
});
window.addEventListener("resize", updateDimensions);
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
videos.forEach(v => v.pause());
}
});
// init
setupClones();
setTimeout(() => {
updateDimensions();
container.classList.add("initialized");
startAnimation();
}, 50);
});

View file

@ -0,0 +1,89 @@
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 = (transition = true) => {
const theme = getCurrentTheme();
const toggle = document.getElementById(
"theme-manual-toggle"
) as HTMLInputElement;
if (transition) {
document.documentElement.classList.add("changing-theme");
document.documentElement.style.setProperty(
"--theme-transition",
"0.3s var(--ease-in-out)"
);
} else {
document.documentElement.style.removeProperty(
"--theme-transition"
);
}
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;
}
if (transition) {
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(false);
};
// auto-init on client
if (typeof document !== "undefined") {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initTheme);
} else {
initTheme();
}
}

View file

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

46
src/env.d.ts vendored
View file

@ -1,2 +1,46 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" /> /// <reference types="astro/client" />
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro-icon/empty-types" />
declare module "astro-icon/components" {
export const Icon: typeof import("astro-icon/components").Icon;
}
interface ImportMetaEnv {
readonly VERSION_FILE_PATH: string;
readonly BASE_URL: string;
readonly PRODUCTION: string | undefined;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
// fix astro-breadcrumbs
declare module "astro-breadcrumbs" {
interface BreadcrumbsProps {
indexText?: string;
mainText?: string;
crumbs: {
text: string;
href: string;
}[];
linkTextFormat?: string;
truncated?: boolean;
case?:
| "lower"
| "upper"
| "capitalize"
| "title"
| "original";
// Add other props you use here
}
export const Breadcrumbs: (props: BreadcrumbsProps) => any;
export default Breadcrumbs;
}
// fix for "?raw" imports
declare module "*?raw" {
const content: string;
export default content;
}

View file

@ -75,11 +75,11 @@ To start with, let's make a clock. To get the time, we'll use the `date` command
> [!note/Note] > [!note/Note]
> Quickshell can do more than just run processes. Read until the end for more information. > Quickshell can do more than just run processes. Read until the end for more information.
We can use a [Process](@docs/types/quickshell.io/process) object to run commands We can use a [Process](@docs/types/Quickshell.Io/Process) object to run commands
and a @@Quickshell.Io.StdioCollector to read their output. and a @@Quickshell.Io.StdioCollector to read their output.
We'll listen to the @@Quickshell.Io.StdioCollector.streamFinished(s) signal with We'll listen to the @@Quickshell.Io.StdioCollector.streamFinished(s) signal with
a [signal handler](@docs/guide/qml-language/#signal-handlers) a [signal handler](@docs/guide/qml-language#signal-handlers)
to update the text on the clock. to update the text on the clock.
> [!note/Note] > [!note/Note]

View file

@ -172,7 +172,7 @@ Name {
Every object can contain [properties](#properties), [functions](#functions), Every object can contain [properties](#properties), [functions](#functions),
and [signals](#signals). You can find out what properties are available for a type and [signals](#signals). You can find out what properties are available for a type
by looking it up in the [Type Reference](@docs/types/). by looking it up in the [Type Reference](@docs/types).
#### Properties #### Properties

View file

@ -2,6 +2,7 @@
title: "QML Language" title: "QML Language"
index: 10 index: 10
--- ---
import Collapsible from "@components/Collapsible.astro";
Quickshell is configured using the Qt Modeling Language, or QML. Quickshell is configured using the Qt Modeling Language, or QML.
This page explains what you need to know about QML to start using Quickshell. This page explains what you need to know about QML to start using Quickshell.
@ -186,7 +187,7 @@ Name {
Every object can contain [properties](#properties), [functions](#functions), Every object can contain [properties](#properties), [functions](#functions),
and [signals](#signals). You can find out what properties are available for a type and [signals](#signals). You can find out what properties are available for a type
by looking it up in the [Type Reference](@docs/types/). by looking it up in the [Type Reference](@docs/types).
#### Properties #### Properties

View file

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

View file

@ -2,29 +2,29 @@
import { Breadcrumbs } from "astro-breadcrumbs"; import { Breadcrumbs } from "astro-breadcrumbs";
import "astro-breadcrumbs/breadcrumbs.css"; import "astro-breadcrumbs/breadcrumbs.css";
import CreateCopyButtons from "@components/hooks/CreateCopyButtons.astro";
import PreTheme from "@config/PreTheme.astro";
import Header from "@components/Header.astro"; import Header from "@components/Header.astro";
import Head from "@config/Head.astro"; import Head from "@config/Head.astro";
import Nav from "@components/navigation/sidebars/nav/index.astro"; import Nav from "@components/navigation/sidebars/nav/index.astro";
import type { ConfigHeading } from "@src/components/navigation/sidebars/types"; import type { ConfigHeading } from "@src/components/navigation/sidebars/types";
import Footer from "@src/components/Footer.astro"; import Footer from "@src/components/Footer.astro";
import type { TypeData } from "@config/io/types"; import type { TypeData } from "@config/_types";
interface Props { interface Props {
title: string; title: string;
description: string; description: string;
headings?: ConfigHeading[]; headings?: ConfigHeading[];
type?: TypeData type?: TypeData;
} }
const { title, description, headings, type } = Astro.props; const { title, description, headings, type } = Astro.props;
let url = Astro.url.pathname.split("/").filter(s => s !== ""); let url = Astro.url.pathname.split("/").filter((s: string) => s !== "");
const breadcrumbs = [{ const breadcrumbs = [
{
text: "custom", text: "custom",
href: "/", href: "/",
}]; },
];
let linkPath = ""; let linkPath = "";
if (url[0] === "docs") { if (url[0] === "docs") {
@ -49,16 +49,27 @@ for (const segment of url) {
<html lang="en" class="dark"> <html lang="en" class="dark">
<head> <head>
<Head description={description} title={title} /> <Head description={description} title={title} />
<PreTheme />
<CreateCopyButtons />
</head> </head>
<body class="docslayout"> <body class="docslayout">
<Header title={title} headings={headings} type={type}/> <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"> <div class="docslayout-root">
<Nav mobile={false}/> <Nav mobile={false} />
<div class="docslayout-inner" data-pagefind-body> <div class="docslayout-inner" data-pagefind-body>
<Breadcrumbs crumbs={breadcrumbs} linkTextFormat="sentence" truncated={true} data-pagefind-ignore> <Breadcrumbs
crumbs={breadcrumbs}
linkTextFormat="sentence"
truncated={true}
data-pagefind-ignore
>
<svg <svg
<!-- @ts-expect-error -->
slot="index" slot="index"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="1em" width="1em"
@ -69,25 +80,61 @@ for (const segment of url) {
<path <path
fill="currentColor" fill="currentColor"
d="m219.31 108.68l-80-80a16 16 0 0 0-22.62 0l-80 80A15.87 15.87 0 0 0 32 120v96a8 8 0 0 0 8 8h64a8 8 0 0 0 8-8v-56h32v56a8 8 0 0 0 8 8h64a8 8 0 0 0 8-8v-96a15.87 15.87 0 0 0-4.69-11.32M208 208h-48v-56a8 8 0 0 0-8-8h-48a8 8 0 0 0-8 8v56H48v-88l80-80l80 80Z" d="m219.31 108.68l-80-80a16 16 0 0 0-22.62 0l-80 80A15.87 15.87 0 0 0 32 120v96a8 8 0 0 0 8 8h64a8 8 0 0 0 8-8v-56h32v56a8 8 0 0 0 8 8h64a8 8 0 0 0 8-8v-96a15.87 15.87 0 0 0-4.69-11.32M208 208h-48v-56a8 8 0 0 0-8-8h-48a8 8 0 0 0-8 8v56H48v-88l80-80l80 80Z"
></path></svg ></path>
> </svg>
<svg <svg
<!-- @ts-expect-error -->
slot="separator" slot="separator"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="1em" width="1em"
height="1em" height="1em"
viewBox="0 0 256 256" viewBox="0 0 256 256"
><path >
<path
fill="currentColor" fill="currentColor"
d="m181.66 133.66l-80 80a8 8 0 0 1-11.32-11.32L164.69 128L90.34 53.66a8 8 0 0 1 11.32-11.32l80 80a8 8 0 0 1 0 11.32" d="m181.66 133.66l-80 80a8 8 0 0 1-11.32-11.32L164.69 128L90.34 53.66a8 8 0 0 1 11.32-11.32l80 80a8 8 0 0 1 0 11.32"
></path></svg ></path>
> </svg>
</Breadcrumbs> </Breadcrumbs>
<slot/> <slot />
</div> </div>
<slot name="alongside-content"/> <slot name="alongside-content" />
</div> </div>
<Footer/> <Footer />
<script>
import "@config/styling/animations_helper.ts";
import "@config/styling/theme_persistence.ts";
</script>
</body> </body>
</html> </html>
<script>
// FIXME: need to make this work properly, or fold into the markdown processor
let headings = document.getElementsByClassName("heading");
if (headings.length > 0) {
//@ts-expect-error
for (const heading of headings) {
let button = heading.querySelector("h2");
if (button) {
button.onclick = () => {
let link = window.location.href.split("#")[0];
link += `#${button.textContent?.trimEnd().replaceAll(" ", "-").toLowerCase()}`;
window.location.href = link;
navigator.clipboard.writeText(link);
heading.classList.toggle("copied");
setTimeout(() => heading.classList.remove("copied"), 1000);
};
}
let spanButton = heading.querySelector("span");
if (spanButton) {
spanButton.onclick = () => {
let link = window.location.href.split("#")[0];
link += `#${spanButton.textContent?.trim().replaceAll(" ", "-").toLowerCase()}`;
window.location.href = link;
navigator.clipboard.writeText(link);
spanButton.classList.toggle("copied");
setTimeout(() => heading.classList.remove("copied"), 1000);
};
}
}
}
</script>

View file

@ -1,7 +1,6 @@
--- ---
import DocsLayout from "@layouts/DocsLayout.astro"; import DocsLayout from "@layouts/DocsLayout.astro";
import TOC from "@components/navigation/sidebars/TOC.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"; import type { ConfigHeading } from "@src/components/navigation/sidebars/types";
export interface Props { export interface Props {
@ -12,15 +11,20 @@ export interface Props {
const { title, description, headings } = Astro.props; const { title, description, headings } = Astro.props;
--- ---
<DocsLayout title={title} description={description} headings={headings}> <DocsLayout title={title} description={description} headings={headings}>
<div class="docs"> <div class="docs">
<div class="docs-content"> <div class="docs-content">
<hr/> <hr>
<h1>{title}</h1> <h1>{title}</h1>
<slot/> <slot />
</div> </div>
</div> </div>
<TOC slot="alongside-content" mobile={false} title={title} headings={headings} data-pagefind-ignore/> <TOC
slot="alongside-content"
mobile={false}
title={title}
headings={headings}
data-pagefind-ignore
/>
</DocsLayout> </DocsLayout>
<TOCIntersectionObserver/>

View file

@ -7,11 +7,15 @@ export interface Props {
frontmatter: { frontmatter: {
title: string; title: string;
description?: string; description?: string;
} };
} }
const { headings, frontmatter: { title, description } } = Astro.props; const {
headings,
frontmatter: { title, description },
} = Astro.props;
--- ---
<GuideLayout title={title} description={description ?? ""} headings={headings}> <GuideLayout title={title} description={description ?? ""} headings={headings}>
<slot/> <slot />
</GuideLayout> </GuideLayout>

View file

@ -5,10 +5,14 @@ import { processMarkdown } from "@config/io/markdown";
const { versions } = await getVersionsData(); const { versions } = await getVersionsData();
const versionsMd = await Promise.all(versions.filter(version => version.changelog).map(async version => ({ const versionsMd = await Promise.all(
versions
.filter(version => version.changelog)
.map(async version => ({
version, version,
changelog: await processMarkdown(version.name, version.changelog!) changelog: await processMarkdown(version.name, version.changelog ?? ""),
}))); }))
);
const headings = versionsMd.map(({ version }) => ({ const headings = versionsMd.map(({ version }) => ({
text: version.name, text: version.name,
@ -16,6 +20,7 @@ const headings = versionsMd.map(({ version }) => ({
depth: 1, depth: 1,
})); }));
--- ---
<GuideLayout title="Changelog" description="" headings={headings}> <GuideLayout title="Changelog" description="" headings={headings}>
{versionsMd.map(({ version, changelog }) => ( {versionsMd.map(({ version, changelog }) => (
<div style="display: flex; justify-content: space-between"> <div style="display: flex; justify-content: space-between">

View file

@ -2,31 +2,39 @@
import GuideLayout from "@layouts/GuideLayout.astro"; import GuideLayout from "@layouts/GuideLayout.astro";
import { getVersionsData } from "@config/io/generateTypeData"; import { getVersionsData } from "@config/io/generateTypeData";
import { getGuideCollection } from "@config/io/guides"; import { getGuideCollection } from "@config/io/guides";
import { processMarkdown } from "@config/io/markdown";
import { render } from "astro:content"; import { render } from "astro:content";
export async function getStaticPaths() { export async function getStaticPaths() {
const { versions } = await getVersionsData(); const { versions } = await getVersionsData();
let pages = await Promise.all(versions.map(async version => { const pages = await Promise.all(
versions.map(async version => {
const pages = await getGuideCollection(version.name); const pages = await getGuideCollection(version.name);
return pages.map(page => ({ return pages.map(page => ({
params: { version: version.name, id: page.id === "index" ? "/" : page.id }, params: {
version: version.name,
id: page.id === "index" ? "/" : page.id,
},
props: { version, page }, props: { version, page },
})); }));
})); })
);
return pages.flat(); return pages.flat();
} }
const { version, page } = Astro.props; const { page } = Astro.props;
const { headings } = await render(page); const { headings, Content } = await render(page);
// xnzf: version is decided before these pages get processed
// V
// we can't use 'Content' because there isn't a way to pass in a version // we can't use 'Content' because there isn't a way to pass in a version
const html = await processMarkdown(version.name, page.body!);
// const html = await processMarkdown(version.name, page.body!);
--- ---
<GuideLayout title={page.data.title} description="" headings={headings}> <GuideLayout title={page.data.title} description="" headings={headings}>
<Fragment set:html={html}/> <Content />
</GuideLayout> </GuideLayout>

View file

@ -11,6 +11,7 @@ export async function getStaticPaths() {
const { version } = Astro.props; const { version } = Astro.props;
--- ---
<DocsLayout title="Quickshell Docs" description="Quickshell Documentation"> <DocsLayout title="Quickshell Docs" description="Quickshell Documentation">
<h2>Docs</h2> <h2>Docs</h2>
<div class="root-nav"> <div class="root-nav">

View file

@ -9,12 +9,25 @@ import Functions from "@components/type/Functions.astro";
import Signals from "@components/type/Signals.astro"; import Signals from "@components/type/Signals.astro";
import Variants from "@components/type/Variants.astro"; import Variants from "@components/type/Variants.astro";
import Badge from "@components/Badge.astro"; import Badge from "@components/Badge.astro";
import type { ModuleData, TypeData } from "@_types";
interface Props {
version: {
name: string;
};
module: ModuleData;
type: TypeData;
}
export async function getStaticPaths() { export async function getStaticPaths() {
return (await getVersionsData()).versions.flatMap(version => { return (await getVersionsData()).versions.flatMap(version => {
return version.modules.flatMap(module => { return version.modules.flatMap(module => {
return module.types.map(type => ({ return module.types.map(type => ({
params: { version: version.name, module: module.name, type: type.name }, params: {
version: version.name,
module: module.name,
type: type.name,
},
props: { version, module, type }, props: { version, module, type },
})); }));
}); });
@ -29,10 +42,15 @@ const details = type.details
? await processMarkdown(version.name, type.details) ? await processMarkdown(version.name, type.details)
: null; : null;
--- ---
<DocsLayout title={`${module.name} - ${type.name}`} description={type.description ?? ""} type={type}>
<DocsLayout
title={`${module.name} - ${type.name}`}
description={type.description ?? ""}
type={type}
>
<div class="docs"> <div class="docs">
<div class="docs-content typedocs-content"> <div class="docs-content typedocs-content">
<hr /> <hr>
<section class="typedocs-title"> <section class="typedocs-title">
<h2 class="typedocs-title-text" data-pagefind-weight="10"> <h2 class="typedocs-title-text" data-pagefind-weight="10">
{type.name}: {type.name}:
@ -45,11 +63,10 @@ const details = type.details
</a> </a>
):( ):(
<span class="type-datatype" data-pagefind-ignore>{type.name}</span> <span class="type-datatype" data-pagefind-ignore>{type.name}</span>
) )}
}
</h2> </h2>
{type.flags && ( {type.flags && (
<div class="type-flags" data-pagefind-ignore>{type.flags.map(flag => ( <div class="type-flags" data-pagefind-ignore>{type.flags.map((flag:string) => (
<Badge badgeText={flag}/> <Badge badgeText={flag}/>
))}</div> ))}</div>
)} )}
@ -59,23 +76,23 @@ const details = type.details
<subheading class="typedocs-subheading"> <subheading class="typedocs-subheading">
{details ? <span class="parsedMD" set:html={details}/> : (<span class="toparse">{type.description}</span>)} {details ? <span class="parsedMD" set:html={details}/> : (<span class="toparse">{type.description}</span>)}
</subheading> </subheading>
{ Object.keys(type.properties ?? {}).length != 0 && ( {Object.keys(type.properties ?? {}).length != 0 && (
<h2>Properties <a href={`/docs/${version.name}/guide/qml-language#properties`}>[?]</a></h2> <h2>Properties <a href={`/docs/${version.name}/guide/qml-language#properties`}>[?]</a></h2>
<Properties props={type.properties!}/> <Properties props={type.properties!}/>
)} )}
{ (type.functions?.length ?? 0) != 0 && ( {(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 <Functions
funcData={type.functions!} funcData={type.functions!}
/> />
)} )}
{ Object.keys(type.signals ?? {}).length != 0 && ( {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
signals={type.signals!} signals={type.signals!}
/> />
)} )}
{ Object.keys(type.variants ?? {}).length != 0 && ( {Object.keys(type.variants ?? {}).length != 0 && (
<h2>Variants</h2> <h2>Variants</h2>
<Variants <Variants
variants={type.variants!} variants={type.variants!}
@ -83,7 +100,6 @@ const details = type.details
)} )}
</section> </section>
</div> </div>
<TOC mobile={false} type={type} data-pagefind-ignore/> <TOC mobile={false} type={type} data-pagefind-ignore />
</div> </div>
</DocsLayout> </DocsLayout>

View file

@ -2,6 +2,7 @@
import DocsLayout from "@layouts/DocsLayout.astro"; import DocsLayout from "@layouts/DocsLayout.astro";
import { getVersionsData } from "@config/io/generateTypeData"; import { getVersionsData } from "@config/io/generateTypeData";
import { processMarkdown } from "@src/config/io/markdown"; import { processMarkdown } from "@src/config/io/markdown";
import type { TypeData } from "@_types";
export async function getStaticPaths() { export async function getStaticPaths() {
return (await getVersionsData()).versions.flatMap(version => { return (await getVersionsData()).versions.flatMap(version => {
@ -23,12 +24,12 @@ const details = module.details
description="Quickshell Type Documentation" description="Quickshell Type Documentation"
> >
<div class="docs-content"> <div class="docs-content">
<hr /> <hr>
<h2 class="typedocs-title">{module.name} Definitions</h2> <h2 class="typedocs-title">{module.name} Definitions</h2>
<section> <section>
<span>{module.description}</span> <span>{module.description}</span>
<div class="root-nav" data-pagefind-ignore> <div class="root-nav" data-pagefind-ignore>
{module.types.map(type => {module.types.map((type: TypeData) =>
( (
<div class="root-nav-entry"> <div class="root-nav-entry">
<a class="root-nav-link" href={`/docs/${version.name}/types/${module.name}/${type.name}`}> <a class="root-nav-link" href={`/docs/${version.name}/types/${module.name}/${type.name}`}>

View file

@ -1,6 +1,7 @@
--- ---
import DocsLayout from "@layouts/DocsLayout.astro"; import DocsLayout from "@layouts/DocsLayout.astro";
import { getVersionsData } from "@config/io/generateTypeData"; import { getVersionsData } from "@config/io/generateTypeData";
import type { ModuleData } from "@_types";
export async function getStaticPaths() { export async function getStaticPaths() {
return (await getVersionsData()).versions.map(version => ({ return (await getVersionsData()).versions.map(version => ({
@ -11,14 +12,18 @@ export async function getStaticPaths() {
const { version } = Astro.props; const { version } = Astro.props;
--- ---
<DocsLayout title="Quickshell Module Listing" description="Quickshell Type Documentation">
<DocsLayout
title="Quickshell Module Listing"
description="Quickshell Type Documentation"
>
<div class="docs-content"> <div class="docs-content">
<hr/> <hr>
<h2>Module Listing</h2> <h2>Module Listing</h2>
<section> <section>
<span>All modules included with Quickshell</span> <span>All modules included with Quickshell</span>
<div class="root-nav" data-pagefind-ignore> <div class="root-nav" data-pagefind-ignore>
{version.modules.map(module => ( {version.modules.map((module: ModuleData) => (
<div class="root-nav-entry"> <div class="root-nav-entry">
<a class="root-nav-link" href={`/docs/${version.name}/types/${module.name}`}> <a class="root-nav-link" href={`/docs/${version.name}/types/${module.name}`}>
{module.name} {module.name}

View file

@ -8,42 +8,52 @@ const defaultVersion = (await getVersionsData()).default;
const title = "Quickshell"; const title = "Quickshell";
--- ---
<BaseLayout title={title} description="A fully user customizable desktop shell" image="/quickshell.png">
<BaseLayout
title={title}
description="A fully user customizable desktop shell"
image="/quickshell.png"
>
<!--<a class="main-page-banner" href="/changelog"> <!--<a class="main-page-banner" href="/changelog">
Quickshell 0.2.1 has been released! | 2025-10-11 Quickshell 0.2.1 has been released! | 2025-10-11
</a>--> </a>-->
<div class="main-page_hero" data-pagefind-ignore> <div class="main-page_hero" data-pagefind-ignore>
<div class="titlebox"> <div class="titlebox">
<img src="/favicon.svg" alt="Quickshell"/> <img src="/favicon.svg" alt="Quickshell">
<h1 class="gradient-text">Quickshell</h1> <h1 class="gradient-text">Quickshell</h1>
</div> </div>
<section class="main-page_hero-text"> <section class="main-page_hero-text">
<h2>building blocks for your desktop</h2> <h2>building blocks for your desktop</h2>
</section> </section>
<Marquee/> <Marquee />
<section class="about"> <section class="about">
<div class="about-txt"> <div class="about-txt">
<p> <p>
Quickshell is a toolkit for building status bars, widgets, lockscreens, Quickshell is a toolkit for building status bars, widgets,
and other desktop components using QtQuick. It can be used alongside your lockscreens, and other desktop components using QtQuick. It can be
wayland compositor or window manager to build a complete desktop environment. used alongside your wayland compositor or window manager to build a
complete desktop environment.
<br class="about-break"> <br class="about-break">
<br class="about-break"> <br class="about-break">
<a href="/about">More information</a> <a href="/about">More information</a>
</p> </p>
</div> </div>
<div class="about-buttons"> <div class="about-buttons">
<a href={`/docs/${defaultVersion}/guide/install-setup`} class="main-page_link-card"> <a
href={`/docs/${defaultVersion}/guide/install-setup`}
class="main-page_link-card"
>
<h3>Install</h3> <h3>Install</h3>
</a> </a>
<a href={`/docs/${defaultVersion}/types`} class="main-page_link-card main-page_bluecard"> <a
href={`/docs/${defaultVersion}/types`}
class="main-page_link-card main-page_bluecard"
>
<h3>Documentation</h3> <h3>Documentation</h3>
</a> </a>
</div> </div>
</section> </section>
<section class="featurelist-section"> <section class="featurelist-section"><FeatureList /></section>
<FeatureList/>
</section>
</div> </div>
<Footer class="frontpage-footer"/> <Footer class="frontpage-footer" />
</BaseLayout> </BaseLayout>

View file

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

View file

@ -12,18 +12,22 @@
.featurelist-item { .featurelist-item {
position: relative; position: relative;
display: flex; display: flex;
gap: 0.618rem; gap: var(--xs);
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin-block: 0.618rem; margin-block: var(--xs);
border-radius: 9px; border-radius: var(--radius-md);
background-color: hsl(var(--blue) 60% 98%); background-color: hsl(var(--blue) 60% 98%);
padding: 0.618rem; padding: 0.618rem;
border: 1px solid hsl(var(--blue) 9% 75%); border: 1px solid hsl(var(--blue) 9% 75%);
transition:
background-color var(--theme-transition),
border-color var(--theme-transition);
&::before { &::before {
content: ""; content: "";
position: absolute; position: absolute;
inset: 0.618rem; inset: var(--xs);
background-image: radial-gradient( background-image: radial-gradient(
hsl(var(--blue) 9% 75%) 1px, hsl(var(--blue) 9% 75%) 1px,
transparent 1px transparent 1px
@ -33,7 +37,8 @@
} }
} }
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); background-color: hsl(var(--blue) 100% 81% / 0.05);
border-color: hsl(0deg 0% 100% / 0.05); border-color: hsl(0deg 0% 100% / 0.05);
@ -46,18 +51,22 @@ html.dark .featurelist-item {
} }
.feature-text { .feature-text {
margin: 1rem 0; margin: var(--sm) 0;
text-align: center; text-align: center;
font-size: 1.2rem; font-size: 1.2em;
& .feature-title { & .feature-title {
margin-bottom: 0.517rem; margin-bottom: 0.517rem;
} }
& .feature-subtitle { & .feature-subtitle {
color: #303030; 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 { & .feature-subtitle {
color: #afafaf; color: #afafaf;
} }
@ -72,7 +81,8 @@ html.dark .feature-text {
& video { & video {
width: 100%; width: 100%;
aspect-ratio: 16 / 9; aspect-ratio: 16 / 9;
border-radius: 0.681rem; border-radius: var(--radius-sm);
box-shadow: var(--shadow-md);
} }
& .shiki { & .shiki {
@ -80,6 +90,7 @@ html.dark .feature-text {
width: 100%; width: 100%;
height: 100%; height: 100%;
/*font-size: 0.55rem;*/ /*font-size: 0.55rem;*/
box-shadow: var(--shadow-md);
} }
& .showcase-desktop { & .showcase-desktop {
@ -98,8 +109,13 @@ html.dark .feature-text {
} }
} }
html:not(.dark) .feature-showcase .shiki, html:not(.dark):not(:has(input#theme-manual-toggle:checked))
html:not(.dark) .feature-showcase .shiki span { .feature-showcase
.shiki,
html:not(.dark):not(:has(input#theme-manual-toggle:checked))
.feature-showcase
.shiki
span {
background-color: #ffffff; background-color: #ffffff;
} }
@ -118,6 +134,7 @@ html:not(.dark) .feature-showcase .shiki span {
position: absolute; position: absolute;
z-index: 2; z-index: 2;
} }
& .cloud-center img { & .cloud-center img {
width: 80px; width: 80px;
height: 80px; height: 80px;
@ -140,6 +157,7 @@ html:not(.dark) .feature-showcase .shiki span {
& > div { & > div {
transform: rotate(0deg); transform: rotate(0deg);
animation: counter-spin 40s linear infinite; animation: counter-spin 40s linear infinite;
& .feature-icon { & .feature-icon {
width: 80px; width: 80px;
height: 80px; height: 80px;
@ -149,26 +167,34 @@ html:not(.dark) .feature-showcase .shiki span {
&.wayland { &.wayland {
transform: translate(-50%, 0) rotate(0deg); transform: translate(-50%, 0) rotate(0deg);
} }
&.hyprland { &.hyprland {
transform: translate(-50%, 0) rotate(72deg); transform: translate(-50%, 0) rotate(72deg);
& .feature-icon { & .feature-icon {
transform: rotate(-72deg); transform: rotate(-72deg);
} }
} }
&.pipewire { &.pipewire {
transform: translate(-50%, 0) rotate(144deg); transform: translate(-50%, 0) rotate(144deg);
& .feature-icon { & .feature-icon {
transform: rotate(-144deg); transform: rotate(-144deg);
} }
} }
&.x-org { &.x-org {
transform: translate(-50%, 0) rotate(216deg); transform: translate(-50%, 0) rotate(216deg);
& .feature-icon { & .feature-icon {
transform: rotate(-216deg); transform: rotate(-216deg);
} }
} }
&.sway { &.sway {
transform: translate(-50%, 0) rotate(288deg); transform: translate(-50%, 0) rotate(288deg);
& .feature-icon { & .feature-icon {
transform: rotate(-288deg); transform: rotate(-288deg);
} }
@ -180,6 +206,7 @@ html:not(.dark) .feature-showcase .shiki span {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
} }
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
@ -189,6 +216,7 @@ html:not(.dark) .feature-showcase .shiki span {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
} }
to { to {
transform: rotate(-360deg); transform: rotate(-360deg);
} }
@ -213,33 +241,42 @@ html:not(.dark) .feature-showcase .shiki span {
width: auto; width: auto;
align-items: center; align-items: center;
} }
.feature-text { .feature-text {
margin: 0 2.218rem; margin: 0 2.218rem;
} }
.featurelist-item { .featurelist-item {
width: 100%; width: 100%;
padding: 1.217rem; padding: 1.217rem;
justify-content: space-between; justify-content: space-between;
flex-direction: row; flex-direction: row;
} }
.featurelist-item.right { .featurelist-item.right {
flex-direction: row-reverse; flex-direction: row-reverse;
} }
.feature-showcase { .feature-showcase {
height: 22rem; height: 22rem;
} }
.feature-text { .feature-text {
text-align: left; text-align: left;
} }
.feature-showcase { .feature-showcase {
width: auto; width: auto;
aspect-ratio: 16 / 9; aspect-ratio: 16 / 9;
& video { & video {
scale: 1; scale: 1;
} }
& .shiki { & .shiki {
font-size: 0.93rem; font-size: 0.93rem;
} }
.feature-cloud { .feature-cloud {
margin-bottom: 0; margin-bottom: 0;
} }

View file

@ -2,10 +2,10 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
gap: 2.217rem; gap: var(--xl);
font-size: 1.874rem; font-size: var(--lg);
font-weight: 600; font-weight: 600;
margin-inline: 0.618rem; margin-inline: var(--sm);
} }
.marquee-button { .marquee-button {
@ -20,36 +20,63 @@
left: 2px; left: 2px;
right: 2px; right: 2px;
height: 3px; height: 3px;
background-color: hsl(var(--accent-400) / 0.3); background-color: hsla(var(--accent-400) / 0.3);
z-index: -1; z-index: -1;
} }
} }
.marquee { .marquee {
position: relative; position: relative;
display: flex; display: flex;
width: 100%; width: 100%;
margin-block: 1.618rem; margin-block: var(--xl);
justify-content: center; 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 { .marquee-content {
width: 100%;
height: 100%; height: 100%;
overflow: hidden;
display: flex; display: flex;
flex-direction: row;
flex-wrap: nowrap;
margin: 0;
padding: 0;
will-change: transform;
transform: translateX(0);
visibility: hidden;
.initialized & {
visibility: visible;
}
} }
.marquee-item { .marquee-item {
flex: 1 0 100%; position: relative;
transition: transform 0.3s cubic-bezier(0.46, 0.03, 0.52, 0.96);
transform: translateX(var(--scroll));
display: flex; display: flex;
justify-content: center; flex-direction: column;
align-items: center;
gap: var(--md);
padding-inline: 0.5rem; padding-inline: 0.5rem;
box-sizing: border-box;
will-change: opacity;
& > div { & > * {
z-index: 11;
}
& video {
position: relative;
max-width: 75rem; max-width: 75rem;
width: 100%;
box-shadow: var(--shadow-md);
} }
} }
@ -58,34 +85,38 @@
} }
.marquee-item-content { .marquee-item-content {
border-radius: 6px; border-radius: var(--radius-sm);
will-change: transform;
} }
.marquee-scroll { .marquee-scroll {
position: absolute; position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 100%; width: 100%;
max-width: 85rem; max-width: 85rem;
height: 100%; height: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
transition:
background-color 0.3s,
opacity 0.3s;
z-index: 10;
user-select: none;
align-items: stretch; align-items: stretch;
transition:
background-color var(--theme-transition),
opacity var(--theme-transition);
z-index: 20;
user-select: none;
pointer-events: none; pointer-events: none;
padding-inline: 1rem;
} }
.marquee-scroll-arrow { .marquee-scroll-arrow {
max-width: 8rem; width: 8rem;
font-size: 2rem; font-size: 2rem;
pointer-events: all; pointer-events: all;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center;
height: 100%;
& > div { & > div {
width: 2.5rem; width: 2.5rem;
@ -95,10 +126,12 @@
justify-content: center; justify-content: center;
opacity: 0.5; opacity: 0.5;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
backdrop-filter: blur(var(--2xs));
} }
&:hover { &:hover {
cursor: pointer; cursor: pointer;
& > div { & > div {
opacity: 0.9; opacity: 0.9;
} }
@ -132,14 +165,10 @@
@media not (min-width: 83rem) { @media not (min-width: 83rem) {
.marquee-scroll-arrow { .marquee-scroll-arrow {
height: unset; height: unset;
& > div { & > div {
background-color: #55555580; background-color: #55555580;
border-radius: 0.2rem; border-radius: var(--radius-xs);
} }
} }
.marquee-scroll {
width: 92%;
align-items: center;
}
} }

View file

@ -116,3 +116,61 @@
--percent-nav-root_filled: 65%; --percent-nav-root_filled: 65%;
} }
} }
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes ping {
75%,
100% {
transform: scale(2);
opacity: 0;
}
}
@keyframes pulse {
50% {
opacity: 0.5;
}
}
@keyframes bounce {
0% {
transform: none;
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
50% {
transform: translateY(-12%);
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
color: hsla(var(--green) 100 69 / 0.75);
}
100% {
transform: none;
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
}
@keyframes fade {
0% {
transform: scale(0.75);
opacity: 0.6;
}
100% {
transform: scale(1);
opacity: 1;
}
}
:root {
--animate-spin: spin 1s linear infinite;
--animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--animate-bounce: bounce 0.6s var(--ease-out) forwards;
--animate-fade: fade 0.3s cubic-bezier(0.4, 0, 0.6, 1);
}

View file

@ -1,129 +1,18 @@
html { html {
font-family: "Rubik Variable", Inter, system-ui, Avenir, Helvetica, Arial, font-family:
sans-serif; "Rubik Variable", Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-size: 14px; font-size: 14px;
line-height: 1.272; line-height: 1.272;
font-weight: 400; font-weight: 400;
height: 100svh; height: 100svh;
/* width: 100svw; causes horizontal overflow due to the scrollbar*/
width: 100%;
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
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%;
/* secondary */
--blue: 224deg;
--secondary-400: var(--blue) 100% 68%;
--secondary-500: var(--blue) 100% 58%;
--secondary-600: var(--blue) 53% 41%;
--secondary-700: var(--blue) 43% 31%;
--secondary-800: var(--blue) 23% 21%;
--secondary-900: var(--blue) 44% 7%;
/* 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-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%;
--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-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%;
}
html.dark {
/* accent */
--green: 141deg;
--accent-400: var(--green) 100% 67%;
--accent-500: var(--green) 95% 55%;
--accent-600: var(--green) 90% 40%;
--accent-700: var(--green) 80% 30%;
/* secondary */
--white: 194deg;
--secondary-400: var(--white) 33% 100%;
--secondary-500: var(--white) 33% 96%;
--secondary-600: var(--white) 33% 76%;
--secondary-700: var(--white) 33% 56%;
--secondary-800: var(--white) 35% 36%;
--secondary-900: var(--white) 44% 7%;
/* primary */
--blue: 224deg;
--bg-400: var(--blue) 90% 65%;
--bg-500: var(--blue) 83% 45%;
--bg-700: var(--blue) 82% 25%;
--bg-800: var(--blue) 82% 15%;
--bg-900: var(--blue) 82% 3%;
/* docs */
--background: var(--bg-900);
--text: var(--white) 0% 100%;
--text-dark: var(--white) 0% 70%;
--text-darker: var(--white) 0% 40%;
--link: var(--green) 60% 44%;
--toc-link: var(--green) 74% 40%;
--toc-link-active: var(--green) 80% 60%;
--prop-color: 350deg 78% 70%;
--prop-link-color: 350deg 78% 60%;
--func-color: 50deg 78% 70%;
--func-link-color: 50deg 78% 60%;
--signal-color: 270deg 78% 70%;
--signal-link-color: 270deg 85% 60%;
--var-color: 190deg 78% 70%;
--var-link-color: 190deg 85% 60%;
--inner-param-color: 215deg 60% 70%;
--inner-param-border-color: 215deg 26% 46%;
--inner-param-color: 215deg 60% 70%;
--nav-hovered-bkg: var(--blue) 40% 10%;
--nav-hovered-weak-bkg: var(--blue) 35% 8%;
--nav-selected-bkg: var(--blue) 40% 13%;
--nav-selected-hovered-bkg: var(--blue) 40% 17%;
--nav-selected-text: var(--blue) 100% 70%;
--nav-indicator-bkg: var(--blue) 30% 30%;
--toc-hovered-bkg: 0deg 0% 100% / 0.07;
--overlay-bkg: var(--blue) 75% 5%;
--overlay-bkg-border: var(--blue) 45% 15%;
--footer-bkg: var(--blue) 66% 5%;
--footer-bkg-border: var(--blue) 75% 21%;
} }
* { * {
@ -131,6 +20,7 @@ html.dark {
position: relative; position: relative;
margin: 0; margin: 0;
padding: 0; padding: 0;
/* transition: all 0.15s var(--ease-in-out); */
} }
body { body {

View file

@ -1,12 +1,12 @@
pre.shiki { pre.shiki {
margin-block: 1.618rem; margin-block: var(--lg);
} }
:where(p, li):has(> code) code { :where(p, li):has(> code) code {
padding-inline: 0.272rem; padding-inline: var(--sm);
border-radius: 0.272rem; border-radius: var(--radius-xs);
color: hsl(var(--blue) 100% 69%); color: hsl(var(--blue) 100 69);
background-color: hsl(var(--blue) 85% 35% / 0.1); background-color: hsla(var(--blue) 85 35 / 0.1);
} }
.shiki, .shiki,
@ -16,7 +16,9 @@ pre.shiki {
} }
html.dark .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); color: var(--shiki-dark);
background-color: var(--shiki-dark-bg); background-color: var(--shiki-dark-bg);
} }
@ -26,8 +28,11 @@ pre {
border-radius: 0.618rem; border-radius: 0.618rem;
overflow: hidden; overflow: hidden;
text-wrap: wrap; text-wrap: wrap;
transition:
background-color var(--theme-transition),
color var(--theme-transition);
& > button { & .copy-button {
all: unset; all: unset;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
@ -41,17 +46,51 @@ pre {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: hsl(var(--blue) 100% 69%); color: hsla(var(--blue) 100 69 / 0.33);
background-color: hsl(var(--blue) 85% 35% / 0.1); background-color: hsla(var(--blue) 85 35 / 0.01);
cursor: pointer; cursor: pointer;
transition: color 0.25s; transition:
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 { &:hover {
color: hsl(var(--blue) 100% 75%); color: hsla(var(--blue) 100 75 / 0.75);
background-color: hsla(var(--blue) 85 35 / 0.1);
} }
&.copied { &.copied {
animation: pulseGreen 0.5s cubic-bezier(0, 1, 0.6, 1); & .copy-icon {
opacity: 0;
transform: scale(0.5);
}
& .check-icon {
opacity: 1;
transform: scale(1);
}
}
}
&.shiki {
box-shadow: var(--shadow-md);
&:hover .copy-button {
transition: background-color var(--theme-transition);
background-color: hsla(var(--blue) 85 35 / 0.07);
} }
} }
} }

View file

@ -1,3 +1,118 @@
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% 35%;
/* secondary */
--blue: 224deg;
--secondary-400: var(--blue) 100% 68%;
--secondary-500: var(--blue) 100% 58%;
--secondary-600: var(--blue) 53% 41%;
--secondary-700: var(--blue) 43% 31%;
--secondary-800: var(--blue) 23% 21%;
--secondary-900: var(--blue) 44% 7%;
/* primary */
--white: 194deg;
--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-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% 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.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;
--accent-400: var(--green) 100% 67%;
--accent-500: var(--green) 95% 55%;
--accent-600: var(--green) 90% 40%;
--accent-700: var(--green) 80% 30%;
/* secondary */
--white: 194deg;
--secondary-400: var(--white) 33% 100%;
--secondary-500: var(--white) 33% 96%;
--secondary-600: var(--white) 33% 76%;
--secondary-700: var(--white) 33% 56%;
--secondary-800: var(--white) 35% 36%;
--secondary-900: var(--white) 44% 7%;
/* primary */
--blue: 224deg;
--bg-400: var(--blue) 90% 65%;
--bg-500: var(--blue) 83% 45%;
--bg-700: var(--blue) 82% 25%;
--bg-800: var(--blue) 82% 15%;
--bg-900: var(--blue) 82% 3%;
/* docs */
--background: var(--bg-900);
--text: var(--white) 0% 100%;
--text-dark: var(--white) 0% 70%;
--text-darker: var(--white) 0% 40%;
--link: var(--green) 60% 44%;
--toc-link: var(--green) 74% 40%;
--toc-link-active: var(--green) 80% 60%;
--prop-color: 350deg 78% 70%;
--prop-link-color: 350deg 78% 60%;
--func-color: 50deg 78% 70%;
--func-link-color: 50deg 78% 60%;
--signal-color: 270deg 78% 70%;
--signal-link-color: 270deg 85% 60%;
--var-color: 190deg 78% 70%;
--var-link-color: 190deg 85% 60%;
--inner-param-color: 215deg 60% 70%;
--inner-param-border-color: 215deg 26% 46%;
--nav-hovered-bkg: var(--blue) 40% 10%;
--nav-hovered-weak-bkg: var(--blue) 35% 8%;
--nav-selected-bkg: var(--blue) 40% 13%;
--nav-selected-hovered-bkg: var(--blue) 40% 17%;
--nav-selected-text: var(--blue) 100% 70%;
--nav-indicator-bkg: var(--blue) 30% 30%;
--toc-hovered-bkg: 0deg 0% 100% / 0.07;
--overlay-bkg: var(--blue) 75% 5%;
--overlay-bkg-border: var(--blue) 45% 15%;
--footer-bkg: var(--blue) 66% 5%;
--footer-bkg-border: var(--blue) 75% 21%;
}
.typeprop-link { .typeprop-link {
color: hsl(var(--prop-link-color)); color: hsl(var(--prop-link-color));

View file

@ -0,0 +1,6 @@
@import "normalize.css";
@import "vars.css";
@import "animations.css";
@import "base.css";
@import "code.css";
@import "colors.css";

475
src/styles/css-config/normalize.css vendored Normal file
View file

@ -0,0 +1,475 @@
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS and IE text size adjust after device orientation change,
* without disabling user zoom.
*/
html {
font-family: "Inter", sans-serif;
/* 1 */
-ms-text-size-adjust: 100%;
/* 2 */
-webkit-text-size-adjust: 100%;
/* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
/* article, */
/* aside, */
details,
/* figcaption, */
/* figure, */
/* footer, */
/* header, */
/* hgroup, */
/* main, */
/* menu, */
/* nav, */
/* section, */
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block;
/* 1 */
vertical-align: baseline;
/* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability of focused elements when they are also in an
* active/hover state.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: var(--hl-onbackground);
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit;
/* 1 */
font: inherit;
/* 2 */
margin: 0;
/* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
* 4. CUSTOM FOR WEBFLOW: Removed the input[type="submit"] selector to reduce
* specificity and defer to the .w-button selector
*/
button,
html input[type="button"],
input[type="reset"] {
-webkit-appearance: button;
/* 2 */
cursor: pointer;
/* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box;
/* 1 */
padding: 0;
/* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. CUSTOM FOR WEBFLOW: changed from `textfield` to `none` to normalize iOS rounded input
* 2. CUSTOM FOR WEBFLOW: box-sizing: content-box rule removed
* (similar to normalize.css >=4.0.0)
*/
input[type="search"] {
-webkit-appearance: none;
/* 1 */
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0;
/* 1 */
padding: 0;
/* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}
p {
margin: 0;
}
/* Remove default margins and padding on all basic HTML elements */
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
}

View file

@ -0,0 +1,82 @@
html {
--scaleFactor: 1.618;
--wholestep: 1.618;
--halfstep: 1.272;
--quarterstep: 1.128;
--eighthstep: 1.062;
--wholestep-dec: 0.618;
--halfstep-dec: 0.272;
--quarterstep-dec: 0.128;
--eighthstep-dec: 0.062;
--md: 1em;
--sm: calc(1em / var(--scaleFactor));
--xs: calc(var(--sm) / var(--scaleFactor));
--2xs: calc(var(--xs) / var(--scaleFactor));
--3xs: calc(var(--2xs) / var(--scaleFactor));
--lg: calc(1em * var(--scaleFactor));
--xl: calc(var(--lg) * var(--scaleFactor));
--2xl: calc(var(--xl) * var(--scaleFactor));
--3xl: calc(var(--2xl) * var(--scaleFactor));
--4xl: calc(var(--3xl) * var(--scaleFactor));
/* INFO: Unitless sizes;
required for adhoc calculations
(division and multiplication in calc() require unitless numbers)
*/
/* NOTE:
in calc() with (x*y) or (x/y) the "y" must be a unitless number
*/
--sm-unitless: calc(1 / var(--scaleFactor));
--xs-unitless: calc(var(--sm-unitless) / var(--scaleFactor));
--2xs-unitless: calc(var(--xs-unitless) / var(--scaleFactor));
--lg-unitless: calc(1 * var(--scaleFactor));
--xl-unitless: calc(var(--lg-unitless) * var(--scaleFactor));
--2xl-unitless: calc(var(--xl-unitless) * var(--scaleFactor));
--radius-xs: var(--xs);
--radius-sm: var(--sm);
--radius-md: var(--md);
--radius-lg: var(--lg);
--radius-xl: var(--xl);
--shadow-sm-units: 0 0 1px 0;
--shadow-md-units-1: 0 4px 6px;
--shadow-md-units-2: 0 2px 4px;
--shadow-md-units-3: 0 0 1px;
--shadow-lg-units-1: 0 11px 15px -3px;
--shadow-lg-units-2: 0 2px 6px;
--shadow-lg-units-3: 0 0 1px;
--shadow-xl-units-1: 0 20px 25px;
--shadow-xl-units-2: 0 5px 11px;
--shadow-xl-units-3: 0 5px 11px;
--shadow-2xl-units-1: 0 25px 50px;
--shadow-2xl-units-2: 0 9px 18px;
--shadow-2xl-units-3: 0 0 1px;
--shadow-sm: var(--shadow-sm-units) rgba(0, 0, 0, 0.11);
--shadow-md:
var(--shadow-md-units-1) rgba(0, 0, 0, 0.08),
var(--shadow-md-units-2) rgba(0, 0, 0, 0.11),
var(--shadow-md-units-3) rgba(0, 0, 0, 0.4);
--shadow-lg:
var(--shadow-lg-units-1) rgba(0, 0, 0, 0.11),
var(--shadow-lg-units-2) rgba(0, 0, 0, 0.07),
var(--shadow-lg-units-3) rgba(0, 0, 0, 0.4);
--shadow-xl:
var(--shadow-xl-units-1) rgba(0, 0, 0, 0.09),
var(--shadow-xl-units-2) rgba(0, 0, 0, 0.12),
var(--shadow-xl-units-3) rgba(0, 0, 0, 0.4);
--shadow-2xl:
var(--shadow-2xl-units-1) rgba(0, 0, 0, 0.23),
var(--shadow-2xl-units-2) rgba(0, 0, 0, 0.1),
var(--shadow-2xl-units-3) rgba(0, 0, 0, 0.4);
--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: 0s var(--ease-in-out);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,8 @@
--search-corners: calc(0.3125rem * 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-size: calc(1.875rem * var(--pagefind-ui-scale));
--search-page-icon-inline-start: calc( --search-page-icon-inline-start: calc(
(var(--search-result-pad-inline-start) - var(--search-page-icon-size)) / 2 (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( --search-tree-diagram-inline-start: calc(
@ -57,9 +58,11 @@
#qs_search .pagefind-ui__search-clear::before { #qs_search .pagefind-ui__search-clear::before {
content: ""; 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; 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; center / 50% no-repeat;
background-color: hsl(0deg 25% 45%); background-color: hsl(0deg 25% 45%);
display: block; display: block;
@ -166,9 +169,11 @@
inset-inline-start: var(--search-tree-diagram-inline-start); inset-inline-start: var(--search-tree-diagram-inline-start);
width: var(--search-tree-diagram-size); width: var(--search-tree-diagram-size);
background: hsl(var(--blue) 10% 30%); 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; 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; 0% 0% / 100% no-repeat;
} }
@ -204,9 +209,11 @@
inset-inline-start: var(--search-tree-diagram-inline-start); inset-inline-start: var(--search-tree-diagram-inline-start);
width: var(--search-tree-diagram-size); width: var(--search-tree-diagram-size);
background: hsl(var(--blue) 10% 30%); 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; 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; 0% 0% / 100% no-repeat;
} }
} }
@ -219,7 +226,8 @@
/* default styles */ /* default styles */
site-search { site-search {
--shadow-lg: 0px 25px 7px hsl(0deg, 0%, 0%, 0.03), --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 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); 0px 4px 4px hsl(0deg, 0%, 0%, 0.75), 0px 4px 2px hsl(0deg, 0%, 0%, 0.25);
display: contents; display: contents;
@ -275,7 +283,8 @@ button > kbd {
background-color: hsl(var(--blue) 15% 80%); 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); background-color: hsl(var(--blue) 5% 20% / 0.5);
} }
@ -368,7 +377,8 @@ 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); background-color: hsla(var(--blue) 15% 15% / 0.5);
color: hsl(var(--blue) 40% 65%); color: hsl(var(--blue) 40% 65%);

View file

@ -30,6 +30,28 @@
font-size: 1.614rem; font-size: 1.614rem;
max-height: 500px; 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 { & > svg {
height: 100%; height: 100%;
width: 24px; width: 24px;
@ -61,7 +83,9 @@
display: none; display: none;
} }
transition: width 0.3s ease, padding 0.3s ease; transition:
width 0.3s ease,
padding 0.3s ease;
&.shown { &.shown {
& .toc-content { & .toc-content {
@ -115,7 +139,7 @@
margin-right: 1.272rem; margin-right: 1.272rem;
& .toc_a { & .toc_a {
transition: color 0.33s; transition: color var(--theme-transition);
color: hsl(var(--toc-link)); color: hsl(var(--toc-link));
} }

View file

@ -1,9 +1,6 @@
@import "remark-github-blockquote-alert/alert.css"; @import "remark-github-blockquote-alert/alert.css";
@import "./css-config/base.css";
@import "./css-config/animations.css";
@import "./css-config/code.css";
@import "./css-config/colors.css";
@import "./css-config/entry.css";
@import "./main-page.css"; @import "./main-page.css";
@import "./docs/nav/nav.css"; @import "./docs/nav/nav.css";
@ -15,42 +12,69 @@
@import "./components/featurelist.css"; @import "./components/featurelist.css";
@import "./components/marquee.css"; @import "./components/marquee.css";
.changing-theme * { .theme-toggle {
transition: none !important; height: 24px;
font-size: 1.614rem;
color: hsla(var(--signal-color) / 0.7);
&:hover {
cursor: pointer;
color: hsla(var(--signal-color) / 1);
}
} }
/* color styling */ /* color styling */
.header { .header {
background-color: hsl(var(--bg-400)); background-color: hsl(var(--bg-400));
box-shadow: 0 1px 1px 1px hsla(var(--white) 100 0 / 0.1); 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, .baselayout,
.docslayout { .docslayout {
background-color: hsl(var(--background)); background-color: hsl(var(--background));
color: hsl(var(--secondary-900)); color: hsl(var(--secondary-900));
transition:
background-color var(--theme-transition),
color var(--theme-transition);
} }
a { a {
color: hsl(var(--link)); color: hsl(var(--link));
text-decoration: none; text-decoration: none;
transition: color var(--theme-transition);
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
cursor: pointer; cursor: pointer;
} }
} }
html.dark .baselayout, 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)); background-color: hsl(var(--bg-900));
color: hsl(var(--secondary-400)); color: hsl(var(--secondary-400));
} }
html.dark { html.dark,
html:has(input#theme-manual-toggle:checked) {
& .header { & .header {
background-color: hsl(var(--secondary-900)); background-color: hsl(var(--secondary-900));
color: hsl(var(--secondary-500)); color: hsl(var(--secondary-500));
} }
& .theme-toggle {
color: hsla(var(--func-color) / 0.7);
&:hover {
color: hsla(var(--func-color) / 1);
}
}
} }
/* layout and positioning */ /* layout and positioning */
@ -65,6 +89,7 @@ html.dark {
transparent transparent
); );
} }
.unset { .unset {
all: unset; all: unset;
} }
@ -133,47 +158,63 @@ body.overflow-toc {
display: block; display: block;
} }
.theme-toggle {
height: 24px;
font-size: 1.614rem;
&:hover {
cursor: pointer;
}
}
footer { footer {
position: relative; position: relative;
margin-top: var(--2xl);
width: 100%; width: 100%;
font-size: 0.9rem; font-size: 0.9rem;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 1rem 2rem; padding: 1rem 2rem;
overflow: hidden;
flex-shrink: 0; flex-shrink: 0;
background: hsl(var(--footer-bkg)); background: hsl(var(--footer-bkg));
transition:
background-color var(--theme-transition),
color var(--theme-transition);
&::before { &::before {
content: ""; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: -1rem; left: 0;
right: calc(50% + 1.25rem);
height: 1px; height: 1px;
width: calc(100% + 1rem);
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
transparent 0%, transparent 0%,
hsl(var(--footer-bkg-border)) 50%, hsl(var(--footer-bkg-border)) 100%
);
}
&::after {
content: "";
position: absolute;
top: 0;
right: 0;
left: calc(50% + 1.25rem);
height: 1px;
background: linear-gradient(
90deg,
hsl(var(--footer-bkg-border)) 0%,
transparent 100% transparent 100%
); );
} }
& .theme-toggle {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
width: max-content;
height: max-content;
}
& a { & a {
color: hsl(var(--text-dark)); color: hsl(var(--text-dark));
transition: color 0.3s ease; transition: color var(--theme-transition);
& .hint { & .hint {
transition: color 0.3s ease; transition: color var(--theme-transition);
} }
&:nth-child(2) .hint { &:nth-child(2) .hint {
@ -223,10 +264,11 @@ footer {
gap: 0.373rem; gap: 0.373rem;
align-items: flex-start; align-items: flex-start;
font-size: 2.5rem; font-size: 2.5rem;
} }
& .changelog { & .changelog {
display: flex; display: flex;
& a { & a {
text-decoration: none; text-decoration: none;
margin-inline: auto; margin-inline: auto;

View file

@ -45,16 +45,15 @@ h1.gradient-text {
hsl(var(--green) 80% 42%), hsl(var(--green) 80% 42%),
hsl(var(--blue) 80% 49%) hsl(var(--blue) 80% 49%)
); );
background-clip: text;
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
} }
html.dark h1.gradient-text { html.dark h1.gradient-text,
background: linear-gradient( html:has(input#theme-manual-toggle:checked) h1.gradient-text {
30deg, background: linear-gradient(30deg, #42b96b, #4281b9);
#42b96b, background-clip: text;
#4281b9
);
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
} }
@ -76,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 { & h2 {
color: hsl(var(--blue) 100% 83%); color: hsl(var(--blue) 100% 83%);
} }
@ -95,7 +95,9 @@ html.dark .main-page_hero-text {
font-size: 1.1rem; font-size: 1.1rem;
} }
.about-break { display: none } .about-break {
display: none;
}
.about-buttons { .about-buttons {
display: flex; display: flex;
@ -130,12 +132,13 @@ html.dark .main-page_hero-text {
align-items: center; align-items: center;
width: 100%; width: 100%;
height: 3.67rem; height: 3.67rem;
border-radius: 9px; border-radius: var(--radius-sm);
overflow: hidden; overflow: hidden;
border: 1px solid hsl(var(--green) 10% 10%); box-shadow: var(--shadow-md);
transition: transition:
background-color 0.3s, background-color var(--theme-transition),
border-color 0.3s; box-shadow var(--theme-transition),
border-color var(--theme-transition);
background-color: hsl(var(--green) 38% 30%); background-color: hsl(var(--green) 38% 30%);
color: hsl(194deg 0% 100%); color: hsl(194deg 0% 100%);
@ -143,17 +146,22 @@ html.dark .main-page_hero-text {
&:hover { &:hover {
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
border-color: hsl(var(--green) 20% 20%);
background-color: hsl(var(--green) 48% 40%); background-color: hsl(var(--green) 48% 40%);
box-shadow:
var(--shadow-md-units-1) hsla(var(--green) 48 45 / 0.08),
var(--shadow-md-units-2) hsla(var(--green) 48 45 / 0.11),
var(--shadow-md-units-3) hsla(var(--green) 48 45 / 0.4);
} }
&.main-page_bluecard { &.main-page_bluecard {
border-color: hsl(var(--blue) 10% 10%);
background-color: hsl(var(--blue) 38% 30%); background-color: hsl(var(--blue) 38% 30%);
&:hover { &:hover {
border-color: hsl(var(--blue) 20% 20%);
background-color: hsl(var(--blue) 48% 40%); background-color: hsl(var(--blue) 48% 40%);
box-shadow:
var(--shadow-md-units-1) hsla(var(--blue) 48 45 / 0.08),
var(--shadow-md-units-2) hsla(var(--blue) 48 45 / 0.11),
var(--shadow-md-units-3) hsla(var(--blue) 48 45 / 0.4);
} }
} }
@ -162,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%); background-color: hsl(var(--green) 38% 25%);
color: hsl(194deg 0% 100%); color: hsl(194deg 0% 100%);
@ -226,7 +235,15 @@ html.dark .main-page_link-card {
flex-direction: row; flex-direction: row;
} }
.about-txt { max-width: 70% } .about-txt {
.about-break { display: unset } max-width: 70%;
.about-buttons { flex-direction: column } }
.about-break {
display: unset;
}
.about-buttons {
flex-direction: column;
}
} }

View file

@ -1,6 +1,8 @@
{ {
"extends": "astro/tsconfigs/strict", "extends": "astro/tsconfigs/strict",
"include": ["src/**/*", "**/**.d.ts", "pagefind.ts"],
"compilerOptions": { "compilerOptions": {
"lib": ["es2023"],
"plugins": [ "plugins": [
{ {
"name": "@astrojs/ts-plugin" "name": "@astrojs/ts-plugin"
@ -9,32 +11,25 @@
"jsx": "preserve", "jsx": "preserve",
"jsxImportSource": "solid-js", "jsxImportSource": "solid-js",
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@*": [ "@*": ["./*"],
"./*" "@/*": ["./src/*"],
], "@config/*": ["./src/config/*"],
"@/*": [ "@icons": ["./src/components/icons.tsx"],
"./src/*" "@icons/*": ["./src/icons/*"],
], "@components/*": ["./src/components/*"],
"@config/*": [ "@layouts/*": ["./src/layouts/*"],
"./src/config/*" "@styles/*": ["./src/styles/*"],
], "@_types": ["./src/config/_types/index.ts"],
"@icons": [ "@_types/*": ["./src/config/_types/*"]
"./src/components/icons.tsx" },
], "types": ["astro/client"]
"@icons/*": [
"./src/icons/*"
],
"@components/*": [
"./src/components/*"
],
"@layouts/*": [
"./src/layouts/*"
],
"@styles/*": [
"./src/styles/*"
]
}
} }
} }

6453
yarn.lock

File diff suppressed because it is too large Load diff