diff --git a/.config/glide/.gitignore b/.config/glide/.gitignore
new file mode 100644
index 00000000..92677af0
--- /dev/null
+++ b/.config/glide/.gitignore
@@ -0,0 +1,2 @@
+# this is a 10k+ line file that's provided by glide anyway
+glide.d.ts
diff --git a/.config/glide/glide.ts b/.config/glide/glide.ts
new file mode 100644
index 00000000..082f9cb4
--- /dev/null
+++ b/.config/glide/glide.ts
@@ -0,0 +1,89 @@
+// Config docs:
+//
+// https://glide-browser.app/config
+//
+// API reference:
+//
+// https://glide-browser.app/api
+//
+// Default config files can be found here:
+//
+// https://github.com/glide-browser/glide/tree/main/src/glide/browser/base/content/plugins
+//
+// Most default keymappings are defined here:
+//
+// https://github.com/glide-browser/glide/blob/main/src/glide/browser/base/content/plugins/keymaps.mts
+//
+// Try typing `glide.` and see what you can do!
+
+glide.unstable.include("hints.glide.ts");
+
+glide.styles.add(`
+ .yank-notification {
+ position: fixed;
+ bottom: 2rem;
+ right: 2rem;
+ z-index: 2147483646;
+ background: var(--glide-cmdl-bg);
+ color: var(--glide-cmdl-fg);
+ border: 1px solid hsla(0, 0%, 100%, 0.1);
+ border-radius: 4px;
+ box-shadow: 0 -8px 32px hsla(0, 0%, 0%, 0.4);
+ font-family: var(--glide-cmdl-font-family);
+ font-size: var(--glide-cmdl-font-size);
+ line-height: var(--glide-cmdl-line-height);
+ padding: 0.75rem 1rem;
+ max-width: 600px;
+ word-break: break-all;
+ }
+`);
+
+glide.keymaps.set("normal", "zi", async ({ tab_id }) => {
+ const zoom = await browser.tabs.getZoom(tab_id);
+ await browser.tabs.setZoom(tab_id, zoom + 0.1);
+});
+
+glide.keymaps.set("normal", "zo", async ({ tab_id }) => {
+ const zoom = await browser.tabs.getZoom(tab_id);
+ await browser.tabs.setZoom(tab_id, zoom - 0.1);
+});
+
+glide.keymaps.set("normal", "zz", async ({ tab_id }) => {
+ await browser.tabs.setZoom(tab_id, 1);
+});
+
+glide.keymaps.set("normal", "u", async () => {
+ const sessions = await browser.sessions.getRecentlyClosed({ maxResults: 1 });
+ const [session, ..._] = sessions;
+ if (session) {
+ await browser.sessions.restore(session.tab?.sessionId ?? session.window?.sessionId);
+ }
+});
+
+glide.keymaps.set("normal", "H", "back");
+glide.keymaps.set("normal", "L", "forward");
+glide.keymaps.set("normal", "d", "tab_close");
+
+glide.keymaps.set("normal", "p", async ({ tab_id }) => {
+ const url = await navigator.clipboard.readText();
+ await browser.tabs.update(tab_id, { url });
+});
+
+glide.keymaps.set("normal", "P", async () => {
+ const url = await navigator.clipboard.readText();
+ await browser.tabs.create({ url });
+});
+
+glide.keymaps.set("normal", "yy", async () => {
+ const url = glide.ctx.url;
+ await navigator.clipboard.writeText(url.toString());
+
+ const notification = DOM.create_element("div", {
+ className: "yank-notification",
+ textContent: `Yanked: ${url}`,
+ });
+
+ document.documentElement.appendChild(notification);
+ setTimeout(() => notification.remove(), 2000);
+});
+
diff --git a/.config/glide/hints.glide.ts b/.config/glide/hints.glide.ts
new file mode 100644
index 00000000..3c0f18cd
--- /dev/null
+++ b/.config/glide/hints.glide.ts
@@ -0,0 +1,79 @@
+// Source: https://github.com/jyn514/dotfiles/blob/master/config/glide.ts
+
+function shorten_unique_prefixes(haystack) {
+ // first, construct a set of duplicates
+ const seen = new Set();
+ const duplicates = new Set();
+ for (let t of haystack) {
+ if (seen.has(t)) {
+ duplicates.add(t);
+ } else {
+ seen.add(t);
+ }
+ }
+
+ // shorten each word
+ return haystack.map((needle, pos) => {
+ if (duplicates.has(needle)) return needle;
+ // while all words have the same character at position i, increment i
+ for (let i = 1; i <= needle.length; i++) {
+ const prefix = needle.slice(0, i);
+ const is_unique = haystack.every((w, j) => pos === j || !w.startsWith(prefix));
+ if (is_unique) {
+ return prefix;
+ }
+ }
+ // no unique prefix
+ return needle;
+ });
+}
+
+function strip(text) {
+ // strip numbers, non-ascii text, and annoying-to-type characters
+ return text.replace(/[0-9]+/g, '').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();
+}
+
+function labels(texts) {
+ // now shorten as much as we can without losing info
+ const haystack = shorten_unique_prefixes(texts);
+
+ let nums_used = 0;
+ const result = new Array(haystack.length);
+
+ // trim prefixes longer than 3 characters by using letters that occur later in the label.
+ // also, try to replace the last letter of duplicates.
+ // note that that can only help if the original text was > 3 characters.
+ const used = new Set();
+ outer: for (const [index, prefix] of haystack.entries()) {
+ if (prefix === "") {
+ result[index] = String(nums_used++);
+ } else if (prefix.length > 3 || used.has(prefix)) {
+ const base = prefix.substring(0, 2);
+ // try each character after the 3rd in turn.
+ // consider the original text, not the shortened prefix.
+ for (const c of texts[index].substring(2)) {
+ const candidate = base + c;
+ if (!used.has(candidate)) {
+ result[index] = candidate;
+ used.add(candidate);
+ continue outer;
+ }
+ }
+ // we couldn't shorten it. use a number.
+ result[index] = String(nums_used++);
+ } else {
+ // already short enough, use it as-is.
+ result[index] = prefix;
+ used.add(prefix);
+ }
+ }
+
+ // try one last time to shorten prefixes—if we gave up earlier, we might have freed up a spot.
+ return shorten_unique_prefixes(result);
+}
+
+glide.o.hint_label_generator = async ({ content }) => {
+ const texts = await content.map(element => [element.textContent, element.ariaLabel]);
+ const haystack = texts.map(([text, label]) => strip(text) || strip(label || ""));
+ return labels(haystack);
+};
diff --git a/.config/glide/package.json b/.config/glide/package.json
new file mode 100644
index 00000000..18866382
--- /dev/null
+++ b/.config/glide/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "glide-config",
+ "private": true,
+ "version": "0.0.1",
+ "description": "",
+ "scripts": {
+ "tsc": "tsc"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "",
+ "dependencies": {
+ "typescript": "^5.9.2"
+ },
+ "devDependencies": {}
+}
\ No newline at end of file
diff --git a/.config/glide/tsconfig.json b/.config/glide/tsconfig.json
new file mode 100644
index 00000000..e6bdc203
--- /dev/null
+++ b/.config/glide/tsconfig.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "exclude": [
+ "node_modules"
+ ],
+ "compilerOptions": {
+ "lib": ["DOM", "DOM.Iterable", "DOM.AsyncIterable", "ESNext"],
+ "types": [
+ "./glide.d.ts"
+ ],
+ "target": "esnext",
+ "module": "esnext",
+ "moduleDetection": "force",
+ "allowJs": true,
+ "noEmit": true,
+ /**
+ * Recommended type checking rules.
+ *
+ * Feel free to modify these.
+ */
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "strictFunctionTypes": true,
+ "strictBindCallApply": true,
+ "strictPropertyInitialization": true,
+ "noImplicitThis": true,
+ "alwaysStrict": true,
+ "noUncheckedIndexedAccess": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "skipLibCheck": true,
+ "noErrorTruncation": true,
+ "erasableSyntaxOnly": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d0de0b04..b79413af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ private
!.config/fish
!.config/git
!.config/ghostty
+!.config/glide
!.config/irb
.config/irb/irb_history
!.config/jj