Rules-based browser launcher for TUI + GNOME. switchyard.aly.codes
tui gome bowser go
0

Configure Feed

Select the types of activity you want to include in your feed.

move docs to website

Aly Raffauf (Jun 22, 2026, 12:41 PM EDT) edeec192 6a0f035b

+453 -125
+1 -28
README.md
··· 1 - [![CI](https://github.com/alyraffauf/switchyard/actions/workflows/ci.yml/badge.svg)](https://github.com/alyraffauf/switchyard/actions/workflows/ci.yml) [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0) [![Ko-fi](https://img.shields.io/badge/Donate-Ko--fi-ff5e5b?logo=ko-fi&logoColor=white)](https://ko-fi.com/alyraffauf) [![Mozilla Add-on](https://img.shields.io/amo/v/switchyard?logo=firefox-browser&logoColor=white&label=Firefox%20Add-on)](https://addons.mozilla.org/firefox/addon/switchyard/) 1 + [![CI](https://github.com/alyraffauf/switchyard/actions/workflows/ci.yml/badge.svg)](https://github.com/alyraffauf/switchyard/actions/workflows/ci.yml) [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0) [![Ko-fi](https://img.shields.io/badge/Donate-Ko--fi-ff5e5b?logo=ko-fi&logoColor=white)](https://ko-fi.com/alyraffauf) [![Firefox](https://img.shields.io/amo/v/switchyard?logo=firefox-browser&logoColor=white&label=Firefox)](https://addons.mozilla.org/firefox/addon/switchyard/) [![Chrome](https://img.shields.io/chrome-web-store/v/ncehhpikkabfdcceimdhjjjodogflokc?logo=googlechrome&logoColor=white&label=Chrome)](https://chromewebstore.google.com/detail/switchyard/ncehhpikkabfdcceimdhjjjodogflokc) 2 2 3 3 <div align="center"> 4 4 <img width="128" height="128" src="data/icons/hicolor/scalable/apps/io.github.alyraffauf.Switchyard.svg" alt="Switchyard Icon"> ··· 13 13 <p align="center"> 14 14 <a href="https://flathub.org/apps/io.github.alyraffauf.Switchyard"><img src="https://flathub.org/api/badge?locale=en&style=flat" alt="Get it on Flathub"></a> 15 15 </p> 16 - <p align="center"> 17 - <a href="https://addons.mozilla.org/firefox/addon/switchyard/"><img src="https://shieldcn.dev/amo/v/switchyard.svg?variant=branded" alt="Get the Firefox Add-on"></a> 18 - </p> 19 - 20 16 ## Features 21 17 22 18 - **Browser rules**: Automatically open links in specific browsers based on conditions you define. 23 19 - **Link redirections**: Clean up links before they open—remove tracking parameters, swap domains, and more. 24 20 - **Quick launcher**: When no rule matches, choose a browser with a click or keyboard shortcut. 25 - - **Lightweight**: Runs only when you click a link. No background processes. 26 21 - **GTK4 + libadwaita**: Native GNOME look and feel. 27 22 28 23 ## Installation ··· 56 51 ```bash 57 52 just flatpak # Build and install 58 53 ``` 59 - 60 - ## Documentation 61 - 62 - - [Using](docs/Using.md) - Set as default browser, usage examples. 63 - - [Configuration](docs/Configuration.md) - Config file format, rules, and settings. 64 - - [URI Scheme](docs/URI%20Scheme.md) - Custom `switchyard://` URLs for specifying browser preferences. 65 - - [Bowser Extension](docs/WebExtension.md) - Extensions for Firefox and Chrome. 66 - - [Prior Art](docs/Prior%20Art.md) - Similar tools that inspired Switchyard. 67 - 68 - ## Notes 69 - 70 - ### Flaktpak on NixOS: host browsers are not detected 71 - 72 - On NixOS, browser desktop files live in `/run/current-system/sw/share/applications`, which is not visible inside Flatpak by default. 73 - 74 - To expose them to Switchyard, add the path to the XDG application search path: 75 - 76 - ```bash 77 - flatpak override --user io.github.alyraffauf.Switchyard \ 78 - --filesystem=/run/current-system/sw/share:ro \ 79 - --env=XDG_DATA_DIRS=/app/share:/usr/share:/run/current-system/sw/share 80 - ```
+5 -1
docs/Configuration.md website/src/content/docs/configuration.md
··· 1 - # Configuration 1 + --- 2 + title: Configuration 3 + description: Configure browser rules, conditions, and link redirections. 4 + order: 20 5 + --- 2 6 3 7 Switchyard can be configured through its settings UI or by editing the config file directly. 4 8
+5 -1
docs/Prior Art.md website/src/content/docs/prior-art.md
··· 1 - # Prior Art 1 + --- 2 + title: Prior Art 3 + description: Related URL routers and browser pickers for Linux, macOS, and Windows. 4 + order: 50 5 + --- 2 6 3 7 Switchyard draws inspiration from other excellent URL routers and browser pickers. 4 8
+5 -1
docs/URI Scheme.md website/src/content/docs/uri-scheme.md
··· 1 - # Switchyard URI Scheme 1 + --- 2 + title: URI Scheme 3 + description: Use switchyard:// links to open URLs with browser preferences. 4 + order: 30 5 + --- 2 6 3 7 Switchyard registers a custom URI scheme that allows links to specify browser preferences directly. This is useful for situations where you want to create links that always open in a specific browser, but don't want a permanent rule. Example use cases include note-taking, to-do apps, etc. 4 8
-11
docs/Using.md
··· 1 - # Using Switchyard 2 - 3 - ## Set as Default Browser 4 - 5 - After installation, set Switchyard as your default browser so it can route all clicked links: 6 - 7 - ```bash 8 - xdg-settings set default-web-browser io.github.alyraffauf.Switchyard.desktop 9 - ``` 10 - 11 - Or use your desktop environment's graphical settings to set Switchyard as the default browser.
+9 -5
docs/WebExtension.md website/src/content/docs/webextension.md
··· 1 - # Switchyard Browser Extension 1 + --- 2 + title: Browser Extension 3 + description: Use the Switchyard browser extension and native messaging integration. 4 + order: 40 5 + --- 2 6 3 7 A companion browser extension that lets you open any page in Switchyard with one click. It lives in the `webextension/` directory, ships as a standard WebExtension compatible with Firefox and Chromium, and is included in GitHub releases as `switchyard-webextension.zip`. 4 8 ··· 6 10 7 11 ## How It Works 8 12 9 - The browser extension stays simple by making use of our [URI Scheme](./URI%20Scheme.md). When you click a browser button or "Switchyard", it redirects the current tab to a `switchyard://open?url=...` URL. The operating system sees the registered `switchyard://` scheme and launches the Switchyard desktop app, which parses the URL and forwards your page to the right browser. 13 + The browser extension stays simple by making use of our [URI Scheme](/docs/uri-scheme/). When you click a browser button or "Switchyard", it redirects the current tab to a `switchyard://open?url=...` URL. The operating system sees the registered `switchyard://` scheme and launches the Switchyard desktop app, which parses the URL and forwards your page to the right browser. 10 14 11 15 With [desktop integration](#desktop-integration) enabled, the extension can also ask Switchyard for your installed browsers and display them directly in the popup for one-click launching. 12 16 ··· 14 18 15 19 The Switchyard extension can also show your installed browsers directly in the popup, letting you send the current tab to a specific browser in one click. This requires installing a native messaging host on your system. 16 20 17 - Please read [`browser-setup/main.py`](../browser-setup/main.py) before running this command: 21 + Please read [`browser-setup/main.py`](https://github.com/alyraffauf/switchyard/blob/master/browser-setup/main.py) before running this command: 18 22 19 23 ```bash 20 - curl -fsSL https://raw.githubusercontent.com/alyraffauf/Switchyard/master/scripts/install-desktop-integration.sh | bash -s -- --install --yes 24 + curl -fsSL https://raw.githubusercontent.com/alyraffauf/switchyard/master/scripts/install-desktop-integration.sh | bash -s -- --install --yes 21 25 ``` 22 26 23 27 After installation, restart your browser(s). The extension popup will list your installed browsers. ··· 25 29 ### Uninstall 26 30 27 31 ```bash 28 - curl -fsSL https://raw.githubusercontent.com/alyraffauf/Switchyard/master/scripts/install-desktop-integration.sh | bash -s -- --uninstall 32 + curl -fsSL https://raw.githubusercontent.com/alyraffauf/switchyard/master/scripts/install-desktop-integration.sh | bash -s -- --uninstall 29 33 ``` 30 34 31 35 This removes all manifests and wrappers, but the `flatpak override` permissions granted during install are left in place. It's up to the user to decide what to do with them.
docs/images/switchyard-picker.png website/public/docs/images/switchyard-picker.png
docs/images/switchyard-rulesedit.png website/public/docs/images/switchyard-rulesedit.png
docs/images/switchyard.png website/public/docs/images/switchyard.png
+61
website/src/components/SiteFooter.astro
··· 1 + --- 2 + import { DEV_URL, footerLinks } from "../site"; 3 + 4 + interface Props { 5 + variant?: "marketing" | "docs"; 6 + } 7 + 8 + const { variant = "marketing" } = Astro.props; 9 + const isDocs = variant === "docs"; 10 + --- 11 + 12 + <footer 13 + class:list={[ 14 + isDocs 15 + ? "border-t border-border bg-bg py-5" 16 + : "py-8 md:py-12 bg-bg-alt", 17 + ]} 18 + > 19 + <div 20 + class:list={[ 21 + "mx-auto px-4 md:px-8 flex gap-5", 22 + isDocs 23 + ? "max-w-[1200px] flex-col md:flex-row md:items-center md:justify-between" 24 + : "max-w-[1100px] flex-col items-center", 25 + ]} 26 + > 27 + <div 28 + class:list={[ 29 + "flex items-center gap-3 font-semibold", 30 + isDocs ? "text-sm" : "text-lg", 31 + ]} 32 + > 33 + <img 34 + src="/icon.svg" 35 + alt="Switchyard" 36 + class:list={[isDocs ? "w-5 h-5" : "w-8 h-8"]} 37 + loading="lazy" 38 + /> 39 + <span>Switchyard</span> 40 + </div> 41 + <nav class="flex flex-wrap gap-5 md:gap-8" aria-label="Footer"> 42 + { 43 + footerLinks.map((link) => ( 44 + <a href={link.href} class="text-text-muted text-sm hover:text-primary"> 45 + {link.label} 46 + </a> 47 + )) 48 + } 49 + </nav> 50 + <p class="text-text-light text-sm"> 51 + Made by{" "} 52 + <a 53 + href={DEV_URL} 54 + class="text-text-muted hover:text-primary transition-colors" 55 + > 56 + Aly Raffauf 57 + </a>{" "} 58 + &middot; GPL-3.0-or-later 59 + </p> 60 + </div> 61 + </footer>
+55
website/src/components/SiteHeader.astro
··· 1 + --- 2 + import Icon from "./Icon.astro"; 3 + import type { IconName } from "../icons"; 4 + 5 + interface BreadcrumbItem { 6 + href: string; 7 + label: string; 8 + icon?: IconName; 9 + switchyardIcon?: boolean; 10 + current?: boolean; 11 + } 12 + 13 + interface Props { 14 + items: BreadcrumbItem[]; 15 + } 16 + 17 + const { items } = Astro.props; 18 + --- 19 + 20 + <header class="sticky top-0 z-50 bg-bg/95 backdrop-blur-sm border-b border-border"> 21 + <nav 22 + class="max-w-[1200px] mx-auto px-4 md:px-8 py-2.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm" 23 + aria-label="Breadcrumb" 24 + > 25 + { 26 + items.map((item, index) => ( 27 + <> 28 + {index > 0 && <span class="text-text-muted/40">›</span>} 29 + <a 30 + href={item.href} 31 + aria-current={item.current ? "page" : undefined} 32 + class:list={[ 33 + "flex items-center gap-1.5 transition-colors whitespace-nowrap", 34 + item.current 35 + ? "text-text hover:text-primary" 36 + : "text-text-muted hover:text-text", 37 + ]} 38 + > 39 + {item.icon && ( 40 + <Icon 41 + name={item.icon} 42 + class="w-4 h-4 shrink-0 text-rose-700" 43 + aria-hidden="true" 44 + /> 45 + )} 46 + {item.switchyardIcon && ( 47 + <img src="/icon.svg" alt="" class="w-4 h-4" /> 48 + )} 49 + {item.label} 50 + </a> 51 + </> 52 + )) 53 + } 54 + </nav> 55 + </header>
+14
website/src/content.config.ts
··· 1 + import { defineCollection } from "astro:content"; 2 + import { glob } from "astro/loaders"; 3 + import { z } from "astro/zod"; 4 + 5 + const docs = defineCollection({ 6 + loader: glob({ pattern: "**/*.md", base: "./src/content/docs" }), 7 + schema: z.object({ 8 + title: z.string(), 9 + description: z.string().optional(), 10 + order: z.number().default(999), 11 + }), 12 + }); 13 + 14 + export const collections = { docs };
+17
website/src/content/docs/nixos-flatpak.md
··· 1 + --- 2 + title: NixOS & Flatpak 3 + description: Allow the Flatpak build of Switchyard to find host browser desktop files on NixOS. 4 + order: 25 5 + --- 6 + 7 + On NixOS, browser desktop files live in `/run/current-system/sw/share/applications`. That path is not visible inside Flatpak by default, so Switchyard may not detect host browsers. 8 + 9 + To expose them to Switchyard, add the path to the Flatpak sandbox and include it in the XDG application search path: 10 + 11 + ```bash 12 + flatpak override --user io.github.alyraffauf.Switchyard \ 13 + --filesystem=/run/current-system/sw/share:ro \ 14 + --env=XDG_DATA_DIRS=/app/share:/usr/share:/run/current-system/sw/share 15 + ``` 16 + 17 + Restart Switchyard after applying the override.
+26
website/src/content/docs/using.md
··· 1 + --- 2 + title: Using Switchyard 3 + description: Learn what Switchyard does and how to start routing links. 4 + order: 10 5 + --- 6 + 7 + Switchyard is a rules-based browser launcher for Linux. Once it is set as your default browser, clicked links pass through Switchyard first. It can then open the link in a specific browser, ask you which browser to use, or rewrite the URL before opening it. 8 + 9 + Use it when different parts of your life belong in different browsers: work links in Chrome, personal links in Firefox, video links in Brave, or privacy-friendly redirects before anything opens. 10 + 11 + The basic flow is: 12 + 13 + 1. Set Switchyard as your default browser. 14 + 2. Add browser rules for domains, keywords, globs, or regular expressions. 15 + 3. Optionally add link redirections to clean up or rewrite URLs. 16 + 4. Let Switchyard route links automatically, or show the launcher when no rule matches. 17 + 18 + ## Set as Default Browser 19 + 20 + After installation, set Switchyard as your default browser so it can route all clicked links: 21 + 22 + ```bash 23 + xdg-settings set default-web-browser io.github.alyraffauf.Switchyard.desktop 24 + ``` 25 + 26 + Or use your desktop environment's graphical settings to set Switchyard as the default browser.
+1
website/src/icons.ts
··· 12 12 "M12 0c-.556 0-1.111.144-1.61.432l-7.603 4.39a3.217 3.217 0 0 0-1.61 2.788v8.78c0 1.151.612 2.212 1.61 2.788l7.603 4.39a3.217 3.217 0 0 0 3.22 0l7.603-4.39a3.217 3.217 0 0 0 1.61-2.788V7.61a3.217 3.217 0 0 0-1.61-2.788L13.61.432A3.218 3.218 0 0 0 12 0Zm0 2.358c.15 0 .299.039.431.115l7.604 4.39c.132.077.24.187.315.316L12 12v9.642a.863.863 0 0 1-.431-.116l-7.604-4.39a.866.866 0 0 1-.431-.746V7.61c0-.153.041-.302.116-.43L12 12Z", 13 13 download: 14 14 "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 14l-4-4h2.5V8h3v4H16l-4 4z", 15 + book: "M4 19.5A2.5 2.5 0 0 1 6.5 17H20 M4 19.5A2.5 2.5 0 0 0 6.5 22H20V2H6.5A2.5 2.5 0 0 0 4 4.5v15z M8 6h8", 15 16 link: "M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1", 16 17 shuffle: "M16 3h5v5m0-5l-6 6m-2 8l8 8m0-5v5h-5M4 4l5 5m11 11l-5-5M4 20l5-5", 17 18 star: "M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z",
+111
website/src/layouts/DocsLayout.astro
··· 1 + --- 2 + import type { CollectionEntry } from "astro:content"; 3 + import SiteFooter from "../components/SiteFooter.astro"; 4 + import SiteHeader from "../components/SiteHeader.astro"; 5 + import { DEV_URL } from "../site"; 6 + 7 + type DocsEntry = CollectionEntry<"docs">; 8 + 9 + interface Props { 10 + doc: DocsEntry; 11 + docs: DocsEntry[]; 12 + } 13 + 14 + const { doc, docs } = Astro.props; 15 + const hrefFor = (entry: DocsEntry) => `/docs/${entry.id}/`; 16 + const pageTitle = `${doc.data.title} - Switchyard Docs`; 17 + --- 18 + 19 + <!doctype html> 20 + <html lang="en"> 21 + <head> 22 + <meta charset="UTF-8" /> 23 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 24 + <title>{pageTitle}</title> 25 + { 26 + doc.data.description && ( 27 + <meta name="description" content={doc.data.description} /> 28 + ) 29 + } 30 + <link rel="icon" type="image/svg+xml" href="/icon.svg" /> 31 + <meta name="theme-color" content="#4f895d" /> 32 + </head> 33 + <body class="min-h-screen flex flex-col"> 34 + <SiteHeader 35 + items={[ 36 + { href: DEV_URL, label: "Aly Raffauf", icon: "rose" }, 37 + { href: "/", label: "Switchyard", switchyardIcon: true }, 38 + { href: "/docs/", label: "Docs", current: true }, 39 + ]} 40 + /> 41 + 42 + <main 43 + class="w-full max-w-[1200px] mx-auto px-4 md:px-8 py-8 md:py-12 grid grid-cols-1 md:grid-cols-[260px_minmax(0,1fr)] gap-8 md:gap-12 flex-1" 44 + > 45 + <aside 46 + class="md:sticky md:top-16 md:self-start border-b md:border-b-0 md:border-r border-border pb-6 md:pb-0 md:pr-6" 47 + > 48 + <a 49 + href="/docs/" 50 + class="flex items-center gap-2.5 text-text hover:text-primary transition-colors" 51 + > 52 + <img src="/icon.svg" alt="" class="w-7 h-7" /> 53 + <span class="text-lg font-bold tracking-tight">Switchyard Docs</span> 54 + </a> 55 + <p class="mt-3 mb-5 text-sm leading-relaxed text-text-muted"> 56 + Guides for browser rules, URL redirections, custom URI links, and the 57 + browser extension. 58 + </p> 59 + 60 + <label for="docs-page" class="sr-only">Docs page</label> 61 + <select 62 + id="docs-page" 63 + class="block md:hidden w-full min-w-0 rounded-lg border border-border bg-bg px-3 py-2.5 text-sm font-medium text-text shadow-sm focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-2" 64 + > 65 + { 66 + docs.map((entry) => ( 67 + <option value={hrefFor(entry)} selected={entry.id === doc.id}> 68 + {entry.data.title} 69 + </option> 70 + )) 71 + } 72 + </select> 73 + 74 + <nav 75 + class="hidden md:flex md:flex-col gap-1 border-t border-border pt-4" 76 + aria-label="Docs" 77 + > 78 + { 79 + docs.map((entry) => ( 80 + <a 81 + href={hrefFor(entry)} 82 + aria-current={entry.id === doc.id ? "page" : undefined} 83 + class:list={[ 84 + "shrink-0 rounded-lg px-3 py-2 text-sm transition-colors hover:bg-bg-alt hover:text-text", 85 + entry.id === doc.id 86 + ? "bg-bg-alt text-primary font-semibold" 87 + : "text-text-muted", 88 + ]} 89 + > 90 + {entry.data.title} 91 + </a> 92 + )) 93 + } 94 + </nav> 95 + </aside> 96 + 97 + <article class="docs-content min-w-0"> 98 + <h1>{doc.data.title}</h1> 99 + <slot /> 100 + </article> 101 + </main> 102 + <SiteFooter variant="docs" /> 103 + <script is:inline> 104 + document.getElementById("docs-page")?.addEventListener("change", (e) => { 105 + const target = e.currentTarget; 106 + if (!(target instanceof HTMLSelectElement)) return; 107 + window.location.href = target.value; 108 + }); 109 + </script> 110 + </body> 111 + </html>
+24
website/src/pages/docs/[slug].astro
··· 1 + --- 2 + import { getCollection, render } from "astro:content"; 3 + import DocsLayout from "../../layouts/DocsLayout.astro"; 4 + import "../../styles/index.css"; 5 + 6 + export async function getStaticPaths() { 7 + const docs = (await getCollection("docs")).sort( 8 + (a, b) => 9 + a.data.order - b.data.order || a.data.title.localeCompare(b.data.title), 10 + ); 11 + 12 + return docs.map((doc) => ({ 13 + params: { slug: doc.id }, 14 + props: { doc, docs }, 15 + })); 16 + } 17 + 18 + const { doc, docs } = Astro.props; 19 + const { Content } = await render(doc); 20 + --- 21 + 22 + <DocsLayout doc={doc} docs={docs}> 23 + <Content /> 24 + </DocsLayout>
+3
website/src/pages/docs/index.astro
··· 1 + --- 2 + return Astro.redirect("/docs/using/"); 3 + ---
+18 -78
website/src/pages/index.astro
··· 1 1 --- 2 2 import "../styles/index.css"; 3 3 import Icon from "../components/Icon.astro"; 4 + import SiteFooter from "../components/SiteFooter.astro"; 5 + import SiteHeader from "../components/SiteHeader.astro"; 4 6 import { Image } from "astro:assets"; 5 7 import imgLauncher from "../assets/screenshots/launcher.png"; 6 8 import imgBrowserRules from "../assets/screenshots/browser-rules.png"; 7 9 import imgLinkRedirections from "../assets/screenshots/link-redirections.png"; 8 - 9 - const REPO_URL = "https://github.com/alyraffauf/switchyard"; 10 - const DONATE_URL = "https://ko-fi.com/alyraffauf"; 11 - const FLATHUB_URL = "https://flathub.org/apps/io.github.alyraffauf.Switchyard"; 12 - const DEV_URL = "https://aly.codes"; 10 + import { DEV_URL, DONATE_URL, FLATHUB_URL, REPO_URL } from "../site"; 13 11 14 12 const screenshots = [ 15 13 { src: imgLauncher, alt: "Browser launcher" }, ··· 19 17 20 18 const heroButtons = [ 21 19 { href: FLATHUB_URL, label: "Flathub", icon: "flatpak", primary: true }, 22 - { href: REPO_URL, label: "GitHub", icon: "github", primary: true }, 20 + { href: "/docs/", label: "Docs", icon: "book" }, 23 21 { href: DONATE_URL, label: "Donate", icon: "heart" }, 24 22 ] as const; 25 23 ··· 111 109 { 112 110 icon: "github", 113 111 title: "Build from Source", 114 - description: "Requires Go 1.24+ and GTK4", 112 + description: "Requires Go 1.25+ and GTK4", 115 113 link: REPO_URL, 116 114 linkText: "View on GitHub", 117 115 }, 118 116 ] as const; 119 117 120 - const footerLinks = [ 121 - { href: REPO_URL, label: "GitHub" }, 122 - { href: `${REPO_URL}/issues`, label: "Issues" }, 123 - { href: DONATE_URL, label: "Donate" }, 124 - ] as const; 125 - 126 118 const installBg = "bg-bg"; 127 - const footerBg = "bg-bg-alt"; 128 119 --- 129 120 130 121 <!doctype html> ··· 178 169 /> 179 170 </head> 180 171 <body> 181 - <!-- Breadcrumb nav --> 182 - <header 183 - class="sticky top-0 z-50 bg-bg/95 backdrop-blur-sm border-b border-border" 184 - > 185 - <div 186 - class="max-w-[1200px] mx-auto px-4 md:px-8 py-2.5 flex items-center gap-2 text-sm" 187 - > 188 - <a 189 - href={DEV_URL} 190 - class="flex items-center gap-1.5 text-text-muted hover:text-text transition-colors" 191 - > 192 - <Icon 193 - name="rose" 194 - class="w-4 h-4 shrink-0 text-rose-700" 195 - aria-hidden="true" 196 - /> 197 - Aly Raffauf 198 - </a> 199 - <span class="text-text-muted/40">›</span> 200 - <span class="text-text flex items-center gap-1.5"> 201 - <img src="/icon.svg" alt="" class="w-4 h-4" /> 202 - Switchyard 203 - </span> 204 - </div> 205 - </header> 172 + <SiteHeader 173 + items={[ 174 + { href: DEV_URL, label: "Aly Raffauf", icon: "rose" }, 175 + { 176 + href: "/", 177 + label: "Switchyard", 178 + switchyardIcon: true, 179 + current: true, 180 + }, 181 + ]} 182 + /> 206 183 207 184 <!-- Hero --> 208 185 <section ··· 239 216 <p 240 217 class="text-base md:text-lg text-text-muted leading-relaxed mb-4 max-w-[480px] mx-auto md:mx-0 order-4" 241 218 > 242 - Work links in Chrome. Personal links in Firefox. 219 + Route, rewrite, and choose where each link opens. 243 220 </p> 244 221 <div 245 222 class="inline-block px-3 py-2 md:px-4 md:py-2 bg-primary/10 text-primary rounded-full text-sm font-semibold mb-4 md:mb-8 order-5" ··· 393 370 </div> 394 371 </section> 395 372 396 - <!-- Footer --> 397 - <footer class={`py-8 md:py-12 ${footerBg}`}> 398 - <div 399 - class="max-w-[1100px] mx-auto px-4 md:px-8 flex flex-col items-center gap-5" 400 - > 401 - <div class="flex items-center gap-3 font-semibold text-lg"> 402 - <img 403 - src="/icon.svg" 404 - alt="Switchyard" 405 - class="w-8 h-8" 406 - loading="lazy" 407 - /> 408 - <span>Switchyard</span> 409 - </div> 410 - <nav class="flex gap-6 md:gap-8"> 411 - { 412 - footerLinks.map((link) => ( 413 - <a 414 - href={link.href} 415 - class="text-text-muted text-sm hover:text-primary" 416 - > 417 - {link.label} 418 - </a> 419 - )) 420 - } 421 - </nav> 422 - <p class="text-text-light text-sm"> 423 - Made by{" "} 424 - <a 425 - href={DEV_URL} 426 - class="text-text-muted hover:text-primary transition-colors" 427 - > 428 - Aly Raffauf 429 - </a>{" "} 430 - &middot; GPL-3.0-or-later 431 - </p> 432 - </div> 433 - </footer> 373 + <SiteFooter /> 434 374 435 375 <script is:inline> 436 376 function closeLightbox() {
+12
website/src/site.ts
··· 1 + export const REPO_URL = "https://github.com/alyraffauf/switchyard"; 2 + export const DONATE_URL = "https://ko-fi.com/alyraffauf"; 3 + export const FLATHUB_URL = 4 + "https://flathub.org/apps/io.github.alyraffauf.Switchyard"; 5 + export const DEV_URL = "https://aly.codes"; 6 + 7 + export const footerLinks = [ 8 + { href: "/docs/", label: "Docs" }, 9 + { href: REPO_URL, label: "GitHub" }, 10 + { href: `${REPO_URL}/issues`, label: "Issues" }, 11 + { href: DONATE_URL, label: "Donate" }, 12 + ] as const;
+86
website/src/styles/index.css
··· 38 38 transparent 70% 39 39 ); 40 40 } 41 + 42 + .docs-content { 43 + max-width: 800px; 44 + line-height: 1.75; 45 + } 46 + 47 + .docs-content h1 { 48 + font-size: clamp(2rem, 4vw, 2.75rem); 49 + line-height: 1.1; 50 + font-weight: 800; 51 + letter-spacing: 0; 52 + margin-bottom: 1.75rem; 53 + } 54 + 55 + .docs-content h2 { 56 + font-size: 1.375rem; 57 + line-height: 1.25; 58 + font-weight: 750; 59 + margin-top: 2.5rem; 60 + margin-bottom: 0.75rem; 61 + } 62 + 63 + .docs-content h3 { 64 + font-size: 1.125rem; 65 + line-height: 1.35; 66 + font-weight: 700; 67 + margin-top: 2rem; 68 + margin-bottom: 0.5rem; 69 + } 70 + 71 + .docs-content p, 72 + .docs-content ul, 73 + .docs-content ol, 74 + .docs-content pre { 75 + margin-bottom: 1rem; 76 + } 77 + 78 + .docs-content ul, 79 + .docs-content ol { 80 + padding-left: 1.5rem; 81 + } 82 + 83 + .docs-content ul { 84 + list-style: disc; 85 + } 86 + 87 + .docs-content ol { 88 + list-style: decimal; 89 + } 90 + 91 + .docs-content li { 92 + margin-bottom: 0.375rem; 93 + } 94 + 95 + .docs-content a { 96 + color: var(--color-primary); 97 + text-decoration: underline; 98 + text-underline-offset: 0.18em; 99 + } 100 + 101 + .docs-content code { 102 + border: 1px solid var(--color-border); 103 + border-radius: 0.375rem; 104 + background: var(--color-bg-alt); 105 + padding: 0.125rem 0.35rem; 106 + font-family: var(--font-mono); 107 + font-size: 0.92em; 108 + } 109 + 110 + .docs-content pre { 111 + overflow-x: auto; 112 + border: 1px solid var(--color-border); 113 + border-radius: 0.75rem; 114 + background: var(--color-bg-alt); 115 + padding: 1rem; 116 + } 117 + 118 + .docs-content pre code { 119 + display: block; 120 + border: 0; 121 + border-radius: 0; 122 + background: transparent; 123 + padding: 0; 124 + font-size: 0.875rem; 125 + line-height: 1.6; 126 + }