aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/docs/src/overrides
diff options
context:
space:
mode:
author简律纯 <i@jyunko.cn>2023-12-15 09:11:47 +0800
committer简律纯 <i@jyunko.cn>2023-12-15 09:11:47 +0800
commitbe8de118db913711eb72ae5187d26e54a0055727 (patch)
tree96cd6c012dafa3f4015e54edef90df5eaaab0ddb /docs/src/overrides
parent9b2d27ba1d91a0d5531bc9c0d52c3887a2dfb2aa (diff)
downloadinfini-be8de118db913711eb72ae5187d26e54a0055727.tar.gz
infini-be8de118db913711eb72ae5187d26e54a0055727.zip
refactor(docs): optmst `docs` dir & `deps`
Diffstat (limited to 'docs/src/overrides')
-rw-r--r--docs/src/overrides/assets/javascripts/components/_/index.ts104
-rw-r--r--docs/src/overrides/assets/javascripts/components/iconsearch/_/index.ts94
-rw-r--r--docs/src/overrides/assets/javascripts/components/iconsearch/index.ts25
-rw-r--r--docs/src/overrides/assets/javascripts/components/iconsearch/query/index.ts96
-rw-r--r--docs/src/overrides/assets/javascripts/components/iconsearch/result/index.ts237
-rw-r--r--docs/src/overrides/assets/javascripts/components/index.ts25
-rw-r--r--docs/src/overrides/assets/javascripts/components/sponsorship/index.ts149
-rw-r--r--docs/src/overrides/assets/javascripts/custom.ts55
-rw-r--r--docs/src/overrides/assets/javascripts/integrations/analytics/index.ts42
-rw-r--r--docs/src/overrides/assets/javascripts/integrations/index.ts23
-rw-r--r--docs/src/overrides/assets/javascripts/templates/iconsearch/index.tsx95
-rw-r--r--docs/src/overrides/assets/javascripts/templates/index.ts24
-rw-r--r--docs/src/overrides/assets/javascripts/templates/sponsorship/index.tsx67
-rw-r--r--docs/src/overrides/assets/stylesheets/custom.scss44
-rw-r--r--docs/src/overrides/assets/stylesheets/custom/_typeset.scss294
-rw-r--r--docs/src/overrides/assets/stylesheets/custom/layout/_banner.scss66
-rw-r--r--docs/src/overrides/assets/stylesheets/custom/layout/_hero.scss123
-rw-r--r--docs/src/overrides/assets/stylesheets/custom/layout/_iconsearch.scss136
-rw-r--r--docs/src/overrides/assets/stylesheets/custom/layout/_sponsorship.scss128
-rw-r--r--docs/src/overrides/home.html106
-rw-r--r--docs/src/overrides/hooks/shortcodes.py283
-rw-r--r--docs/src/overrides/hooks/translations.html54
-rw-r--r--docs/src/overrides/hooks/translations.py193
-rw-r--r--docs/src/overrides/main.html59
24 files changed, 2522 insertions, 0 deletions
diff --git a/docs/src/overrides/assets/javascripts/components/_/index.ts b/docs/src/overrides/assets/javascripts/components/_/index.ts
new file mode 100644
index 00000000..3cb4c18e
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/components/_/index.ts
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { getElement, getElements } from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Component type
+ */
+export type ComponentType =
+ | "iconsearch" /* Icon search */
+ | "iconsearch-query" /* Icon search input */
+ | "iconsearch-result" /* Icon search results */
+ | "sponsorship" /* Sponsorship */
+ | "sponsorship-count" /* Sponsorship count */
+ | "sponsorship-total" /* Sponsorship total */
+
+/**
+ * Component
+ *
+ * @template T - Component type
+ * @template U - Reference type
+ */
+export type Component<
+ T extends {} = {},
+ U extends HTMLElement = HTMLElement
+> =
+ T & {
+ ref: U /* Component reference */
+ }
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Component type map
+ */
+interface ComponentTypeMap {
+ "iconsearch": HTMLElement /* Icon search */
+ "iconsearch-query": HTMLInputElement /* Icon search input */
+ "iconsearch-result": HTMLElement /* Icon search results */
+ "sponsorship": HTMLElement /* Sponsorship */
+ "sponsorship-count": HTMLElement /* Sponsorship count */
+ "sponsorship-total": HTMLElement /* Sponsorship total */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve the element for a given component or throw a reference error
+ *
+ * @template T - Component type
+ *
+ * @param type - Component type
+ * @param node - Node of reference
+ *
+ * @returns Element
+ */
+export function getComponentElement<T extends ComponentType>(
+ type: T, node: ParentNode = document
+): ComponentTypeMap[T] {
+ return getElement(`[data-mdx-component=${type}]`, node)
+}
+
+/**
+ * Retrieve all elements for a given component
+ *
+ * @template T - Component type
+ *
+ * @param type - Component type
+ * @param node - Node of reference
+ *
+ * @returns Elements
+ */
+export function getComponentElements<T extends ComponentType>(
+ type: T, node: ParentNode = document
+): ComponentTypeMap[T][] {
+ return getElements(`[data-mdx-component=${type}]`, node)
+}
diff --git a/docs/src/overrides/assets/javascripts/components/iconsearch/_/index.ts b/docs/src/overrides/assets/javascripts/components/iconsearch/_/index.ts
new file mode 100644
index 00000000..f509a6f9
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/components/iconsearch/_/index.ts
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { Observable, merge } from "rxjs"
+
+import { configuration } from "~/_"
+import { requestJSON } from "~/browser"
+
+import { Component, getComponentElement } from "../../_"
+import {
+ IconSearchQuery,
+ mountIconSearchQuery
+} from "../query"
+import {
+ IconSearchResult,
+ mountIconSearchResult
+} from "../result"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Icon category
+ */
+export interface IconCategory {
+ base: string /* Category base URL */
+ data: Record<string, string> /* Category data */
+}
+
+/**
+ * Icon search index
+ */
+export interface IconSearchIndex {
+ icons: IconCategory /* Icons */
+ emojis: IconCategory /* Emojis */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Icon search
+ */
+export type IconSearch =
+ | IconSearchQuery
+ | IconSearchResult
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount icon search
+ *
+ * @param el - Icon search element
+ *
+ * @returns Icon search component observable
+ */
+export function mountIconSearch(
+ el: HTMLElement
+): Observable<Component<IconSearch>> {
+ const config = configuration()
+ const index$ = requestJSON<IconSearchIndex>(
+ new URL("assets/javascripts/iconsearch_index.json", config.base)
+ )
+
+ /* Retrieve query and result components */
+ const query = getComponentElement("iconsearch-query", el)
+ const result = getComponentElement("iconsearch-result", el)
+
+ /* Create and return component */
+ const query$ = mountIconSearchQuery(query)
+ const result$ = mountIconSearchResult(result, { index$, query$ })
+ return merge(query$, result$)
+}
diff --git a/docs/src/overrides/assets/javascripts/components/iconsearch/index.ts b/docs/src/overrides/assets/javascripts/components/iconsearch/index.ts
new file mode 100644
index 00000000..9d856774
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/components/iconsearch/index.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./query"
+export * from "./result"
diff --git a/docs/src/overrides/assets/javascripts/components/iconsearch/query/index.ts b/docs/src/overrides/assets/javascripts/components/iconsearch/query/index.ts
new file mode 100644
index 00000000..03a3daad
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/components/iconsearch/query/index.ts
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ combineLatest,
+ delay,
+ distinctUntilChanged,
+ filter,
+ fromEvent,
+ map,
+ merge,
+ startWith,
+ withLatestFrom
+} from "rxjs"
+
+import { watchElementFocus } from "~/browser"
+
+import { Component } from "../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Icon search query
+ */
+export interface IconSearchQuery {
+ value: string /* Query value */
+ focus: boolean /* Query focus */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount icon search query
+ *
+ * @param el - Icon search query element
+ *
+ * @returns Icon search query component observable
+ */
+export function mountIconSearchQuery(
+ el: HTMLInputElement
+): Observable<Component<IconSearchQuery, HTMLInputElement>> {
+
+ /* Intercept focus and input events */
+ const focus$ = watchElementFocus(el)
+ const value$ = merge(
+ fromEvent(el, "keyup"),
+ fromEvent(el, "focus").pipe(delay(1))
+ )
+ .pipe(
+ map(() => el.value),
+ startWith(el.value),
+ distinctUntilChanged(),
+ )
+
+ /* Log search on blur */
+ focus$
+ .pipe(
+ filter(active => !active),
+ withLatestFrom(value$)
+ )
+ .subscribe(([, value]) => {
+ const path = document.location.pathname
+ if (typeof ga === "function" && value.length)
+ ga("send", "pageview", `${path}?q=[icon]+${value}`)
+ })
+
+ /* Combine into single observable */
+ return combineLatest([value$, focus$])
+ .pipe(
+ map(([value, focus]) => ({ ref: el, value, focus })),
+ )
+}
diff --git a/docs/src/overrides/assets/javascripts/components/iconsearch/result/index.ts b/docs/src/overrides/assets/javascripts/components/iconsearch/result/index.ts
new file mode 100644
index 00000000..2b9d97fb
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/components/iconsearch/result/index.ts
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { filter as search } from "fuzzaldrin-plus"
+import {
+ Observable,
+ Subject,
+ bufferCount,
+ combineLatest,
+ distinctUntilKeyChanged,
+ filter,
+ finalize,
+ map,
+ merge,
+ of,
+ switchMap,
+ tap,
+ withLatestFrom,
+ zipWith
+} from "rxjs"
+
+import {
+ getElement,
+ watchElementBoundary
+} from "~/browser"
+import { round } from "~/utilities"
+
+import { Icon, renderIconSearchResult } from "_/templates"
+
+import { Component } from "../../_"
+import { IconSearchIndex } from "../_"
+import { IconSearchQuery } from "../query"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Icon search result
+ */
+export interface IconSearchResult {
+ data: Icon[] /* Search result data */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ index$: Observable<IconSearchIndex> /* Search index observable */
+ query$: Observable<IconSearchQuery> /* Search query observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ index$: Observable<IconSearchIndex> /* Search index observable */
+ query$: Observable<IconSearchQuery> /* Search query observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch icon search result
+ *
+ * @param el - Icon search result element
+ * @param options - Options
+ *
+ * @returns Icon search result observable
+ */
+export function watchIconSearchResult(
+ el: HTMLElement, { index$, query$ }: WatchOptions
+): Observable<IconSearchResult> {
+ switch (el.getAttribute("data-mdx-mode")) {
+
+ case "file":
+ return combineLatest([
+ query$.pipe(distinctUntilKeyChanged("value")),
+ index$
+ .pipe(
+ map(({ icons }) => Object.values(icons.data)
+ .map(icon => icon.replace(/\.svg$/, ""))
+ )
+ )
+ ])
+ .pipe(
+ map(([{ value }, data]) => search(data, value)),
+ switchMap(files => index$.pipe(
+ map(({ icons }) => ({
+ data: files.map<Icon>(shortcode => {
+ return {
+ shortcode,
+ url: [
+ icons.base,
+ shortcode,
+ ".svg"
+ ].join("")
+ }
+ })
+ }))
+ ))
+ )
+
+ default:
+ return combineLatest([
+ query$.pipe(distinctUntilKeyChanged("value")),
+ index$
+ .pipe(
+ map(({ icons, emojis }) => [
+ ...Object.keys(icons.data),
+ ...Object.keys(emojis.data)
+ ])
+ )
+ ])
+ .pipe(
+ map(([{ value }, data]) => search(data, value)),
+ switchMap(shortcodes => index$.pipe(
+ map(({ icons, emojis }) => ({
+ data: shortcodes.map<Icon>(shortcode => {
+ const category =
+ shortcode in icons.data
+ ? icons
+ : emojis
+ return {
+ shortcode,
+ url: [
+ category.base,
+ category.data[shortcode]
+ ].join("")
+ }
+ })
+ }))
+ ))
+ )
+ }
+}
+
+/**
+ * Mount icon search result
+ *
+ * @param el - Icon search result element
+ * @param options - Options
+ *
+ * @returns Icon search result component observable
+ */
+export function mountIconSearchResult(
+ el: HTMLElement, { index$, query$ }: MountOptions
+): Observable<Component<IconSearchResult, HTMLElement>> {
+ const push$ = new Subject<IconSearchResult>()
+ const boundary$ = watchElementBoundary(el)
+ .pipe(
+ filter(Boolean)
+ )
+
+ /* Update search result metadata */
+ const meta = getElement(":scope > :first-child", el)
+ push$
+ .pipe(
+ withLatestFrom(query$)
+ )
+ .subscribe(([{ data }, { value }]) => {
+ if (value) {
+ switch (data.length) {
+
+ /* No results */
+ case 0:
+ meta.textContent = "No matches"
+ break
+
+ /* One result */
+ case 1:
+ meta.textContent = "1 match"
+ break
+
+ /* Multiple result */
+ default:
+ meta.textContent = `${round(data.length)} matches`
+ }
+ } else {
+ meta.textContent = "Type to start searching"
+ }
+ })
+
+ /* Update icon search result list */
+ const file = el.getAttribute("data-mdx-mode") === "file"
+ const list = getElement(":scope > :last-child", el)
+ push$
+ .pipe(
+ tap(() => list.innerHTML = ""),
+ switchMap(({ data }) => merge(
+ of(...data.slice(0, 10)),
+ of(...data.slice(10))
+ .pipe(
+ bufferCount(10),
+ zipWith(boundary$),
+ switchMap(([chunk]) => chunk)
+ )
+ )),
+ withLatestFrom(query$)
+ )
+ .subscribe(([result, { value }]) => list.appendChild(
+ renderIconSearchResult(result, value, file)
+ ))
+
+ /* Create and return component */
+ return watchIconSearchResult(el, { query$, index$ })
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+}
diff --git a/docs/src/overrides/assets/javascripts/components/index.ts b/docs/src/overrides/assets/javascripts/components/index.ts
new file mode 100644
index 00000000..ec6c9dce
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/components/index.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./iconsearch"
+export * from "./sponsorship"
diff --git a/docs/src/overrides/assets/javascripts/components/sponsorship/index.ts b/docs/src/overrides/assets/javascripts/components/sponsorship/index.ts
new file mode 100644
index 00000000..711f423a
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/components/sponsorship/index.ts
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { Observable, map } from "rxjs"
+
+import { getElement, requestJSON } from "~/browser"
+
+import { renderPrivateSponsor, renderPublicSponsor } from "_/templates"
+
+import { Component, getComponentElement } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Sponsor type
+ */
+export type SponsorType =
+ | "user" /* Sponsor is a user */
+ | "organization" /* Sponsor is an organization */
+
+/**
+ * Sponsor visibility
+ */
+export type SponsorVisibility =
+ | "public" /* Sponsor is a user */
+ | "private" /* Sponsor is an organization */
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Sponsor user
+ */
+export interface SponsorUser {
+ type: SponsorType /* Sponsor type */
+ name: string /* Sponsor login name */
+ image: string /* Sponsor image URL */
+ url: string /* Sponsor URL */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Public sponsor
+ */
+export interface PublicSponsor {
+ type: "public" /* Sponsor visibility */
+ user: SponsorUser /* Sponsor user */
+}
+
+/**
+ * Private sponsor
+ */
+export interface PrivateSponsor {
+ type: "private" /* Sponsor visibility */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Sponsor
+ */
+export type Sponsor =
+ | PublicSponsor
+ | PrivateSponsor
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Sponsorship
+ */
+export interface Sponsorship {
+ sponsors: Sponsor[] /* Sponsors */
+ total: number /* Total amount */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount sponsorship
+ *
+ * @param el - Sponsorship element
+ *
+ * @returns Sponsorship component observable
+ */
+export function mountSponsorship(
+ el: HTMLElement
+): Observable<Component<Sponsorship>> {
+ const sponsorship$ = requestJSON<Sponsorship>(
+ "https://3if8u9o552.execute-api.us-east-1.amazonaws.com/_/"
+ )
+
+ /* Retrieve adjacent components */
+ const count = getComponentElement("sponsorship-count")
+ const total = getComponentElement("sponsorship-total")
+
+ /* Render sponsorship */
+ sponsorship$.subscribe(sponsorship => {
+ el.removeAttribute("hidden")
+
+ /* Render public sponsors with avatar and links */
+ const list = getElement(":scope > :first-child", el)
+ for (const sponsor of sponsorship.sponsors)
+ if (sponsor.type === "public")
+ list.appendChild(renderPublicSponsor(sponsor.user))
+
+ /* Render combined private sponsors */
+ list.appendChild(renderPrivateSponsor(
+ sponsorship.sponsors.filter(({ type }) => (
+ type === "private"
+ )).length
+ ))
+
+ /* Render sponsorship count and total */
+ count.innerText = `${sponsorship.sponsors.length}`
+ total.innerText = `$ ${sponsorship.total
+ .toString()
+ .replace(/\B(?=(\d{3})+(?!\d))/g, ",")
+ } a month`
+ })
+
+ // /* Create and return component */
+ return sponsorship$
+ .pipe(
+ map(state => ({ ref: el, ...state }))
+ )
+}
diff --git a/docs/src/overrides/assets/javascripts/custom.ts b/docs/src/overrides/assets/javascripts/custom.ts
new file mode 100644
index 00000000..7c3c3847
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/custom.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { merge, switchMap } from "rxjs"
+
+import {
+ getComponentElements,
+ mountIconSearch,
+ mountSponsorship
+} from "./components"
+import { setupAnalytics } from "./integrations"
+
+/* ----------------------------------------------------------------------------
+ * Application
+ * ------------------------------------------------------------------------- */
+
+/* Set up extra analytics events */
+setupAnalytics()
+
+/* Set up extra component observables */
+const component$ = document$
+ .pipe(
+ switchMap(() => merge(
+
+ /* Icon search */
+ ...getComponentElements("iconsearch")
+ .map(el => mountIconSearch(el)),
+
+ /* Sponsorship */
+ ...getComponentElements("sponsorship")
+ .map(el => mountSponsorship(el))
+ ))
+ )
+
+/* Subscribe to all components */
+component$.subscribe()
diff --git a/docs/src/overrides/assets/javascripts/integrations/analytics/index.ts b/docs/src/overrides/assets/javascripts/integrations/analytics/index.ts
new file mode 100644
index 00000000..658add2a
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/integrations/analytics/index.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { fromEvent } from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Set up extra analytics events
+ */
+export function setupAnalytics(): void {
+ const { origin } = new URL(location.href)
+ fromEvent(document.body, "click")
+ .subscribe(ev => {
+ if (ev.target instanceof HTMLElement) {
+ const el = ev.target.closest("a")
+ if (el && el.origin !== origin)
+ ga("send", "event", "outbound", "click", el.href)
+ }
+ })
+}
diff --git a/docs/src/overrides/assets/javascripts/integrations/index.ts b/docs/src/overrides/assets/javascripts/integrations/index.ts
new file mode 100644
index 00000000..9179f2a2
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/integrations/index.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./analytics"
diff --git a/docs/src/overrides/assets/javascripts/templates/iconsearch/index.tsx b/docs/src/overrides/assets/javascripts/templates/iconsearch/index.tsx
new file mode 100644
index 00000000..13cafa6d
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/templates/iconsearch/index.tsx
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { wrap } from "fuzzaldrin-plus"
+
+import { translation } from "~/_"
+import { h } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Icon
+ */
+export interface Icon {
+ shortcode: string /* Icon shortcode */
+ url: string /* Icon URL */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Highlight an icon search result
+ *
+ * @param icon - Icon
+ * @param query - Search query
+ *
+ * @returns Highlighted result
+ */
+function highlight(icon: Icon, query: string): string {
+ return wrap(icon.shortcode, query, {
+ wrap: {
+ tagOpen: "<b>",
+ tagClose: "</b>"
+ }
+ })
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render an icon search result
+ *
+ * @param icon - Icon
+ * @param query - Search query
+ * @param file - Render as file
+ *
+ * @returns Element
+ */
+export function renderIconSearchResult(
+ icon: Icon, query: string, file?: boolean
+): HTMLElement {
+ return (
+ <li class="mdx-iconsearch-result__item">
+ <span class="twemoji">
+ <img src={icon.url} />
+ </span>
+ <button
+ class="md-clipboard--inline"
+ title={translation("clipboard.copy")}
+ data-clipboard-text={file ? icon.shortcode : `:${icon.shortcode}:`}
+ >
+ <code>{
+ file
+ ? highlight(icon, query)
+ : `:${highlight(icon, query)}:`
+ }</code>
+ </button>
+ </li>
+ )
+}
diff --git a/docs/src/overrides/assets/javascripts/templates/index.ts b/docs/src/overrides/assets/javascripts/templates/index.ts
new file mode 100644
index 00000000..02376b3d
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/templates/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./iconsearch"
+export * from "./sponsorship"
diff --git a/docs/src/overrides/assets/javascripts/templates/sponsorship/index.tsx b/docs/src/overrides/assets/javascripts/templates/sponsorship/index.tsx
new file mode 100644
index 00000000..7891c2e0
--- /dev/null
+++ b/docs/src/overrides/assets/javascripts/templates/sponsorship/index.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { h } from "~/utilities"
+
+import { SponsorUser } from "_/components"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render public sponsor
+ *
+ * @param user - Sponsor user
+ *
+ * @returns Element
+ */
+export function renderPublicSponsor(
+ user: SponsorUser
+): HTMLElement {
+ const title = `@${user.name}`
+ return (
+ <a href={user.url} title={title} class="mdx-sponsorship__item">
+ <img src={user.image} />
+ </a>
+ )
+}
+
+/**
+ * Render private sponsor
+ *
+ * @param count - Number of private sponsors
+ *
+ * @returns Element
+ */
+export function renderPrivateSponsor(
+ count: number
+): HTMLElement {
+ return (
+ <a
+ href="https://github.com/sponsors/squidfunk?metadata_origin=docs"
+ class="mdx-sponsorship__item mdx-sponsorship__item--private"
+ >
+ +{count}
+ </a>
+ )
+}
diff --git a/docs/src/overrides/assets/stylesheets/custom.scss b/docs/src/overrides/assets/stylesheets/custom.scss
new file mode 100644
index 00000000..8235e7d0
--- /dev/null
+++ b/docs/src/overrides/assets/stylesheets/custom.scss
@@ -0,0 +1,44 @@
+////
+/// Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Dependencies
+// ----------------------------------------------------------------------------
+
+@import "material-color";
+@import "material-shadows";
+
+// ----------------------------------------------------------------------------
+// Local imports
+// ----------------------------------------------------------------------------
+
+@import "utilities/break";
+@import "utilities/convert";
+
+@import "config";
+
+@import "custom/typeset";
+
+@import "custom/layout/banner";
+@import "custom/layout/hero";
+@import "custom/layout/iconsearch";
+@import "custom/layout/sponsorship";
diff --git a/docs/src/overrides/assets/stylesheets/custom/_typeset.scss b/docs/src/overrides/assets/stylesheets/custom/_typeset.scss
new file mode 100644
index 00000000..bef30073
--- /dev/null
+++ b/docs/src/overrides/assets/stylesheets/custom/_typeset.scss
@@ -0,0 +1,294 @@
+////
+/// Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Keyframes
+// ----------------------------------------------------------------------------
+
+// Pumping heart animation
+@keyframes heart {
+ 0%,
+ 40%,
+ 80%,
+ 100% {
+ transform: scale(1);
+ }
+
+ 20%,
+ 60% {
+ transform: scale(1.15);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Twitter icon
+ .twitter {
+ color: #00acee;
+ }
+
+ // Mastodon icon - it's not the exact brand color, because that doesn't work
+ // well on dark backgrounds, so we lightened it up a bit.
+ .mastodon {
+ color: #897ff8;
+ }
+
+ // Insiders video
+ .mdx-video {
+ width: auto;
+
+ // Insiders video container
+ &__inner {
+ position: relative;
+ width: 100%;
+ height: 0;
+ padding-bottom: 56.138%;
+ }
+
+ // Insiders video iframe
+ iframe {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ border: none;
+ }
+ }
+
+ // Pumping heart
+ .mdx-heart {
+ animation: heart 1000ms infinite;
+ }
+
+ // Insiders color (for links, etc.) // remove
+ .mdx-insiders {
+ color: $clr-pink-500;
+ }
+
+ // BETA #####################################################################
+
+ // Badge
+ .mdx-badge {
+ font-size: 0.85em;
+
+ // Badge with heart
+ &--heart {
+ --md-typeset-a-color: hsla(#{hex2hsl($clr-pink-500)}, 1);
+ --md-accent-fg-color: hsla(#{hex2hsl($clr-pink-a200)}, 1);
+ --md-accent-fg-color--transparent: hsla(#{hex2hsl($clr-pink-500)}, 0.1);
+
+ // Animate icon
+ .twemoji {
+ animation: heart 1000ms infinite;
+ }
+ }
+
+ // Badge moved to the right
+ &--right {
+ float: right;
+ margin-left: 0.35em;
+ }
+
+ // Badge icon
+ &__icon {
+ padding: px2rem(4px);
+ background: var(--md-accent-fg-color--transparent);
+ border-start-start-radius: px2rem(2px);
+ border-end-start-radius: px2rem(2px);
+
+ // If icon is alone, round corners
+ &:last-child {
+ border-radius: px2rem(2px);
+ }
+ }
+
+ // Badge text
+ &__text {
+ padding: px2rem(4px) px2rem(6px);
+ border-start-end-radius: px2rem(2px);
+ border-end-end-radius: px2rem(2px);
+ box-shadow: 0 0 0 1px inset var(--md-accent-fg-color--transparent);
+ }
+ }
+
+ // BETA #####################################################################
+
+ // Switch buttons
+ .mdx-switch button {
+ cursor: pointer;
+ transition: opacity 250ms;
+
+ // Button on focus/hover
+ &:is(:focus, :hover) {
+ opacity: 0.75;
+ }
+
+ // Code block
+ > code {
+ display: block;
+ color: var(--md-primary-bg-color);
+ background-color: var(--md-primary-fg-color);
+ }
+ }
+
+ // Two-column layout
+ .mdx-columns {
+
+ // Column
+ ol,
+ ul {
+ columns: 2;
+
+ // [mobile portrait -]: Reset columns on mobile
+ @include break-to-device(mobile portrait) {
+ columns: initial;
+ }
+ }
+
+ // Column item
+ li {
+ break-inside: avoid;
+ }
+ }
+
+ // Language list
+ .mdx-flags {
+ margin: 2em auto;
+
+ // Language list
+ ol {
+ list-style: none;
+
+ // Language list item
+ li {
+ margin-bottom: 1em;
+ }
+ }
+
+ // Language item
+ &__item {
+ display: flex;
+ gap: px2rem(12px);
+ }
+
+ // Language content
+ &__content {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+
+ // Language name
+ span {
+ display: inline-flex;
+ align-items: baseline;
+ justify-content: space-between;
+ }
+
+ // Language link
+ > span:nth-child(2) {
+ font-size: 80%;
+ }
+
+ // Language code
+ code {
+ float: right;
+ }
+ }
+ }
+
+ // Social card
+ .mdx-social {
+ position: relative;
+ height: min(#{px2rem(540px)}, 80vw);
+
+ // Social card image on hover
+ &:hover .mdx-social__image {
+ background-color: rgba(228, 228, 228, 0.05);
+ }
+
+ // Social card layer
+ &__layer {
+ position: absolute;
+ margin-top: px2rem(80px);
+ transition: 250ms cubic-bezier(0.7, 0, 0.3, 1);
+ transform-style: preserve-3d;
+
+ // Social card layer on hover
+ &:hover {
+
+ // Social card label
+ .mdx-social__label {
+ opacity: 1;
+ }
+
+ // Social card image
+ .mdx-social__image {
+ background-color: rgba(127, 127, 127, 0.99);
+ }
+
+ // Hide top layers
+ ~ .mdx-social__layer {
+ opacity: 0;
+ }
+ }
+ }
+
+ // Social card image
+ &__image {
+ box-shadow:
+ px2rem(-5px) px2rem(5px) px2rem(10px)
+ rgba(0, 0, 0, 0.05);
+ transition: all 250ms;
+ transform: rotate(-40deg) skew(15deg, 15deg) scale(0.7);
+
+ // Actual image
+ img {
+ display: block;
+ }
+ }
+
+ // Social card label
+ &__label {
+ position: absolute;
+ display: block;
+ padding: px2rem(4px) px2rem(8px);
+ color: var(--md-default-bg-color);
+ background-color: var(--md-default-fg-color--light);
+ opacity: 0;
+ transition: all 250ms;
+ }
+
+ // Transform on hover
+ @for $i from 6 through 0 {
+ &:hover .mdx-social__layer:nth-child(#{$i}) {
+ transform: translateY(#{($i - 3) * -10}px);
+ }
+ }
+ }
+}
diff --git a/docs/src/overrides/assets/stylesheets/custom/layout/_banner.scss b/docs/src/overrides/assets/stylesheets/custom/layout/_banner.scss
new file mode 100644
index 00000000..b67d7fff
--- /dev/null
+++ b/docs/src/overrides/assets/stylesheets/custom/layout/_banner.scss
@@ -0,0 +1,66 @@
+////
+/// Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Banner for announcements and warnings
+.md-banner {
+ color: var(--md-footer-fg-color--lighter);
+
+ // Don't wrap name of blog article
+ strong {
+ color: var(--md-footer-fg-color);
+ white-space: nowrap;
+ }
+
+ a {
+ color: var(--md-footer-fg-color);
+
+ &:focus,
+ &:hover {
+ color: currentcolor;
+
+ .twemoji {
+ background-color: var(--md-footer-fg-color);
+ box-shadow: none;
+ }
+ }
+ }
+
+ .twemoji {
+ display: inline-block;
+ width: px2rem(24px);
+ height: px2rem(24px);
+ padding: px2rem(5px);
+ vertical-align: bottom;
+ border-radius: 100%;
+ box-shadow: 0 0 0 px2rem(1px) currentcolor inset;
+ transition: all 250ms;
+
+ svg {
+ display: block;
+ max-height: initial;
+ }
+ }
+}
diff --git a/docs/src/overrides/assets/stylesheets/custom/layout/_hero.scss b/docs/src/overrides/assets/stylesheets/custom/layout/_hero.scss
new file mode 100644
index 00000000..428cd37e
--- /dev/null
+++ b/docs/src/overrides/assets/stylesheets/custom/layout/_hero.scss
@@ -0,0 +1,123 @@
+////
+/// Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Landing page container
+.mdx-container {
+ padding-top: px2rem(20px);
+ background:
+ url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1123 258'><path d='M1124,2c0,0 0,256 0,256l-1125,0l0,-48c0,0 16,5 55,5c116,0 197,-92 325,-92c121,0 114,46 254,46c140,0 214,-167 572,-166Z' style='fill: hsla(0, 0%, 100%, 1)' /></svg>") no-repeat bottom,
+ linear-gradient(
+ to bottom,
+ var(--md-primary-fg-color),
+ hsla(280, 67%, 55%, 1) 99%,
+ var(--md-default-bg-color) 99%
+ );
+
+ // Adjust background for slate theme
+ [data-md-color-scheme="slate"] & {
+ background:
+ url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1123 258'><path d='M1124,2c0,0 0,256 0,256l-1125,0l0,-48c0,0 16,5 55,5c116,0 197,-92 325,-92c121,0 114,46 254,46c140,0 214,-167 572,-166Z' style='fill: hsla(230, 15%, 14%, 1)' /></svg>") no-repeat bottom,
+ linear-gradient(
+ to bottom,
+ var(--md-primary-fg-color),
+ hsla(230, 15%, 25%, 1) 99%,
+ var(--md-default-bg-color) 99%
+ );
+ }
+}
+
+// Landing page hero
+.mdx-hero {
+ margin: 0 px2rem(16px);
+ color: var(--md-primary-bg-color);
+
+ // Hero headline
+ h1 {
+ margin-bottom: px2rem(20px);
+ font-weight: 700;
+ color: currentcolor;
+
+ // [mobile portrait -]: Larger hero headline
+ @include break-to-device(mobile portrait) {
+ font-size: px2rem(28px);
+ }
+ }
+
+ // Hero content
+ &__content {
+ padding-bottom: px2rem(120px);
+ }
+
+ // [tablet landscape +]: Columnar display
+ @include break-from-device(tablet landscape) {
+ display: flex;
+ align-items: stretch;
+
+ // Adjust spacing and set dimensions
+ &__content {
+ max-width: px2rem(380px);
+ padding-bottom: 14vw;
+ margin-top: px2rem(70px);
+ }
+
+ // Hero image
+ &__image {
+ order: 1;
+ width: px2rem(760px);
+ transform: translateX(#{px2rem(80px)});
+ }
+ }
+
+ // [screen +]: Columnar display and adjusted spacing
+ @include break-from-device(screen) {
+
+ // Hero image
+ &__image {
+ transform: translateX(#{px2rem(160px)});
+ }
+ }
+
+ // Button
+ .md-button {
+ margin-top: px2rem(10px);
+ margin-right: px2rem(10px);
+ color: var(--md-primary-bg-color);
+
+ // Button on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-accent-bg-color);
+ background-color: var(--md-accent-fg-color);
+ border-color: var(--md-accent-fg-color);
+ }
+
+ // Primary button
+ &--primary {
+ color: hsla(280, 37%, 48%, 1);
+ background-color: var(--md-primary-bg-color);
+ border-color: var(--md-primary-bg-color);
+ }
+ }
+}
diff --git a/docs/src/overrides/assets/stylesheets/custom/layout/_iconsearch.scss b/docs/src/overrides/assets/stylesheets/custom/layout/_iconsearch.scss
new file mode 100644
index 00000000..651c4135
--- /dev/null
+++ b/docs/src/overrides/assets/stylesheets/custom/layout/_iconsearch.scss
@@ -0,0 +1,136 @@
+////
+/// Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Icon search
+ .mdx-iconsearch {
+ position: relative;
+ background-color: var(--md-default-bg-color);
+ border-radius: px2rem(2px);
+ box-shadow: var(--md-shadow-z1);
+ transition: box-shadow 125ms;
+
+ // Icon search on focus/hover
+ &:is(:focus-within, :hover) {
+ box-shadow: var(--md-shadow-z2);
+ }
+
+ // Icon search input
+ .md-input {
+ background: var(--md-default-bg-color);
+ box-shadow: none;
+
+ // Slate theme, i.e. dark mode
+ [data-md-color-scheme="slate"] & {
+ background: var(--md-code-bg-color);
+ }
+ }
+ }
+
+ // Icon search result
+ .mdx-iconsearch-result {
+ max-height: 50vh;
+ overflow-y: auto;
+ // Hack: promote to own layer to reduce jitter
+ backface-visibility: hidden;
+ touch-action: pan-y;
+ scrollbar-width: thin;
+ scrollbar-color: var(--md-default-fg-color--lighter) transparent;
+
+ // Icon search result inside tooltip
+ .md-tooltip & {
+ max-height: px2rem(205px);
+ }
+
+ // Webkit scrollbar
+ &::-webkit-scrollbar {
+ width: px2rem(4px);
+ height: px2rem(4px);
+ }
+
+ // Webkit scrollbar thumb
+ &::-webkit-scrollbar-thumb {
+ background-color: var(--md-default-fg-color--lighter);
+
+ // Webkit scrollbar thumb on hover
+ &:hover {
+ background-color: var(--md-accent-fg-color);
+ }
+ }
+
+ // Icon search result metadata
+ &__meta {
+ position: absolute;
+ top: px2rem(8px);
+ right: px2rem(12px);
+ font-size: px2rem(12.8px);
+ color: var(--md-default-fg-color--lighter);
+ }
+
+ // Icon search result list
+ &__list {
+ padding: 0;
+ margin: 0;
+ // Hack: necessary because of increased specificity due to the PostCSS
+ // plugin which prefixes this with `[dir=...]` selectors.
+ margin-inline-start: 0;
+ list-style: none;
+ }
+
+ // Icon search result item
+ &__item {
+ padding: px2rem(4px) px2rem(12px);
+ margin: 0;
+ // Hack: necessary because of increased specificity due to the PostCSS
+ // plugin which prefixes this with `[dir=...]` selectors.
+ margin-inline-start: 0;
+ border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);
+
+ // Omit border on last child
+ &:last-child {
+ border-bottom: none;
+ }
+
+ // Item content
+ > * {
+ margin-right: px2rem(12px);
+ }
+
+ // Set icon dimensions to fit
+ img {
+ width: px2rem(18px);
+ height: px2rem(18px);
+
+ // Slate theme, i.e. dark mode
+ [data-md-color-scheme="slate"] &[src*="squidfunk"] {
+ filter: invert(1); /* stylelint-disable-line */
+ }
+ }
+ }
+ }
+}
diff --git a/docs/src/overrides/assets/stylesheets/custom/layout/_sponsorship.scss b/docs/src/overrides/assets/stylesheets/custom/layout/_sponsorship.scss
new file mode 100644
index 00000000..e2b16570
--- /dev/null
+++ b/docs/src/overrides/assets/stylesheets/custom/layout/_sponsorship.scss
@@ -0,0 +1,128 @@
+////
+/// Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Premium sponsors
+ .mdx-premium {
+
+ // Paragraphs
+ p {
+ margin: 2em 0;
+ text-align: center;
+ }
+
+ // Premium sponsor image
+ img {
+ height: px2rem(65px);
+ }
+
+ // Premium sponsor list
+ p:last-child {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ // Premium sponsor link
+ > a {
+ display: block;
+ flex-shrink: 0;
+ }
+ }
+ }
+
+ // Sponsorship
+ .mdx-sponsorship {
+
+ // Sponsorship list
+ &__list {
+ margin: 2em 0;
+
+ // Clearfix, because we can't use overflow: auto
+ &::after {
+ display: block;
+ clear: both;
+ content: "";
+ }
+ }
+
+ // Sponsorship item
+ &__item {
+ display: block;
+ float: inline-start;
+ width: px2rem(32px);
+ height: px2rem(32px);
+ margin: px2rem(4px);
+ overflow: hidden;
+ border-radius: 100%;
+ transition:
+ color 125ms,
+ transform 125ms;
+ transform: scale(1);
+
+ // Sponsor item on focus/hover
+ &:is(:focus, :hover) {
+ transform: scale(1.1);
+
+ // Sponsor avatar
+ img {
+ filter: grayscale(0%);
+ }
+ }
+
+ // Private sponsor
+ &--private {
+ font-size: px2rem(12px);
+ font-weight: 700;
+ line-height: px2rem(32px);
+ color: var(--md-default-fg-color--lighter);
+ text-align: center;
+ background: var(--md-default-fg-color--lightest);
+ }
+
+ // Sponsor avatar
+ img {
+ display: block;
+ width: 100%;
+ height: auto;
+ filter: grayscale(100%) opacity(75%);
+ transition: filter 125ms;
+ }
+ }
+ }
+
+ // Sponsorship button
+ .mdx-sponsorship-button {
+ font-weight: 400;
+ }
+
+ // Sponsorship count and total
+ .mdx-sponsorship-count,
+ .mdx-sponsorship-total {
+ font-weight: 700;
+ }
+}
diff --git a/docs/src/overrides/home.html b/docs/src/overrides/home.html
new file mode 100644
index 00000000..3f54ca82
--- /dev/null
+++ b/docs/src/overrides/home.html
@@ -0,0 +1,106 @@
+<!--
+ Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+{% extends "main.html" %}
+
+<!-- Render hero under tabs -->
+{% block tabs %}
+ {{ super() }}
+
+ <!-- Additional styles for landing page -->
+ <style>
+
+ /* Application header should be static for the landing page */
+ .md-header {
+ position: initial;
+ }
+
+ /* Remove spacing, as we cannot hide it completely */
+ .md-main__inner {
+ margin: 0;
+ }
+
+ /* Hide main content for now */
+ .md-content {
+ display: none;
+ }
+
+ /* Hide table of contents */
+ @media screen and (min-width: 60em) {
+ .md-sidebar--secondary {
+ display: none;
+ }
+ }
+
+ /* Hide navigation */
+ @media screen and (min-width: 76.25em) {
+ .md-sidebar--primary {
+ display: none;
+ }
+ }
+ </style>
+
+ <!-- Hero for landing page -->
+ <section class="mdx-container">
+ <div class="md-grid md-typeset">
+ <div class="mdx-hero">
+
+ <!-- Hero image -->
+ <div class="mdx-hero__image">
+ <img
+ src="assets/images/illustration.png"
+ alt=""
+ width="1659"
+ height="1200"
+ draggable="false"
+ >
+ </div>
+
+ <!-- Hero content -->
+ <div class="mdx-hero__content">
+ <h1>Technical documentation that just works</h1>
+ <p>{{ config.site_description }}. Set up in 5 minutes.</p>
+ <a
+ href="{{ page.next_page.url | url }}"
+ title="{{ page.next_page.title | e }}"
+ class="md-button md-button--primary"
+ >
+ Quick start
+ </a>
+ <a
+ href="{{ 'insiders/' | url }}"
+ title="Material for MkDocs Insiders"
+ class="md-button"
+ >
+ Get Insiders
+ </a>
+ </div>
+ </div>
+ </div>
+ </section>
+{% endblock %}
+
+<!-- Content -->
+{% block content %}{% endblock %}
+
+<!-- Application footer -->
+{% block footer %}{% endblock %}
diff --git a/docs/src/overrides/hooks/shortcodes.py b/docs/src/overrides/hooks/shortcodes.py
new file mode 100644
index 00000000..5b02e3cf
--- /dev/null
+++ b/docs/src/overrides/hooks/shortcodes.py
@@ -0,0 +1,283 @@
+# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from __future__ import annotations
+
+import posixpath
+import re
+
+from mkdocs.config.defaults import MkDocsConfig
+from mkdocs.structure.files import File, Files
+from mkdocs.structure.pages import Page
+from re import Match
+
+# -----------------------------------------------------------------------------
+# Hooks
+# -----------------------------------------------------------------------------
+
+# @todo
+def on_page_markdown(
+ markdown: str, *, page: Page, config: MkDocsConfig, files: Files
+):
+
+ # Replace callback
+ def replace(match: Match):
+ type, args = match.groups()
+ args = args.strip()
+ if type == "version":
+ if args.startswith("insiders-"):
+ return _badge_for_version_insiders(args, page, files)
+ else:
+ return _badge_for_version(args, page, files)
+ elif type == "sponsors": return _badge_for_sponsors(page, files)
+ elif type == "flag": return flag(args, page, files)
+ elif type == "option": return option(args)
+ elif type == "setting": return setting(args)
+ elif type == "feature": return _badge_for_feature(args, page, files)
+ elif type == "plugin": return _badge_for_plugin(args, page, files)
+ elif type == "extension": return _badge_for_extension(args, page, files)
+ elif type == "utility": return _badge_for_utility(args, page, files)
+ elif type == "example": return _badge_for_example(args, page, files)
+ elif type == "default":
+ if args == "none": return _badge_for_default_none(page, files)
+ elif args == "computed": return _badge_for_default_computed(page, files)
+ else: return _badge_for_default(args, page, files)
+
+ # Otherwise, raise an error
+ raise RuntimeError(f"Unknown shortcode: {type}")
+
+ # Find and replace all external asset URLs in current page
+ return re.sub(
+ r"<!-- md:(\w+)(.*?) -->",
+ replace, markdown, flags = re.I | re.M
+ )
+
+# -----------------------------------------------------------------------------
+# Helper functions
+# -----------------------------------------------------------------------------
+
+# Create a flag of a specific type
+def flag(args: str, page: Page, files: Files):
+ type, *_ = args.split(" ", 1)
+ if type == "experimental": return _badge_for_experimental(page, files)
+ elif type == "required": return _badge_for_required(page, files)
+ elif type == "customization": return _badge_for_customization(page, files)
+ elif type == "metadata": return _badge_for_metadata(page, files)
+ elif type == "multiple": return _badge_for_multiple(page, files)
+ raise RuntimeError(f"Unknown type: {type}")
+
+# Create a linkable option
+def option(type: str):
+ _, *_, name = re.split(r"[.:]", type)
+ return f"[`{name}`](#+{type}){{ #+{type} }}\n\n"
+
+# Create a linkable setting - @todo append them to the bottom of the page
+def setting(type: str):
+ _, *_, name = re.split(r"[.*]", type)
+ return f"`{name}` {{ #{type} }}\n\n[{type}]: #{type}\n\n"
+
+# -----------------------------------------------------------------------------
+
+# Resolve path of file relative to given page - the posixpath always includes
+# one additional level of `..` which we need to remove
+def _resolve_path(path: str, page: Page, files: Files):
+ path, anchor, *_ = f"{path}#".split("#")
+ path = _resolve(files.get_file_from_path(path), page)
+ return "#".join([path, anchor]) if anchor else path
+
+# Resolve path of file relative to given page - the posixpath always includes
+# one additional level of `..` which we need to remove
+def _resolve(file: File, page: Page):
+ path = posixpath.relpath(file.src_uri, page.file.src_uri)
+ return posixpath.sep.join(path.split(posixpath.sep)[1:])
+
+# -----------------------------------------------------------------------------
+
+# Create badge
+def _badge(icon: str, text: str = "", type: str = ""):
+ classes = f"mdx-badge mdx-badge--{type}" if type else "mdx-badge"
+ return "".join([
+ f"<span class=\"{classes}\">",
+ *([f"<span class=\"mdx-badge__icon\">{icon}</span>"] if icon else []),
+ *([f"<span class=\"mdx-badge__text\">{text}</span>"] if text else []),
+ f"</span>",
+ ])
+
+# Create sponsors badge
+def _badge_for_sponsors(page: Page, files: Files):
+ icon = "material-heart"
+ href = _resolve_path("insiders/index.md", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Sponsors only')",
+ type = "heart"
+ )
+
+# Create badge for version
+def _badge_for_version(text: str, page: Page, files: Files):
+ spec = text
+ path = f"changelog/index.md#{spec}"
+
+ # Return badge
+ icon = "material-tag-outline"
+ href = _resolve_path("conventions.md#version", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Minimum version')",
+ text = f"[{text}]({_resolve_path(path, page, files)})" if spec else ""
+ )
+
+# Create badge for version of Insiders
+def _badge_for_version_insiders(text: str, page: Page, files: Files):
+ spec = text.replace("insiders-", "")
+ path = f"insiders/changelog/index.md#{spec}"
+
+ # Return badge
+ icon = "material-tag-heart-outline"
+ href = _resolve_path("conventions.md#version-insiders", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Minimum version')",
+ text = f"[{text}]({_resolve_path(path, page, files)})" if spec else ""
+ )
+
+# Create badge for feature
+def _badge_for_feature(text: str, page: Page, files: Files):
+ icon = "material-toggle-switch"
+ href = _resolve_path("conventions.md#feature", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Optional feature')",
+ text = text
+ )
+
+# Create badge for plugin
+def _badge_for_plugin(text: str, page: Page, files: Files):
+ icon = "material-floppy"
+ href = _resolve_path("conventions.md#plugin", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Plugin')",
+ text = text
+ )
+
+# Create badge for extension
+def _badge_for_extension(text: str, page: Page, files: Files):
+ icon = "material-language-markdown"
+ href = _resolve_path("conventions.md#extension", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Markdown extension')",
+ text = text
+ )
+
+# Create badge for utility
+def _badge_for_utility(text: str, page: Page, files: Files):
+ icon = "material-package-variant"
+ href = _resolve_path("conventions.md#utility", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Third-party utility')",
+ text = text
+ )
+
+# Create badge for example
+def _badge_for_example(text: str, page: Page, files: Files):
+ return "\n".join([
+ _badge_for_example_download(text, page, files),
+ _badge_for_example_view(text, page, files)
+ ])
+
+# Create badge for example view
+def _badge_for_example_view(text: str, page: Page, files: Files):
+ icon = "material-folder-eye"
+ href = f"https://mkdocs-material.github.io/examples/{text}/"
+ return _badge(
+ icon = f"[:{icon}:]({href} 'View example')",
+ type = "right"
+ )
+
+# Create badge for example download
+def _badge_for_example_download(text: str, page: Page, files: Files):
+ icon = "material-folder-download"
+ href = f"https://mkdocs-material.github.io/examples/{text}.zip"
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Download example')",
+ text = f"[`.zip`]({href})",
+ type = "right"
+ )
+
+# Create badge for default value
+def _badge_for_default(text: str, page: Page, files: Files):
+ icon = "material-water"
+ href = _resolve_path("conventions.md#default", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Default value')",
+ text = text
+ )
+
+# Create badge for empty default value
+def _badge_for_default_none(page: Page, files: Files):
+ icon = "material-water-outline"
+ href = _resolve_path("conventions.md#default", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Default value is empty')"
+ )
+
+# Create badge for computed default value
+def _badge_for_default_computed(page: Page, files: Files):
+ icon = "material-water-check"
+ href = _resolve_path("conventions.md#default", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Default value is computed')"
+ )
+
+# Create badge for metadata property flag
+def _badge_for_metadata(page: Page, files: Files):
+ icon = "material-list-box-outline"
+ href = _resolve_path("conventions.md#metadata", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Metadata property')"
+ )
+
+# Create badge for required value flag
+def _badge_for_required(page: Page, files: Files):
+ icon = "material-alert"
+ href = _resolve_path("conventions.md#required", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Required value')"
+ )
+
+# Create badge for customization flag
+def _badge_for_customization(page: Page, files: Files):
+ icon = "material-brush-variant"
+ href = _resolve_path("conventions.md#customization", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Customization')"
+ )
+
+# Create badge for multiple instance flag
+def _badge_for_multiple(page: Page, files: Files):
+ icon = "material-inbox-multiple"
+ href = _resolve_path("conventions.md#multiple-instances", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Multiple instances')"
+ )
+
+# Create badge for experimental flag
+def _badge_for_experimental(page: Page, files: Files):
+ icon = "material-flask-outline"
+ href = _resolve_path("conventions.md#experimental", page, files)
+ return _badge(
+ icon = f"[:{icon}:]({href} 'Experimental')"
+ )
diff --git a/docs/src/overrides/hooks/translations.html b/docs/src/overrides/hooks/translations.html
new file mode 100644
index 00000000..ab41c77d
--- /dev/null
+++ b/docs/src/overrides/hooks/translations.html
@@ -0,0 +1,54 @@
+<!--
+ Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Render translation language -->
+{% macro render_language(language) %}
+ <div class="mdx-flags__item" markdown>
+ :flag_{{ language.flag }}:{ .lg .middle }
+ <span class="mdx-flags__content">
+ <span>
+ <strong>{{ language.name }}</strong>
+ <code>{{ language.code }}</code>
+ </span>
+ {% if language.miss %}
+ <span>
+ <a href="{{ language.link }}">
+ {{ language.miss | length }} translations missing
+ </a>
+ </span>
+ {% else %}
+ <small>Complete</small>
+ {% endif %}
+ </span>
+ </div>
+{% endmacro %}
+
+<!-- Render translations -->
+{% macro render(translations, start = 1) %}
+ <div class="mdx-columns mdx-flags" markdown>
+ <ol markdown>
+ {% for language in translations %}
+ <li markdown>{{ render_language(language) }}</li>
+ {% endfor %}
+ </ol>
+ </div>
+{% endmacro %}
diff --git a/docs/src/overrides/hooks/translations.py b/docs/src/overrides/hooks/translations.py
new file mode 100644
index 00000000..661fd18e
--- /dev/null
+++ b/docs/src/overrides/hooks/translations.py
@@ -0,0 +1,193 @@
+# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import os
+import re
+
+from glob import iglob
+from mkdocs.config.defaults import MkDocsConfig
+from mkdocs.structure.pages import Page
+from urllib.parse import urlencode, urlparse
+
+# -----------------------------------------------------------------------------
+# Hooks
+# -----------------------------------------------------------------------------
+
+# Determine missing translations and render language overview in the setup
+# guide, including links to provide missing translations.
+def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files):
+ issue_url = "https://github.com/squidfunk/mkdocs-material/issues/new"
+ if page.file.src_uri != "setup/changing-the-language.md":
+ return
+
+ # Collect all existing languages
+ names: dict[str, str] = {}
+ known: dict[str, dict[str, str]] = {}
+ for path in iglob("src/templates/partials/languages/*.html"):
+ with open(path, "r", encoding = "utf-8") as f:
+ data = f.read()
+
+ # Extract language code and name
+ name, = re.findall(r"<!-- Translations: (.+) -->", data)
+ code, _ = os.path.splitext(os.path.basename(path))
+
+ # Map names and available translations
+ names[code] = name
+ known[code] = dict(re.findall(
+ r"^ \"([^\"]+)\": \"([^\"]*)\"(?:,|$)?", data,
+ re.MULTILINE
+ ))
+
+ # Remove technical stuff
+ for key in [
+ "direction",
+ "search.config.pipeline",
+ "search.config.lang",
+ "search.config.separator"
+ ]:
+ if key in known[code]:
+ del known[code][key]
+
+ # Traverse all languages and compute missing translations
+ languages = []
+ reference = set(known["en"])
+ for code, name in names.items():
+ miss = reference - set(known[code])
+
+ # Check each translations
+ translations: list[str] = []
+ for key, value in known["en"].items():
+ if key in known[code]:
+ translations.append(
+ f" \"{key}\": \"{known[code][key]}\""
+ )
+ else:
+ translations.append(
+ f" \"{key}\": \"{value} ⬅️\""
+ )
+
+ # Assemble GitHub issue URL
+ link = urlparse(issue_url)
+ link = link._replace(query = urlencode({
+ "template": "04-add-translations.yml",
+ "title": f"Update {name} translations",
+ "translations": "\n".join([
+ "{% macro t(key) %}{{ {",
+ ",\n".join(translations),
+ "}[key] }}{% endmacro %}"
+ ]),
+ "country-flag": f":flag_{countries[code]}:"
+ }))
+
+ # Add translation
+ languages.append({
+ "flag": countries[code],
+ "code": code,
+ "name": name,
+ "link": link.geturl(),
+ "miss": miss
+ })
+
+ # Load template and render translations
+ env = config.theme.get_env()
+ template = env.get_template( "hooks/translations.html")
+ translations = template.module.render(
+ sorted(languages, key = lambda language: language["name"])
+ )
+
+ # Replace translation marker
+ return markdown.replace(
+ "<!-- hooks/translations.py -->", "\n".join(
+ [line.lstrip() for line in translations.split("\n")
+ ]
+ ))
+
+# -----------------------------------------------------------------------------
+# Data
+# -----------------------------------------------------------------------------
+
+# Map ISO 639-1 (languages) to ISO 3166 (countries)
+countries = dict({
+ "af": "za",
+ "ar": "ae",
+ "be": "by",
+ "bg": "bg",
+ "bn": "bd",
+ "ca": "es",
+ "cs": "cz",
+ "da": "dk",
+ "de": "de",
+ "el": "gr",
+ "en": "us",
+ "eo": "eu",
+ "es": "es",
+ "et": "ee",
+ "eu": "es",
+ "fa": "ir",
+ "fi": "fi",
+ "fr": "fr",
+ "gl": "es",
+ "he": "il",
+ "hi": "in",
+ "hr": "hr",
+ "hu": "hu",
+ "hy": "am",
+ "id": "id",
+ "is": "is",
+ "it": "it",
+ "ja": "jp",
+ "ka": "ge",
+ "kn": "in",
+ "ko": "kr",
+ "ku-IQ": "iq",
+ "lb": "lu",
+ "lt": "lt",
+ "lv": "lv",
+ "mk": "mk",
+ "mn": "mn",
+ "ms": "my",
+ "my": "mm",
+ "nb": "no",
+ "nl": "nl",
+ "nn": "no",
+ "pl": "pl",
+ "pt-BR": "br",
+ "pt": "pt",
+ "ro": "ro",
+ "ru": "ru",
+ "sa": "in",
+ "sh": "rs",
+ "si": "lk",
+ "sk": "sk",
+ "sl": "si",
+ "sr": "rs",
+ "sv": "se",
+ "te": "in",
+ "th": "th",
+ "tl": "ph",
+ "tr": "tr",
+ "uk": "ua",
+ "ur": "pk",
+ "uz": "uz",
+ "vi": "vn",
+ "zh": "cn",
+ "zh-Hant": "cn",
+ "zh-TW": "tw"
+})
diff --git a/docs/src/overrides/main.html b/docs/src/overrides/main.html
new file mode 100644
index 00000000..39b68b5a
--- /dev/null
+++ b/docs/src/overrides/main.html
@@ -0,0 +1,59 @@
+<!--
+ Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+{% extends "base.html" %}
+
+<!-- Custom front matter -->
+{% block extrahead %}
+
+ <!-- Extra style sheets (can't be set in mkdocs.yml due to content hash) -->
+ <link
+ rel="stylesheet"
+ href="{{ 'assets/stylesheets/custom.css' | url }}"
+ />
+{% endblock %}
+
+<!-- Announcement bar -->
+{% block announce %}
+ For updates follow <strong>@squidfunk</strong> on
+ <a rel="me" href="https://fosstodon.org/@squidfunk">
+ <span class="twemoji mastodon">
+ {% include ".icons/fontawesome/brands/mastodon.svg" %}
+ </span>
+ <strong>Fosstodon</strong>
+ </a>
+ and
+ <a href="https://twitter.com/squidfunk">
+ <span class="twemoji twitter">
+ {% include ".icons/fontawesome/brands/twitter.svg" %}
+ </span>
+ <strong>Twitter</strong>
+ </a>
+{% endblock %}
+
+<!-- Theme-related JavaScript -->
+{% block scripts %}
+ {{ super() }}
+
+ <!-- Extra JavaScript (can't be set in mkdocs.yml due to content hash) -->
+ <script src="{{ 'assets/javascripts/custom.js' | url }}"></script>
+{% endblock %}