diff options
Diffstat (limited to 'src/overrides/assets')
19 files changed, 1827 insertions, 0 deletions
diff --git a/src/overrides/assets/javascripts/components/_/index.ts b/src/overrides/assets/javascripts/components/_/index.ts new file mode 100644 index 00000000..3cb4c18e --- /dev/null +++ b/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/src/overrides/assets/javascripts/components/iconsearch/_/index.ts b/src/overrides/assets/javascripts/components/iconsearch/_/index.ts new file mode 100644 index 00000000..f509a6f9 --- /dev/null +++ b/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/src/overrides/assets/javascripts/components/iconsearch/index.ts b/src/overrides/assets/javascripts/components/iconsearch/index.ts new file mode 100644 index 00000000..9d856774 --- /dev/null +++ b/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/src/overrides/assets/javascripts/components/iconsearch/query/index.ts b/src/overrides/assets/javascripts/components/iconsearch/query/index.ts new file mode 100644 index 00000000..03a3daad --- /dev/null +++ b/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/src/overrides/assets/javascripts/components/iconsearch/result/index.ts b/src/overrides/assets/javascripts/components/iconsearch/result/index.ts new file mode 100644 index 00000000..2b9d97fb --- /dev/null +++ b/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/src/overrides/assets/javascripts/components/index.ts b/src/overrides/assets/javascripts/components/index.ts new file mode 100644 index 00000000..ec6c9dce --- /dev/null +++ b/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/src/overrides/assets/javascripts/components/sponsorship/index.ts b/src/overrides/assets/javascripts/components/sponsorship/index.ts new file mode 100644 index 00000000..711f423a --- /dev/null +++ b/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/src/overrides/assets/javascripts/custom.ts b/src/overrides/assets/javascripts/custom.ts new file mode 100644 index 00000000..7c3c3847 --- /dev/null +++ b/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/src/overrides/assets/javascripts/integrations/analytics/index.ts b/src/overrides/assets/javascripts/integrations/analytics/index.ts new file mode 100644 index 00000000..658add2a --- /dev/null +++ b/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/src/overrides/assets/javascripts/integrations/index.ts b/src/overrides/assets/javascripts/integrations/index.ts new file mode 100644 index 00000000..9179f2a2 --- /dev/null +++ b/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/src/overrides/assets/javascripts/templates/iconsearch/index.tsx b/src/overrides/assets/javascripts/templates/iconsearch/index.tsx new file mode 100644 index 00000000..13cafa6d --- /dev/null +++ b/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/src/overrides/assets/javascripts/templates/index.ts b/src/overrides/assets/javascripts/templates/index.ts new file mode 100644 index 00000000..02376b3d --- /dev/null +++ b/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/src/overrides/assets/javascripts/templates/sponsorship/index.tsx b/src/overrides/assets/javascripts/templates/sponsorship/index.tsx new file mode 100644 index 00000000..7891c2e0 --- /dev/null +++ b/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/src/overrides/assets/stylesheets/custom.scss b/src/overrides/assets/stylesheets/custom.scss new file mode 100644 index 00000000..8235e7d0 --- /dev/null +++ b/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/src/overrides/assets/stylesheets/custom/_typeset.scss b/src/overrides/assets/stylesheets/custom/_typeset.scss new file mode 100644 index 00000000..bef30073 --- /dev/null +++ b/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/src/overrides/assets/stylesheets/custom/layout/_banner.scss b/src/overrides/assets/stylesheets/custom/layout/_banner.scss new file mode 100644 index 00000000..b67d7fff --- /dev/null +++ b/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/src/overrides/assets/stylesheets/custom/layout/_hero.scss b/src/overrides/assets/stylesheets/custom/layout/_hero.scss new file mode 100644 index 00000000..428cd37e --- /dev/null +++ b/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/src/overrides/assets/stylesheets/custom/layout/_iconsearch.scss b/src/overrides/assets/stylesheets/custom/layout/_iconsearch.scss new file mode 100644 index 00000000..651c4135 --- /dev/null +++ b/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/src/overrides/assets/stylesheets/custom/layout/_sponsorship.scss b/src/overrides/assets/stylesheets/custom/layout/_sponsorship.scss new file mode 100644 index 00000000..e2b16570 --- /dev/null +++ b/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; + } +} |
