Showing preview only (5,269K chars total). Download the full file or copy to clipboard to get everything.
Repository: fmhy/edit
Branch: main
Commit: 4a33c13cd339
Files: 286
Total size: 5.0 MB
Directory structure:
gitextract_e2k2ctcw/
├── .devcontainer/
│ └── devcontainer.json
├── .dockerignore
├── .gitattributes
├── .github/
│ ├── CODEOWNERS
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ └── wiki.yml
│ ├── POST_TEMPLATE.md
│ ├── README.md
│ ├── assets/
│ │ └── nginx.conf
│ └── workflows/
│ ├── deploy-api.yml
│ └── deploy-gh-pages.yml
├── .gitignore
├── .gitpod.yml
├── .licenserc.json
├── .mise.toml
├── .npmrc
├── .prettierignore
├── .prettierrc.yaml
├── Dockerfile
├── api/
│ ├── README.md
│ ├── middleware/
│ │ ├── cors.ts
│ │ └── ratelimit.ts
│ ├── routes/
│ │ ├── feedback.post.ts
│ │ ├── index.ts
│ │ └── single-page.ts
│ ├── tsconfig.json
│ └── worker-configuration.d.ts
├── docker-compose.yaml
├── docs/
│ ├── .vitepress/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── config.mts
│ │ ├── constants.ts
│ │ ├── fonts/
│ │ │ ├── Inter-Bold.otf
│ │ │ ├── Inter-Medium.otf
│ │ │ ├── Inter-Regular.otf
│ │ │ └── Inter-SemiBold.otf
│ │ ├── hooks/
│ │ │ ├── Template.vue
│ │ │ ├── index.ts
│ │ │ ├── meta.ts
│ │ │ ├── opengraph.ts
│ │ │ ├── rss.ts
│ │ │ └── satoriConfig.ts
│ │ ├── markdown/
│ │ │ ├── base64.ts
│ │ │ ├── emoji.ts
│ │ │ ├── headers.ts
│ │ │ └── toggleStarred.ts
│ │ ├── notes/
│ │ │ ├── 1337x-ranks.md
│ │ │ ├── CodeRabbit.md
│ │ │ ├── advanced-logic-calculators.md
│ │ │ ├── affine-note.md
│ │ │ ├── alt-twitch-player-extensions.md
│ │ │ ├── alt-warp-clients.md
│ │ │ ├── android-spotify-note.md
│ │ │ ├── apkmirror-extensions.md
│ │ │ ├── audiobookbay-warning.md
│ │ │ ├── aurora-note.md
│ │ │ ├── better-reasoning.md
│ │ │ ├── bookmarkeddit.md
│ │ │ ├── buster-note.md
│ │ │ ├── buzzheavier-warning.md
│ │ │ ├── bypass-freedlink.md
│ │ │ ├── captcha-4pda.md
│ │ │ ├── chatgpt-limits.md
│ │ │ ├── clipboard2file-addons.md
│ │ │ ├── cofi-note.md
│ │ │ ├── crystaldiskinfo.md
│ │ │ ├── csrin-search.md
│ │ │ ├── cute-save-button-icon.md
│ │ │ ├── dodi-warning.md
│ │ │ ├── dolby-access-atmos-note.md
│ │ │ ├── driver-note.md
│ │ │ ├── eaglercraft-note.md
│ │ │ ├── eruda.md
│ │ │ ├── filebin-warning.md
│ │ │ ├── filelu-warning.md
│ │ │ ├── filezilla-warning.md
│ │ │ ├── flicker-proxy.md
│ │ │ ├── fluxy-repacks.md
│ │ │ ├── forest-extensions.md
│ │ │ ├── foxit-warning.md
│ │ │ ├── freegogpcgames-note.md
│ │ │ ├── gemai.md
│ │ │ ├── general-tweak-warning.md
│ │ │ ├── glitchwave-note.md
│ │ │ ├── google-song-identification.md
│ │ │ ├── google-translate-note.md
│ │ │ ├── hdo-box-note.md
│ │ │ ├── hugging-face-warning.md
│ │ │ ├── instaeclipse-note.md
│ │ │ ├── irc-highway-note.md
│ │ │ ├── jdownloader-warning.md
│ │ │ ├── limit-bypass-note.md
│ │ │ ├── liteapk-modyolo-note.md
│ │ │ ├── malware-removal-forums.md
│ │ │ ├── megabasterd-note.md
│ │ │ ├── mobilism-ranks.md
│ │ │ ├── modelscope.md
│ │ │ ├── mori-note.md
│ │ │ ├── movie-web-sources.md
│ │ │ ├── movieparadise-code.md
│ │ │ ├── mp-opensubs.md
│ │ │ ├── mvsep-note.md
│ │ │ ├── oneclick-note.md
│ │ │ ├── openasar.md
│ │ │ ├── openrgb-beta.md
│ │ │ ├── pollinations-limits.md
│ │ │ ├── printeditwe-addons.md
│ │ │ ├── proton-torrenting.md
│ │ │ ├── reaper-note.md
│ │ │ ├── redditfilter-note.md
│ │ │ ├── rgshows-autoplay.md
│ │ │ ├── sanet-warning.md
│ │ │ ├── savepagewe.md
│ │ │ ├── scrollanywhere-addons.md
│ │ │ ├── sd-maid.md
│ │ │ ├── sh-note.md
│ │ │ ├── site-favicon-dl.md
│ │ │ ├── soft98-note.md
│ │ │ ├── sora.md
│ │ │ ├── spacewar.md
│ │ │ ├── spicetify-note.md
│ │ │ ├── sport7.md
│ │ │ ├── steam-controller-support.md
│ │ │ ├── steam-currency-converter-note.md
│ │ │ ├── tabiverse-extensions.md
│ │ │ ├── tautulli-note.md
│ │ │ ├── teamspeak-warning.md
│ │ │ ├── thunderbird.md
│ │ │ ├── tinyurl-note.md
│ │ │ ├── video-downloadhelper.md
│ │ │ ├── welib-note.md
│ │ │ ├── winrar.md
│ │ │ ├── yet-another-call-blocker-note.md
│ │ │ ├── youtube-tweaks.md
│ │ │ └── yts-yify-note.md
│ │ ├── shared.ts
│ │ ├── theme/
│ │ │ ├── Appearance.vue
│ │ │ ├── Layout.vue
│ │ │ ├── PostLayout.vue
│ │ │ ├── Posts.vue
│ │ │ ├── components/
│ │ │ │ ├── Announcement.vue
│ │ │ │ ├── Authors.vue
│ │ │ │ ├── Base64Dialog.vue
│ │ │ │ ├── CardField.vue
│ │ │ │ ├── ColorPicker.vue
│ │ │ │ ├── Feedback.vue
│ │ │ │ ├── InputField.vue
│ │ │ │ ├── SidebarCard.vue
│ │ │ │ ├── Switch.vue
│ │ │ │ ├── ThemeDropdown.vue
│ │ │ │ ├── ThemeSelector.vue
│ │ │ │ ├── ToggleIndexes.vue
│ │ │ │ ├── ToggleStarred.vue
│ │ │ │ ├── Tooltip.vue
│ │ │ │ ├── VPLocalSearchBox.vue
│ │ │ │ ├── VPNav.vue
│ │ │ │ ├── WallpaperCard.vue
│ │ │ │ └── startpage/
│ │ │ │ ├── Bookmarks.vue
│ │ │ │ ├── Clock.vue
│ │ │ │ ├── SearchBar.vue
│ │ │ │ └── Startpage.vue
│ │ │ ├── composables/
│ │ │ │ └── nprogress.ts
│ │ │ ├── index.ts
│ │ │ ├── posts.data.ts
│ │ │ ├── style.scss
│ │ │ └── themes/
│ │ │ ├── README.md
│ │ │ ├── configs/
│ │ │ │ ├── catppuccin.ts
│ │ │ │ ├── christmas.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── monochrome.ts
│ │ │ ├── index.ts
│ │ │ ├── themeHandler.ts
│ │ │ └── types.ts
│ │ ├── transformer/
│ │ │ ├── constants.ts
│ │ │ └── core.ts
│ │ ├── transformer.ts
│ │ ├── types/
│ │ │ └── Feedback.ts
│ │ ├── utils/
│ │ │ ├── markdown.ts
│ │ │ └── tooltips.ts
│ │ ├── utils.ts
│ │ └── vue-shim.d.ts
│ ├── ai.md
│ ├── audio.md
│ ├── beginners-guide.md
│ ├── developer-tools.md
│ ├── downloading.md
│ ├── educational.md
│ ├── feedback.md
│ ├── file-tools.md
│ ├── gaming-tools.md
│ ├── gaming.md
│ ├── image-tools.md
│ ├── index.md
│ ├── internet-tools.md
│ ├── linux-macos.md
│ ├── misc.md
│ ├── mobile.md
│ ├── non-english.md
│ ├── other/
│ │ ├── FAQ.md
│ │ ├── backups.md
│ │ ├── contributing.md
│ │ ├── selfhosting.md
│ │ └── wallpapers.md
│ ├── posts/
│ │ ├── FCC.md
│ │ ├── Internet-Archive.md
│ │ ├── KeepAndroidOpen.md
│ │ ├── Nov-2025.md
│ │ ├── WWH.md
│ │ ├── april-2023.md
│ │ ├── april-2024.md
│ │ ├── april-2025.md
│ │ ├── aug-2023.md
│ │ ├── aug-2024.md
│ │ ├── aug-2025.md
│ │ ├── changelog-sites.md
│ │ ├── dec-2023.md
│ │ ├── dec-2024.md
│ │ ├── dec-2025.md
│ │ ├── discord.md
│ │ ├── feb-2024.md
│ │ ├── feb-2025.md
│ │ ├── feb-2026.md
│ │ ├── jan-2024.md
│ │ ├── jan-2025.md
│ │ ├── jan-2026.md
│ │ ├── july-2023.md
│ │ ├── july-2024.md
│ │ ├── july-2025.md
│ │ ├── jun-2023.md
│ │ ├── june-2024.md
│ │ ├── june-2025.md
│ │ ├── mar-2025.md
│ │ ├── mar-2026.md
│ │ ├── march-2024.md
│ │ ├── may-2023.md
│ │ ├── may-2024.md
│ │ ├── may-2025.md
│ │ ├── new-site.md
│ │ ├── nov-2023.md
│ │ ├── nov-2024.md
│ │ ├── oct-2023.md
│ │ ├── oct-2024.md
│ │ ├── oct-2025.md
│ │ ├── search.md
│ │ ├── sept-2023.md
│ │ ├── sept-2024.md
│ │ ├── sept-2025.md
│ │ └── support-ia.md
│ ├── posts.md
│ ├── privacy.md
│ ├── public/
│ │ ├── _headers
│ │ ├── key.txt
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── reading.md
│ ├── sandbox.md
│ ├── social-media-tools.md
│ ├── startpage.md
│ ├── storage.md
│ ├── system-tools.md
│ ├── text-tools.md
│ ├── torrenting.md
│ ├── unsafe.md
│ ├── video-tools.md
│ └── video.md
├── flake.nix
├── nitro.config.ts
├── package.json
├── pests-repellent/
│ ├── .editorconfig
│ ├── .gitignore
│ ├── .prettierrc
│ ├── .vscode/
│ │ └── settings.json
│ ├── package.json
│ ├── src/
│ │ └── index.ts
│ ├── test/
│ │ ├── env.d.ts
│ │ ├── index.spec.ts
│ │ └── tsconfig.json
│ ├── tsconfig.json
│ ├── vitest.config.mts
│ ├── worker-configuration.d.ts
│ └── wrangler.jsonc
├── scripts/
│ ├── lint-markdown.js
│ └── typos.csv
├── tsconfig.json
├── unocss.config.ts
└── wrangler.toml
================================================
FILE CONTENTS
================================================
================================================
FILE: .devcontainer/devcontainer.json
================================================
{
"appPort": 5173,
"features": {},
"image": "mcr.microsoft.com/devcontainers/universal:2",
"name": "FMHYedit",
"postAttachCommand": "pnpm docs:dev",
"postStartCommand": "pnpm install"
}
================================================
FILE: .dockerignore
================================================
node_modules
.git
.gitignore
dist
cache
================================================
FILE: .gitattributes
================================================
# Common settings that generally should always be used with your language specific settings
# Auto detect text files and perform LF normalization
* text=auto
#
# The above will handle all files NOT found below
#
# Documents
*.bibtex text diff=bibtex
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.md text diff=markdown
*.mdx text diff=markdown
*.tex text diff=tex
*.adoc text
*.textile text
*.mustache text
*.csv text eol=crlf
*.tab text
*.tsv text
*.txt text
*.sql text
*.epub diff=astextplain
# Graphics
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.tif binary
*.tiff binary
*.ico binary
# SVG treated as text by default.
*.svg text
# If you want to treat it as binary,
# use the following line instead.
# *.svg binary
*.eps binary
# Scripts
*.bash text eol=lf
*.fish text eol=lf
*.ksh text eol=lf
*.sh text eol=lf
*.zsh text eol=lf
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
# Serialisation
*.json text
*.toml text
*.xml text
*.yaml text
*.yml text
# Archives
*.7z binary
*.bz binary
*.bz2 binary
*.bzip2 binary
*.gz binary
*.lz binary
*.lzma binary
*.rar binary
*.tar binary
*.taz binary
*.tbz binary
*.tbz2 binary
*.tgz binary
*.tlz binary
*.txz binary
*.xz binary
*.Z binary
*.zip binary
*.zst binary
# Text files where line endings should be preserved
*.patch -text
#
# Exclude files from exporting
#
.gitattributes export-ignore
.gitignore export-ignore
.gitkeep export-ignore
## GITATTRIBUTES FOR WEB PROJECTS
#
# These settings are for any web project.
#
# Details per file setting:
# text These files should be normalized (i.e. convert CRLF to LF).
# binary These files are binary and should be left untouched.
#
# Note that binary is a macro for -text -diff.
######################################################################
# Auto detect
## Handle line endings automatically for files detected as
## text and leave all files detected as binary untouched.
## This will handle all files NOT defined below.
* text=auto
# Source code
*.bash text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.coffee text
*.css text diff=css
*.htm text diff=html
*.html text diff=html
*.inc text
*.ini text
*.js text
*.mjs text
*.cjs text
*.json text
*.jsx text
*.less text
*.ls text
*.map text -diff
*.od text
*.onlydata text
*.php text diff=php
*.pl text
*.ps1 text eol=crlf
*.py text diff=python
*.rb text diff=ruby
*.sass text
*.scm text
*.scss text diff=css
*.sh text eol=lf
.husky/* text eol=lf
*.sql text
*.styl text
*.tag text
*.ts text
*.tsx text
*.xml text
*.xhtml text diff=html
# Docker
Dockerfile text
# Documentation
*.ipynb text eol=lf
*.markdown text diff=markdown
*.md text diff=markdown
*.mdwn text diff=markdown
*.mdown text diff=markdown
*.mkd text diff=markdown
*.mkdn text diff=markdown
*.mdtxt text
*.mdtext text
*.txt text
AUTHORS text
CHANGELOG text
CHANGES text
CONTRIBUTING text
COPYING text
copyright text
*COPYRIGHT* text
INSTALL text
license text
LICENSE text
NEWS text
readme text
*README* text
TODO text
# Templates
*.dot text
*.ejs text
*.erb text
*.haml text
*.handlebars text
*.hbs text
*.hbt text
*.jade text
*.latte text
*.mustache text
*.njk text
*.phtml text
*.svelte text
*.tmpl text
*.tpl text
*.twig text
*.vue text diff=vue
# Configs
*.cnf text
*.conf text
*.config text
.editorconfig text
.env text
.gitattributes text
.gitconfig text
.htaccess text
*.lock text -diff
package.json text eol=lf
package-lock.json text eol=lf -diff
pnpm-lock.yaml text eol=lf -diff
.prettierrc text
yarn.lock text -diff
*.toml text
*.yaml text
*.yml text
browserslist text
Makefile text
makefile text
# Fixes syntax highlighting on GitHub to allow comments
tsconfig.json linguist-language=JSON-with-Comments
# Heroku
Procfile text
# Graphics
*.ai binary
*.bmp binary
*.eps binary
*.gif binary
*.gifv binary
*.ico binary
*.jng binary
*.jp2 binary
*.jpg binary
*.jpeg binary
*.jpx binary
*.jxr binary
*.pdf binary
*.png binary
*.psb binary
*.psd binary
# SVG treated as an asset (binary) by default.
*.svg text
# If you want to treat it as binary,
# use the following line instead.
# *.svg binary
*.svgz binary
*.tif binary
*.tiff binary
*.wbmp binary
*.webp binary
# Audio
*.kar binary
*.m4a binary
*.mid binary
*.midi binary
*.mp3 binary
*.ogg binary
*.ra binary
# Video
*.3gpp binary
*.3gp binary
*.as binary
*.asf binary
*.asx binary
*.avi binary
*.fla binary
*.flv binary
*.m4v binary
*.mng binary
*.mov binary
*.mp4 binary
*.mpeg binary
*.mpg binary
*.ogv binary
*.swc binary
*.swf binary
*.webm binary
# Archives
*.7z binary
*.gz binary
*.jar binary
*.rar binary
*.tar binary
*.zip binary
# Fonts
*.ttf binary
*.eot binary
*.otf binary
*.woff binary
*.woff2 binary
# Executables
*.exe binary
*.pyc binary
# Prevents massive diffs caused by vendored, minified files
**/.yarn/releases/** binary
**/.yarn/plugins/** binary
# RC files (like .babelrc or .eslintrc)
*.*rc text
# Ignore files (like .npmignore or .gitignore)
*.*ignore text
# Prevents massive diffs from built files
dist/* binary
================================================
FILE: .github/CODEOWNERS
================================================
docs/.vitepress/**/* @taskylizard
api/**/* @taskylizard
index.md @taskylizard
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contribute Guide
> [!INFO] NOTE
> Some of these steps are easier if you're in our [Discord](https://github.com/fmhy/FMHY/wiki/FMHY-Discord). It opens every Friday.
Here you'll find some general guidelines for those who would like to start contributing. There are multiple ways to do this:
- [Link Submissions](#submissions)
- [Reporting an Existing Site](#reporting-a-site)
- [How to Edit and Preview Changes](#making-changes)
- [Finding New Sites](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/find-new-sites/)
## Submissions
> [!INFO] NOTE
> For bigger changes to the wiki, such as debloating efforts or the restructuring of a page/section, you must first discuss these with us via [Discord](https://github.com/fmhy/FMHY/wiki/FMHY-Discord) before opening a [Pull Request](https://github.com/fmhy/edit/pulls).
**Don't submit any of the following:**
- **💰️ Paid / Trial Sites** - We don't accept any paid or free trial only entries, with the exception of select paid [VPNs](/privacy#vpn) and [Debrid](/downloading#debrid-leeches).
- **🕹️ Emulators** - Already listed on [Index Sites](/gaming#emulators).
- **🌐 Web Browsers** - Good open-source browsers are already listed, so we just accept indexes, privacy-focused, and good mobile ones.
- **🔻 Leeches** - Unless it's not already listed on existing [Leech Lists](/downloading#debrid-leeches), don't submit these.
- **🐧 Linux Distros** - Already listed on [Index Sites](/linux-macos#linux-distros).
- **🌍 Non-english Software** - We don't add non-english software sites (APKs, games, torrents, etc.) unless they have a very good reputation.
- **🗂️ Coding Libraries** - There's too many of them and there are better places to find them.
- **🎲 Mining / Betting Sites** - Don't submit anything related to betting, mining, BINs, CCs, etc.
- **🎮 Multiplayer Hacks** - Don't submit any hacks or exploits that give an unfair advantage in multiplayer games.
- **🖥️ Custom OSs** - We don't recommend people use these.
### Adding a Site
For submitting new links, follow these steps:
- Make sure it's not already in the wiki. The easiest way to do this is to check our [Single Page](https://api.fmhy.net/single-page) using `ctrl+f`.
- Don't spam a bunch of un-tested links at once. Try to only send things you genuinely feel might be worth adding.
- Reach out via the feedback system, [GitHub](https://github.com/fmhy/edit), or join our [Discord](https://github.com/fmhy/FMHY/wiki/FMHY-Discord). Note that we have to check sites ourselves, so using a issue, rather than pull request is easier.
- You can optionally include socials, tools, or any other additional info alongside the entry.
### Reporting a Site
> [!INFO] NOTE
> If it's urgent, you're allowed to request an invite via our feedback system.
For changes to existing entries, follow these steps:
- Reach out via the feedback system, [GitHub](https://github.com/fmhy/edit), or join our [Discord](https://github.com/fmhy/FMHY/wiki/FMHY-Discord).
- Feel free to leave contact info when using the feedback system, if needed. Only trusted staff can view this.
- If you'd like to report a site removal or star change, you must include details as to why your changes should be accepted.
### Link Testing
All additions have to first go through our testing process on [Discord](https://github.com/fmhy/FMHY/wiki/FMHY-Discord).
You can help us test new sites to figure out their use-case, safety, and whether they'd be a good fit for the wiki.
Keep in mind certain sites (such as piracy sites) require more testing/vetting before they can be added.
### Formatting Rules
The wiki will always have some variation either due to exceptions being made, the layout/structure, or the flexible nature of markdown itself.
For these reasons, there are too many conditions and nuances to satisfy to make an easy-to-follow guide. However, you can generally get an idea by looking at how existing links are structured.
Note that we do try to order sections from best to worst, and if multiple links are on the same line, only the **bold** ones are considered stars.
If you're unsure, ask in the wiki channels on [Discord](https://github.com/fmhy/FMHY/wiki/FMHY-Discord) and wait for a staff member to reply.
## Making Changes
Instructions on various ways to edit the wiki and preview changes.
### GitHub Editor
You can use the built-in web editor in two ways:
1. Find the file you want to edit, look for the edit icon (of a pencil) and click on it, then make your changes.

2. When you're done, click "Commit Changes..." then "Propose changes". Optionally add a commit description.
3. You should now see a comparison page showing all your edits. Click "Create pull request", fill in the box describing your changes, then hit submit.
**OR**
1. Fork the repository by clicking the "Fork" button in the top right.
2. Navigate to your fork's homepage and press the `.` (period) key on your keyboard. This will open the repository in a VSCode-like environment on `github.dev`.
3. Make your changes, then commit via the source control tab.

4. Back on your fork's homepage, click "Contribute" then "Open pull request".
5. You should now see a comparison page showing all your edits. Click "Create pull request", fill in the box describing your changes, then hit submit.
### Dev Environment
If you're going to work on the site itself, or simply want to preview the site and any changes, you can do so by setting up a development environment.
#### GitHub Codespaces
This creates an environment in the browser [(with 60h/month free quota)](https://docs.github.com/en/billing/concepts/product-billing/github-codespaces#free-and-billed-use-by-personal-accounts). To use Codespaces, follow these steps:
1. Fork the repository by clicking the "Fork" button in the top right.
2. Navigate to your fork's homepage and click on the green "Code" button above your repository, open "Codespaces" tab and click "Create codespace".
3. You may have to wait ~5-10 minutes for the codespace to load.

4. Once it has loaded, run `pnpm i && pnpm docs:dev` to start the preview. Then when it appears, click "Open in Browser" in the bottom right.
5. Make your changes and commit.
6. To shut it down, click the "Code" button again and look for the `...` dropdown next to your codespace, then click "Stop codespace".
#### Local Instance
Making changes on a local repository may require a basic understanding of Git. You can find learning resources [here](/educational#developer-learning).
More info on manual setup can be found [here](/other/selfhosting).
================================================
FILE: .github/ISSUE_TEMPLATE/wiki.yml
================================================
name: Create Issue
description: 'Help us improve FMHY for everyone'
title: 'Issue form title'
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this issue! Please select your issue type.
- type: markdown
attributes:
value: |
### Things to note
* Anyone can suggest [changes or corrections](https://rentry.org/fmhyedit) to the wiki. Please read our [Contribution Guide](https://rentry.co/Contrib-Guide) before trying to add or remove anything.
* If you're adding a new site, please [search](https://raw.githubusercontent.com/fmhy/edit/main/single-page) (control + f) first to make sure we don't already have it.
* Approved changes will be applied to the [site](https://fmhy.net) and all [🔒 backups](https://github.com/fmhy/FMHY/wiki/Backups).
* You can send us stuff directly via [💬 Discord](https://github.com/fmhy/FMHY/wiki/FMHY-Discord).
* You can also check out our [website](https://fmhy.net) and the [posts](https://fmhy.net/posts) section to know about any major updates to the wiki.
- type: dropdown
id: type
attributes:
label: Type
description: Type that best describes this issue.
options:
- Bad sites
- Grammar / Markdown
- Site suggestion
- Wiki section suggestion
default: 0
validations:
required: true
- type: textarea
id: context
attributes:
label: Add additional context
description:
A clear and concise description of what the issue is, as per your
selected issue type.
placeholder: https://fmhy.net is cool and should be added
validations:
required: true
================================================
FILE: .github/POST_TEMPLATE.md
================================================
### Post Template
> [!NOTE]
> This is a template for creating a post for the freemediaheckyeah blog, and is meant for **collaborators** only.
Notes:
- The title should be a short, descriptive title.
- The date should be in the format `YYYY-MM-DD`.
- The filename should be in the format `month-year.md`.
- The description should be a short, descriptive description of the post.
- Authors should be in the format `['username', ....]`
- The ending shouldn't have a line break (`---`/`***`).
- Links to the website should be turned into relative links, i.e. `[Text Tools](https://fmhy.net/text-tools)` -> `[Text Tools](/text-tools)`.
Template:
```
---
title: Monthly Updates [Month]
description: Month 20YY updates
date: 20YY-MM-DD
next: false
aside: right
prev: false
sidebar: false
footer: true
---
<Post authors="['nbats']"/>
:::info
These update threads only contains major updates. If you're interested
in seeing all minor changes you can follow our
[Commits Page](https://github.com/fmhy/edit/commits/main) on GitHub or
[Updates Channel](https://redd.it/17f8msf) in Discord.
:::
### Wiki Updates
....
---
### Stars Added ⭐
....
---
### Things Removed
....
```
================================================
FILE: .github/README.md
================================================
# FMHY

<p align="center"> <b> The largest collection of free stuff on the internet! </b> </p>
## 📖 Wiki
- Website: [fmhy.net](https://fmhy.net)
- News & Monthly Updates: [fmhy.net/posts](https://fmhy.net/posts)
- Backups, Markdown, JSON API: [github.com/fmhy/FMHY/wiki/Backups](https://github.com/fmhy/FMHY/wiki/Backups)
- Neither the site nor GitHub host any files
## 🗺️ Emoji Legend
* 🌐 - **3rd Party Indexes**
* ↪️ - **Storage Page Links**
* ⭐ - **Community Recommendations**
## 📝 Contribute
We invite you to contribute and help improve the wiki! 💙
Here are a few ways you can get involved:
* Anyone can suggest changes or corrections to the wiki. Please read our [contribution guide](https://fmhy.net/other/contributing) before trying to add or remove anything.
* If you're adding a new site, please [search](https://api.fmhy.net/single-page) (`Ctrl + F`) first to make sure we don't already have it.
* Approved changes will be applied to the [site](https://fmhy.net) and all [🔒 backups](https://github.com/fmhy/FMHY/wiki/Backups).
* You can send us stuff directly via [💬 Discord](https://github.com/fmhy/FMHY/wiki/FMHY-Discord).
* To help us find new sites, check out the lists of links in [site hunting](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/find-new-sites/).
## 🔔 Follow
<p>
<a href="https://github.com/fmhy/FMHY/wiki/FMHY-Discord"><img width="30px" src="./assets/discord.svg" alt="Discord"></a> <a href="https://github.com/fmhy"><img width="30px" src="./assets/github.svg" alt="GitHub"></a>
</p>
================================================
FILE: .github/assets/nginx.conf
================================================
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri.html $uri/ =404;
add_header X-Frame-Options "DENY";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "no-referrer-when-downgrade";
}
location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|woff2?)$ {
expires 30d;
add_header Cache-Control "public";
}
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log;
}
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 1000;
gzip_proxied any;
gzip_vary on;
================================================
FILE: .github/workflows/deploy-api.yml
================================================
name: Deploy API
# Only run this workflow when the API directory changes
on:
push:
paths:
- 'api/**'
branches:
- main
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
ci:
name: Build and Deploy
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 6
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm api:build
env:
WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
- name: Publish to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
vars: WEBHOOK_URL
env:
WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
================================================
FILE: .github/workflows/deploy-gh-pages.yml
================================================
name: Deploy to GitHub Pages
on:
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: pnpm/action-setup@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Install dependencies
run: pnpm install
- name: Build with VitePress
run: |
pnpm docs:build
touch docs/.vitepress/dist/.nojekyll
- name: Upload artifact
id: deployment
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
name: Deploy
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .gitignore
================================================
**/.vitepress/dist
**/.vitepress/cache
node_modules
*.log*
.nitro
.cache
.output
.env
dist
.eslintcache
docs/.vitepress/.temp
result
================================================
FILE: .gitpod.yml
================================================
tasks:
- init: pnpm install
command: pnpm run docs:dev
ports:
- port: 5173
onOpen: open-preview
================================================
FILE: .licenserc.json
================================================
{
"**/*.ts": [
"/**",
"* Copyright (c) 2025 taskylizard. Apache License 2.0.",
"*",
"* Licensed under the Apache License, Version 2.0 (the \"License\");",
"* you may not use this file except in compliance with the License.",
"* You may obtain a copy of the License at",
"*",
"* http://www.apache.org/licenses/LICENSE-2.0",
"*",
"* Unless required by applicable law or agreed to in writing, software",
"* distributed under the License is distributed on an \"AS IS\" BASIS,",
"* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.",
"* See the License for the specific language governing permissions and",
"* limitations under the License.",
"*/"
],
"**/*.css": [
"/**",
"* Copyright (c) 2025 taskylizard. Apache License 2.0.",
"* Licensed under the Apache License, Version 2.0 (the \"License\");",
"* you may not use this file except in compliance with the License.",
"* You may obtain a copy of the License at",
"* http://www.apache.org/licenses/LICENSE-2.0",
"*/"
],
"ignore": [
"node_modules",
"dist",
"docs/.vitepress/dist",
"docs/.vitepress/cache"
]
}
================================================
FILE: .mise.toml
================================================
# https://github.com/vitejs/vite/issues/17291
[tools]
node = "latest"
pnpm = "latest"
[tasks]
d = "nrr docs:dev --host"
b = "nrr docs:build"
s = "nrr docs:preview --host"
bb = "nrr docs:build && nrr docs:preview --host"
================================================
FILE: .npmrc
================================================
package-manager-strict=false
shell-emulator=true
auto-install-peers=false
================================================
FILE: .prettierignore
================================================
**/*.md
!docs/index.md
pnpm-lock.yaml
.turbo
.cache
================================================
FILE: .prettierrc.yaml
================================================
proseWrap: always
semi: false
singleQuote: true
printWidth: 80
trailingComma: none
htmlWhitespaceSensitivity: ignore
plugins:
- prettier-plugin-tailwindcss
- prettier-plugin-pkgsort
- '@ianvs/prettier-plugin-sort-imports'
importOrder:
[
'',
'<TYPES>^(node:)',
'<TYPES>',
'<TYPES>^[.]',
'<BUILTIN_MODULES>',
'<THIRD_PARTY_MODULES>',
'^[.]',
'^(?!.*[.]css$)[./].*$',
'.css$'
]
================================================
FILE: Dockerfile
================================================
FROM node:25.7-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN npm install -g pnpm@10.30.3
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --config.autoInstallPeers=false
COPY . .
RUN pnpm run docs:build
FROM nginx:alpine-slim
COPY --from=build /app/docs/.vitepress/dist /usr/share/nginx/html
COPY .github/assets/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
================================================
FILE: api/README.md
================================================
This is the API for the website and other related services.
Licensed under the Apache License v2.0, see [LICENSE](../docs/.vitepress/LICENSE) for more information.
================================================
FILE: api/middleware/cors.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { corsEventHandler } from 'nitro-cors'
export default corsEventHandler(
(_event) => {
/** no-op */
},
{
origin: '*',
methods: '*'
}
)
================================================
FILE: api/middleware/ratelimit.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default defineEventHandler(async (event) => {
const { cloudflare } = event.context
// FIXME: THIS IS NOT RECOMMENDED. BUT I WILL USE IT FOR NOW
// Not recommended: many users may share a single IP, especially on mobile networks
// or when using privacy-enabling proxies
const ipAddress = getHeader(event, 'CF-Connecting-IP') ?? ''
const { success } = await // KILL YOURSELF
(cloudflare.env as unknown as Env).RATE_LIMITER.limit({
key: ipAddress
})
if (!success) {
throw createError('Failure – global rate limit exceeded')
}
})
================================================
FILE: api/routes/feedback.post.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { fetcher } from 'itty-fetcher'
import {
FeedbackSchema,
getFeedbackOption
} from '../../docs/.vitepress/types/Feedback'
export default defineEventHandler(async (event) => {
const { message, page, type, heading } = await readValidatedBody(
event,
FeedbackSchema.parseAsync
)
const env = useRuntimeConfig(event)
const pageURL = `https://fmhy.net${page}`
const fields = [
{
name: 'Page',
value: `[${page}](${pageURL})`,
inline: true
},
{
name: 'Message',
value: message,
inline: false
}
]
if (heading) {
fields.unshift({
name: 'Section',
value: heading,
inline: true
})
}
// FIXME: somehow this is not working, but it worked before
// const path = 'feedback'
//
// const { success } = await env.MY_RATE_LIMITER.limit({ key: path })
// if (!success) {
// return new Response('429 Failure – global rate limit exceeded', {
// status: 429
// })
// }
await fetcher()
.post(env.WEBHOOK_URL, {
username: 'Feedback',
avatar_url:
'https://i.kym-cdn.com/entries/icons/facebook/000/043/403/cover3.jpg',
embeds: [
{
color: 3447003,
title: getFeedbackOption(type).label,
fields
}
]
})
.catch((error) => {
throw new Error(error)
})
return { status: 'ok' }
})
================================================
FILE: api/routes/index.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default eventHandler(() => {
return { nitro: 'works' }
})
================================================
FILE: api/routes/single-page.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const files = (
[
'privacy.md',
'ai.md',
'mobile.md',
'audio.md',
'beginners-guide.md',
'developer-tools.md',
'downloading.md',
'educational.md',
'file-tools.md',
'gaming-tools.md',
'gaming.md',
'image-tools.md',
'internet-tools.md',
'linux-macos.md',
'misc.md',
'non-english.md',
'reading.md',
'social-media-tools.md',
'storage.md',
'system-tools.md',
'text-tools.md',
'torrenting.md',
'unsafe.md',
'video-tools.md',
'video.md'
] as const
).map((file) => ({
name: file,
url: `https://raw.githubusercontent.com/fmhy/edit/main/docs/${file}`
}))
export default defineCachedEventHandler(
async (event) => {
let body = '<!-- This is autogenerated content, do not edit manually. -->\n'
const contents = await Promise.all(
files.map(async (file) => {
const content = await $fetch<string>(file.url)
return content
})
)
body += contents.join('\n\n')
appendResponseHeaders(event, {
'content-type': 'text/markdown;charset=utf-8',
'cache-control': 'public, max-age=7200'
})
return body
},
{
maxAge: 60 * 60,
name: 'single-page',
getKey: () => 'default' /* Can be extended in the future */
}
)
================================================
FILE: api/tsconfig.json
================================================
{
"extends": "../.nitro/types/tsconfig.json",
"compilerOptions": {
"types": ["@cloudflare/workers-types"]
}
}
================================================
FILE: api/worker-configuration.d.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Generated by Wrangler by running `wrangler types api/worker-configuration.d.ts`
interface Env {
STORAGE: KVNamespace
RATE_LIMITER: RateLimit
}
================================================
FILE: docker-compose.yaml
================================================
services:
docs:
networks: [fmhy]
build:
context: .
dockerfile: Dockerfile
image: fmhy-docs
container_name: docs
restart: unless-stopped
ports:
- '4173:80'
networks:
fmhy:
================================================
FILE: docs/.vitepress/LICENSE
================================================
Copyright (c) taskylizard. Apache License 2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: docs/.vitepress/README.md
================================================
This is the website source code to be used with [VitePress](https://vitepress.dev/).
Licensed under the Apache License v2.0, see [LICENSE](./LICENSE) for more information.
================================================
FILE: docs/.vitepress/config.mts
================================================
import { fileURLToPath } from 'node:url'
import consola from 'consola'
import UnoCSS from 'unocss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import OptimizeExclude from 'vite-plugin-optimize-exclude'
import Terminal from 'vite-plugin-terminal'
import { VitePWA } from 'vite-plugin-pwa'
import { defineConfig } from 'vitepress'
import {
commitRef,
feedback,
meta,
nav,
search,
sidebar,
socialLinks
} from './constants'
import { generateFeed, generateImages, generateMeta } from './hooks'
import { defs, emojiRender, movePlugin } from './markdown/emoji'
import { headersPlugin } from './markdown/headers'
import { toggleStarredPlugin } from './markdown/toggleStarred'
import { transformsPlugin } from './transformer'
import { replaceNoteLink } from './utils/markdown'
// @unocss-include
const baseUrl = process.env.GITHUB_ACTIONS ? '/edit' : '/'
export default defineConfig({
title: 'FMHY',
description: meta.description,
titleTemplate: ':title • freemediaheckyeah',
lang: 'en-US',
lastUpdated: false,
cleanUrls: true,
appearance: true,
base: baseUrl,
srcExclude: ['README.md', 'single-page'],
ignoreDeadLinks: true,
sitemap: {
hostname: meta.hostname
},
head: [
['meta', { name: 'theme-color', content: '#7bc5e4' }],
['meta', { name: 'og:type', content: 'website' }],
['meta', { name: 'og:locale', content: 'en' }],
['link', { rel: 'icon', href: '/test.png' }],
// PWA
['link', { rel: 'manifest', href: '/manifest.json' }],
['link', { rel: 'icon', href: '/pwa_icon.png', type: 'image/svg+xml' }],
['link', { rel: 'alternate icon', href: '/pwa_icon.png' }],
['link', { rel: 'mask-icon', href: '/pwa_icon.png', color: '#000000ff' }],
['meta', { name: 'keywords', content: meta.keywords.join(' ') }],
['link', { rel: 'apple-touch-icon', href: '/pwa_icon.png', sizes: '192x192' }],
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'default' }],
// Bing site verification
[
'meta',
{
name: 'msvalidate.01',
content: 'F3028112EF6F929B562F4B18E58E3691'
}
],
// Google site verification
[
'meta',
{
name: 'google-site-verification',
content: 'XCq-ZTw6VJPQ7gVNEOl8u0JRqfadK7WcsJ0H598Wv9E'
}
],
// Redirect to main site if embedded in iframe
[
'script',
{},
`
(function() {
if (window.self !== window.top) {
window.top.location = window.location.href;
}
})();
`
]
],
transformHead: async (context) => generateMeta(context, meta.hostname),
buildEnd: async (context) => {
generateImages(context)
.then(() => generateFeed(context))
.finally(() => consola.success('Success!'))
},
vite: {
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
ssr: {
noExternal: ['@fmhy/components']
},
resolve: {
alias: [
{
find: /^.*VPSwitchAppearance\.vue$/,
replacement: fileURLToPath(
new URL('./theme/components/ThemeDropdown.vue', import.meta.url)
)
},
{
find: /^.*VPLocalSearchBox\.vue$/,
replacement: fileURLToPath(
new URL('./theme/components/VPLocalSearchBox.vue', import.meta.url)
)
},
{
find: /^.*VPNav\.vue$/,
replacement: fileURLToPath(
new URL('./theme/components/VPNav.vue', import.meta.url)
)
}
]
},
optimizeDeps: { exclude: ['workbox-window'] },
plugins: [
OptimizeExclude(),
Terminal({
console: 'terminal',
output: ['console', 'terminal']
}),
UnoCSS({
configFile: fileURLToPath(
new URL('../../unocss.config.ts', import.meta.url)
)
}),
AutoImport({
dts: '../.cache/imports.d.ts',
imports: ['vue', 'vitepress'],
vueTemplate: true,
biomelintrc: {
enabled: true,
filepath: './.cache/imports.json'
}
}),
VitePWA({
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365 // 365 days
},
cacheableResponse: {
statuses: [0, 200]
}
}
}
]
},
manifest: {
name: 'FMHY - freemediaheckyeah',
short_name: 'FMHY',
description: 'The largest collection of free stuff on the internet!',
theme_color: '#000000ff',
background_color: '#000000ff',
display: 'standalone',
orientation: 'portrait',
scope: '/',
start_url: '/',
icons: [
{
src: '/fmhy.ico',
sizes: '16x16',
type: 'image/x-icon'
},
{
src: '/pwa_icon.png',
sizes: '192x192',
type: 'image/png',
purpose: 'any maskable'
},
{
src: '/pwa_icon.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any maskable'
}
]
}
}),
transformsPlugin(),
{
name: 'custom:adjust-order',
configResolved(c) {
movePlugin(
c.plugins as any,
'vitepress',
'before',
'unocss:transformers:pre'
)
movePlugin(
c.plugins as any,
'custom:transform-content',
'before',
'vitepress'
)
}
}
],
build: {
// Shut the fuck up
chunkSizeWarningLimit: Number.POSITIVE_INFINITY
}
},
markdown: {
emoji: { defs },
config(md) {
md.use(emojiRender)
md.use(toggleStarredPlugin)
meta.build.api && md.use(headersPlugin)
replaceNoteLink(md)
}
},
themeConfig: {
search,
footer: {
message: `${feedback} (rev: ${commitRef})`,
copyright:
`© ${new Date().getFullYear()}, <a href="https://i.ibb.co/VJQmQ9t/image.png">Estd 2018.</a>` +
`<br/> This site does not host any files.`
},
editLink: {
pattern: 'https://github.com/fmhy/edit/edit/main/docs/:path',
text: '📝 Edit this page'
},
outline: 'deep',
logo: {
src: '/fmhy.ico',
alt: 'FMHY Logo'
},
nav,
sidebar,
socialLinks
}
})
================================================
FILE: docs/.vitepress/constants.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { DefaultTheme } from 'vitepress'
import { excluded } from './shared'
import { transform, transformGuide } from './transformer'
// @unocss-include
export * from './shared'
export const search: DefaultTheme.Config['search'] = {
options: {
_render(src, env, md) {
// Check if current file should be excluded from search
const relativePath = env.relativePath || env.path || ''
const shouldExclude = excluded.some(excludedFile =>
relativePath.includes(excludedFile) ||
relativePath.endsWith(excludedFile)
)
// Return empty content for excluded files so they don't appear in search
if (shouldExclude) {
return ''
}
let contents = src
// I do this as env.frontmatter is not available until I call `md.render`
if (contents.includes('Beginners Guide'))
contents = transformGuide(contents)
contents = transform(contents)
const html = md.render(contents, env)
return html
},
miniSearch: {
options: {
tokenize: (text) => text.replace(/[\u2060\u200B]/g, '').split(/[\n\r #%*,=/:;?[\]{}()&]+/u), // simplified charset: removed [-_.@] and non-english chars (diacritics etc.)
processTerm: (term, fieldName) => {
// biome-ignore lint/style/noParameterAssign: h
term = term
.trim()
.toLowerCase()
.replace(/^\.+/, '')
.replace(/\.+$/, '')
const stopWords = [
'frontmatter',
'$frontmatter.synopsis',
'and',
'about',
'but',
'now',
'the',
'with',
'you'
]
if (term.length < 2 || stopWords.includes(term)) return false
if (fieldName === 'text') {
const parts = term.split('.')
if (parts.length > 1) {
const newTerms = [term, ...parts]
.filter((t) => t.length >= 2)
.filter((t) => !stopWords.includes(t))
return newTerms
}
}
return term
}
},
searchOptions: {
combineWith: 'AND',
fuzzy: false,
// @ts-ignore
boostDocument: (documentId, term, storedFields: Record) => {
const titles = (storedFields?.titles as string[])
.filter((t) => Boolean(t))
.map((t) => t.toLowerCase())
// Downrank posts
if (documentId.match(/\/posts/)) return -5
// Downrank /other
if (documentId.match(/\/other/)) return -5
// Uprate if term appears in titles. Add bonus for higher levels (i.e. lower index)
const titleIndex =
titles
.map((t, i) => (t?.includes(term) ? i : -1))
.find((i) => i >= 0) ?? -1
if (titleIndex >= 0) return 10000 - titleIndex
return 1
}
}
},
detailedView: true
},
provider: 'local'
}
================================================
FILE: docs/.vitepress/hooks/Template.vue
================================================
<script setup lang="ts">
defineProps<{ title: string; description?: string; image?: string }>()
</script>
<template>
<span
tw="w-full h-full bg-black flex flex-col"
:style="{
backgroundImage: `url(${image})`
}"
>
<span
tw="p-10 w-full min-h-0 grow flex flex-col items-center justify-between"
>
<span tw="w-full flex justify-between items-center text-5xl font-medium">
<span tw="flex items-center">
<span tw="text-zinc-100 ml-2 mt-1 font-semibold">
freemediaheckyeah
</span>
</span>
</span>
<span tw="w-full pr-56 flex flex-col items-start justify-end">
<span style="color: #f3f4f6" tw="text-6xl font-bold" v-html="title" />
<span style="color: #c0caf5" tw="mt-2 text-4xl" v-html="description" />
</span>
</span>
</span>
</template>
================================================
FILE: docs/.vitepress/hooks/index.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Barrel generated using @taskylizard/tasker.
*/
export * from './meta'
export * from './opengraph'
export * from './rss'
export * from './satoriConfig'
================================================
FILE: docs/.vitepress/hooks/meta.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { HeadConfig, TransformContext } from 'vitepress'
export function generateMeta(context: TransformContext, hostname: string) {
const head: HeadConfig[] = []
const { pageData } = context
if (pageData.isNotFound) return head
if (Object.keys(pageData.frontmatter).length === 0) return head
const url = `${hostname}/${pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2')}`
head.push(
['link', { rel: 'canonical', href: url }],
['meta', { property: 'og:url', content: url }],
['meta', { name: 'twitter:url', content: url }],
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
['meta', { property: 'og:title', content: pageData.frontmatter.title }],
['meta', { name: 'twitter:title', content: pageData.frontmatter.title }]
)
if (pageData.frontmatter.description) {
head.push(
[
'meta',
{
property: 'og:description',
content: pageData.frontmatter.description
}
],
[
'meta',
{
name: 'twitter:description',
content: pageData.frontmatter.description
}
]
)
}
if (pageData.frontmatter.image) {
head.push([
'meta',
{
property: 'og:image',
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, '')}`
}
])
head.push([
'meta',
{
name: 'twitter:image',
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, '')}`
}
])
} else {
const url = pageData.filePath.replace('index.md', '').replace('.md', '')
const imageUrl = `${url}/__og_image__/og.webp`
.replaceAll('//', '/')
.replace(/^\//, '')
head.push(
['meta', { property: 'og:image', content: `${hostname}/${imageUrl}` }],
['meta', { property: 'og:image:width', content: '1200' }],
['meta', { property: 'og:image:height', content: '630' }],
['meta', { property: 'og:image:type', content: 'image/png' }],
[
'meta',
{ property: 'og:image:alt', content: pageData.frontmatter.title }
],
['meta', { name: 'twitter:image', content: `${hostname}/${imageUrl}` }],
['meta', { name: 'twitter:image:width', content: '1200' }],
['meta', { name: 'twitter:image:height', content: '630' }],
[
'meta',
{ name: 'twitter:image:alt', content: pageData.frontmatter.title }
]
)
}
if (pageData.frontmatter.tag) {
head.push([
'meta',
{ property: 'article:tag', content: pageData.frontmatter.tag }
])
}
if (pageData.frontmatter.date) {
head.push([
'meta',
{
property: 'article:published_time',
content: pageData.frontmatter.date
}
])
}
if (pageData.lastUpdated && pageData.frontmatter.lastUpdated !== false) {
head.push([
'meta',
{
property: 'article:modified_time',
content: new Date(pageData.lastUpdated).toISOString()
}
])
}
return head
}
================================================
FILE: docs/.vitepress/hooks/opengraph.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ContentData, SiteConfig } from 'vitepress'
import type { SatoriOptions } from 'x-satori/vue'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { renderAsync } from '@resvg/resvg-js'
import sharp from 'sharp'
import consola from 'consola'
import { createContentLoader } from 'vitepress'
import { satoriVue } from 'x-satori/vue'
import { headers } from '../transformer/constants'
const __dirname = dirname(fileURLToPath(import.meta.url))
const __fonts = resolve(__dirname, '../fonts')
export async function generateImages(config: SiteConfig) {
const pages = await createContentLoader('**/*.md', { excerpt: true }).load()
const template = await readFile(resolve(__dirname, './Template.vue'), 'utf-8')
const fonts: SatoriOptions['fonts'] = [
{
name: 'Inter',
data: await readFile(resolve(__fonts, 'Inter-Regular.otf')),
weight: 400,
style: 'normal'
},
{
name: 'Inter',
data: await readFile(resolve(__fonts, 'Inter-Medium.otf')),
weight: 500,
style: 'normal'
},
{
name: 'Inter',
data: await readFile(resolve(__fonts, 'Inter-SemiBold.otf')),
weight: 600,
style: 'normal'
},
{
name: 'Inter',
data: await readFile(resolve(__fonts, 'Inter-Bold.otf')),
weight: 700,
style: 'normal'
}
]
for (const page of pages) {
await generateImage({
page,
template,
outDir: config.outDir,
fonts
})
}
return consola.info('Generated opengraph images.')
}
interface GenerateImagesOptions {
page: ContentData
template: string
outDir: string
fonts: SatoriOptions['fonts']
}
async function generateImage({
page,
template,
outDir,
fonts
}: GenerateImagesOptions) {
const { frontmatter, url } = page
const _page = getPage(url)
const title =
frontmatter.layout === 'home'
? (frontmatter.hero.name ?? frontmatter.title)
: frontmatter.title
? frontmatter.title
: _page?.title
const description =
frontmatter.layout === 'home'
? (frontmatter.hero.tagline ?? frontmatter.description)
: frontmatter.description
? frontmatter.description
: _page?.description
// consola.info(url, title, description)
const options: SatoriOptions = {
width: 1200,
height: 630,
fonts,
props: {
title,
description,
image: 'https://i.fmhy.net/og-base.jpg'
}
}
const svg = await satoriVue(options, template)
const render = await renderAsync(svg)
const compressed = await sharp(render.asPng())
.webp({ quality: 75 })
.toBuffer()
const outputFolder = resolve(outDir, url.slice(1), '__og_image__')
const outputFile = resolve(outputFolder, 'og.webp')
await mkdir(outputFolder, { recursive: true })
await writeFile(outputFile, compressed)
}
function getPage(page: string) {
// Get the page name
const pageName = `${page}.md`.slice(1).split('/').at(-1)
// Find the header
// TODO: This is a hacky way to find the header
const header = Object.entries(headers).find(([key]) => key === pageName)
if (!header) return
const { title, description } = header[1]
return {
title,
description
}
}
================================================
FILE: docs/.vitepress/hooks/rss.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ContentData, SiteConfig } from 'vitepress'
import { writeFileSync } from 'node:fs'
import path from 'node:path'
import consola from 'consola'
import { Feed } from 'feed'
import { createContentLoader } from 'vitepress'
import { meta } from '../constants'
export async function generateFeed(config: SiteConfig): Promise<void> {
const feed: Feed = new Feed({
id: meta.hostname,
link: meta.hostname,
title: 'FMHY blog',
description: meta.description,
language: 'en-US',
image: 'https://github.com/fmhy.png',
favicon: `${meta.hostname}/favicon.ico`,
copyright: 'Copyright (c) 2023-present FMHY'
})
const posts: ContentData[] = await createContentLoader('posts/*.md', {
excerpt: true,
render: true,
transform: (rawData) => {
return rawData.sort((a, b) => {
return (
Number(new Date(b.frontmatter.date)) -
Number(new Date(a.frontmatter.date))
)
})
}
}).load()
for (const { url, frontmatter, html } of posts) {
feed.addItem({
title: frontmatter.title as string,
id: `${meta.hostname}${url.replace(/\/\d+\./, '/')}`,
link: `${meta.hostname}${url.replace(/\/\d+\./, '/')}`,
date: frontmatter.date,
content: html?.replaceAll('​', '')
})
}
writeFileSync(path.join(config.outDir, 'feed.rss'), feed.rss2())
return consola.info('Generated rss feed.')
}
================================================
FILE: docs/.vitepress/hooks/satoriConfig.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { SatoriOptions } from 'x-satori/vue'
import { readFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { defineSatoriConfig } from 'x-satori/vue'
const __dirname = dirname(fileURLToPath(import.meta.url))
const __fonts = resolve(__dirname, '../fonts')
const fonts: SatoriOptions['fonts'] = [
{
name: 'Inter',
data: await readFile(resolve(__fonts, 'Inter-Regular.otf')),
weight: 400,
style: 'normal'
},
{
name: 'Inter',
data: await readFile(resolve(__fonts, 'Inter-Medium.otf')),
weight: 500,
style: 'normal'
},
{
name: 'Inter',
data: await readFile(resolve(__fonts, 'Inter-SemiBold.otf')),
weight: 600,
style: 'normal'
},
{
name: 'Inter',
data: await readFile(resolve(__fonts, 'Inter-Bold.otf')),
weight: 700,
style: 'normal'
}
]
export default defineSatoriConfig({
width: 1800,
height: 900,
fonts,
props: {
title: 'Title',
description:
'Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.',
dir: '/j',
// I almost killed myself for this shit
image: 'https://i.fmhy.net/og-base.jpg'
}
})
================================================
FILE: docs/.vitepress/markdown/base64.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { MarkdownRenderer } from 'vitepress'
// FIXME: tasky: possibly write less horror jank?
export function base64DecodePlugin(md: MarkdownRenderer) {
const decode = (str: string): string =>
Buffer.from(str, 'base64').toString('binary')
// Save the original rule for backticks
const defaultRender =
md.renderer.rules.code_inline ||
function (tokens, idx, options, _env, self) {
return self.renderToken(tokens, idx, options)
}
md.renderer.rules.code_inline = function (tokens, idx, options, env, self) {
if (
!env.frontmatter.title ||
(env.frontmatter.title && env.frontmatter.title !== 'base64')
) {
return defaultRender(tokens, idx, options, env, self)
}
const token = tokens[idx]
const content = token.content
return `<button class='base64' onclick="(function(btn){ const codeEl = btn.querySelector('code'); navigator.clipboard.writeText('${decode(
content
)}').then(() => { const originalText = codeEl.textContent; codeEl.textContent = 'Copied'; setTimeout(() => codeEl.textContent = originalText, 3000); }).catch(console.error); })(this)"><code>${content}</code></button>`
}
}
================================================
FILE: docs/.vitepress/markdown/emoji.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { MarkdownRenderer } from 'vitepress'
import { icons as twemoji } from '@iconify-json/twemoji'
export const defs = {
...Object.fromEntries(
Object.entries(twemoji.icons).map(([key]) => {
return [key, '']
})
)
}
export function emojiRender(md: MarkdownRenderer) {
md.renderer.rules.emoji = (tokens, idx) => {
if (tokens[idx].markup.startsWith('star')) {
return `<span class="i-twemoji-${tokens[idx].markup} starred"></span>`
}
return `<span class="i-twemoji-${tokens[idx].markup}"></span>`
}
}
export function movePlugin(
plugins: { name: string }[],
pluginAName: string,
order: 'before' | 'after',
pluginBName: string
) {
const pluginBIndex = plugins.findIndex((p) => p.name === pluginBName)
if (pluginBIndex === -1) return
const pluginAIndex = plugins.findIndex((p) => p.name === pluginAName)
if (pluginAIndex === -1) return
if (order === 'before' && pluginAIndex > pluginBIndex) {
const pluginA = plugins.splice(pluginAIndex, 1)[0]
plugins.splice(pluginBIndex, 0, pluginA)
}
if (order === 'after' && pluginAIndex < pluginBIndex) {
const pluginA = plugins.splice(pluginAIndex, 1)[0]
plugins.splice(pluginBIndex, 0, pluginA)
}
}
================================================
FILE: docs/.vitepress/markdown/headers.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Copyright (c) 2024 taskylizard
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { MarkdownRenderer } from 'vitepress'
import { headers } from '../transformer/constants'
const titles = Object.keys(headers).map((key) => headers[key].title)
export const headersPlugin = (md: MarkdownRenderer) => {
// Add the Feedback component in the heading, before the link.
//
// Adding it after the link is closed prevents vitepress from properly
// indexing the file's content.
md.renderer.rules.heading_open = (tokens, idx, options, env, self) => {
const result = self.renderToken(tokens, idx, options)
const idxClose =
idx +
tokens.slice(idx).findIndex((token) => token.type === 'heading_close')
if (idxClose <= idx) return result
const level = tokens[idx].tag.slice(1)
if (!titles.includes(env.frontmatter.title) || level !== '2') return result
// Find the token for the link.
//
// The token after `heading_open` contains the link as a child token.
const children = tokens[idx + 1].children || []
const linkOpenToken = children.findLast((c) => c.type === 'link_open')
if (!linkOpenToken) return result
const heading = tokens[idxClose - 1]
linkOpenToken.meta = linkOpenToken.meta || {}
linkOpenToken.meta.feedback = {
heading: heading.content
}
return result
}
const defaultRender = md.renderer.rules.link_open
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
const result = defaultRender!!!!!!!!!!(tokens, idx, options, env, self)
const meta = tokens[idx].meta
if (!meta || !meta.feedback) return result
const heading = meta.feedback.heading || ''
if (!heading) return result
return `<Feedback heading="${heading}" />${result}`
}
}
================================================
FILE: docs/.vitepress/markdown/toggleStarred.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { MarkdownRenderer } from 'vitepress'
const excluded = ['Beginners Guide']
const starredMarkers = [':star:', ':glowing-star:', '⭐', '🌟']
const indexMarkers = ['🌐', ':globe_with_meridians:', ':globe-with-meridians:']
export function toggleStarredPlugin(md: MarkdownRenderer) {
md.renderer.rules.list_item_open = (tokens, index, options, env, self) => {
const contentToken = tokens[index + 2]
if (!contentToken) return self.renderToken(tokens, index, options)
const content = contentToken.content
const isStarred =
!excluded.includes(env.frontmatter.title) &&
starredMarkers.some((marker) => content.includes(marker))
const isIndex = indexMarkers.some((marker) => content.includes(marker))
if (!isStarred && !isIndex) return self.renderToken(tokens, index, options)
const classes = []
if (isStarred) classes.push('starred')
if (isIndex) classes.push('index')
return `<li class="${classes.join(' ')}">`
}
}
================================================
FILE: docs/.vitepress/notes/1337x-ranks.md
================================================
#### 1337x Ranks
* ⬛ Black - Administrators
* 🟩 Green - Moderators
* 🟦 Blue - VIP Uploaders (Very Trusted)
* 🟨 Yellow - Uploaders (Trusted)
* 🟥 Red - Trial Uploaders
* ⬜ Grey - Users
================================================
FILE: docs/.vitepress/notes/CodeRabbit.md
================================================
Note you can get free pro by installing code rabbit on a public github/gitlab repo, as they offer a free open source plan with no popularity requirements or approval/forms.
================================================
FILE: docs/.vitepress/notes/advanced-logic-calculators.md
================================================
#### Advanced Logic Calculators
* Analytic tableaux generator: https://www.umsu.de/trees/
* Natural deduction proof checker: https://proofs.openlogicproject.org/
* Propositional logic calculator (finds models): https://www.inf.unibz.it/~franconi/teaching/propcalc/
* A tutorial on sequent calculus: http://logitext.mit.edu/tutorial
* Modal logic playground (for constructing models): https://rkirsling.github.io/modallogic/
================================================
FILE: docs/.vitepress/notes/affine-note.md
================================================
#### AFFiNE Note
Downloads for Windows, Mac, Linux are available on their [GitHub](https://github.com/toeverything/AFFiNE)
================================================
FILE: docs/.vitepress/notes/alt-twitch-player-extensions.md
================================================
#### Alternative Twitch Player Extensions
* https://addons.mozilla.org/en-US/firefox/addon/twitch_5/
* https://chrome.google.com/webstore/detail/alternate-player-for-twit/bhplkbgoehhhddaoolmakpocnenplmhf
================================================
FILE: docs/.vitepress/notes/alt-warp-clients.md
================================================
#### Alternative Warp Clients
If you can't connect, try `Scanner Settings` -> `Endpoint` -> `Suggested` -> then try different IP's to find one that works
* https://github.com/bepass-org/oblivion-desktop
* https://github.com/bepass-org/oblivion
================================================
FILE: docs/.vitepress/notes/android-spotify-note.md
================================================
#### Android Spotify Note
None of Spotify apks (for rooted and non-rooted users) works for now due to server side restriction.
================================================
FILE: docs/.vitepress/notes/apkmirror-extensions.md
================================================
#### APKMirror Extensions
* https://addons.mozilla.org/en-US/firefox/addon/toolbox-google-play-store/
* https://chrome.google.com/webstore/detail/toolbox-for-google-play-s/fepaalfjfchbdianlgginbmpeeacahoo
* https://addons.opera.com/en/extensions/details/toolbox-for-google-play-storetm/
================================================
FILE: docs/.vitepress/notes/audiobookbay-warning.md
================================================
#### AudiobookBay Warning
Avoid fake download links, use [Torrents / Magnets](https://i.ibb.co/8sV2061/0fa8159b11bb.png), or paste info hash into torrent client.
================================================
FILE: docs/.vitepress/notes/aurora-note.md
================================================
#### Aurora Note
Keep in mind that some apps will not work unless you installed them from the Google Play Store. This is usually true for things like banking, and other institutional apps.
================================================
FILE: docs/.vitepress/notes/better-reasoning.md
================================================
#### Better Reasoning
To get better reasoning, switch to "Think Deeper" mode.
================================================
FILE: docs/.vitepress/notes/bookmarkeddit.md
================================================
#### Bookmarkeddit
This also extends the amount of saved posts you can view (Reddit caps at 1000 by default).
================================================
FILE: docs/.vitepress/notes/buster-note.md
================================================
#### Buster Note
The client app simulates user interactions which greatly improves the success rate of buster. You can download the app through the extensions option page, or from the link below:
https://github.com/dessant/buster-client
The app is available for Windows, Linux, and macOS.
================================================
FILE: docs/.vitepress/notes/buzzheavier-warning.md
================================================
#### Buzzheavier Warning
Make sure you have an [adblocker](https://fmhy.net/adblockvpnguide#adblocking) when using Buzzheavier as there are hidden ads on download pages with malicious content. The download button should automatically start a download in your browser, NOT redirect you to another page.
================================================
FILE: docs/.vitepress/notes/bypass-freedlink.md
================================================
#### Bypass FREEdlink
You still need to bypass Cloudflare captcha by yourself. This only bypasses timer on single downloads. You may still need to wait normal time to download another file which is enforced from server-side.
================================================
FILE: docs/.vitepress/notes/captcha-4pda.md
================================================
#### Captcha 4PDA
Use Google Gemini to translate the captcha.
================================================
FILE: docs/.vitepress/notes/chatgpt-limits.md
================================================
#### ChatGPT Limits
GPT-5.2 Instant (no reasoning; 16K context) / 10 messages every 5 hours, then GPT-5-mini.
================================================
FILE: docs/.vitepress/notes/clipboard2file-addons.md
================================================
#### Clipboard2File Addons
* https://github.com/vord1080/clipboard2file/
* https://github.com/daijro/Clipboard2File-Chrome
================================================
FILE: docs/.vitepress/notes/cofi-note.md
================================================
#### Cofi Note
Useful if you're a coffee enthusiast. The methods are created by James Hoffmann, he's a world champion barista and popular YouTuber.
================================================
FILE: docs/.vitepress/notes/crystaldiskinfo.md
================================================
#### CrystalDiskInfo
Avoid versions labeled "Ads".
================================================
FILE: docs/.vitepress/notes/csrin-search.md
================================================
#### CS.RIN Search
If your initial search doesn't work, trying searching the same term again within the "search these results" engine on the results screen.
<img width="1307" height="97" alt="image" src="https://github.com/user-attachments/assets/b2f149b9-8a9a-4250-8754-e63f50b82c59" />
================================================
FILE: docs/.vitepress/notes/cute-save-button-icon.md
================================================
#### Changing the Cute Save Button Icon
You can change the icon of the save button in the extension's settings. The setting is labeled "Your custom cute icon:" You can find standard image download icons to use instead here: https://rentry.co/image-download-icons.
================================================
FILE: docs/.vitepress/notes/dodi-warning.md
================================================
#### DODI Warning
It is highly recommended to stick to DODI's 1337x page or main website, as sites they linked to have malicious fake download buttons, and shouldn't be used without an [adblocker](https://fmhy.net/privacy#adblocking).
================================================
FILE: docs/.vitepress/notes/dolby-access-atmos-note.md
================================================
#### Dolby Access / Atmos Note
Many headsets come with Dolby Access for free without letting users know. You can check if you're licensed by opening Dolby Access, going to settings, and looking in the [bottom right corner](https://i.imgur.com/9vJA6CL.png). It's much better than things like iCue or similar apps.
================================================
FILE: docs/.vitepress/notes/driver-note.md
================================================
#### Driver Note
Only install the drivers you actually need. Don't install new drivers all at once, as this could lead to things breaking, especially system audio.
================================================
FILE: docs/.vitepress/notes/eaglercraft-note.md
================================================
#### Eaglercraft Note
Play on Chromium-based browsers for the best performance.
================================================
FILE: docs/.vitepress/notes/eruda.md
================================================
#### Eruda
Eruda Console for mobile browsers [bookmarklet](https://wikipedia.org/wiki/Bookmarklet):
```
javascript:(function () { var script = document.createElement('script'); script.src="//cdn.jsdelivr.net/npm/eruda"; document.body.appendChild(script); script.onload = function () { eruda.init() } })();
```
================================================
FILE: docs/.vitepress/notes/filebin-warning.md
================================================
#### Filebin Warning
Anyone with a link to a "bin" has full access to it. They can add new files, delete existing files, etc.
================================================
FILE: docs/.vitepress/notes/filelu-warning.md
================================================
#### Filelu Warning
According to their FAQ, you must login to your account at least once every 180 days to prevent your account and it's files being deleted.
================================================
FILE: docs/.vitepress/notes/filezilla-warning.md
================================================
#### FileZilla Warning
The version of FileZilla on FileZilla's front page has adware, but the non-adware version is the only link on FMHY. You can also find the non-adware version by pressing download on the FileZilla front page, then clicking "Show additional download options" under "More download options" at the download page.
================================================
FILE: docs/.vitepress/notes/flicker-proxy.md
================================================
#### Flicker Proxy
Note that the proxy may be slower, but it can be used in cases where the site or TMDb is blocked.
================================================
FILE: docs/.vitepress/notes/fluxy-repacks.md
================================================
#### Fluxy Repacks
Note that although it has repacks in the name, its not actually a repack site.
================================================
FILE: docs/.vitepress/notes/forest-extensions.md
================================================
#### Forest Extensions
* https://addons.mozilla.org/en-US/firefox/addon/forest-stay-focused-be-present/
* https://chrome.google.com/webstore/detail/forest-stay-focused-be-pr/kjacjjdnoddnpbbcjilcajfhhbdhkpgk
================================================
FILE: docs/.vitepress/notes/foxit-warning.md
================================================
#### Foxit Warning
The installer tries to install McAfee WebAdvisor + PhantomPDF Business. They can be skipped by clicking "decline" both times.
================================================
FILE: docs/.vitepress/notes/freegogpcgames-note.md
================================================
#### FreeGOGPCGames Note
The file checksum may not match with the original GOG installer. This is because many titles on the site are the older versions of the installers, the digital signature on the old installers are signed by *GOG Limited*, which is the old company's name before it was merged with *GOG Sp. z o.o* and all digital file signatures were updated to reflect this name change. The hash does not match the gog-games database because the digital file signatures differ on the installer. Installing either version will produce identical sets of files since the game version remains unchanged.
- [/u/AtariRiot66](https://www.reddit.com/r/PiratedGames/comments/1br4m7o/comment/kx8hzz3/)
================================================
FILE: docs/.vitepress/notes/gemai.md
================================================
#### Nano Banana Pro Note
Nano Banana Pro is a bit glitchy as of now, but it is being worked on according to their Discord staff.
================================================
FILE: docs/.vitepress/notes/general-tweak-warning.md
================================================
#### General Tweak Warning
Make sure you know what you're doing before you apply these tweaks. Always research first, never just "Apply All" without knowing what what will happen.
================================================
FILE: docs/.vitepress/notes/glitchwave-note.md
================================================
#### Glitchwave Note
For charts you can specify months and days using URLs like the following examples:
January 2006:
`https://glitchwave.com/charts/popular/game/2006.01/excl:ratings/`
Jan - Feb 2018:
`https://glitchwave.com/charts/popular/game/2018.01-2018.02/excl:ratings/`
================================================
FILE: docs/.vitepress/notes/google-song-identification.md
================================================
#### Google Song Identification
Google and YouTube Music mobile apps have song identification button next to the search box.
================================================
FILE: docs/.vitepress/notes/google-translate-note.md
================================================
#### Google Translate Note
Google Translate can be used as a web proxy. Simply paste your URL into the translate field and then click on the result and view the page in the original language. This way you can navigate any web-page via google.com. Google is almost never blocked so this trick works on most occasions.
================================================
FILE: docs/.vitepress/notes/hdo-box-note.md
================================================
#### HDO Box Note
To use the app, HDO Box may ask you to install a third-party video player which contains ads. To block the ads, use the tools linked in [DNS Adblocking](https://fmhy.net/privacy#dns-adblocking).
================================================
FILE: docs/.vitepress/notes/hugging-face-warning.md
================================================
#### Hugging Face Warning
HuggingFace uses a system called ZeroGPU to manage access to their high-end GPUs. To make sure that their GPUs don't get fully used up, there are limits on how long you can use the GPU on Spaces like this one.
The rate limit is 120 seconds per day for non-logged in users. You can get around the limit by changing your IP address using a [proxy](https://fmhy.net/privacy#proxy) or [VPN](https://fmhy.net/privacy#vpn) while logged out. If you sign up for a free account, you get a much higher 300 second daily limit, but changing your IP address won't reset it.
================================================
FILE: docs/.vitepress/notes/instaeclipse-note.md
================================================
#### InstaEclipse Note
Use [this guide](https://wispydocs.pages.dev/revanced-morphe-obtainium/#advanced) to build clean APKs, or use AntiSplit M with ReVanced manager.
================================================
FILE: docs/.vitepress/notes/irc-highway-note.md
================================================
#### IRC Highway Note
To request a book run: @request [author] [title] - Requests without both [author] and [title] are deleted.
To view request status and rules run: @request-list
================================================
FILE: docs/.vitepress/notes/jdownloader-warning.md
================================================
#### JDownloader Warning
The version of JDownloader linked on JDownloader's front page has adware. The version linked on FMHY, however, does not contain any adware.
================================================
FILE: docs/.vitepress/notes/limit-bypass-note.md
================================================
#### Limit Bypass Note
- SparseBox: iOS 17.0 - 18.1 Beta 4 (not including 17.7.1, 17.7.2)
- Live container: iOS 16+
================================================
FILE: docs/.vitepress/notes/liteapk-modyolo-note.md
================================================
#### LiteAPK + Modyolo Note
The site is safe, but they are known for mislabeling things like RockMods releases as their own, and mislabeling versions to make it look like they have newer things than they really do.
================================================
FILE: docs/.vitepress/notes/malware-removal-forums.md
================================================
#### Malware Removal Forums
Note that many of these will suggest removing pirated software, but if you got everything from trusted sources, there is no real need to do that.
================================================
FILE: docs/.vitepress/notes/megabasterd-note.md
================================================
#### Megabasterd Note
Free proxies work but they are very hit and miss.
================================================
FILE: docs/.vitepress/notes/mobilism-ranks.md
================================================
#### Mobilism Ranks
See what the different Mobilism Ranks mean [here](https://i.imgur.com/WpShSFp.png).
================================================
FILE: docs/.vitepress/notes/modelscope.md
================================================
### ModelScope Note
This site uses credits (called *magicubes*) to generate images and videos, you get 100 daily. It costs 2 magicubes per image for Qwen, 1 for Z-Image, and 28 for Wan 2.2 14b I2V. You can link an Alibaba Cloud account for free if you ignore the final part of account setup where it asks for payment info and link the account anyways, which gets you 50 extra magicubes daily.
================================================
FILE: docs/.vitepress/notes/mori-note.md
================================================
#### Māori Note
Māori is the indigenous language of mainland New Zealand. Due to the [Native Schools Act](https://en.wikipedia.org/wiki/M%C4%81ori_language#Suppression_and_decline) in 1867, children were forbidden to speak it in the classroom, under penalty of corporal punishment, which led to a rapid decline of speakers. There are now [revitalization efforts](https://en.wikipedia.org/wiki/M%C4%81ori_language_revival) (such as Tōku Reo) attempting to promote and reinforce its use.
================================================
FILE: docs/.vitepress/notes/movie-web-sources.md
================================================
#### Adding sources to P-Stream (and all movie-web forks)
You can [enable an extension](https://pstream.net/onboarding/) / [script](https://github.com/p-stream/userscript) that will add more sources, but it needs to connect to all sites to function. The extension is safe, and many people use it, the permissions are just needed in order for the extension to work correctly.
Note that you can run it in a new browser or fresh browser profile if you don't want to use your main browser.
Documentation and self-hosting guides can be found here: https://p-stream.github.io/docs/
================================================
FILE: docs/.vitepress/notes/movieparadise-code.md
================================================
#### MovieParadise Code
In order to unlock the better host (1fichier) you need a signup code. This is important as without it the site will be only Rapidgator links, which are very slow. You can get a code from the link below, or from the pinned messages in our `#free-stuff` [Discord channel](https://github.com/fmhy/FMHY/wiki/FMHY-Discord).
**[Click Here To Get Code](https://rentry.org/he8fhzku)**
================================================
FILE: docs/.vitepress/notes/mp-opensubs.md
================================================
### OpenSubtitles with MPC-HC
You can create an OpenSubtitles account and link it in MPC-HC to bypass quota limits.
You can do this via a panel in MPC-HC located at: `Options` -> `Subtitles` -> `Misc.` > Right-click on `OpenSubtitles.com` -> `Setup` -> Fill in username and password.
================================================
FILE: docs/.vitepress/notes/mvsep-note.md
================================================
#### MVSEP Note
Register to get .wav and .flac output, and lower queue times.
================================================
FILE: docs/.vitepress/notes/oneclick-note.md
================================================
#### OneClick Note
Main features include:
- Download links straight to Google Drive.
- Torrent to Google Drive.
- Google Drive Download Manager (similar to pyLoad).
- Spotify Downloader.
- Jellyfin Support.
- RClone + WebUI.
- And much more.
================================================
FILE: docs/.vitepress/notes/openasar.md
================================================
#### OpenAsar Note
The Vencord installer has an option to install OpenAsar, but you may need to click the install button twice (only once more after clicking "Accept").
================================================
FILE: docs/.vitepress/notes/openrgb-beta.md
================================================
#### OpenRGB Beta
The latest stable release (0.9) is from July, 2023. It is lacking support for many devices, so you may want to use a newer experimental release instead.
* Supported devices for the latest stable release (0.9): https://openrgb.org/devices_0.9.html
* Supported devices for the latest experimental release: https://openrgb.org/devices.html
To use an experimental release go to https://gitlab.com/CalcProgrammer1/OpenRGB and in the left sidebar go to `Build` -> `Pipelines`, then click the download icon for a pipeline that has three green checkmarks, and pick the appropriate version for your computer.
================================================
FILE: docs/.vitepress/notes/pollinations-limits.md
================================================
#### Pollinations Limits
For chat.pollinations.ai (and the underlying API), the rate limits depend on how you're using it:
**Anonymous / Free Tier (No Login)**
- **Text/Chat**: ~1 request every **3 seconds** (per IP).
- **Images**: ~1 request every **5 seconds** (per IP).
**Logged In (Pollen System)**
- Users get a **daily free Pollen allowance** based on their tier.
- **Publishable Keys (`pk_`)**: Rate limited to prevent abuse (e.g., ~1 pollen/hour per IP).
- **Secret Keys (`sk_`)**: **No rate limits** (requests run as fast as you can pay for them with Pollen).
If you're hitting limits on the chat site:
1. Slow down slightly (wait 3-5s between messages).
2. **Log in** at [enter.pollinations.ai](https://enter.pollinations.ai) to use your daily free credits.
3. If you need massive throughput, use an API key (`sk_`) with purchased credits.
================================================
FILE: docs/.vitepress/notes/printeditwe-addons.md
================================================
#### PrintEditWe Addons
* https://addons.mozilla.org/en-US/firefox/addon/print-edit-we/
* https://chrome.google.com/webstore/detail/print-edit-we/olnblpmehglpcallpnbgmikjblmkopia
================================================
FILE: docs/.vitepress/notes/proton-torrenting.md
================================================
#### Proton Torrenting
Torrenting on Proton VPN's free plan is only possible when using an OpenVPN configuration / [Guide](https://protonvpn.com/support/vpn-config-download). Note that they do expire, so you'll have to make new ones occasionally.
OpenVPN login credentials are located [here](https://account.protonvpn.com/account-password).
================================================
FILE: docs/.vitepress/notes/reaper-note.md
================================================
#### Reaper Note
Reaper asks you to buy it after 60 days, but you can just close the popup and keep using it for free.
================================================
FILE: docs/.vitepress/notes/redditfilter-note.md
================================================
#### RedditFilter Note
Go to `Settings` -> `Feed Filter` and untoggle `Promoted` to not see ads. You can also untoggle `Recommended` to hide AI suggestions.
================================================
FILE: docs/.vitepress/notes/rgshows-autoplay.md
================================================
#### RGShows Autoplay
To enable autoplay on Firefox:
* Click the permissions button located to the left of your search bar and click `Allow Audio and Video` next to `Autoplay`.
or
* Do `Ctrl-I` -> `Permissions` -> set `Autoplay` to `Allow Audio and Video`.
================================================
FILE: docs/.vitepress/notes/sanet-warning.md
================================================
#### Sanet Warning
Note that Sanet has been known to host unsafe things like KMS Matrix, so it's best to avoid it for software and games.
SoftArchive Mirrors
- https://sanet.download/
- https://softarchive.is/
- https://sanet.lc/
- https://sanet.ws/
- https://sanet.st/
- https://sanet.sb/
- https://soft.ac/
================================================
FILE: docs/.vitepress/notes/savepagewe.md
================================================
#### SavePageWe
* https://addons.mozilla.org/en-US/firefox/addon/save-page-we/
* https://chrome.google.com/webstore/detail/save-page-we/dhhpefjklgkmgeafimnjhojgjamoafof
================================================
FILE: docs/.vitepress/notes/scrollanywhere-addons.md
================================================
#### ScrollAnywhere Addons
* https://addons.mozilla.org/en-US/firefox/addon/scroll_anywhere/
* https://chrome.google.com/webstore/detail/scrollanywhere/jehmdpemhgfgjblpkilmeoafmkhbckhi
* https://addons.opera.com/en/extensions/details/scrollanywhere/?display=en
================================================
FILE: docs/.vitepress/notes/sd-maid.md
================================================
#### SD Maid Note
The Google Play Store version is paid only. On the F-Droid and GitHub versions, however, you can use paid features for free by pressing `Support the development` and not donating.
================================================
FILE: docs/.vitepress/notes/sh-note.md
================================================
#### SH Note
Based on [this](https://wikipedia.org/wiki/Secret_Hitler) popular card game which was created by a co-founder of [Cards Against Humanity](https://wikipedia.org/wiki/Cards_Against_Humanity).
================================================
FILE: docs/.vitepress/notes/site-favicon-dl.md
================================================
#### Site Favicon Downloading
You can also go to `https://www.google.com/s2/favicons?domain=URL&sz=64` where `URL` is the URL of the site you want the favicon of and `sz` is the size in pixels.
================================================
FILE: docs/.vitepress/notes/soft98-note.md
================================================
#### Soft98 Note
Enable the `AdGuard - Ads` filter list in uBlock to allow downloads to work. To remove all ads, you can also get the [AdGuard Extra Userscript](https://github.com/AdguardTeam/AdGuardExtra?tab=readme-ov-file#userscript) (not the extension) and enable it in your [userscript manager](https://fmhy.net/internet-tools#userscripts). Note that you may need to disable filter `ir: PersianBlocker`.
================================================
FILE: docs/.vitepress/notes/sora.md
================================================
#### Sora
Bypass the need for a invite code by installing Sora Mobile, and logging into OpenAI.
================================================
FILE: docs/.vitepress/notes/spacewar.md
================================================
Spacewar! is a [1962 multiplayer game](https://wikipedia.org/wiki/Spacewar!) made for the DEC PDP-1 minicomputer. It was later ported to other systems, making it the first ever multi-computer game.
================================================
FILE: docs/.vitepress/notes/spicetify-note.md
================================================
#### Spicetify Note
Join their [Discord](https://discord.gg/VnevqPp2Rr) for version compatibility.
Note that you can use the store built in to get a full list of addons and themes.
================================================
FILE: docs/.vitepress/notes/sport7.md
================================================
#### Sport7
Many sites use this player but this was the original.
================================================
FILE: docs/.vitepress/notes/steam-controller-support.md
================================================
#### Steam Controller Support
Steam has built in support for most controller types, just add your games to Steam, right click the game, and turn on your controller.
================================================
FILE: docs/.vitepress/notes/steam-currency-converter-note.md
================================================
#### Steam Currency Converter Note
For instant currency conversion:
Go to Firefox's add-on settings (or the link `about:addons`) -> click on the add-on -> go to the `Permissions and data` section -> enable the optional sites.
================================================
FILE: docs/.vitepress/notes/tabiverse-extensions.md
================================================
#### Tabiverse Extensions
* https://addons.mozilla.org/firefox/addon/tabiverse/
* https://chromewebstore.google.com/detail/hpplgjkooibhfkmmepoikcjpadcojcik
================================================
FILE: docs/.vitepress/notes/tautulli-note.md
================================================
#### Tautulli Note
This will sometimes get falsely flagged by Windows Defender and removed automatically, so it may need to be allowed manually.
================================================
FILE: docs/.vitepress/notes/teamspeak-warning.md
================================================
#### TeamSpeak Warning
Note that TeamSpeak server admins can view user IP addresses, so only join servers you trust.
================================================
FILE: docs/.vitepress/notes/thunderbird.md
================================================
#### Thunderbird Notifications
To get real-time notifications:
Press the three lines in the top left corner -> select the account you want to configure -> select `Manage Folders` -> select the folder you want from below. You can then select inbox and enable push. (Notifications must be enabled).
================================================
FILE: docs/.vitepress/notes/tinyurl-note.md
================================================
#### TinyURL Note
To reveal the destination URL, replace "www" with "preview" in the URL like so:
https://preview.tinyurl.com/5erwtst5
================================================
FILE: docs/.vitepress/notes/video-downloadhelper.md
================================================
#### Video DownloadHelper
Note that some versions of this extension give a watermark on sites that need conversion. It seems to happen on the Windows + Firefox version.
================================================
FILE: docs/.vitepress/notes/welib-note.md
================================================
#### WeLib Note
WeLib is *not* connected to Anna's Archive, they simply mirror Anna's content onto their own site that has a different UI. It is not updated as often, and they don't share their codebase improvements publicly, so they aren't endorsed by Anna's themselves.
================================================
FILE: docs/.vitepress/notes/winrar.md
================================================
#### WinRAR Note
WinRAR does not auto-update, and because it had a remote code execution vulnerability in the past, you should make sure you've manually updated **to 7.13 or later** to be safe.
================================================
FILE: docs/.vitepress/notes/yet-another-call-blocker-note.md
================================================
#### Yet Another Call Blocker Note
The app itself isn't updated, but the blocklists are. It has a main local blocklist by default, and if you have "Auto-update database" enabled the app receives daily blocklist updates directly from third-party services. More info in their [GitLab repository](https://gitlab.com/xynngh/YetAnotherCallBlocker#yet-another-call-blocker).
================================================
FILE: docs/.vitepress/notes/youtube-tweaks.md
================================================
#### YouTube Tweaks
* https://addons.mozilla.org/firefox/addon/youtube-tweaks/
* https://chrome.google.com/webstore/detail/youtube-tweaks/oeakphpfoaeggagmgphfejmfjbhjfhhh
================================================
FILE: docs/.vitepress/notes/yts-yify-note.md
================================================
#### YTS / Yify Note
YTS / Yify has many fake copycat sites out there, make sure you're on one of the official domains before downloading anything. To be extra protected from fake sites, check out [FMHY SafeGuard](https://github.com/fmhy/FMHY-SafeGuard) and the [FMHY Filterlist](https://github.com/fmhy/FMHYFilterlist).
================================================
FILE: docs/.vitepress/shared.ts
================================================
/**
* Copyright (c) 2025 taskylizard. Apache License 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { DefaultTheme } from 'vitepress'
// @unocss-include
export const meta = {
name: 'freemediaheckyeah',
description: 'The largest collection of free stuff on the internet!',
hostname: 'https://fmhy.net',
keywords: ['stream', 'movies', 'gaming', 'reading', 'anime'],
build: {
api: true,
nsfw: true
}
}
export const excluded = [
'readme.md',
'single-page',
'feedback.md',
'index.md',
'sandbox.md',
'startpage.md'
]
const safeEnv = (key: string) => typeof process !== 'undefined' ? process.env?.[key] : undefined
if (safeEnv('FMHY_BUILD_NSFW') === 'false') {
meta.build.nsfw = false
}
if (safeEnv('FMHY_BUILD_API') === 'false') {
meta.build.api = false
}
const formatCommitRef = (commitRef: string) =>
`<a href="https://github.com/fmhy/edit/commit/${commitRef}">${commitRef.slice(0, 8)}</a>`
const cfStart = safeEnv('CF_PAGES_COMMIT_SHA')
const commitStart = safeEnv('COMMIT_REF')
export const commitRef =
safeEnv('CF_PAGES') && cfStart
? formatCommitRef(cfStart)
: commitStart
? formatCommitRef(commitStart)
: 'dev'
export const feedback = `<a href="/feedback" class="feedback-footer">Made with ❤</a>`
export const socialLinks: DefaultTheme.SocialLink[] = [
{ icon: 'github', link: 'https://github.com/fmhy/edit' },
{ icon: 'discord', link: 'https://github.com/fmhy/FMHY/wiki/FMHY-Discord' },
{
icon: 'reddit',
link: 'https://reddit.com/r/FREEMEDIAHECKYEAH'
}
]
export const nav: DefaultTheme.NavItem[] = [
{ text: '📑 Changelog', link: '/posts/changelog-sites' },
{ text: '📖 Glossary', link: 'https://rentry.org/The-Piracy-Glossary' },
{
text: '💾 Backups',
link: '/other/backups'
},
{
text: '🌱 Ecosystem',
items: [
{ text: '🌐 Search', link: '/posts/search' },
{ text: '❓ FAQs', link: '/other/FAQ' },
{ text: '🔖 Bookmarks', link: 'https://github.com/fmhy/bookmarks' },
{ text: '✅ SafeGuard', link: 'https://github.com/fmhy/FMHY-SafeGuard' },
{ text: '🚀 Startpage', link: 'https://fmhy.net/startpage' },
{ text: '✴️ rss.fmhy', link: 'https://rss.fmhy.bid/' },
{ text: '🔎 SearXNG', link: 'https://searx.fmhy.net/' },
{
text: '💡 Site Hunting',
link: 'https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/find-new-sites/'
},
{
text: '😇 SFW FMHY',
link: 'https://fmhy.xyz/'
},
{
text: '🏠 Selfhosting',
link: '/other/selfhosting'
},
{ text: '🏞 Wallpapers', link: '/other/wallpapers' },
{ text: '💙 Feedback', link: '/feedback' }
]
}
]
export const sidebar: DefaultTheme.Sidebar | DefaultTheme.NavItemWithLink[] = [
{
text: '<span class="i-twemoji:books"></span> Beginners Guide',
link: '/beginners-guide'
},
{
text: '<span class="i-twemoji:newspaper"></span> Posts',
link: '/posts'
},
{
text: '<span class="i-twemoji:light-bulb"></span> Contribute',
link: '/other/contributing'
},
{
text: 'Wiki',
collapsed: false,
items: [
{
text: '<span class="i-twemoji:name-badge"></span> Adblocking / Privacy',
link: '/privacy'
},
{
text: '<span class="i-twemoji:robot"></span> Artificial Intelligence',
link: '/ai'
},
{
text: '<span class="i-twemoji:television"></span> Movies / TV / Anime',
link: '/video'
},
{
text: '<span class="i-twemoji:musical-note"></span> Music / Podcasts / Radio',
link: '/audio'
},
{
text: '<span class="i-twemoji:video-game"></span> Gaming / Emulation',
link: '/gaming'
},
{
text: '<span class="i-twemoji:green-book"></span> Books / Comics / Manga',
link: '/reading'
},
{
text: '<span class="i-twemoji:floppy-disk"></span> Downloading',
link: '/downloading'
},
{
text: '<span class="i-twemoji:cyclone"></span> Torrenting',
link: '/torrenting'
},
{
text: '<span class="i-twemoji:brain"></span> Educational',
link: '/educational'
},
{
text: '<span class="i-twemoji:mobile-phone"></span> Android / iOS',
link: '/mobile'
},
{
text: '<span class="i-twemoji:penguin"></span> Linux / macOS',
link: '/linux-macos'
},
{
text: '<span class="i-twemoji:globe-showing-asia-australia"></span> Non-English',
link: '/non-english'
},
{
text: '<span class="i-twemoji:file-folder"></span> Miscellaneous',
link: '/misc'
}
]
},
{
text: 'Tools',
collapsed: false,
items: [
{
text: '<span class="i-twemoji:laptop"></span> System Tools',
link: '/system-tools'
},
{
text: '<span class="i-twemoji:card-file-box"></span> File Tools',
link: '/file-tools'
},
{
text: '<span class="i-twemoji:paperclip"></span> Internet Tools',
link: '/internet-tools'
},
{
text: '<span class="i-twemoji:left-speech-bubble"></span> Social Media Tools',
link: '/social-media-tools'
},
{
text: '<span class="i-twemoji:memo"></span> Text Tools',
link: '/text-tools'
},
{
text: '<span class="i-twemoji:alien-monster"></span> Gaming Tools',
link: '/gaming-tools'
},
{
text: '<span class="i-twemoji:camera"></span> Image Tools',
link: '/image-tools'
},
{
text: '<span class="i-twemoji:videocassette"></span> Video Tools',
link: '/video-tools'
},
{
text: '<span class="i-twemoji:speaker-high-volume"></span> Audio Tools',
link: '/audio#audio-tools'
},
{
text: '<span class="i-twemoji:red-apple"></span> Educational Tools',
link: '/educational#educational-tools'
},
{
text: '<span class="i-twemoji:man-technologist"></span> Developer Tools',
link: '/developer-tools'
}
]
},
{
text: 'More',
collapsed: true,
items: [
meta.build.nsfw
? {
text: '<span class="i-twemoji:no-one-under-eighteen"></span> NSFW',
link: 'https://rentry.org/NSFW-Checkpoint'
}
: {},
{
text: '<span class="i-twemoji:warning"></span> Unsafe Sites',
link: '/unsafe'
},
{
text: '<span class="i-twemoji:package"></span> Storage',
link: '/storage'
}
]
}
]
================================================
FILE: docs/.vitepress/theme/Appearance.vue
================================================
<script setup lang="ts">
import VPIconMoon from 'vitepress/dist/client/theme-default/components/icons/VPIconMoon.vue'
import VPIconSun from 'vitepress/dist/client/theme-default/components/icons/VPIconSun.vue'
const { isDark } = useData()
const toggleAppearance = inject('toggle-appearance', () => {
isDark.value = !isDark.value
})
const supportsViewTransition = ref(false)
onMounted(() => {
supportsViewTransition.value =
'startViewTransition' in document &&
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
})
</script>
<template>
<button
type="button"
role="switch"
title="VPSwitchAppearance"
class="VPSwitchAppearance"
:aria-checked="isDark"
:data-view-transition="supportsViewTransition"
@click="toggleAppearance"
>
<ClientOnly>
<Transition name="fade" mode="out-in">
<div v-if="!isDark" class="sun text-xl i-ph-sun-duotone" />
<div v-else class="moon text-xl i-ph-moon-duotone" />
</Transition>
</ClientOnly>
</button>
</template>
<style lang="scss" scoped>
.VPSwitchAppearance {
display: flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
color: var(--vp-c-text-2);
transition: color 0.5s;
&:hover {
color: var(--vp-c-text-1);
transition: color 0.25s;
}
& > :deep(svg) {
width: 20px;
height: 20px;
fill: currentColor;
}
&[data-view-transition='false'] {
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.1s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
}
}
</style>
================================================
FILE: docs/.vitepress/theme/Layout.vue
================================================
<script setup lang="ts">
import { useData } from 'vitepress'
import { ref, onMounted, onUnmounted, provide, nextTick } from 'vue'
import DefaultTheme from 'vitepress/theme'
import Announcement from './components/Announcement.vue'
import Sidebar from './components/SidebarCard.vue'
import Base64Dialog from './components/Base64Dialog.vue'
import { useTheme } from './themes/themeHandler'
const { isDark } = useData()
const { setMode } = useTheme()
const enableTransitions = () =>
'startViewTransition' in document &&
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
if (!enableTransitions()) {
isDark.value = !isDark.value
// Sync with theme handler
setMode(isDark.value ? 'dark' : 'light')
return
}
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
)}px at ${x}px ${y}px)`
]
await document.startViewTransition(async () => {
isDark.value = !isDark.value
// Sync with theme handler
setMode(isDark.value ? 'dark' : 'light')
await nextTick()
}).ready
document.documentElement.animate(
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
{
duration: 300,
easing: 'ease-in',
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
}
)
})
const { Layout } = DefaultTheme
const showBase64Dialog = ref(false)
const formattedUrl = ref('')
const handleClick = (e: MouseEvent) => {
// Check if the clicked element is a link or within a link
const target = e.target as HTMLElement
const link = target.closest ? target.closest('a') : null
if (link) {
const href = (link as HTMLAnchorElement).href
if (typeof href === 'string') {
if (href.includes('https://rentry.co/FMHYB64') || href.startsWith('https://rentry.co/FMHYB64')) {
const dontShow = localStorage.getItem('fmhy-base64-dialog-preference')
if (dontShow === 'true') {
return // Let the link click proceed normally
}
e.preventDefault()
e.stopPropagation()
formattedUrl.value = href
showBase64Dialog.value = true
}
}
}
}
onMounted(() => {
window.addEventListener('click', handleClick, { capture: true })
})
onUnmounted(() => {
window.removeEventListener('click', handleClick, { capture: true })
})
</script>
<template>
<Layout>
<template #sidebar-nav-after>
<Sidebar />
</template>
<template #home-hero-info-before>
<Announcement />
</template>
<template #home-features-before>
<p class="text-center text-lg text-text-2 mb-2">
Or browse these pages
<span class="inline-block i-twemoji:sparkles" />
</p>
</template>
<Content />
</Layout>
<Base64Dialog :show="showBase64Dialog" :url="formattedUrl" @close="showBase64Dialog = false" />
</template>
<style>
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
::view-transition-old(root),
.dark::view-transition-new(root) {
z-index: 1;
}
::view-transition-new(root),
.dark::view-transition-old(root) {
z-index: 9999;
}
.VPSwitchAppearance {
width: 22px !important;
}
.VPSwitchAppearance .check {
transform: none !important;
}
</style>
================================================
FILE: docs/.vitepress/theme/PostLayout.vue
================================================
<script setup lang="ts">
import Authors from './components/Authors.vue'
const props = defineProps<{
authors: string
}>()
const formatDate = (raw: string): string => {
const date = new Date(raw)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
})
}
const { frontmatter } = useData()
const authors = computed(() => props.authors.split(','))
</script>
<template>
<h1>{{ frontmatter.title }}</h1>
<div>{{ frontmatter.description }} • {{ formatDate(frontmatter.date) }}</div>
<Authors :authors="authors" />
</template>
================================================
FILE: docs/.vitepress/theme/Posts.vue
================================================
<!-- eslint-disable vue/require-v-for-key -->
<script setup lang="ts">
import { data as posts } from './posts.data'
const formatDate = (raw: string): string => {
const date = new Date(raw)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
})
}
</script>
<template>
<div>
<section>
<h1 class="flex items-center gap-2">Posts</h1>
<p>Monthly updates, announcements, and more.</p> We also have an <a href="/feed.rss" target="_blank" title="RSS feed">
<div class="i-carbon-rss vertical-top" style="width: 16px; height: 24px;" />
RSS feed.
</a>
</section>
<template v-for="year in Object.keys(posts).reverse()" :key="year">
<h2>{{ year }}</h2>
<ul>
<li v-for="post of posts[year]" :key="post.url">
<article>
<a :href="post.url" class="border-none">{{ post.title }}</a>
-
<dl class="m-0 inline">
<dt class="sr-only">Published on</dt>
<dd class="m-0 inline">
<time :datetime="post.date" class="font-bold">
{{ formatDate(post.date) }}
</time>
</dd>
</dl>
</article>
</li>
</ul>
</template>
</div>
</template>
<style scoped>
.VPBadge {
border: 1px solid transparent;
border-radius: 8px;
display: inline-flex;
margin-left: 2px;
padding: 0 10px;
line-height: 22px;
font-size: 12px;
font-weight: 500;
transform: translateY(-2px);
align-items: center;
gap: 0.2rem;
padding-right: 10px;
vertical-align: middle;
color: var(--vp-badge-tip-text);
background-color: transparent;
border-color: var(--vp-custom-block-tip-outline);
}
</style>
================================================
FILE: docs/.vitepress/theme/components/Announcement.vue
================================================
<script setup lang="ts">
const { frontmatter } = useData()
</script>
<template>
<a
v-if="frontmatter.hero.announcement"
:href="frontmatter.hero.announcement.link"
class="mb-3 inline-flex items-center rounded-lg bg-[var(--vp-c-default-soft)] px-4 py-1 text-sm font-semibold"
>
{{ frontmatter.hero.announcement.title }}
</a>
</template>
================================================
FILE: docs/.vitepress/theme/components/Authors.vue
================================================
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
authors: string[]
}>()
interface Author {
name: string
github: string
}
const data = [
{
name: 'nbats',
github: 'https://github.com/nbats'
},
{
name: 'Kai',
github: 'https://github.com/Kai-FMHY'
},
{
name: 'taskylizard',
github: 'https://github.com/taskylizard'
},
{
name: 'zinklog',
github: 'https://github.com/zinklog2'
},
{
name: 'Q',
github: 'https://github.com/qiracy'
}
] satisfies Author[]
const authors = computed(() =>
data.filter((author) => props.authors.includes(author.name))
)
</script>
<template>
<div class="flex flex-wrap gap-4 pt-2">
<div v-for="(c, index) of authors" class="flex items-center gap-2">
<img :src="`${c.github}.png`" class="h-8 w-8 rounded-full" />
<a :href="c.github">{{ c.name }}</a>
<span v-if="index < authors.length - 1">•</span>
</div>
</div>
</template>
================================================
FILE: docs/.vitepress/theme/components/Base64Dialog.vue
================================================
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps<{
show: boolean
url: string
}>()
const emit = defineEmits(['close'])
const dontShowAgain = ref(false)
const close = () => {
emit('close')
}
const openLink = () => {
if (dontShowAgain.value) {
localStorage.setItem('fmhy-base64-dialog-preference', 'true')
}
window.open(props.url, '_blank')
close()
}
</script>
<template>
<Teleport to="body">
<div v-show="show" class="fixed inset-0 z-[99999] flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm" @click="close">
<div
class="p-6 rounded-xl shadow-2xl max-w-md w-full"
style="background-color: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);"
@click.stop
>
<h2 class="text-xl font-bold mb-4 flex items-center gap-2">
<div class="i-carbon:information-filled text-primary" />
Base64 Encoded Link
</h2>
<p class="mb-4 text-text-1">
The link you clicked leads to a Base64 encoded string.
</p>
<p class="mb-2 text-text-1">
To decode it, you can use:
</p>
<ul class="list-disc list-inside mb-4 space-y-2 text-text-1">
<li>
An online tool: <a href="https://www.base64decode.org/" target="_blank" rel="noreferrer" class="text-primary hover:underline font-medium">Base64 Decode</a>
</li>
<li>
A userscript: <a href="https://greasyfork.org/en/scripts/485772-fmhy-base64-auto-decoder" target="_blank" rel="noreferrer" class="text-primary hover:underline font-medium">FMHY Base64 Auto Decoder</a> (using a <a href="/internet-tools#userscripts" target="_blank" class="text-primary hover:underline font-medium">userscript manager</a>)
</li>
</ul>
<p class="mb-6 text-sm text-text-2">
For more options: <a href="/text-tools#encode-decode" target="_blank" class="text-primary hover:underline font-medium">Base64 Decoders</a>
</p>
<div class="flex items-center gap-2 mb-4">
<input
type="checkbox"
id="dont-show"
v-model="dontShowAgain"
class="rounded border-border bg-bg-input text-brand focus:ring-brand"
>
<label for="dont-show" class="text-sm text-text-1 select-none">Don't show again</label>
</div>
<div class="flex justify-end gap-3">
<button
@click="close"
class="px-4 py-2 border border-border rounded-lg hover:bg-bg-input transition-colors font-medium text-text-2"
>
Cancel
</button>
<button
@click="openLink"
class="px-4 py-2 border-2 border-brand text-brand bg-[var(--vp-c-bg-alt)] hover:bg-brand hover:text-white rounded-lg transition-colors font-medium"
>
Open Link
</button>
</div>
</div>
</div>
</Teleport>
</template>
================================================
FILE: docs/.vitepress/theme/components/CardField.vue
================================================
<script setup lang="ts">
defineProps<{
icon: string
}>()
</script>
<template>
<div class="g-[12px] mb-[8px] flex items-center">
<span class="flex items-center">
<div class="text-2xl" :class="icon" />
<div class="ml-2 text-sm text-[var(--vp-c-text-2)]">
<slot />
</div>
</span>
</div>
</template>
================================================
FILE: docs/.vitepress/theme/components/ColorPicker.vue
================================================
<script setup lang="ts">
import { colors } from '@fmhy/colors'
import { useStorage } from '@vueuse/core'
import { watch, onMounted, nextTick } from 'vue'
import { useTheme } from '../themes/themeHandler'
import { themeRegistry } from '../themes/configs'
import type { Theme } from '../themes/types'
import Switch from './Switch.vue'
type ColorNames = keyof typeof colors
const selectedColor = useStorage<ColorNames>('preferred-color', 'swarm')
// Use the theme system
const { amoledEnabled, setAmoledEnabled, setTheme, state, mode, themeName } = useTheme()
const colorOptions = Object.keys(colors).filter(
(key) => typeof colors[key as keyof typeof colors] === 'object'
) as Array<ColorNames>
// Preset themes (exclude dynamically generated color- themes)
const presetThemeNames = Object.keys(themeRegistry).filter((k) => !k.startsWith('color-'))
const getThemePreviewStyle = (name: string) => {
const theme = themeRegistry[name]
if (!theme) return {}
const modeKey = (mode && (mode as any).value) ? (mode as any).value as keyof typeof theme.modes : 'light'
const modeColors = theme.modes[modeKey]
if (theme.preview) {
// If preview is a URL or gradient, use it directly
if (theme.preview.startsWith('http') || theme.preview.startsWith('data:')) {
return { backgroundImage: `url(${theme.preview})`, backgroundSize: 'cover' }
}
return { background: theme.preview }
}
if (modeColors?.brand && modeColors.brand[1] && modeColors.brand[2]) {
return {
background: `linear-gradient(135deg, ${modeColors.brand[1]} 0%, ${modeColors.brand[2]} 100%)`
}
}
// Fallback to CSS var brand if present
return { background: 'var(--vp-c-brand-1)' }
}
const generateThemeFromColor = (colorName: ColorNames): Theme => {
const colorSet = colors[colorName]
return {
name: `color-${colorName}`,
displayName: normalizeColorName(colorName),
modes: {
light: {
brand: {
1: colorSet[500],
2: colorSet[600],
3: colorSet[800],
soft: colorSet[400]
},
bg: '#f8fafc',
bgAlt: '#eef2f5',
bgElv: 'rgba(255, 255, 255, 0.8)',
bgMark: 'rgb(226, 232, 240)',
text: {
1: '#0f172a',
2: '#334155',
3: '#64748b'
},
button: {
brand: {
bg: colorSet[500],
border: colorSet[400],
text: 'rgba(255, 255, 255)',
hoverBorder: colorSet[400],
hoverText: 'rgba(255, 255, 255)',
hoverBg: colorSet[400],
activeBorder: colorSet[400],
activeText: 'rgba(255, 255, 255)',
activeBg: colorSet[500]
},
alt: {
bg: '#484848',
text: '#f0eeee',
hoverBg: '#484848',
hoverText: '#f0eeee'
}
},
customBlock: {
info: {
bg: `${colorSet[100]}`,
border: `${colorSet[800]}`,
text: `${colorSet[800]}`,
textDeep: `${colorSet[900]}`
},
tip: {
bg: '#D8F8E4',
border: '#447A61',
text: '#2D6A58',
textDeep: '#166534'
},
warning: {
bg: '#FCEFC3',
border: '#9A8034',
text: '#9C701B',
textDeep: '#92400e'
},
danger: {
bg: '#FBE1E2',
border: '#B3565E',
text: '#912239',
textDeep: '#991b1b'
}
},
selection: {
bg: colorSet[200]
},
home: {
heroNameColor: 'transparent',
heroNameBackground: '-webkit-linear-gradient(120deg, #c4b5fd 30%, #7bc5e4)',
heroImageBackground: 'linear-gradient(-45deg, #c4b5fd 50%, #47caff 50%)',
heroImageFilter: 'blur(44px)'
}
},
dark: {
brand: {
1: colorSet[400],
2: colorSet[500],
3: colorSet[600],
soft: colorSet[300]
},
bg: '#1A1A1A',
bgAlt: '#171717',
bgElv: '#1a1a1acc',
button: {
brand: {
bg: colorSet[400],
border: colorSet[300],
text: 'rgba(15, 23, 42)',
hoverBorder: colorSet[300],
hoverText: 'rgba(15, 23, 42)',
hoverBg: colorSet[300],
activeBorder: colorSet[300],
activeText: 'rgba(15, 23, 42)',
activeBg: colorSet[400]
},
alt: {
bg: '#484848',
text: '#f0eeee',
hoverBg: '#484848',
hoverText: '#f0eeee'
}
},
customBlock: {
info: {
bg: `${colorSet[950]}`,
border: `${colorSet[700]}`,
text: `${colorSet[200]}`,
textDeep: `${colorSet[200]}`
},
tip: {
bg: '#0C2A20',
border: '#184633',
text: '#B0EBC9',
textDeep: '#166534'
},
warning: {
bg: '#403207',
border: '#7E6211',
text: '#F9DE88',
textDeep: '#92400e'
},
danger: {
bg: '#3F060A',
border: '#7C0F18',
text: '#F7C1BC',
textDeep: '#991b1b'
}
},
selection: {
bg: colorSet[800]
},
home: {
heroNameColor: 'transparent',
heroNameBackground: '-webkit-linear-gradient(120deg, #c4b5fd 30%, #7bc5e4)',
heroImageBackground: 'linear-gradient(-45deg, #c4b5fd 50%, #47caff 50%)',
heroImageFilter: 'blur(44px)'
}
}
}
}
}
const normalizeColorName = (colorName: string) =>
colorName.replaceAll(/-/g, ' ').charAt(0).toUpperCase() +
colorName.slice(1).replaceAll(/-/g, ' ')
onMounted(async () => {
// apply saved theme on load
if (selectedColor.value) {
const theme = generateThemeFromColor(selectedColor.value)
themeRegistry[`color-${selectedColor.value}`] = theme
await nextTick()
setTheme(`color-${selectedColor.value}`)
}
// Wait for next tick to ensure theme handler is fully initialized
await nextTick()
})
watch(selectedColor, async (color) => {
if (!color) return;
const theme = generateThemeFromColor(color)
themeRegistry[`color-${color}`] = theme
await nextTick()
setTheme(`color-${color}`)
})
const toggleAmoled = () => {
setAmoledEnabled(!amoledEnabled.value)
}
</script>
<template>
<div>
<div class="flex flex-wrap gap-2">
<!-- Color picker generated themes (render first) -->
<div v-for="color in colorOptions" :key="color">
<button
:class="[
'inline-block w-6 h-6 rounded-full transition-all duration-200 border-2',
(themeName && themeName.value === `color-${color}`)
? 'border-slate-200 dark:border-slate-400 shadow-lg'
: 'border-transparent'
]"
@click="selectedColor = color"
:title="normalizeColorName(color)"
>
<span
class="inline-block w-full h-full rounded-full"
:style="{ backgroundColor: colors[color][500], backgroundSize: 'cover', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }"
></span>
</button>
</div>
<!-- Preset themes (render at the end) -->
<div v-for="t in presetThemeNames" :key="t">
<button
:class="[
'inline-block w-6 h-6 rounded-full transition-all duration-200 border-2',
(themeName && themeName.value === t)
? 'border-slate-200 dark:border-slate-400 shadow-lg'
: 'border-transparent'
]"
@click="selectedColor = '' as ColorNames; setTheme(t)"
:title="themeRegistry[t].displayName"
>
<span
class="inline-block w-full h-full rounded-full"
:style="Object.assign({ backgroundSize: 'cover', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }, getThemePreviewStyle(t))"
></span>
</button>
</div>
</div>
</div>
</template>
================================================
FILE: docs/.vitepress/theme/components/Feedback.vue
================================================
<script setup lang="ts">
import type { FeedbackType } from '../../types/Feedback'
import { useRouter } from 'vitepress'
import { computed, reactive, ref } from 'vue'
import { feedbackOptions, getFeedbackOption } from '../../types/Feedback'
const props = defineProps<{
heading?: string
}>()
const prompts = [
'Make it count!',
'Leave some feedback for us!',
`We're all ears 🐰`,
'Tell us what is missing in FMHY',
'Your thoughts matter to us 💡',
'Feedback is a gift 🎁',
'What do you think?',
'We appreciate your support 🙏',
'Help us make FMHY better 🤝',
'We need your help 👋',
'Your feedback is valuable 💯',
'So... what do you think?',
"I guess you don't need to say anything 😉",
'Spill the beans 💣',
"We're always looking for ways to improve!",
'Your feedback is valuable and helps us make FMHY better.',
'aliens are watching you 👽',
'tasky was here 👀',
'The internet is full of crap 😱'
]
function getPrompt() {
return prompts[Math.floor(Math.random() * prompts.length)]
}
const messages = {
suggestion: [
"We're glad you want to share your ideas!",
'Nix the fluff and just tell us what you think!',
"We'll be happy to read your thoughts and incorporate them into our wiki.",
"Hello! We're glad you want to share your ideas!"
],
appreciation: [
'We appreciate your support!',
"We're always looking for ways to improve!.",
'Your feedback is valuable and helps us make FMHY better.'
],
other: [
"We're always looking for ways to improve!",
'Your feedback is valuable and helps us make FMHY better.'
]
}
function getMessage(type: FeedbackType['type']) {
return messages[type][Math.floor(Math.random() * messages[type].length)]
}
const loading = ref<boolean>(false)
const error = ref<unknown>(null)
const success = ref<boolean>(false)
const isDisabled = computed(() => {
return (
!feedback.message.length ||
feedback.message.length < 5 ||
feedback.message.length > 1000
)
})
const router = useRouter()
const feedback = reactive<{
message: string
page: string
type?: FeedbackType['type']
}>({
page: router.route.path,
message: ''
})
const selectedOption = ref(feedbackOptions[0])
async function handleSubmit(type?: FeedbackType['type']) {
if (type) {
feedback.type = type
selectedOption.value = getFeedbackOption(type)!
}
loading.value = true
const body: FeedbackType = {
message: feedback.message,
type: feedback.type!,
page: feedback.page,
...(props.heading && { heading: props.heading })
}
try {
const response = await fetch('https://api.fmhy.net/feedback', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
const data = await response.json()
if (data.error) {
error.value = data.error
return
}
if (data.status === 'ok') {
success.value = true
}
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
const isCardShown = ref<boolean>(false)
const helpfulText = props.heading
? 'What do you think about this section?'
: 'What do you think about this page?'
const helpfulDescription = props.heading
? 'Let us know how helpful this section is.'
: 'Let us know how helpful this page is.'
const prompt = computed(() => getPrompt())
const message = computed(() => getMessage(feedback.type!))
const toggleCard = () => (isCardShown.value = !isCardShown.value)
</script>
<template>
<template v-if="props.heading">
<button
@click="toggleCard()"
class="bg-$vp-c-default-soft text-primary border-$vp-c-default-soft hover:border-primary ml-3 inline-flex h-7 items-center justify-center whitespace-nowrap rounded-md border-2 border-solid px-1.5 py-3.5 text-sm font-medium transition-all duration-300 sm:h-6"
>
<span
:class="isCardShown === false ? `i-lucide:mail` : `i-lucide:mail-x`"
/>
</button>
</template>
<template v-else>
<div
class="mt-2 p-4 border-2 border-solid bg-$vp-c-bg-alt border-$vp-c-divider rounded-xl col-span-3 transition-colors duration-250"
>
<div class="flex items-start md:items-center gap-3">
<div class="pt-1 md:pt-0">
<div class="w-10 h-10 rounded-full flex items-center justify-center bg-$vp-c-brand-3">
<span
:class="
isCardShown === false
? `i-lucide:mail w-6 h-6 text-white`
: `i-lucide:mail-x w-6 h-6 text-white`
"
/>
</div>
</div>
<div class="flex-grow flex items-start md:items-center gap-3 flex-col md:flex-row">
<div class="flex-grow">
<div class="font-semibold text-$vp-c-text-1">Got feedback?</div>
<div class="text-sm text-$vp-c-text-2">We'd love to know what you think about this page.</div>
</div>
<div>
<button
class="bg-[#25262B] inline-block text-center rounded-full px-4 py-2.5 text-sm font-medium border-2 border-solid text-white border-$vp-c-divider"
@click="toggleCard()"
>
Share Feedback
</button>
</div>
</div>
</div>
</div>
</template>
<Transition name="fade" mode="out-in">
<div
v-if="isCardShown"
class="border-$vp-c-divider bg-$vp-c-bg-alt b-rd-4 m-[2rem 0] mt-4 border-2 border-solid p-6"
>
<Transition name="fade" mode="out-in">
<div v-if="!feedback.type">
<p class="heading">
{{ helpfulText }}
</p>
<div class="flex flex-wrap gap-2">
<button
v-for="item in feedbackOptions"
:key="item.value"
class="bg-[#25262B] border-$vp-c-default-soft hover:border-primary mt-2 select-none rounded border-2 border-solid font-bold transition-all duration-250 rounded-lg text-[14px] text-white font-500 leading-normal m-0 px-3 py-1.5 text-center align-middle whitespace-nowrap"
@click="handleSubmit(item.value)"
>
<span>{{ item.label }}</span>
</button>
</div>
</div>
<div v-else-if="feedback.type && !success">
<div>
<p class="desc">{{ helpfulDescription }} - {{ prompt }}</p>
<span>{{ getFeedbackOption(feedback.type)?.label }}</span>
</div>
<p class="heading" v-text="message"></p>
<div v-if="feedback.type === 'suggestion'" class="mb-2 text-sm">
<p>Please read the <a href="/other/contributing">Contribute Guide</a> before submitting your feedback!</p>
</div>
<textarea
v-model="feedback.message"
autofocus
class="bg-$vp-c-bg-alt text-$vp-c-text-2 w-full h-[100px] border border-$vp-c-divider rounded px-3 py-1.5 border-$vp-c-divider bg-$vp-c-bg-alt b-rd-4 border-2 border-solid"
placeholder="What a lovely wiki!"
/>
<p class="desc mb-2">
Add your Discord handle if you would like a response, or if we need
more information from you, otherwise join our
<a
class="text-primary text-underline font-semibold"
href="https://github.com/fmhy/FMHY/wiki/FMHY-Discord"
>
Discord.
</a>
</p>
<div class="flex flex-row gap-2">
<button
class="bg-$vp-c-default-soft text-primary border-$vp-c-default-soft inline-flex h-7 items-center justify-center whitespace-nowrap rounded-md border-2 border-solid px-1.5 py-3.5 text-sm font-medium transition-all duration-300 sm:h-6"
@click="feedback.type = undefined"
>
<span class="i-lucide:panel-left-close">close</span>
</button>
<button
type="submit"
class="btn btn-primary"
:disabled="isDisabled"
@click="handleSubmit()"
:style="isDisabled ? {} : { 'background-color': 'var(--vp-button-brand-bg)', 'border-color': 'var(--vp-button-brand-border)', color: 'var(--vp-button-brand-text)' }"
>
Send Feedback 📩
</button>
</div>
</div>
<div v-else>
<p class="heading">Thanks for your feedback!</p>
</div>
</Transition>
</div>
</Transition>
</template>
<style scoped lang="css">
.btn {
border: 1px solid var(--vp-c-divider);
background-color: var(--vp-c-bg);
border-radius: 8px;
transition:
border-color 0.25s,
background-color 0.25s;
display: inline-block;
font-size: 14px;
font-weight: 500;
line-height: 1.5;
margin: 0;
padding: 0.375rem 0.75rem;
text-align: center;
vertical-align: middle;
white-space: nowrap;
}
.btn:disabled {
opacity: 0.5;
}
.btn:hover {
border-color: var(--vp-c-brand);
}
.btn-primary {
color: var(--vp-button-brand-text);
background-color: var(--vp-button-brand-bg);
border-color: var(--vp-button-brand-border);
}
.btn-primary:hover {
background-color: var(--vp-button-brand-hover-bg);
border-color: var(--vp-button-brand-hover-border);
}
.heading {
font-size: 1.2rem;
font-weight: 700;
}
.desc {
display: block;
line-height: 20px;
font-size: 12px;
font-weight: 500;
color: var(--vp-c-text-2);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.25s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
================================================
FILE: docs/.vitepress/theme/components/InputField.vue
================================================
<script setup lang="ts">
defineProps<{
label: string
id: string
}>()
</script>
<template>
<div class="input-field">
<div v-if="label" class="input-label">
<label :for="id" class="pane-label">
{{ label }}
</label>
<div class="display-value">
<slot name="display" />
</div>
</div>
<slot />
</div>
</template>
<style scoped>
.pane-label {
line-height: 20px;
font-size: 13px;
font-weight: 600;
color: var(--vt-c-text-1);
display: block;
}
.input-field:not(:last-child) {
margin-bottom: 16px;
}
.display-value {
font-size: 13px;
color: var(--vp-c-text-2);
}
.input-label {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
gap: 12px;
}
</style>
================================================
FILE: docs/.vitepress/theme/components/SidebarCard.vue
================================================
<script setup lang="ts">
import Field from './CardField.vue'
import ColorPicker from './ColorPicker.vue'
import ThemeSelector from './ThemeSelector.vue'
import InputField from './InputField.vue'
import ToggleStarred from './ToggleStarred.vue'
import ToggleIndexes from './ToggleIndexes.vue'
</script>
<template>
<div
class="bg-$vp-c-bg hover:bg-$vp-c-bg/40 border-$vp-c-default-soft hover:border-primary transition-border relative z-0 rounded-lg border-2 border-solid p-5 duration-500"
>
<div class="align-center mb-4 flex justify-between">
<div class="text-$vp-c-text-1 lh-relaxed text-sm font-bold">
Emoji Legend
</div>
</div>
<Field icon="i-twemoji-globe-with-meridians">Indexes</Field>
<Field icon="i-twemoji-repeat-button">Storage Links</Field>
<Field icon="i-twemoji-star">Recommendations</Field>
<div class="align-center mb-4 mt-4 flex justify-between">
<div class="text-$vp-c-text-1 lh-relaxed text-sm font-bold">Options</div>
</div>
<InputField id="toggle-starred" label="Toggle Starred">
<template #display>
<ToggleStarred />
</template>
</InputField>
<InputField id="toggle-indexes" label="Toggle Indexes">
<template #display>
<ToggleIndexes />
</template>
</InputField>
<div class="mt-4">
<ColorPicker />
</div>
<div class="mt-6 pt-6 border-t border-$vp-c-divider">
<ThemeSelector />
</div>
</div>
</template>
================================================
FILE: docs/.vitepress/theme/components/Switch.vue
================================================
<script setup lang="ts">
import { Switch as HeadlessSwitch } from '@headlessui/vue'
const props = defineProps<{
modelValue: boolean
disabled?: boolean
}>()
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
}>()
</script>
<template>
<HeadlessSwitch
:model-value="props.modelValue"
:disabled="props.disabled"
class="switch"
:class="{ enabled: props.modelValue, disabled: props.disabled }"
@update:modelValue="emit('update:modelValue', $event)"
>
<span class="thumb" />
</HeadlessSwitch>
</template>
<style>
.switch {
display: inline-flex;
position: relative;
width: 40px;
height: 22px;
flex-shrink: 0;
border: 1px solid var(--vp-input-border-color);
background-color: var(--vp-input-switch-bg-color);
transition:
border-color 0.25s,
background-color 0.4s ease;
border-radius: 11px;
}
.switch.enabled {
background-color: var(--vp-c-brand);
}
.switch.disabled {
opacity: 0.5;
pointer-events: none;
background-color: var(--vp-c-bg-soft, #2f2f2f);
border-color: var(--vp-c-divider, #666);
}
.switch.disabled .thumb {
background-color: #fff;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.2), var(--vp-shadow-1);
}
.dark .switch.disabled {
background-color: #2f2f2f;
border-color: #7d7d7d;
}
</style>
<style scoped>
.switch:hover {
border-color: var(--vp-input-hover-border-color);
}
.thumb {
display: inline-block;
background-color: #fff;
transition: transform 0.25s;
width: 20px;
height: 20px;
border-radius: 50%;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.08), var(--vp-shadow-1);
}
.switch.enabled .thumb {
transform: translateX(18px);
}
</style>
================================================
FILE: docs/.vitepress/theme/components/ThemeDropdown.vue
================================================
<script setup lang="ts">
import { computed, ref, onMounted, onUnmounted } from 'vue'
import { useTheme } from '../themes/themeHandler'
import type { DisplayMode } from '../themes/types'
const { mode, amoledEnabled, setAppearance } = useTheme()
const wrapperRef = ref<HTMLElement | null>(null)
interface ModeChoice {
mode: DisplayMode
label: string
icon: string
isAmoled?: boolean
}
const modeChoices: ModeChoice[] = [
{ mode: 'light', label: 'Light', icon: 'i-ph-sun-duotone' },
{ mode: 'dark', label: 'Dark', icon: 'i-ph-moon-duotone' },
{ mode: 'dark', label: 'AMOLED', icon: 'i-ph-moon-stars-duotone', isAmoled: true }
]
const currentChoice = computed(() => {
const current = (mode && (mode as any).value) ? (mode as any).value : 'light'
if (current === 'dark' && amoledEnabled.value) {
return modeChoices[2] // AMOLED option
}
return modeChoices.find(choice => choice.mode === current && !choice.isAmoled) || modeChoices[0]
})
const selectMode = (choice: ModeChoice) => {
if (choice.isAmoled) {
setAppearance('dark', true)
} else {
setAppearance(choice.mode, false)
}
}
const isActiveChoice = (choice: ModeChoice) => {
const current = (mode && (mode as any).value) ? (mode as any).value : 'light'
if (choice.isAmoled) {
return current === 'dark' && amoledEnabled.value
}
return choice.mode === current && !choice.isAmoled && !amoledEnabled.value
}
// Logic to override the parent VPFlyout behavior to be click-based
const setupParentFlyoutOverride = () => {
if (!wrapperRef.value) return
const flyout = wrapperRef.value.closest('.VPFlyout')
if (!flyout) return
// Add class to disable CSS hover via global style
flyout.classList.add('click-based-flyout')
// Find the toggle button
const button = flyout.querySelector('button')
if (!button) return
// Click handler for toggle
const toggleFlyout = (e: MouseEvent) => {
flyout.classList.toggle('open')
}
button.addEventListener('click', toggleFlyout)
// Global click listener to close when clicking outside
const closeFlyout = (e: MouseEvent) => {
if (!flyout.contains(e.target as Node)) {
flyout.classList.remove('open')
}
}
document.addEventListener('click', closeFlyout)
;(wrapperRef.value as any)._cleanup = () => {
flyout.classList.remove('click-based-flyout')
button.removeEventListener('click', toggleFlyout)
document.removeEventListener('click', closeFlyout)
}
}
onMounted(() => {
// defer slightly to ensuring DOM is ready
setTimeout(setupParentFlyoutOverride, 100)
})
onUnmounted(() => {
if (wrapperRef.value && (wrapperRef.value as any)._cleanup) {
;(wrapperRef.value as any)._cleanup()
}
})
</script>
<template>
<div ref="wrapperRef" class="theme-dropdown-wrapper">
<VDropdown
class="theme-dropdown"
theme="theme-selector"
:distance="12"
placement="bottom-end"
:triggers="['click']"
:popper-triggers="['click']"
:auto-hide="true"
>
<button
type="button"
class="theme-dropdown-toggle"
:title="currentChoice.label"
>
<ClientOnly>
<Transition name="fade" mode="out-in">
<div :key="currentChoice.label" :class="[currentChoice.icon, 'text-xl']" />
</Transition>
</ClientOnly>
</button>
<template #popper>
<div class="theme-dropdown-content">
<button
v-for="(choice, index) in modeChoices"
:key="index"
class="theme-dropdown-item"
:class="{ active: isActiveChoice(choice) }"
@click="selectMode(choice)"
v-close-popper
>
<Transition name="fade" mode="out-in">
<div :key="choice.label" :class="[choice.icon, 'text-lg']" />
</Transition>
<span>{{ choice.label }}</span>
<div v-if="isActiveChoice(choice)" class="i-ph-check text-lg ml-auto" />
</button>
</div>
</template>
</VDropdown>
</div>
</template>
<style lang="scss" scoped>
.theme-dropdown-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.theme-dropdown {
display: flex;
align-items: center;
height: 100%;
}
.theme-dropdown-toggle {
display: flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
color: var(--vp-c-text-2);
transition: color 0.5s;
background: transparent;
border: none;
cursor: pointer;
border-radius: 8px;
&:hover {
color: var(--vp-c-text-1);
background: var(--vp-c-bg-elv);
transition: color 0.25s, background 0.25s;
backdrop-filter: blur(12px);
}
}
.theme-dropdown-content {
min-width: 180px;
}
.theme-dropdown-item {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 8px 12px;
background: transparent;
border: none;
border-radius: 6px;
color: var(--vp-c-text-1);
cursor: pointer;
transition: background 0.2s;
font-size: 14px;
text-align: left;
&:hover {
background: var(--vp-c-bg);
}
&.active {
color: var(--vp-c-brand-1);
font-weight: 500;
}
span {
flex: 1;
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.25s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
================================================
FILE: docs/.vitepress/theme/components/ThemeSelector.vue
================================================
<script setup lang="ts">
import { computed } from 'vue'
import { useTheme } from '../themes/themeHandler'
import { themeRegistry } from '../themes/configs'
const { themeName, setTheme, getAvailableThemes, state, mode } = useTheme()
const availableThemes = computed(() => getAvailableThemes())
const getThemePreview = (name: string) => {
const theme = themeRegistry[name]
if (theme?.preview) {
return theme.preview
}
// Fallback: create gradient from theme's brand colors if they exist
const modeKey = (mode && (mode as any).value) ? (mode as any).value : 'light'
const colors = modeKey === 'dark' ? theme?.modes.dark : theme?.modes.light
if (colors?.brand && colors.brand[1] && colors.brand[2] && colors.brand[3]) {
return `linear-gradient(135deg, ${colors.brand[1]} 0%, ${colors.brand[2]} 50%, ${colors.brand[3]} 100%)`
}
return 'linear-gradient(135deg, var(--vp-c-brand-1) 0%, var(--vp-c-brand-2) 100%)'
}
const normalizeThemeName = (name: string) =>
name.replaceAll(/-/g, ' ').charAt(0).toUpperCase() +
name.slice(1).replaceAll(/-/g, ' ')
const currentDisplayName = computed(() => {
const t = themeName && (themeName as any).value ? (themeName as any).value : ''
if (!t) return 'Default'
const cfg = themeRegistry[t]
if (cfg && cfg.displayName) return cfg.displayName
// fallback: humanize the key
return normalizeThemeName(t)
})
</script>
<template>
<div>
<div class="text-sm text-$vp-c-text-2">
<span class="font-medium">Theme:</span>
<span class="ml-1">{{ currentDisplayName }}</span>
</div>
</div>
</template>
================================================
FILE: docs/.vitepress/theme/components/ToggleIndexes.vue
================================================
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import Switch from './Switch.vue'
const isDisabled = ref(false)
const isOn = ref(false)
const syncState = () => {
const root = document.documentElement
isDisabled.value = root.classList.contains('starred-only')
isOn.value = root.classList.contains('indexes-only')
}
let observer: MutationObserver | undefined
onMounted(() =>
(observer = new MutationObserver(syncState)).observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
})
)
onMounted(syncState)
onBeforeUnmount(() => observer?.disconnect())
const toggleIndexes = (value: boolean) => {
if (isDisabled.value) {
isOn.value = document.documentElement.classList.contains('indexes-only')
return
}
const root = document.documentElement
const enabling = value
const wasStarred = root.classList.contains('starred-only')
root.classList.toggle('indexes-only', enabling)
if (enabling) {
root.dataset.starredWasOn = wasStarred ? 'true' : 'false'
if (wasStarred) {
root.classList.remove('starred-only')
}
} else {
if (root.dataset.starredWasOn === 'true') {
root.classList.add('starred-only')
}
delete root.dataset.starredWasOn
}
isOn.value = enabling
}
</script>
<template>
<Switch v-model="isOn"
:disabled="isDisabled"
:class="{ disabled: isDisabled }"@update:modelValue="toggleIndexes" />
</template>
<style>
.indexes-only .vp-doc li:not(.index) {
display: none;
}
</style>
================================================
FILE: docs/.vitepress/theme/components/ToggleStarred.vue
================================================
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import Switch from './Switch.vue'
const isDisabled = ref(false)
const isOn = ref(false)
const syncState = () => {
const root = document.documentElement
isDisabled.value = root.classList.contains('indexes-only')
isOn.value = root.classList.contains('starred-only')
}
let observer: MutationObserver | undefined
onMounted(() =>
(observer = new MutationObserver(syncState)).observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
})
)
onMounted(syncState)
onBeforeUnmount(() => observer?.disconnect())
const toggleStarred = (value: boolean) => {
if (isDisabled.value) {
isOn.value = document.documentElement.classList.contains('starred-only')
return
}
const root = document.documentElement
root.classList.toggle('starred-only', value)
root.dataset.starredWasOn = value ? 'true' : 'false'
isOn.value = value
}
</script>
<template>
<Switch
v-model="isOn"
:disabled="isDisabled"
:class="{ disabled: isDisabled }"
@update:modelValue="toggleStarred"
/>
</template>
<style>
.starred-only .vp-doc li:not(.starred) {
display: none;
}
</style>
================================================
FILE: docs/.vitepress/theme/components/Tooltip.vue
================================================
<script setup lang="ts">
import { withBase } from 'vitepress'
import { computed } from 'vue'
import { useMediaQuery } from '@vueuse/core'
const props = withDefaults(
defineProps<{ title?: string; icon?: string }>(),
{ icon: '/note.svg' }
)
const resolvedIcon = computed(() => withBase(props.icon))
const isHoverable = useMediaQuery('(hover: hover)')
const triggers = computed(() => isHoverable.value ? ['hover'] : ['click'])
</script>
<template>
<VDropdown :triggers="triggers" :popper-triggers="triggers" :delay="{ show: 50, hide: 50 }" :auto-hide="true" :distance="15" placement="auto">
<button
aria-label="Tooltip"
class="text-brand-1 relative inline-flex align-middle items-center justify-center leading-none p-0 select-none font-bold cursor-pointer transition-all h-[1.2em] w-[1.5em]"
>
<div
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1.2 w-[1.3em] h-[1.3em] bg-current transition-all"
:style="{
mask: `url(${resolvedIcon}) no-repeat center / contain`,
'-webkit-mask': `url(${resolvedIcon}) no-repeat center / contain`,
}"
/>
</button>
<template #popper>
<div class="border-$vp-c-divider bg-$vp-c-bg-alt b-rd-4 max-w-md max-h-md border-2 border-solid flex flex-col transition-all overflow-hidden">
<div class="overflow-y-auto p-4">
<h3 v-if="title" class="text-$vp-c-text-1 mb-2 text-lg font-semibold" v-text="title" />
<div class="text-$vp-c-text-1 text-sm content vp-doc">
<slot />
</div>
</div>
</div>
</template>
</VDropdown>
</template>
<style>
.v-popper__popper { --uno: z-5000; }
.v-popper { display: inline-flex !important; }
.v-popper--theme-dropdown .v-popper__inner {
background: transparent !important;
box-shadow: none !important;
border: none !important;
padding: 0 !important;
}
.vp-doc a.tooltip-source-link {
color: inherit;
text-decoration: none;
}
</style>
================================================
FILE: docs/.vitepress/theme/components/VPLocalSearchBox.vue
================================================
<script lang="ts" setup>
/**
* VPLocalSearchBox - Enhanced Local Search Modal Component
*
* Base: VitePress default local search component
*
* Custom Features Added:
* ----------------------
* 1. Fuzzy Search Toggle
* - Toggle between exact and fuzzy matching modes
* - Fuzzy mode includes typo tolerance and multi-word queries
* - Searches both space-separated and dash-separated variants
*
* 2. Smart Highlight Merging (Fuzzy Mode)
* - Automatically merges nearby highlights (< 20px apart) in fuzzy mode
* - Reduces navigation tedium when multiple words are highlighted
* - Preserves text between merged highlights
*
* 3. Match Navigation System
* - Navigate through highlights with left/right arrow keys
* - Visual controls: prev/next buttons with match counter (e.g., "2/5")
* - Smooth scrolling to center the active match
* - Yellow highlight for currently focused match
*
*/
import localSearchIndex from '@localSearchIndex'
import {
computedAsync,
debouncedWatch,
onKeyStroke,
useEventListener,
useLocalStorage,
useScrollLock,
useSessionStorage
} from '@vueuse/core'
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import Mark from 'mark.js/src/vanilla.js'
import MiniSearch, { type SearchResult } from 'minisearch'
import { dataSymbol, inBrowser, useRouter } from 'vitepress'
import {
computed,
createApp,
markRaw,
nextTick,
onBeforeUnmount,
onMounted,
ref,
shallowRef,
triggerRef,
watch,
watchEffect,
type Ref
} from 'vue'
import type { ModalTranslations } from 'vitepress/types/local-search'
import { pathToFile } from 'vitepress/dist/client/app/utils'
import { escapeRegExp } from 'vitepress/dist/client/shared'
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
import { LRUCache } from 'vitepress/dist/client/theme-default/support/lru'
import { createSearchTranslate } from 'vitepress/dist/client/theme-default/support/translation'
import Tooltip from './Tooltip.vue'
import FloatingVue from 'floating-vue'
import { sidebar } from '../../shared'
const emit = defineEmits<{
(e: 'close'): void
}>()
const show = ref(true)
function close() {
show.value = false
}
const el = shallowRef<HTMLElement>()
const resultsEl = shallowRef<HTMLElement>()
/* Search */
const searchIndexData = shallowRef(localSearchIndex)
// Hot Module Replacement - updates search index without full page reload during development
if (import.meta.hot) {
import.meta.hot.accept('/@localSearchIndex', (m) => {
if (m) {
searchIndexData.value = m.default
}
})
}
interface Result {
title: string
titles: string[]
text?: string
}
const vitePressData = useData()
const { activate } = useFocusTrap(el, {
immediate: true,
allowOutsideClick: true,
clickOutsideDeactivates: true,
escapeDeactivates: true
})
const { localeIndex, theme } = vitePressData
/**
* Fuzzy search toggle state.
* - false: Exact phrase matching (default)
* - true: Fuzzy matching with typo tolerance and multi-word queries
* Persisted in localStorage for user preference across sessions.
*/
const isFuzzySearch = useLocalStorage('vitepress:local-search-fuzzy', false)
const searchIndex = computedAsync(async () =>
markRaw(
MiniSearch.loadJSON<Result>(
(await searchIndexData.value[localeIndex.value]?.())?.default,
{
fields: ['title', 'titles', 'text'],
storeFields: ['title', 'titles'],
tokenize: (text: string) =>
text.replace(/[\u2060\u200B]/g, '').split(/[^a-zA-Z0-9\u00C0-\u00FF-]+/).filter((t) => t),
searchOptions: {
fuzzy: false,
prefix: true,
boost: { title: 4, text: 2, titles: 1 },
...(theme.value.search?.provider === 'local' &&
theme.value.search.options?.miniSearch?.searchOptions)
},
...(theme.value.search?.provider === 'local' &&
theme.value.search.options?.miniSearch?.options)
}
)
)
)
const disableQueryPersistence = computed(() => {
return (
theme.value.search?.provider === 'local' &&
theme.value.search.options?.disableQueryPersistence === true
)
})
const filterText = disableQueryPersistence.value
? ref('')
: useSessionStorage('vitepress:local-search-filter', '')
const showDetailedList = useLocalStorage(
'vitepress:local-search-detailed-list',
theme.value.search?.provider === 'local' &&
theme.value.search.options?.detailedView === true
)
const disableDetailedView = computed(() => {
return (
theme.value.search?.provider === 'local' &&
(theme.value.search.options?.disableDetailedView === true ||
theme.value.search.options?.detailedView === false)
)
})
const buttonText = computed(() => {
const options = theme.value.search?.options ?? theme.value.algolia
return (
options?.locales?.[localeIndex.value]?.translations?.button?.buttonText ||
options?.translations?.button?.buttonText ||
'Search'
)
})
watchEffect(() => {
if (disableDetailedView.value) {
showDetailedList.value = false
}
})
const results: Ref<(SearchResult & Result)[]> = shallowRef([])
const enableNoResults = ref(false)
watch(filterText, () => {
enableNoResults.value = false
})
const mark = computedAsync(async () => {
if (!resultsEl.value) return
return markRaw(new Mark(resultsEl.value))
}, null)
// LRU cache for rendered excerpts (16 most recently viewed files)
const cache = new LRUCache<string, Map<string, string>>(16)
/**
* Main search handler - debounced to avoid excessive re-renders while typing.
* Watches: search index, filter text, detail view toggle, and fuzzy search mode.
*/
debouncedWatch(
() => [searchIndex.value, filterText.value, showDetailedList.value, isFuzzySearch.value] as const,
async ([index, filterTextValue, showDetailedListValue, fuzzySearchValue], old, onCleanup) => {
if (old?.[0] !== index) {
// Clear cache on index change (e.g., locale switch or HMR update)
cache.clear()
}
let canceled = false
onCleanup(() => {
canceled = true
})
if (!index) return
/**
* Configure search options based on fuzzy mode.
* Fuzzy search splits multi-word queries and searches for:
* 1. All words present (AND) - matches "PC Optimization Hub"
* 2. Dashed version - matches "PC-Optimization-Hub"
* This allows flexible matching of space-separated or dash-separated content.
*/
const searchOptions = {
fuzzy: isFuzzySearch.value ? 0.2 : false
}
let query: any = filterTextValue
if (isFuzzySearch.value) {
const parts = filterTextValue.split(/\s+/).filter((p) => p)
if (parts.length > 0) {
const dashed = parts.join('-')
query = {
combineWith: 'OR',
queries: [
{
queries: parts,
combineWith: 'AND',
fuzzy: 0.2
},
{
queries: [dashed],
combineWith: 'AND',
fuzzy: 0.2
}
]
}
}
}
/**
* Suffix Search / Substring Matching:
* If the user searches for a suffix (e.g. "abolic"), scan the index for terms
* containing that substring (e.g. "parabolic") and add them to the query.
* This mimics "contains" behavior which is missing in strict prefix search.
*/
if (!isFuzzySearch.value && filterTextValue.length > 2) {
const candidateTerms: string[] = []
const miniSearch = index as any
if (miniSearch._index) {
const it = miniSearch._index.keys()
const match = filterTextValue.toLowerCase()
let result = it.next()
while (!result.done) {
const term = result.value
if (term.includes(match) && term !== match) {
candidateTerms.push(term)
}
result = it.next()
}
}
if (candidateTerms.length > 0) {
// In exact mode, use an explicit OR query.
// This ensures that if the original search term ("arabolic") returns no results
// due to prefix matching, the substring matches ("Parabolic") are still returned.
// A string query might default to AND depending on global config, which would fail here.
query = {
combineWith: 'OR',
queries: [
filterTextValue,
...candidateTerms
]
}
}
}
function findPageTitle(items: any[], path: string): string | null {
for (const item of items) {
if (item.link === path) return item.text
if (item.items) {
const found = findPageTitle(item.items, path)
if (found) return found
}
}
return null
}
const rawResults = index
.search(query, searchOptions)
.slice(0, 16) as (SearchResult & Result)[]
results.value = rawResults.map((r) => {
const [id] = r.id.split('#')
const cleanPath = '/' + id.replace(/\.html$/, '').replace(/^\//, '')
const pageTitle = findPageTitle(Array.isArray(sidebar) ? sidebar : [], cleanPath)
const titles = [...r.titles]
if (pageTitle && !titles.includes(pageTitle) && r.title !== pageTitle) {
titles.unshift(pageTitle)
}
return { ...r, titles }
})
enableNoResults.value = true
// Fetch and process excerpts for detailed view highlighting
const mods = showDetailedListValue
? await Promise.all(results.value.map((r) => fetchExcerpt(r.id)))
: []
if (canceled) return
await processExcerpts(mods, vitePressData, () => canceled)
if (canceled) return
const terms = new Set<string>()
results.value = results.value.map((r) => {
const [id, anchor] = r.id.split('#')
const map = cache.get(id)
const text = map?.get(anchor) ?? ''
if (isFuzzySearch.value) {
for (const term in r.match) {
terms.add(term)
}
}
return { ...r, text }
})
if (!isFuzzySearch.value) {
// Only highlight search term if results exist to avoid highlighting "No results" message
if (results.value.length > 0) {
terms.add(filterTextValue)
}
results.value = filterResults(results.value, filterTextValue)
}
await nextTick()
if (canceled) return
await new Promise((r) => {
mark.value?.unmark({
done: () => {
mark.value?.markRegExp(formMarkRegex(terms), { done: r })
}
})
})
/**
* Custom feature: Merge nearby highlights in fuzzy mode.
* Combines individual word highlights that are close together (< 20px apart)
* into single continuous highlights, reducing navigation tedium.
*/
if (isFuzzySearch.value) {
await mergeNearbyMarks()
}
const excerpts = Array.from(el.value?.querySelectorAll('.result .excerpt') ?? []) as HTMLElement[]
for (const excerpt of excerpts) {
const mark = excerpt.querySelector('mark[data-markjs="true"]') as HTMLElement | null
if (mark) {
const markTop = mark.offsetTop
const markHeight = mark.offsetHeight
const excerptHeight = excerpt.clientHeight
excerpt.scrollTop = markTop - excerptHeight / 2 + markHeight / 2
}
}
/**
* Custom feature: Initialize match navigation state.
* Collects all highlight marks in each result for prev/next navigation.
* Each result tracks its own array of marks and current position.
*/
const newResultMarks = new Map<number, HTMLElement[]>()
const newCurrentMarkIndex = new Map<number, number>()
results.value.forEach((_, index) => {
const item = el.value?.querySelector(`#localsearch-item-${index}`)
const marks = Array.from(item?.querySelectorAll('.excerpt mark[data-markjs="true"]') ?? []) as HTMLElement[]
if (marks.length > 0) {
newResultMarks.set(index, marks)
newCurrentMarkIndex.set(index, 0)
}
})
resultMarks.value = newResultMarks
currentMarkIndex.value = newCurrentMarkIndex
// Reset scroll position to top
if (resultsEl.value) {
resultsEl.value.scrollTop = 0
}
},
{ debounce: 200, immediate: true }
)
/* Custom Feature: Match Navigation State */
const resultMarks = shallowRef<Map<number, HTMLElement[]>>(new Map())
const currentMarkIndex = shallowRef<Map<number, number>>(new Map())
/**
* Merges adjacent highlight marks that are visually close together.
* This reduces the number of navigation stops in fuzzy mode where
* each individual word match would otherwise be a separate highlight.
*
* Merging criteria:
* - Marks must be on the same line (within 5px vertical distance)
* - Marks must be close horizontally (< 20px apart)
*/
async function mergeNearbyMarks() {
const excerpts = Array.from(el.value?.querySelectorAll('.result .excerpt') ?? [])
for (const excerpt of excerpts) {
const marks = Array.from(excerpt.querySelectorAll('mark[data-markjs="true"]')) as HTMLElement[]
if (marks.length <= 1) continue
// Process marks to merge those within 20 characters of each other
let i = 0
while (i < marks.length - 1) {
const currentMark = marks[i]
const nextMark = marks[i + 1]
// Ensure they are siblings to safely merge
if (currentMark.parentNode !== nextMark.parentNode) {
i++
continue
}
// Calculate distance between marks
const currentEnd = currentMark.offsetLeft + currentMark.offsetWidth
const nextStart = nextMark.offsetLeft
const distance = nextStart - currentEnd
// Also check if they're on the same line (similar offsetTop)
const onSameLine = Math.abs(currentMark.offsetTop - nextMark.offsetTop) < 5
// Merge if they're close (within 20px) and on the same line
if (distance >= 0 && distance < 20 && onSameLine) {
// Collect text between and remove intermediate nodes
let textBetween = ''
let node = currentMark.nextSibling
while (node && node !== nextMark) {
textBetween += node.textContent || ''
const next = node.nextSibling
node.parentNode?.removeChild(node)
node = next
}
const mergedText = currentMark.textContent + textBetween + nextMark.textContent
currentMark.textContent = mergedText
// Remove the next mark
nextMark.remove()
marks.splice(i + 1, 1)
} else {
i++
}
}
}
}
/**
* Custom feature: Navigate to the next highlighted match in the current result.
* Cycles back to the first match when reaching the end.
* Smoothly scrolls the excerpt to center the highlighted match.
*/
function nextMatch(index: number) {
const marks = resultMarks.value.get(index)
let curr = currentMarkIndex.value.get(index) ?? 0
if (!marks) return
// Remove 'current' class from previous mark
marks[curr].classList.remove('current')
curr = (curr + 1) % marks.length
currentMarkIndex.value.set(index, curr)
triggerRef(currentMarkIndex)
// Add 'current' class to new mark
const newMark = marks[curr]
newMark.classList.add('current')
const excerpt = newMark.closest('.excerpt')
if (excerpt) {
const markTop = newMark.offsetTop
const markHeight = newMark.offsetHeight
const excerptHeight = excerpt.clientHeight
excerpt.scrollTo({
top: markTop - excerptHeight / 2 + markHeight / 2,
behavior: 'smooth'
})
}
}
/**
* Custom feature: Navigate to the previous highlighted match in the current result.
* Cycles to the last match when going before the first.
* Smoothly scrolls the excerpt to center the highlighted match.
*/
function prevMatch(index: number) {
const marks = resultMarks.value.get(index)
let curr = currentMarkIndex.value.get(index) ?? 0
if (!marks) return
// Remove 'current' class from previous mark
marks[curr].classList.remove('current')
curr = (curr - 1 + marks.length) % marks.length
currentMarkIndex.value.set(index, curr)
triggerRef(currentMarkIndex)
// Add 'current' class to new mark
const newMark = marks[curr]
newMark.classList.add('current')
const excerpt = newMark.closest('.excerpt')
if (excerpt) {
const markTop = newMark.offsetTop
const markHeight = newMark.offsetHeight
const excerptHeight = excerpt.clientHeight
excerpt.scrollTo({
top: markTop - excerptHeight / 2 + markHeight / 2,
behavior: 'smooth'
})
}
}
async function fetchExcerpt(id: string) {
const file = pathToFile(id.slice(0, id.indexOf('#')))
try {
if (!file) throw new Error(`Cannot find file for id: ${id}`)
return { id, mod: await import(/*@vite-ignore*/ file) }
} catch (e) {
console.error(e)
return { id, mod: {} }
}
}
async function processExcerpts(
mods: { id: string; mod: any }[],
vitePressData: any,
isCanceled: () => boolean
) {
for (const { id, mod } of mods) {
if (isCanceled()) return
const mapId = id.slice(0, id.indexOf('#'))
let map = cache.get(mapId)
if (map) continue
map = new Map()
cache.set(mapId, map)
const comp = mod.default ?? mod
if (comp?.render || comp?.setup) {
const app = createApp(comp)
app.use(FloatingVue)
app.component('Tooltip', Tooltip)
app.config.warnHandler = () => {}
app.provide(dataSymbol, vitePressData)
Object.defineProperties(app.config.globalProperties, {
$frontmatter: {
get() {
return vitePressData.frontmatter.value
}
},
$params: {
get() {
return vitePressData.page.value.params
}
}
})
const div = document.createElement('div')
app.mount(div)
const headings = div.querySelectorAll('h1, h2, h3, h4, h5, h6')
headings.forEach((el) => {
const href = el.querySelector('a')?.getAttribute('href')
const anchor = href?.startsWith('#') && href.slice(1)
if (!anchor) return
let html = ''
while ((el = el.nextElementSibling!) && !/^h[1-6]$/i.test(el.tagName))
html += el.outerHTML
map!.set(anchor, html)
})
app.unmount()
}
}
}
function filterResults(results: (SearchResult & Result)[], filterTextValue: string) {
return results.filter((r) => {
const phrase = filterTextValue.toLowerCase()
const inText = r.text?.toLowerCase().includes(phrase)
const inTitle = r.title.toLowerCase().includes(phrase)
const inTitles = r.titles.some((t) => t.toLowerCase().includes(phrase))
return inText || inTitle || inTitles
})
}
/* Search input focus */
const searchInput = ref<HTMLInputElement>()
const disableReset = computed(() => {
return filterText.value?.length <= 0
})
function focusSearchInput(select = true) {
searchInput.value?.focus()
select && searchInput.value?.select()
}
onMounted(() => {
focusSearchInput()
})
function onSearchBarClick(event: PointerEvent) {
if (event.pointerType === 'mouse') {
focusSearchInput(false)
}
}
/* Search keyboard selection */
const selectedIndex = ref(-1)
const disableMouseOver = ref(true)
watch(results, (r) => {
selectedIndex.value = r.length ? 0 : -1
scrollToSelectedResult()
})
function scrollToSelectedResult() {
nextTick(() => {
const selectedEl = document.querySelector('.result.selected')
selectedEl?.scrollIntoView({ block: 'nearest' })
})
}
onKeyStroke('ArrowUp', (event) => {
event.preventDefault()
if (resultsEl.value && document.activeElement === resultsEl.value && selectedIndex.value === 0) {
selectedIndex.value = -1
searchInput.value?.focus()
return
}
if (resultsEl.value && document.activeElement === searchInput.value) {
resultsEl.value.focus()
// Fall through to wrap to bottom
}
selectedIndex.value--
if (selectedIndex.value < 0) {
selectedIndex.value = results.value.length - 1
}
disableMouseOver.value = true
scrollToSelectedResult()
})
onKeyStroke('ArrowDown', (event) => {
event.preventDefault()
if (resultsEl.value && document.activeElement === searchInput.value) {
resultsEl.value.focus()
// Fall through to select first item (from -1 to 0)
}
selectedIndex.value++
if (selectedIndex.value >= results.value.length) {
selectedIndex.value = 0
}
disableMouseOver.value = true
scrollToSelectedResult()
})
const router = useRouter()
onKeyStroke('Enter', (e) => {
if (e.isComposing) return
if (e.target instanceof HTMLButtonElement && e.target.type !== 'submit')
return
const selectedPackage = results.value[selectedIndex.value]
if (e.target instanceof HTMLInputElement && !selectedPackage) {
e.preventDefault()
return
}
if (selectedPackage) {
router.go(selectedPackage.id)
close()
}
})
onKeyStroke('Escape', () => {
close()
})
/**
* Custom feature: Keyboard navigation for cycling through highlights.
* Left/Right arrow keys navigate prev/next match within the selected result.
* Only active when detailed view is enabled and matches exist.
*/
onKeyStroke('ArrowLeft', (event) => {
// Navigate to previous match - only when viewing detailed excerpts with highlights
const targetIndex = selectedIndex.value === -1 ? 0 : selectedIndex.value
if (showDetailedList.value && (resultMarks.value.get(targetIndex)?.length ?? 0) > 0) {
if (document.activeElement === searchInput.value) {
// Only hijack if modifier is pressed
if (!event.altKey && !event.ctrlKey) return
}
event.preventDefault()
prevMatch(targetIndex)
}
})
onKeyStroke('ArrowRight', (event) => {
// Navigate to next match - only when viewing detailed excerpts with highlights
const targetIndex = selectedIndex.value === -1 ? 0 : selectedIndex.value
if (showDetailedList.value && (resultMarks.value.get(targetIndex)?.length ?? 0) > 0) {
if (document.activeElement === searchInput.value) {
if (event.shiftKey) return
if (event.altKey || event.ctrlKey) {
// Allow modifier to force nav
} else {
// Shortcut: If at end of input, go to next match AND focus results
const { selectionStart, selectionEnd, value } = searchInput.value!
if (selectionStart !== value.length || selectionEnd !== value.length) return
// Use the target index (0) if we were at -1
if (selectedIndex.value === -1) selectedIndex.value = 0
resultsEl.value?.focus()
}
}
event.preventDefault()
nextMatch(targetIndex) // Use targetIndex as we might have just updated selectedIndex from -1 to 0 or kept valid index
}
})
// Translations
const defaultTranslations: { modal: ModalTranslations } = {
modal: {
displayDetails: 'Display detailed list',
resetButtonTitle: 'Reset search',
backButtonTitle: 'Close search',
noResultsText: 'No results for',
footer: {
selectText: 'to select',
selectKeyAriaLabel: 'enter',
navigateText: 'to navigate',
navigateUpKeyAriaLabel: 'up arrow',
navigateDownKeyAriaLabel: 'down arrow',
closeText: 'to close',
closeKeyAriaLabel: 'escape'
}
}
}
const translate = createSearchTranslate(defaultTranslations)
// Back
onMounted(() => {
// Prevents going to previous site
window.history.pushState(null, '', null)
})
useEventListener('popstate', (event) => {
event.preventDefault()
close()
})
/** Lock body */
const isLocked = useScrollLock(inBrowser ? document.body : null)
onMounted(() => {
nextTick(() => {
isLocked.value = true
nextTick().then(() => activate())
})
})
onBeforeUnmount(() => {
isLocked.value = false
})
function resetSearch() {
filterText.value = ''
nextTick().then(() => focusSearchInput(false))
}
function handleInput(e: Event) {
filterText.value = (e.target as HTMLInputElement).value
}
function toggleFuzzySearch() {
isFuzzySearch.value = !isFuzzySearch.value
}
function formMarkRegex(terms: Set<string>) {
return new RegExp(
[...terms]
.sort((a, b) => b.length - a.length)
.map((term) => `(${escapeRegExp(term)})`)
.join('|'),
'gi'
)
}
function onMouseMove(e: MouseEvent) {
if (!disableMouseOver.value) return
const el = (e.target as HTMLElement)?.closest<HTMLElement>('.result-item')
const index = el?.dataset?.index ? Number.parseInt(el.dataset.index) : -1
if (index >= 0 && index !== selectedIndex.value) {
selectedIndex.value = index
}
disableMouseOver.value = false
}
</script>
<template>
<Teleport to="body">
<Transition name="vp-local-search" appear :duration="200" @after-leave="$emit('close')">
<div
v-if="show"
ref="el"
role="button"
:aria-owns="results?.length ? 'localsearch-list' : undefined"
aria-expanded="true"
aria-haspopup="listbox"
aria-labelledby="localsearch-label"
class="VPLocalSearchBox"
>
<div class="backdrop" @click="close" />
<div class="shell">
<form
class="search-bar"
@pointerup="onSearchBarClick($event)"
@submit.prevent=""
>
<label
:title="buttonText"
id="localsearch-label"
for="localsearch-input"
>
<span aria-hidden="true" class="vpi-search search-icon local-search-icon" />
</label>
<div class="search-actions before">
<button
class="back-button"
:title="translate('modal.backButtonTitle')"
@click="close"
>
<span class="vpi-arrow-left local-search-icon" />
</button>
</div>
<input
ref="searchInput"
:value="filterText"
@input="handleInput"
:aria-activedescendant="selectedIndex > -1 ? ('localsearch-item-' + selectedIndex) : undefined"
aria-autocomplete="both"
:aria-controls="results?.length ? 'localsearch-list' : undefined"
aria-labelledby="localsearch-label"
autocapitalize="off"
autocomplete="off"
autocorrect="off"
class="search-input"
id="localsearch-input"
enterkeyhint="go"
maxlength="64"
:placeholder="buttonText"
spellcheck="false"
type="search"
/>
<div class="search-actions">
<button
v-if="!disableDetailedView"
class="toggle-layout-button"
type="button"
:class="{ 'detailed-list': showDetailedList }"
:title="translate('modal.displayDetails')"
@click="
selectedIndex > -1 && (showDetailedList = !showDetailedList)
"
>
<span class="vpi-layout-list local-search-icon" />
</button>
<button
class="toggle-fuzzy-button"
type="button"
:class="{ 'fuzzy-active': isFuzzySearch }"
:title="isFuzzySearch ? 'Switch to Exact Search' : 'Switch to Fuzzy Search'"
@click="toggleFuzzySearch"
>
<span v-if="isFuzzySearch" class="fuzzy-icon">~</span>
<span v-else class="exact-icon">=</span>
</button>
<button
class="clear-button"
type="reset"
:disabled="disableReset"
:title="translate('modal.resetButtonTitle')"
@click="resetSearch"
>
<span class="vpi-delete local-search-icon" />
</button>
</div>
</form>
<ul
ref="resultsEl"
:id="results?.length ? 'localsearch-list' : undefined"
:role="results?.length ? 'listbox' : undefined"
:aria-labelledby="results?.length ? 'localsearch-label' : undefined"
class="results"
tabindex="-1"
@mousemove="onMouseMove"
>
<li
v-for="(p, index) in results"
:key="p.id"
:id="'localsearch-item-' + index"
:aria-selected="selectedIndex === index ? 'true' : 'false'"
role="option"
class="result-item"
:data-index="index"
>
<a
:href="p.id"
class="result"
:class="{
selected: selectedIndex === index
}"
:aria-label="[...p.titles, p.title].join(' > ')"
@mouseenter="!disableMouseOver && (selectedIndex = index)"
@focusin="selectedIndex = index"
@click="close"
:data-index="index"
>
<div>
<div class="titles">
<span class="title-icon">#</span>
<span
v-for="(t, index) in p.titles"
:key="index"
class="title"
>
<span class="text" v-html="t" />
<span class="vpi-chevron-right local-search-icon" />
</span>
<span class="title main">
<span class="text" v-html="p.title" />
</span>
</div>
<div v-if="showDetailedList" class="excerpt-wrapper">
<div v-if="p.text" class="excerpt" inert>
<div class="vp-doc" v-html="p.text" />
</div>
<div class="excerpt-gradient-bottom" />
<div class="excerpt-gradient-top" />
</div>
</div>
</a>
<div
v-if="showDetailedList && (resultMarks.get(index)?.length ?? 0) > 1"
class="excerpt-actions"
>
<button type="button" class="match-nav-button" @click="prevMatch(index)" title="Previous match">
<span class="vpi-chevron-left navigate-icon" />
</button>
<span class="match-count">{{ (currentMarkIndex.get(index) ?? 0) + 1 }}/{{ resultMarks.get(index)?.length }}</span>
<button type="button" class="match-nav-button" @click="nextMatch(index)" title="Next match">
<span class="vpi-chevron-right navigate-icon" />
</button>
</div>
</li>
<li
v-if="filterText && !results.length && enableNoResults"
class="no-results"
>
{{ translate('modal.noResultsText') }} "{{ filterText }}"
</li>
</ul>
<div class="search-keyboard-shortcuts">
<span>
<kbd :aria-label="translate('modal.footer.navigateUpKeyAriaLabel')">
<span class="vpi-arrow-up navigate-icon" />
</kbd>
<kbd :aria-label="translate('modal.footer.navigateDownKeyAriaLabel')">
<span class="vpi-arrow-down navigate-icon" />
</kbd>
{{ translate('modal.footer.navigateText') }}
</span>
<span>
<kbd :aria-label="translate('modal.footer.selectKeyAriaLabel')">
<span class="vpi-corner-down-left navigate-icon" />
</kbd>
{{ translate('modal.footer.selectText') }}
</span>
<span v-if="showDetailedList">
<kbd>
<span class="vpi-arrow-left navigate-icon" />
</kbd>
<kbd>
<span class="vpi-arrow-right navigate-icon" />
</kbd>
to cycle matches
</span>
<span>
<kbd :aria-label="translate('modal.footer.closeKeyAriaLabel')">esc</kbd>
{{ translate('modal.footer.closeText') }}
</span>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.VPLocalSearchBox {
position: fixed;
z-index: 100;
inset: 0;
display: flex;
}
.backdrop {
position: absolute;
inset: 0;
background: var(--vp-backdrop-bg-color);
}
.shell {
position: relative;
padding: 12px;
margin: 64px auto;
display: flex;
flex-direction: column;
gap: 16px;
background: var(--vp-local-search-bg);
width: min(100vw - 60px, 900px);
height: min-content;
max-height: min(100vh - 128px, 900px);
border-radius: 6px;
}
@media (max-width: 767px) {
.shell {
margin: 0;
width: 100vw;
height: 100vh;
max-height: none;
border-radius: 0;
}
}
.search-bar {
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
display: flex;
align-items: center;
padding: 0 12px;
cursor: text;
}
@media (max-width: 767px) {
.search-bar {
padding: 0 8px;
}
}
.search-bar:focus-within {
border-color: var(--vp-c-brand-1);
}
.local-search-icon {
display: block;
font-size: 18px;
}
.navigate-icon {
display: block;
font-size: 14px;
}
.search-icon {
margin: 8px;
}
@media (max-width: 767px) {
.search-icon {
display: none;
}
}
.search-input {
padding: 6px 12px;
font-size: inherit;
width: 100%;
}
/* Custom Feature: Match navigation controls overlay */
.result-item {
position: relative;
}
.excerpt-actions {
position: absolute;
/* (12px margin + 2px border + 5px spacing) */
bottom: 19px;
right: 19px;
z-index: 2000;
cursor: default;
display: flex;
align-items: center;
gap: 4px;
background-color: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
padding: 2px 4px;
box-shadow: var(--vp-shadow-1);
}
@media (max-width: 767px) {
.excerpt-actions {
/* (8px margin + 2px border + 5px spacing) */
bottom: 15px;
right: 15px;
}
}
.match-nav-button {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 2px;
color: var(--vp-c-text-2);
transition: color 0.2s, background-color 0.2s;
cursor: pointer;
}
.match-nav-button:hover {
color: var(--vp-c-text-1);
background-color: var(--vp-c-bg-soft);
}
.match-count {
font-size: 11px;
font-family: var(--vp-font-family-mono);
color: var(--vp-c-text-2);
user-select: none;
min-width: 24px;
text-align: center;
}
@media (max-width: 767px) {
.search-input {
padding: 6px 4px;
}
}
.search-actions {
display: flex;
gap: 4px;
}
@media (any-pointer: coarse) {
.search-actions {
gap: 8px;
}
}
@media (min-width: 769px) {
.search-actions.before {
display: none;
}
}
.search-actions button {
padding: 8px;
}
.search-actions button:not([disabled]):hover,
.toggle-layout-button.detailed-list {
color: var(--vp-c-brand-1);
}
.search-actions button.clear-button:disabled {
opacity: 0.37;
}
/* Custom Feature: Fuzzy search toggle button */
.toggle-fuzzy-button {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
font-size: 16px;
font-weight: bold;
border-radius: 4px;
transition: all 0.2s;
}
.toggle-fuzzy-button .fuzzy-icon,
.toggle-fuzzy-button .exact-icon {
font-size: 18px;
font-weight: bold;
line-height: 1;
}
.toggle-fuzzy-button:hover {
background: var(--vp-c-bg-soft);
}
.toggle-fuzzy-button.fuzzy-active {
color: var(--vp-c-brand-1);
background: var(--vp-c-bg-soft);
}
.search-keyboard-shortcuts {
font-size: 0.8rem;
opacity: 75%;
display: flex;
flex-wrap: wrap;
gap: 16px;
line-height: 14px;
}
.search-keyboard-shortcuts span {
display: flex;
align-items: center;
gap: 4px;
}
@media (max-width: 767px) {
.search-keyboard-shortcuts {
display: none;
}
}
.search-keyboard-shortcuts kbd {
background: rgba(128, 128, 128, 0.1);
border-radius: 4px;
padding: 3px 6px;
min-width: 24px;
display: inline-block;
text-align: center;
vertical-align: middle;
border: 1px solid rgba(128, 128, 128, 0.15);
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1);
}
.results {
display: flex;
flex-direction: column;
outline: none;
gap: 6px;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
}
.result {
display: flex;
align-items: center;
gap: 8px;
border-radius: 4px;
transition: none;
line-height: 1rem;
border: solid 2px var(--vp-local-search-result-border);
outline: none;
}
.result > div {
margin: 12px;
width: 100%;
overflow: hidden;
}
@media (max-width: 767px) {
.result > div {
margin: 8px;
}
}
.titles {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
position: relative;
z-index: 1001;
padding: 2px 0;
}
.title {
display: flex;
align-items: center;
gap: 4px;
}
.title-icon + .title .text {
font-weight: 600;
border-bottom: 1px solid var(--vp-c-brand-1);
}
.title.main {
font-weight: 500;
}
.title-icon {
opacity: 0.5;
font-weight: 500;
color: var(--vp-c-brand-1);
}
.title svg {
opacity: 0.5;
}
.result.selected {
--vp-local-search-result-bg: var(--vp-local-search-result-selected-bg);
border-color: var(--vp-local-search-result-selected-border);
}
.excerpt-wrapper {
position: relative;
}
.excerpt {
pointer-events: none;
max-height: 140px;
overflow: hidden;
position: relative;
margin-top: 4px;
}
@media (hover: hover) {
.excerpt {
opacity: 0.5;
}
}
.result.selected .excerpt {
opacity: 1;
}
.excerpt :deep(*) {
font-size: 0.8rem !important;
line-height: 130% !important;
}
/* Ensure excerpt content is visible and correctly styled */
.excerpt :deep(*) {
font-size: 0.8rem !important;
line-height: 130% !important;
}
.excerpt :deep(li) {
display: list-item !important;
}
/* Highlight styles - default state */
.titles :deep(mark),
.excerpt :deep(mark) {
background-color: var(--vp-local-search-highlight-bg);
color: var(--vp-local-search-highlight-text);
border-radius: 2px;
padding: 0 1px;
transition: background-color 0.2s;
}
/* Custom Feature: Currently focused highlight (during navigation) */
.excerpt :deep(mark.current) {
background-color: var(--vp-c-yellow-3);
color: #000;
font-weight: bold;
}
.excerpt :deep(.vp-code-group) .tabs {
display: none;
}
.excerpt :deep(.vp-code-group) div[class*='language-'] {
border-radius: 8px !important;
}
.excerpt-gradient-bottom {
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 8px;
background: linear-gradient(transparent, var(--vp-local-search-result-bg));
z-index: 1000;
}
.excerpt-gradient-top {
position: absolute;
top: -1px;
left: 0;
width: 100%;
height: 8px;
background: linear-gradient(var(--vp-local-search-result-bg), transparent);
z-index: 1000;
}
.result.selected .titles,
.result.selected .title-icon {
color: var(--vp-c-brand-1) !important;
}
.no-results {
font-size: 0.9rem;
text-align: center;
padding: 12px;
}
svg {
flex: none;
}
@keyframes vp-backdrop-enter {
from { opacity: 0; }
to { opacity: 1; }
}
.vp-local-search-enter-active .backdrop {
animation: vp-backdrop-enter 0.2s ease-out both;
}
.vp-local-search-leave-active {
animation: vp-backdrop-enter 0.2s ease-in reverse both;
}
</style>
=========
gitextract_e2k2ctcw/ ├── .devcontainer/ │ └── devcontainer.json ├── .dockerignore ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ └── wiki.yml │ ├── POST_TEMPLATE.md │ ├── README.md │ ├── assets/ │ │ └── nginx.conf │ └── workflows/ │ ├── deploy-api.yml │ └── deploy-gh-pages.yml ├── .gitignore ├── .gitpod.yml ├── .licenserc.json ├── .mise.toml ├── .npmrc ├── .prettierignore ├── .prettierrc.yaml ├── Dockerfile ├── api/ │ ├── README.md │ ├── middleware/ │ │ ├── cors.ts │ │ └── ratelimit.ts │ ├── routes/ │ │ ├── feedback.post.ts │ │ ├── index.ts │ │ └── single-page.ts │ ├── tsconfig.json │ └── worker-configuration.d.ts ├── docker-compose.yaml ├── docs/ │ ├── .vitepress/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── config.mts │ │ ├── constants.ts │ │ ├── fonts/ │ │ │ ├── Inter-Bold.otf │ │ │ ├── Inter-Medium.otf │ │ │ ├── Inter-Regular.otf │ │ │ └── Inter-SemiBold.otf │ │ ├── hooks/ │ │ │ ├── Template.vue │ │ │ ├── index.ts │ │ │ ├── meta.ts │ │ │ ├── opengraph.ts │ │ │ ├── rss.ts │ │ │ └── satoriConfig.ts │ │ ├── markdown/ │ │ │ ├── base64.ts │ │ │ ├── emoji.ts │ │ │ ├── headers.ts │ │ │ └── toggleStarred.ts │ │ ├── notes/ │ │ │ ├── 1337x-ranks.md │ │ │ ├── CodeRabbit.md │ │ │ ├── advanced-logic-calculators.md │ │ │ ├── affine-note.md │ │ │ ├── alt-twitch-player-extensions.md │ │ │ ├── alt-warp-clients.md │ │ │ ├── android-spotify-note.md │ │ │ ├── apkmirror-extensions.md │ │ │ ├── audiobookbay-warning.md │ │ │ ├── aurora-note.md │ │ │ ├── better-reasoning.md │ │ │ ├── bookmarkeddit.md │ │ │ ├── buster-note.md │ │ │ ├── buzzheavier-warning.md │ │ │ ├── bypass-freedlink.md │ │ │ ├── captcha-4pda.md │ │ │ ├── chatgpt-limits.md │ │ │ ├── clipboard2file-addons.md │ │ │ ├── cofi-note.md │ │ │ ├── crystaldiskinfo.md │ │ │ ├── csrin-search.md │ │ │ ├── cute-save-button-icon.md │ │ │ ├── dodi-warning.md │ │ │ ├── dolby-access-atmos-note.md │ │ │ ├── driver-note.md │ │ │ ├── eaglercraft-note.md │ │ │ ├── eruda.md │ │ │ ├── filebin-warning.md │ │ │ ├── filelu-warning.md │ │ │ ├── filezilla-warning.md │ │ │ ├── flicker-proxy.md │ │ │ ├── fluxy-repacks.md │ │ │ ├── forest-extensions.md │ │ │ ├── foxit-warning.md │ │ │ ├── freegogpcgames-note.md │ │ │ ├── gemai.md │ │ │ ├── general-tweak-warning.md │ │ │ ├── glitchwave-note.md │ │ │ ├── google-song-identification.md │ │ │ ├── google-translate-note.md │ │ │ ├── hdo-box-note.md │ │ │ ├── hugging-face-warning.md │ │ │ ├── instaeclipse-note.md │ │ │ ├── irc-highway-note.md │ │ │ ├── jdownloader-warning.md │ │ │ ├── limit-bypass-note.md │ │ │ ├── liteapk-modyolo-note.md │ │ │ ├── malware-removal-forums.md │ │ │ ├── megabasterd-note.md │ │ │ ├── mobilism-ranks.md │ │ │ ├── modelscope.md │ │ │ ├── mori-note.md │ │ │ ├── movie-web-sources.md │ │ │ ├── movieparadise-code.md │ │ │ ├── mp-opensubs.md │ │ │ ├── mvsep-note.md │ │ │ ├── oneclick-note.md │ │ │ ├── openasar.md │ │ │ ├── openrgb-beta.md │ │ │ ├── pollinations-limits.md │ │ │ ├── printeditwe-addons.md │ │ │ ├── proton-torrenting.md │ │ │ ├── reaper-note.md │ │ │ ├── redditfilter-note.md │ │ │ ├── rgshows-autoplay.md │ │ │ ├── sanet-warning.md │ │ │ ├── savepagewe.md │ │ │ ├── scrollanywhere-addons.md │ │ │ ├── sd-maid.md │ │ │ ├── sh-note.md │ │ │ ├── site-favicon-dl.md │ │ │ ├── soft98-note.md │ │ │ ├── sora.md │ │ │ ├── spacewar.md │ │ │ ├── spicetify-note.md │ │ │ ├── sport7.md │ │ │ ├── steam-controller-support.md │ │ │ ├── steam-currency-converter-note.md │ │ │ ├── tabiverse-extensions.md │ │ │ ├── tautulli-note.md │ │ │ ├── teamspeak-warning.md │ │ │ ├── thunderbird.md │ │ │ ├── tinyurl-note.md │ │ │ ├── video-downloadhelper.md │ │ │ ├── welib-note.md │ │ │ ├── winrar.md │ │ │ ├── yet-another-call-blocker-note.md │ │ │ ├── youtube-tweaks.md │ │ │ └── yts-yify-note.md │ │ ├── shared.ts │ │ ├── theme/ │ │ │ ├── Appearance.vue │ │ │ ├── Layout.vue │ │ │ ├── PostLayout.vue │ │ │ ├── Posts.vue │ │ │ ├── components/ │ │ │ │ ├── Announcement.vue │ │ │ │ ├── Authors.vue │ │ │ │ ├── Base64Dialog.vue │ │ │ │ ├── CardField.vue │ │ │ │ ├── ColorPicker.vue │ │ │ │ ├── Feedback.vue │ │ │ │ ├── InputField.vue │ │ │ │ ├── SidebarCard.vue │ │ │ │ ├── Switch.vue │ │ │ │ ├── ThemeDropdown.vue │ │ │ │ ├── ThemeSelector.vue │ │ │ │ ├── ToggleIndexes.vue │ │ │ │ ├── ToggleStarred.vue │ │ │ │ ├── Tooltip.vue │ │ │ │ ├── VPLocalSearchBox.vue │ │ │ │ ├── VPNav.vue │ │ │ │ ├── WallpaperCard.vue │ │ │ │ └── startpage/ │ │ │ │ ├── Bookmarks.vue │ │ │ │ ├── Clock.vue │ │ │ │ ├── SearchBar.vue │ │ │ │ └── Startpage.vue │ │ │ ├── composables/ │ │ │ │ └── nprogress.ts │ │ │ ├── index.ts │ │ │ ├── posts.data.ts │ │ │ ├── style.scss │ │ │ └── themes/ │ │ │ ├── README.md │ │ │ ├── configs/ │ │ │ │ ├── catppuccin.ts │ │ │ │ ├── christmas.ts │ │ │ │ ├── index.ts │ │ │ │ └── monochrome.ts │ │ │ ├── index.ts │ │ │ ├── themeHandler.ts │ │ │ └── types.ts │ │ ├── transformer/ │ │ │ ├── constants.ts │ │ │ └── core.ts │ │ ├── transformer.ts │ │ ├── types/ │ │ │ └── Feedback.ts │ │ ├── utils/ │ │ │ ├── markdown.ts │ │ │ └── tooltips.ts │ │ ├── utils.ts │ │ └── vue-shim.d.ts │ ├── ai.md │ ├── audio.md │ ├── beginners-guide.md │ ├── developer-tools.md │ ├── downloading.md │ ├── educational.md │ ├── feedback.md │ ├── file-tools.md │ ├── gaming-tools.md │ ├── gaming.md │ ├── image-tools.md │ ├── index.md │ ├── internet-tools.md │ ├── linux-macos.md │ ├── misc.md │ ├── mobile.md │ ├── non-english.md │ ├── other/ │ │ ├── FAQ.md │ │ ├── backups.md │ │ ├── contributing.md │ │ ├── selfhosting.md │ │ └── wallpapers.md │ ├── posts/ │ │ ├── FCC.md │ │ ├── Internet-Archive.md │ │ ├── KeepAndroidOpen.md │ │ ├── Nov-2025.md │ │ ├── WWH.md │ │ ├── april-2023.md │ │ ├── april-2024.md │ │ ├── april-2025.md │ │ ├── aug-2023.md │ │ ├── aug-2024.md │ │ ├── aug-2025.md │ │ ├── changelog-sites.md │ │ ├── dec-2023.md │ │ ├── dec-2024.md │ │ ├── dec-2025.md │ │ ├── discord.md │ │ ├── feb-2024.md │ │ ├── feb-2025.md │ │ ├── feb-2026.md │ │ ├── jan-2024.md │ │ ├── jan-2025.md │ │ ├── jan-2026.md │ │ ├── july-2023.md │ │ ├── july-2024.md │ │ ├── july-2025.md │ │ ├── jun-2023.md │ │ ├── june-2024.md │ │ ├── june-2025.md │ │ ├── mar-2025.md │ │ ├── mar-2026.md │ │ ├── march-2024.md │ │ ├── may-2023.md │ │ ├── may-2024.md │ │ ├── may-2025.md │ │ ├── new-site.md │ │ ├── nov-2023.md │ │ ├── nov-2024.md │ │ ├── oct-2023.md │ │ ├── oct-2024.md │ │ ├── oct-2025.md │ │ ├── search.md │ │ ├── sept-2023.md │ │ ├── sept-2024.md │ │ ├── sept-2025.md │ │ └── support-ia.md │ ├── posts.md │ ├── privacy.md │ ├── public/ │ │ ├── _headers │ │ ├── key.txt │ │ ├── manifest.json │ │ └── robots.txt │ ├── reading.md │ ├── sandbox.md │ ├── social-media-tools.md │ ├── startpage.md │ ├── storage.md │ ├── system-tools.md │ ├── text-tools.md │ ├── torrenting.md │ ├── unsafe.md │ ├── video-tools.md │ └── video.md ├── flake.nix ├── nitro.config.ts ├── package.json ├── pests-repellent/ │ ├── .editorconfig │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── settings.json │ ├── package.json │ ├── src/ │ │ └── index.ts │ ├── test/ │ │ ├── env.d.ts │ │ ├── index.spec.ts │ │ └── tsconfig.json │ ├── tsconfig.json │ ├── vitest.config.mts │ ├── worker-configuration.d.ts │ └── wrangler.jsonc ├── scripts/ │ ├── lint-markdown.js │ └── typos.csv ├── tsconfig.json ├── unocss.config.ts └── wrangler.toml
SYMBOL INDEX (571 symbols across 24 files)
FILE: api/worker-configuration.d.ts
type Env (line 18) | interface Env {
FILE: docs/.vitepress/constants.ts
method _render (line 27) | _render(src, env, md) {
FILE: docs/.vitepress/hooks/meta.ts
function generateMeta (line 19) | function generateMeta(context: TransformContext, hostname: string) {
FILE: docs/.vitepress/hooks/opengraph.ts
function generateImages (line 32) | async function generateImages(config: SiteConfig) {
type GenerateImagesOptions (line 74) | interface GenerateImagesOptions {
function generateImage (line 81) | async function generateImage({
function getPage (line 132) | function getPage(page: string) {
FILE: docs/.vitepress/hooks/rss.ts
function generateFeed (line 25) | async function generateFeed(config: SiteConfig): Promise<void> {
FILE: docs/.vitepress/markdown/base64.ts
function base64DecodePlugin (line 20) | function base64DecodePlugin(md: MarkdownRenderer) {
FILE: docs/.vitepress/markdown/emoji.ts
function emojiRender (line 28) | function emojiRender(md: MarkdownRenderer) {
function movePlugin (line 37) | function movePlugin(
FILE: docs/.vitepress/markdown/toggleStarred.ts
function toggleStarredPlugin (line 23) | function toggleStarredPlugin(md: MarkdownRenderer) {
FILE: docs/.vitepress/theme/composables/nprogress.ts
function loadProgress (line 21) | function loadProgress(
FILE: docs/.vitepress/theme/index.ts
method enhanceApp (line 34) | enhanceApp({ router, app }) {
FILE: docs/.vitepress/theme/posts.data.ts
type Post (line 21) | interface Post {
type Dictionary (line 27) | type Dictionary = ReturnType<typeof createContentLoader>
function transformRawPosts (line 32) | function transformRawPosts(rawPosts: ContentData[]): Record<string, Post...
FILE: docs/.vitepress/theme/themes/themeHandler.ts
constant STORAGE_KEY_THEME (line 21) | const STORAGE_KEY_THEME = 'vitepress-theme-name'
constant STORAGE_KEY_MODE (line 22) | const STORAGE_KEY_MODE = 'vitepress-display-mode'
constant STORAGE_KEY_AMOLED (line 23) | const STORAGE_KEY_AMOLED = 'vitepress-amoled-enabled'
class ThemeHandler (line 25) | class ThemeHandler {
method constructor (line 33) | constructor() {
method initializeTheme (line 37) | private initializeTheme() {
method applyTheme (line 76) | public applyTheme() {
method applyDOMClasses (line 106) | private applyDOMClasses(mode: DisplayMode) {
method applyCSSVariables (line 127) | private applyCSSVariables(colors: ModeColors, theme: Theme) {
method setTheme (line 275) | public setTheme(themeName: string) {
method setMode (line 290) | public setMode(mode: DisplayMode) {
method toggleMode (line 296) | public toggleMode() {
method setAppearance (line 305) | public setAppearance(mode: DisplayMode, amoled: boolean) {
method setAmoledEnabled (line 313) | public setAmoledEnabled(enabled: boolean) {
method getAmoledEnabled (line 319) | public getAmoledEnabled() {
method toggleAmoled (line 323) | public toggleAmoled() {
method getAmoledEnabledRef (line 327) | public getAmoledEnabledRef() {
method ensureColorPickerColors (line 331) | private ensureColorPickerColors() {
method getState (line 346) | public getState() {
method getMode (line 349) | public getMode() {
method getTheme (line 353) | public getTheme() {
method getCurrentTheme (line 357) | public getCurrentTheme() {
method getAvailableThemes (line 361) | public getAvailableThemes() {
method isDarkMode (line 368) | public isDarkMode() {
method isAmoledMode (line 373) | public isAmoledMode() {
function useThemeHandler (line 381) | function useThemeHandler() {
function useTheme (line 389) | function useTheme() {
FILE: docs/.vitepress/theme/themes/types.ts
type DisplayMode (line 17) | type DisplayMode = 'light' | 'dark'
type ModeColors (line 19) | interface ModeColors {
type Theme (line 104) | interface Theme {
type ThemeRegistry (line 126) | interface ThemeRegistry {
type ThemeState (line 130) | interface ThemeState {
FILE: docs/.vitepress/transformer.ts
function transformsPlugin (line 22) | function transformsPlugin(): Plugin {
function transform (line 102) | function transform(text: string): string {
FILE: docs/.vitepress/transformer/constants.ts
type Header (line 18) | interface Header {
function getHeader (line 146) | function getHeader(id: string) {
FILE: docs/.vitepress/transformer/core.ts
type Transform (line 19) | type Transform = {
type TransformerFunc (line 25) | type TransformerFunc = (name: string, transforms: Transform[]) => Replacer
type Replacer (line 27) | interface Replacer {
method get (line 34) | get(target: { text: string }, prop: string | symbol) {
function replaceUnderscore (line 60) | function replaceUnderscore(text: string): string {
FILE: docs/.vitepress/types/Feedback.ts
type Option (line 27) | interface Option {
function getFeedbackOption (line 44) | function getFeedbackOption(
type FeedbackType (line 50) | type FeedbackType = z.infer<typeof FeedbackSchema>
FILE: docs/.vitepress/utils.ts
function groupBy (line 16) | function groupBy<T, K extends keyof any>(
FILE: docs/.vitepress/utils/markdown.ts
constant NOTE_MATCH_RE (line 4) | const NOTE_MATCH_RE = /\.vitepress\/notes\/([\w-]+)(?:\.md)?$/
function replaceNoteLink (line 6) | function replaceNoteLink(md: MarkdownRenderer) {
FILE: docs/.vitepress/utils/tooltips.ts
type TooltipData (line 4) | interface TooltipData {
function getTooltip (line 11) | function getTooltip(id: string): TooltipData | undefined {
FILE: pests-repellent/src/index.ts
method fetch (line 2) | async fetch(request, env, ctx): Promise<Response> {
FILE: pests-repellent/test/env.d.ts
type ProvidedEnv (line 2) | interface ProvidedEnv extends Env {}
FILE: pests-repellent/worker-configuration.d.ts
type Env (line 5) | interface Env {}
type Env (line 7) | interface Env extends Cloudflare.Env {}
class DOMException (line 32) | class DOMException extends Error {
type WorkerGlobalScopeEventMap (line 72) | type WorkerGlobalScopeEventMap = {
type Console (line 83) | interface Console {
type BufferSource (line 124) | type BufferSource = ArrayBufferView | ArrayBuffer;
type TypedArray (line 125) | type TypedArray =
class CompileError (line 138) | class CompileError extends Error {
class RuntimeError (line 141) | class RuntimeError extends Error {
type ValueType (line 144) | type ValueType = 'anyfunc' | 'externref' | 'f32' | 'f64' | 'i32' | 'i64'...
type GlobalDescriptor (line 145) | interface GlobalDescriptor {
class Global (line 149) | class Global {
type ImportValue (line 154) | type ImportValue = ExportValue | number;
type ModuleImports (line 155) | type ModuleImports = Record<string, ImportValue>;
type Imports (line 156) | type Imports = Record<string, ModuleImports>;
type ExportValue (line 157) | type ExportValue = Function | Global | Memory | Table;
type Exports (line 158) | type Exports = Record<string, ExportValue>;
class Instance (line 159) | class Instance {
type MemoryDescriptor (line 163) | interface MemoryDescriptor {
class Memory (line 168) | class Memory {
type ImportExportKind (line 173) | type ImportExportKind = 'function' | 'global' | 'memory' | 'table';
type ModuleExportDescriptor (line 174) | interface ModuleExportDescriptor {
type ModuleImportDescriptor (line 178) | interface ModuleImportDescriptor {
type TableKind (line 188) | type TableKind = 'anyfunc' | 'externref';
type TableDescriptor (line 189) | interface TableDescriptor {
class Table (line 194) | class Table {
type ServiceWorkerGlobalScope (line 210) | interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
type TestController (line 356) | interface TestController {}
type ExecutionContext (line 357) | interface ExecutionContext {
type ExportedHandlerFetchHandler (line 362) | type ExportedHandlerFetchHandler<Env = unknown, CfHostMetadata = unknown...
type ExportedHandlerTailHandler (line 367) | type ExportedHandlerTailHandler<Env = unknown> = (events: TraceItem[], e...
type ExportedHandlerTraceHandler (line 368) | type ExportedHandlerTraceHandler<Env = unknown> = (traces: TraceItem[], ...
type ExportedHandlerTailStreamHandler (line 369) | type ExportedHandlerTailStreamHandler<Env = unknown> = (
type ExportedHandlerScheduledHandler (line 374) | type ExportedHandlerScheduledHandler<Env = unknown> = (
type ExportedHandlerQueueHandler (line 379) | type ExportedHandlerQueueHandler<Env = unknown, Message = unknown> = (
type ExportedHandlerTestHandler (line 384) | type ExportedHandlerTestHandler<Env = unknown> = (controller: TestContro...
type ExportedHandler (line 385) | interface ExportedHandler<Env = unknown, QueueHandlerMessage = unknown, ...
type StructuredSerializeOptions (line 395) | interface StructuredSerializeOptions {
type Performance (line 421) | interface Performance {
type AlarmInvocationInfo (line 427) | interface AlarmInvocationInfo {
type Cloudflare (line 431) | interface Cloudflare {
type DurableObject (line 434) | interface DurableObject {
type DurableObjectStub (line 441) | type DurableObjectStub<T extends Rpc.DurableObjectBranded | undefined = ...
type DurableObjectId (line 448) | interface DurableObjectId {
type DurableObjectNamespace (line 453) | interface DurableObjectNamespace<T extends Rpc.DurableObjectBranded | un...
type DurableObjectJurisdiction (line 460) | type DurableObjectJurisdiction = 'eu' | 'fedramp' | 'fedramp-high';
type DurableObjectNamespaceNewUniqueIdOptions (line 461) | interface DurableObjectNamespaceNewUniqueIdOptions {
type DurableObjectLocationHint (line 464) | type DurableObjectLocationHint = 'wnam' | 'enam' | 'sam' | 'weur' | 'eeu...
type DurableObjectNamespaceGetDurableObjectOptions (line 465) | interface DurableObjectNamespaceGetDurableObjectOptions {
type DurableObjectState (line 468) | interface DurableObjectState {
type DurableObjectTransaction (line 484) | interface DurableObjectTransaction {
type DurableObjectStorage (line 497) | interface DurableObjectStorage {
type DurableObjectListOptions (line 517) | interface DurableObjectListOptions {
type DurableObjectGetOptions (line 527) | interface DurableObjectGetOptions {
type DurableObjectGetAlarmOptions (line 531) | interface DurableObjectGetAlarmOptions {
type DurableObjectPutOptions (line 534) | interface DurableObjectPutOptions {
type DurableObjectSetAlarmOptions (line 539) | interface DurableObjectSetAlarmOptions {
class WebSocketRequestResponsePair (line 543) | class WebSocketRequestResponsePair {
type AnalyticsEngineDataset (line 548) | interface AnalyticsEngineDataset {
type AnalyticsEngineDataPoint (line 551) | interface AnalyticsEngineDataPoint {
class Event (line 561) | class Event {
type EventInit (line 676) | interface EventInit {
type EventListener (line 681) | type EventListener<EventType extends Event = Event> = (event: EventType)...
type EventListenerObject (line 682) | interface EventListenerObject<EventType extends Event = Event> {
type EventListenerOrEventListenerObject (line 685) | type EventListenerOrEventListenerObject<EventType extends Event = Event>...
class EventTarget (line 691) | class EventTarget<EventMap extends Record<string, Event> = Record<string...
type EventTargetEventListenerOptions (line 732) | interface EventTargetEventListenerOptions {
type EventTargetAddEventListenerOptions (line 735) | interface EventTargetAddEventListenerOptions {
type EventTargetHandlerObject (line 741) | interface EventTargetHandlerObject {
class AbortController (line 749) | class AbortController {
type Scheduler (line 791) | interface Scheduler {
type SchedulerWaitOptions (line 794) | interface SchedulerWaitOptions {
class CustomEvent (line 807) | class CustomEvent<T = any> extends Event {
type CustomEventCustomEventInit (line 816) | interface CustomEventCustomEventInit {
class Blob (line 827) | class Blob {
type BlobOptions (line 844) | interface BlobOptions {
class File (line 852) | class File extends Blob {
type FileOptions (line 859) | interface FileOptions {
type CacheQueryOptions (line 886) | interface CacheQueryOptions {
type CryptoKeyPair (line 1009) | interface CryptoKeyPair {
type JsonWebKey (line 1013) | interface JsonWebKey {
type RsaOtherPrimesInfo (line 1033) | interface RsaOtherPrimesInfo {
type SubtleCryptoDeriveKeyAlgorithm (line 1038) | interface SubtleCryptoDeriveKeyAlgorithm {
type SubtleCryptoEncryptAlgorithm (line 1046) | interface SubtleCryptoEncryptAlgorithm {
type SubtleCryptoGenerateKeyAlgorithm (line 1055) | interface SubtleCryptoGenerateKeyAlgorithm {
type SubtleCryptoHashAlgorithm (line 1063) | interface SubtleCryptoHashAlgorithm {
type SubtleCryptoImportKeyAlgorithm (line 1066) | interface SubtleCryptoImportKeyAlgorithm {
type SubtleCryptoSignAlgorithm (line 1073) | interface SubtleCryptoSignAlgorithm {
type CryptoKeyKeyAlgorithm (line 1079) | interface CryptoKeyKeyAlgorithm {
type CryptoKeyAesKeyAlgorithm (line 1082) | interface CryptoKeyAesKeyAlgorithm {
type CryptoKeyHmacKeyAlgorithm (line 1086) | interface CryptoKeyHmacKeyAlgorithm {
type CryptoKeyRsaKeyAlgorithm (line 1091) | interface CryptoKeyRsaKeyAlgorithm {
type CryptoKeyEllipticKeyAlgorithm (line 1097) | interface CryptoKeyEllipticKeyAlgorithm {
type CryptoKeyArbitraryKeyAlgorithm (line 1101) | interface CryptoKeyArbitraryKeyAlgorithm {
class DigestStream (line 1107) | class DigestStream extends WritableStream<ArrayBuffer | ArrayBufferView> {
class TextDecoder (line 1117) | class TextDecoder {
class TextEncoder (line 1144) | class TextEncoder {
type TextDecoderConstructorOptions (line 1160) | interface TextDecoderConstructorOptions {
type TextDecoderDecodeOptions (line 1164) | interface TextDecoderDecodeOptions {
type TextEncoderEncodeIntoResult (line 1167) | interface TextEncoderEncodeIntoResult {
class ErrorEvent (line 1176) | class ErrorEvent extends Event {
type ErrorEventErrorEventInit (line 1189) | interface ErrorEventErrorEventInit {
class FormData (line 1201) | class FormData {
type ContentOptions (line 1228) | interface ContentOptions {
class HTMLRewriter (line 1231) | class HTMLRewriter {
type HTMLRewriterElementContentHandlers (line 1237) | interface HTMLRewriterElementContentHandlers {
type HTMLRewriterDocumentContentHandlers (line 1242) | interface HTMLRewriterDocumentContentHandlers {
type Doctype (line 1248) | interface Doctype {
type Element (line 1253) | interface Element {
type EndTag (line 1272) | interface EndTag {
type Comment (line 1278) | interface Comment {
type Text (line 1286) | interface Text {
type DocumentEnd (line 1295) | interface DocumentEnd {
type HeadersInit (line 1310) | type HeadersInit = Headers | Iterable<Iterable<string>> | Record<string,...
class Headers (line 1316) | class Headers {
type BodyInit (line 1340) | type BodyInit = ReadableStream<Uint8Array> | string | ArrayBuffer | Arra...
type Response (line 1376) | interface Response extends Body {
type ResponseInit (line 1396) | interface ResponseInit {
type RequestInfo (line 1404) | type RequestInfo<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetad...
type Request (line 1422) | interface Request<CfHostMetadata = unknown, Cf = CfProperties<CfHostMeta...
type RequestInit (line 1476) | interface RequestInit<Cf = CfProperties> {
type Service (line 1495) | type Service<T extends Rpc.WorkerEntrypointBranded | undefined = undefin...
type Fetcher (line 1496) | type Fetcher<T extends Rpc.EntrypointBranded | undefined = undefined, Re...
type KVNamespaceListKey (line 1502) | interface KVNamespaceListKey<Metadata, Key extends string = string> {
type KVNamespaceListResult (line 1507) | type KVNamespaceListResult<Metadata, Key extends string = string> =
type KVNamespace (line 1519) | interface KVNamespace<Key extends string = string> {
type KVNamespaceListOptions (line 1585) | interface KVNamespaceListOptions {
type KVNamespaceGetOptions (line 1590) | interface KVNamespaceGetOptions<Type> {
type KVNamespacePutOptions (line 1594) | interface KVNamespacePutOptions {
type KVNamespaceGetWithMetadataResult (line 1599) | interface KVNamespaceGetWithMetadataResult<Value, Metadata> {
type QueueContentType (line 1604) | type QueueContentType = 'text' | 'bytes' | 'json' | 'v8';
type Queue (line 1605) | interface Queue<Body = unknown> {
type QueueSendOptions (line 1609) | interface QueueSendOptions {
type QueueSendBatchOptions (line 1613) | interface QueueSendBatchOptions {
type MessageSendRequest (line 1616) | interface MessageSendRequest<Body = unknown> {
type QueueRetryOptions (line 1621) | interface QueueRetryOptions {
type Message (line 1624) | interface Message<Body = unknown> {
type QueueEvent (line 1632) | interface QueueEvent<Body = unknown> extends ExtendableEvent {
type MessageBatch (line 1638) | interface MessageBatch<Body = unknown> {
type R2Error (line 1644) | interface R2Error extends Error {
type R2ListOptions (line 1651) | interface R2ListOptions {
type R2MultipartUpload (line 1681) | interface R2MultipartUpload {
type R2UploadedPart (line 1692) | interface R2UploadedPart {
type R2ObjectBody (line 1711) | interface R2ObjectBody extends R2Object {
type R2Range (line 1720) | type R2Range =
type R2Conditional (line 1732) | interface R2Conditional {
type R2GetOptions (line 1739) | interface R2GetOptions {
type R2PutOptions (line 1744) | interface R2PutOptions {
type R2MultipartOptions (line 1756) | interface R2MultipartOptions {
type R2Checksums (line 1762) | interface R2Checksums {
type R2StringChecksums (line 1770) | interface R2StringChecksums {
type R2HTTPMetadata (line 1777) | interface R2HTTPMetadata {
type R2Objects (line 1785) | type R2Objects = {
type R2UploadPartOptions (line 1797) | interface R2UploadPartOptions {
type ScheduledController (line 1805) | interface ScheduledController {
type QueuingStrategy (line 1810) | interface QueuingStrategy<T = any> {
type UnderlyingSink (line 1814) | interface UnderlyingSink<W = any> {
type UnderlyingByteSource (line 1821) | interface UnderlyingByteSource {
type UnderlyingSource (line 1828) | interface UnderlyingSource<R = any> {
type Transformer (line 1835) | interface Transformer<I = any, O = any> {
type StreamPipeOptions (line 1844) | interface StreamPipeOptions {
type ReadableStreamReadResult (line 1867) | type ReadableStreamReadResult<R = any> =
type ReadableStream (line 1881) | interface ReadableStream<R = any> {
class ReadableStreamDefaultReader (line 1910) | class ReadableStreamDefaultReader<R = any> {
class ReadableStreamBYOBReader (line 1920) | class ReadableStreamBYOBReader {
type ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions (line 1930) | interface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions {
type ReadableStreamGetReaderOptions (line 1933) | interface ReadableStreamGetReaderOptions {
type ReadableWritablePair (line 1997) | interface ReadableWritablePair<R = any, W = any> {
class WritableStream (line 2011) | class WritableStream<W = any> {
class WritableStreamDefaultWriter (line 2027) | class WritableStreamDefaultWriter<W = any> {
class TransformStream (line 2045) | class TransformStream<I = any, O = any> {
class FixedLengthStream (line 2052) | class FixedLengthStream extends IdentityTransformStream {
class IdentityTransformStream (line 2055) | class IdentityTransformStream extends TransformStream<ArrayBuffer | Arra...
type IdentityTransformStreamQueuingStrategy (line 2058) | interface IdentityTransformStreamQueuingStrategy {
type ReadableStreamValuesOptions (line 2061) | interface ReadableStreamValuesOptions {
class CompressionStream (line 2065) | class CompressionStream extends TransformStream<ArrayBuffer | ArrayBuffe...
class DecompressionStream (line 2069) | class DecompressionStream extends TransformStream<ArrayBuffer | ArrayBuf...
class TextEncoderStream (line 2073) | class TextEncoderStream extends TransformStream<string, Uint8Array> {
class TextDecoderStream (line 2078) | class TextDecoderStream extends TransformStream<ArrayBuffer | ArrayBuffe...
type TextDecoderStreamTextDecoderStreamInit (line 2084) | interface TextDecoderStreamTextDecoderStreamInit {
class ByteLengthQueuingStrategy (line 2093) | class ByteLengthQueuingStrategy implements QueuingStrategy<ArrayBufferVi...
class CountQueuingStrategy (line 2105) | class CountQueuingStrategy implements QueuingStrategy {
type QueuingStrategyInit (line 2112) | interface QueuingStrategyInit {
type ScriptVersion (line 2120) | interface ScriptVersion {
type TraceItem (line 2129) | interface TraceItem {
type TraceItemAlarmEventInfo (line 2158) | interface TraceItemAlarmEventInfo {
type TraceItemCustomEventInfo (line 2161) | interface TraceItemCustomEventInfo {}
type TraceItemScheduledEventInfo (line 2162) | interface TraceItemScheduledEventInfo {
type TraceItemQueueEventInfo (line 2166) | interface TraceItemQueueEventInfo {
type TraceItemEmailEventInfo (line 2170) | interface TraceItemEmailEventInfo {
type TraceItemTailEventInfo (line 2175) | interface TraceItemTailEventInfo {
type TraceItemTailEventInfoTailItem (line 2178) | interface TraceItemTailEventInfoTailItem {
type TraceItemFetchEventInfo (line 2181) | interface TraceItemFetchEventInfo {
type TraceItemFetchEventInfoRequest (line 2185) | interface TraceItemFetchEventInfoRequest {
type TraceItemFetchEventInfoResponse (line 2192) | interface TraceItemFetchEventInfoResponse {
type TraceItemJsRpcEventInfo (line 2195) | interface TraceItemJsRpcEventInfo {
type TraceItemHibernatableWebSocketEventInfo (line 2198) | interface TraceItemHibernatableWebSocketEventInfo {
type TraceItemHibernatableWebSocketEventInfoMessage (line 2204) | interface TraceItemHibernatableWebSocketEventInfoMessage {
type TraceItemHibernatableWebSocketEventInfoClose (line 2207) | interface TraceItemHibernatableWebSocketEventInfoClose {
type TraceItemHibernatableWebSocketEventInfoError (line 2212) | interface TraceItemHibernatableWebSocketEventInfoError {
type TraceLog (line 2215) | interface TraceLog {
type TraceException (line 2220) | interface TraceException {
type TraceDiagnosticChannelEvent (line 2226) | interface TraceDiagnosticChannelEvent {
type TraceMetrics (line 2231) | interface TraceMetrics {
type UnsafeTraceMetrics (line 2235) | interface UnsafeTraceMetrics {
class URL (line 2243) | class URL {
class URLSearchParams (line 2303) | class URLSearchParams {
class URLPattern (line 2356) | class URLPattern {
type URLPatternInit (line 2370) | interface URLPatternInit {
type URLPatternComponentResult (line 2381) | interface URLPatternComponentResult {
type URLPatternResult (line 2385) | interface URLPatternResult {
type URLPatternOptions (line 2396) | interface URLPatternOptions {
class CloseEvent (line 2404) | class CloseEvent extends Event {
type CloseEventInit (line 2425) | interface CloseEventInit {
class MessageEvent (line 2435) | class MessageEvent extends Event {
type MessageEventInit (line 2444) | interface MessageEventInit {
type WebSocketEventMap (line 2447) | type WebSocketEventMap = {
type WebSocket (line 2475) | interface WebSocket extends EventTarget<WebSocketEventMap> {
type SqlStorage (line 2522) | interface SqlStorage {
type SqlStorageValue (line 2529) | type SqlStorageValue = ArrayBuffer | string | number | null;
type Socket (line 2548) | interface Socket {
type SocketOptions (line 2558) | interface SocketOptions {
type SocketAddress (line 2563) | interface SocketAddress {
type TlsOptions (line 2567) | interface TlsOptions {
type SocketInfo (line 2570) | interface SocketInfo {
class EventSource (line 2575) | class EventSource extends EventTarget {
type EventSourceEventSourceInit (line 2618) | interface EventSourceEventSourceInit {
type Container (line 2622) | interface Container {
type ContainerStartupOptions (line 2630) | interface ContainerStartupOptions {
type AiImageClassificationInput (line 2635) | type AiImageClassificationInput = {
type AiImageClassificationOutput (line 2638) | type AiImageClassificationOutput = {
type AiImageToTextInput (line 2646) | type AiImageToTextInput = {
type AiImageToTextOutput (line 2660) | type AiImageToTextOutput = {
type AiImageTextToTextInput (line 2667) | type AiImageTextToTextInput = {
type AiImageTextToTextOutput (line 2682) | type AiImageTextToTextOutput = {
type AiObjectDetectionInput (line 2689) | type AiObjectDetectionInput = {
type AiObjectDetectionOutput (line 2692) | type AiObjectDetectionOutput = {
type AiSentenceSimilarityInput (line 2700) | type AiSentenceSimilarityInput = {
type AiSentenceSimilarityOutput (line 2704) | type AiSentenceSimilarityOutput = number[];
type AiAutomaticSpeechRecognitionInput (line 2709) | type AiAutomaticSpeechRecognitionInput = {
type AiAutomaticSpeechRecognitionOutput (line 2712) | type AiAutomaticSpeechRecognitionOutput = {
type AiSummarizationInput (line 2725) | type AiSummarizationInput = {
type AiSummarizationOutput (line 2729) | type AiSummarizationOutput = {
type AiTextClassificationInput (line 2736) | type AiTextClassificationInput = {
type AiTextClassificationOutput (line 2739) | type AiTextClassificationOutput = {
type AiTextEmbeddingsInput (line 2747) | type AiTextEmbeddingsInput = {
type AiTextEmbeddingsOutput (line 2750) | type AiTextEmbeddingsOutput = {
type RoleScopedChatInput (line 2758) | type RoleScopedChatInput = {
type AiTextGenerationToolLegacyInput (line 2763) | type AiTextGenerationToolLegacyInput = {
type AiTextGenerationToolInput (line 2777) | type AiTextGenerationToolInput = {
type AiTextGenerationFunctionsInput (line 2794) | type AiTextGenerationFunctionsInput = {
type AiTextGenerationResponseFormat (line 2798) | type AiTextGenerationResponseFormat = {
type AiTextGenerationInput (line 2802) | type AiTextGenerationInput = {
type AiTextGenerationOutput (line 2819) | type AiTextGenerationOutput = {
type AiTextToSpeechInput (line 2830) | type AiTextToSpeechInput = {
type AiTextToSpeechOutput (line 2834) | type AiTextToSpeechOutput =
type AiTextToImageInput (line 2843) | type AiTextToImageInput = {
type AiTextToImageOutput (line 2856) | type AiTextToImageOutput = ReadableStream<Uint8Array>;
type AiTranslationInput (line 2861) | type AiTranslationInput = {
type AiTranslationOutput (line 2866) | type AiTranslationOutput = {
type Ai_Cf_Baai_Bge_Base_En_V1_5_Input (line 2873) | type Ai_Cf_Baai_Bge_Base_En_V1_5_Input =
type Ai_Cf_Baai_Bge_Base_En_V1_5_Output (line 2893) | type Ai_Cf_Baai_Bge_Base_En_V1_5_Output =
type AsyncResponse (line 2906) | interface AsyncResponse {
type Ai_Cf_Openai_Whisper_Input (line 2916) | type Ai_Cf_Openai_Whisper_Input =
type Ai_Cf_Openai_Whisper_Output (line 2924) | interface Ai_Cf_Openai_Whisper_Output {
type Ai_Cf_Meta_M2M100_1_2B_Input (line 2947) | type Ai_Cf_Meta_M2M100_1_2B_Input =
type Ai_Cf_Meta_M2M100_1_2B_Output (line 2981) | type Ai_Cf_Meta_M2M100_1_2B_Output =
type Ai_Cf_Baai_Bge_Small_En_V1_5_Input (line 2993) | type Ai_Cf_Baai_Bge_Small_En_V1_5_Input =
type Ai_Cf_Baai_Bge_Small_En_V1_5_Output (line 3013) | type Ai_Cf_Baai_Bge_Small_En_V1_5_Output =
type Ai_Cf_Baai_Bge_Large_En_V1_5_Input (line 3030) | type Ai_Cf_Baai_Bge_Large_En_V1_5_Input =
type Ai_Cf_Baai_Bge_Large_En_V1_5_Output (line 3050) | type Ai_Cf_Baai_Bge_Large_En_V1_5_Output =
type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input (line 3067) | type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input =
type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output (line 3108) | interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output {
type Ai_Cf_Openai_Whisper_Tiny_En_Input (line 3115) | type Ai_Cf_Openai_Whisper_Tiny_En_Input =
type Ai_Cf_Openai_Whisper_Tiny_En_Output (line 3123) | interface Ai_Cf_Openai_Whisper_Tiny_En_Output {
type Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input (line 3146) | interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input {
type Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output (line 3172) | interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output {
type Ai_Cf_Baai_Bge_M3_Input (line 3252) | type Ai_Cf_Baai_Bge_M3_Input =
type BGEM3InputQueryAndContexts (line 3261) | interface BGEM3InputQueryAndContexts {
type BGEM3InputEmbedding (line 3280) | interface BGEM3InputEmbedding {
type BGEM3InputQueryAndContexts1 (line 3287) | interface BGEM3InputQueryAndContexts1 {
type BGEM3InputEmbedding1 (line 3306) | interface BGEM3InputEmbedding1 {
type Ai_Cf_Baai_Bge_M3_Output (line 3313) | type Ai_Cf_Baai_Bge_M3_Output = BGEM3OuputQuery | BGEM3OutputEmbeddingFo...
type BGEM3OuputQuery (line 3314) | interface BGEM3OuputQuery {
type BGEM3OutputEmbeddingForContexts (line 3326) | interface BGEM3OutputEmbeddingForContexts {
type BGEM3OuputEmbedding (line 3334) | interface BGEM3OuputEmbedding {
type Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input (line 3349) | interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input {
type Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output (line 3359) | interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output {
type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input (line 3369) | type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = Prompt | Messages;
type Prompt (line 3370) | interface Prompt {
type Messages (line 3421) | interface Messages {
type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output (line 3595) | type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = {
type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input (line 3618) | type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input =
type Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt (line 3622) | interface Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt {
type JSONMode (line 3673) | interface JSONMode {
type Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages (line 3677) | interface Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages {
type AsyncBatch (line 3827) | interface AsyncBatch {
type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output (line 3872) | type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output =
type Ai_Cf_Meta_Llama_Guard_3_8B_Input (line 3914) | interface Ai_Cf_Meta_Llama_Guard_3_8B_Input {
type Ai_Cf_Meta_Llama_Guard_3_8B_Output (line 3946) | interface Ai_Cf_Meta_Llama_Guard_3_8B_Output {
type Ai_Cf_Baai_Bge_Reranker_Base_Input (line 3981) | interface Ai_Cf_Baai_Bge_Reranker_Base_Input {
type Ai_Cf_Baai_Bge_Reranker_Base_Output (line 4000) | interface Ai_Cf_Baai_Bge_Reranker_Base_Output {
type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input (line 4016) | type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input = Qwen2_5_Coder_32B_Ins...
type Qwen2_5_Coder_32B_Instruct_Prompt (line 4017) | interface Qwen2_5_Coder_32B_Instruct_Prompt {
type Qwen2_5_Coder_32B_Instruct_Messages (line 4068) | interface Qwen2_5_Coder_32B_Instruct_Messages {
type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output (line 4218) | type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = {
type Ai_Cf_Qwen_Qwq_32B_Input (line 4258) | type Ai_Cf_Qwen_Qwq_32B_Input = Qwen_Qwq_32B_Prompt | Qwen_Qwq_32B_Messa...
type Qwen_Qwq_32B_Prompt (line 4259) | interface Qwen_Qwq_32B_Prompt {
type Qwen_Qwq_32B_Messages (line 4309) | interface Qwen_Qwq_32B_Messages {
type Ai_Cf_Qwen_Qwq_32B_Output (line 4490) | type Ai_Cf_Qwen_Qwq_32B_Output = {
type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input (line 4530) | type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input = Mistral_Smal...
type Mistral_Small_3_1_24B_Instruct_Prompt (line 4531) | interface Mistral_Small_3_1_24B_Instruct_Prompt {
type Mistral_Small_3_1_24B_Instruct_Messages (line 4581) | interface Mistral_Small_3_1_24B_Instruct_Messages {
type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output (line 4762) | type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = {
type Ai_Cf_Google_Gemma_3_12B_It_Input (line 4802) | type Ai_Cf_Google_Gemma_3_12B_It_Input = Google_Gemma_3_12B_It_Prompt | ...
type Google_Gemma_3_12B_It_Prompt (line 4803) | interface Google_Gemma_3_12B_It_Prompt {
type Google_Gemma_3_12B_It_Messages (line 4853) | interface Google_Gemma_3_12B_It_Messages {
type Ai_Cf_Google_Gemma_3_12B_It_Output (line 5030) | type Ai_Cf_Google_Gemma_3_12B_It_Output = {
type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input (line 5070) | type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input = Ai_Cf_Meta_Llama_...
type Ai_Cf_Meta_Llama_4_Prompt (line 5071) | interface Ai_Cf_Meta_Llama_4_Prompt {
type Ai_Cf_Meta_Llama_4_Messages (line 5122) | interface Ai_Cf_Meta_Llama_4_Messages {
type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output (line 5304) | type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = {
type AiModels (line 5357) | interface AiModels {
type AiOptions (line 5429) | type AiOptions = {
type ConversionResponse (line 5440) | type ConversionResponse = {
type AiModelsSearchParams (line 5447) | type AiModelsSearchParams = {
type AiModelsSearchObject (line 5456) | type AiModelsSearchObject = {
type InferenceUpstreamError (line 5472) | interface InferenceUpstreamError extends Error {}
type AiInternalError (line 5473) | interface AiInternalError extends Error {}
type AiModelListType (line 5474) | type AiModelListType = Record<string, any>;
type GatewayRetries (line 5516) | type GatewayRetries = {
type GatewayOptions (line 5521) | type GatewayOptions = {
type AiGatewayPatchLog (line 5532) | type AiGatewayPatchLog = {
type AiGatewayLog (line 5537) | type AiGatewayLog = {
type AIGatewayProviders (line 5564) | type AIGatewayProviders =
type AIGatewayHeaders (line 5585) | type AIGatewayHeaders = {
type AIGatewayUniversalRequest (line 5609) | type AIGatewayUniversalRequest = {
type AiGatewayInternalError (line 5615) | interface AiGatewayInternalError extends Error {}
type AiGatewayLogNotFound (line 5616) | interface AiGatewayLogNotFound extends Error {}
type AutoRAGInternalError (line 5629) | interface AutoRAGInternalError extends Error {}
type AutoRAGNotFoundError (line 5630) | interface AutoRAGNotFoundError extends Error {}
type AutoRAGUnauthorizedError (line 5631) | interface AutoRAGUnauthorizedError extends Error {}
type AutoRAGNameNotSetError (line 5632) | interface AutoRAGNameNotSetError extends Error {}
type ComparisonFilter (line 5633) | type ComparisonFilter = {
type CompoundFilter (line 5638) | type CompoundFilter = {
type AutoRagSearchRequest (line 5642) | type AutoRagSearchRequest = {
type AutoRagAiSearchRequest (line 5652) | type AutoRagAiSearchRequest = AutoRagSearchRequest & {
type AutoRagAiSearchRequestStreaming (line 5655) | type AutoRagAiSearchRequestStreaming = Omit<AutoRagAiSearchRequest, 'str...
type AutoRagSearchResponse (line 5658) | type AutoRagSearchResponse = {
type AutoRagListResponse (line 5674) | type AutoRagListResponse = {
type AutoRagAiSearchResponse (line 5683) | type AutoRagAiSearchResponse = AutoRagSearchResponse & {
type BasicImageTransformations (line 5693) | interface BasicImageTransformations {
type BasicImageTransformationsGravityCoordinates (line 5754) | interface BasicImageTransformationsGravityCoordinates {
type RequestInitCfProperties (line 5768) | interface RequestInitCfProperties extends Record<string, unknown> {
type RequestInitCfPropertiesImageDraw (line 5819) | interface RequestInitCfPropertiesImageDraw extends BasicImageTransformat...
type RequestInitCfPropertiesImage (line 5856) | interface RequestInitCfPropertiesImage extends BasicImageTransformations {
type RequestInitCfPropertiesImageMinify (line 6013) | interface RequestInitCfPropertiesImageMinify {
type RequestInitCfPropertiesR2 (line 6018) | interface RequestInitCfPropertiesR2 {
type IncomingRequestCfProperties (line 6027) | type IncomingRequestCfProperties<HostMetadata = unknown> = IncomingReque...
type IncomingRequestCfPropertiesBase (line 6032) | interface IncomingRequestCfPropertiesBase extends Record<string, unknown> {
type IncomingRequestCfPropertiesBotManagementBase (line 6110) | interface IncomingRequestCfPropertiesBotManagementBase {
type IncomingRequestCfPropertiesBotManagement (line 6137) | interface IncomingRequestCfPropertiesBotManagement {
type IncomingRequestCfPropertiesBotManagementEnterprise (line 6149) | interface IncomingRequestCfPropertiesBotManagementEnterprise extends Inc...
type IncomingRequestCfPropertiesCloudflareForSaaSEnterprise (line 6161) | interface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMet...
type IncomingRequestCfPropertiesCloudflareAccessOrApiShield (line 6170) | interface IncomingRequestCfPropertiesCloudflareAccessOrApiShield {
type IncomingRequestCfPropertiesExportedAuthenticatorMetadata (line 6190) | interface IncomingRequestCfPropertiesExportedAuthenticatorMetadata {
type IncomingRequestCfPropertiesGeographicInformation (line 6219) | interface IncomingRequestCfPropertiesGeographicInformation {
type IncomingRequestCfPropertiesTLSClientAuth (line 6296) | interface IncomingRequestCfPropertiesTLSClientAuth {
type IncomingRequestCfPropertiesTLSClientAuthPlaceholder (line 6389) | interface IncomingRequestCfPropertiesTLSClientAuthPlaceholder {
type CertVerificationStatus (line 6409) | type CertVerificationStatus =
type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus (line 6427) | type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus =
type Iso3166Alpha2Code (line 6435) | type Iso3166Alpha2Code =
type ContinentCode (line 6686) | type ContinentCode = 'AF' | 'AN' | 'AS' | 'EU' | 'NA' | 'OC' | 'SA';
type CfProperties (line 6687) | type CfProperties<HostMetadata = unknown> = IncomingRequestCfProperties<...
type D1Meta (line 6688) | interface D1Meta {
type D1Response (line 6711) | interface D1Response {
type D1Result (line 6716) | type D1Result<T = unknown> = D1Response & {
type D1ExecResult (line 6719) | interface D1ExecResult {
type D1SessionConstraint (line 6723) | type D1SessionConstraint =
type D1SessionBookmark (line 6732) | type D1SessionBookmark = string;
type Disposable (line 6774) | interface Disposable {}
type EmailMessage (line 6778) | interface EmailMessage {
type ForwardableEmailMessage (line 6791) | interface ForwardableEmailMessage extends EmailMessage {
type SendEmail (line 6827) | interface SendEmail {
type EmailExportedHandler (line 6833) | type EmailExportedHandler<Env = unknown> = (
type Hyperdrive (line 6845) | interface Hyperdrive {
type ImageInfoResponse (line 6895) | type ImageInfoResponse =
type ImageTransform (line 6905) | type ImageTransform = {
type ImageDrawOptions (line 6960) | type ImageDrawOptions = {
type ImageOutputOptions (line 6968) | type ImageOutputOptions = {
type ImagesBinding (line 6973) | interface ImagesBinding {
type ImageTransformer (line 6987) | interface ImageTransformer {
type ImageTransformationResult (line 7008) | interface ImageTransformationResult {
type ImagesError (line 7022) | interface ImagesError extends Error {
type Params (line 7027) | type Params<P extends string = any> = Record<P, string | string[]>;
type EventContext (line 7028) | type EventContext<Env, P extends string, Data> = {
type PagesFunction (line 7042) | type PagesFunction<Env = unknown, Params extends string = any, Data exte...
type EventPluginContext (line 7045) | type EventPluginContext<Env, P extends string, Data, PluginArgs> = {
type PagesPluginFunction (line 7060) | type PagesPluginFunction<
type PipelineRecord (line 7090) | type PipelineRecord = Record<string, unknown>;
type PipelineBatchMetadata (line 7091) | type PipelineBatchMetadata = {
type Pipeline (line 7095) | interface Pipeline<T extends PipelineRecord = PipelineRecord> {
type PubSubMessage (line 7108) | interface PubSubMessage {
type JsonWebKeyWithKid (line 7134) | interface JsonWebKeyWithKid extends JsonWebKey {
type RateLimitOptions (line 7138) | interface RateLimitOptions {
type RateLimitOutcome (line 7141) | interface RateLimitOutcome {
type RateLimit (line 7144) | interface RateLimit {
type RpcTargetBranded (line 7165) | interface RpcTargetBranded {
type WorkerEntrypointBranded (line 7168) | interface WorkerEntrypointBranded {
type DurableObjectBranded (line 7171) | interface DurableObjectBranded {
type WorkflowEntrypointBranded (line 7174) | interface WorkflowEntrypointBranded {
type EntrypointBranded (line 7177) | type EntrypointBranded = WorkerEntrypointBranded | DurableObjectBranded ...
type Stubable (line 7179) | type Stubable = RpcTargetBranded | ((...args: any[]) => any);
type Serializable (line 7184) | type Serializable<T> =
type StubBase (line 7200) | interface StubBase<T extends Stubable> extends Disposable {
type Stub (line 7204) | type Stub<T extends Stubable> = Provider<T> & StubBase<T>;
type BaseType (line 7206) | type BaseType =
type Stubify (line 7227) | type Stubify<T> = T extends Stubable ? Stub<T> : T extends Map<infer K, ...
type Unstubify (line 7236) | type Unstubify<T> = T extends StubBase<infer V> ? V : T extends Map<infe...
type UnstubifyAll (line 7241) | type UnstubifyAll<A extends any[]> = {
type MaybeProvider (line 7246) | type MaybeProvider<T> = T extends object ? Provider<T> : unknown;
type MaybeDisposable (line 7247) | type MaybeDisposable<T> = T extends object ? Disposable : unknown;
type Result (line 7256) | type Result<R> = R extends Stubable ? Promise<Stub<R>> & Provider<R> : R...
type MethodOrProperty (line 7262) | type MethodOrProperty<V> = V extends (...args: infer P) => infer R
type MaybeCallableProvider (line 7267) | type MaybeCallableProvider<T> = T extends (...args: any[]) => any ? Meth...
type Provider (line 7271) | type Provider<T extends object, Reserved extends string = never> = Maybe...
type Env (line 7276) | interface Env {}
type RpcStub (line 7279) | type RpcStub<T extends Rpc.Stubable> = Rpc.Stub<T>;
type WorkflowDurationLabel (line 7310) | type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'wee...
type WorkflowSleepDuration (line 7311) | type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ...
type WorkflowDelayDuration (line 7312) | type WorkflowDelayDuration = WorkflowSleepDuration;
type WorkflowTimeoutDuration (line 7313) | type WorkflowTimeoutDuration = WorkflowSleepDuration;
type WorkflowRetentionDuration (line 7314) | type WorkflowRetentionDuration = WorkflowSleepDuration;
type WorkflowBackoff (line 7315) | type WorkflowBackoff = 'constant' | 'linear' | 'exponential';
type WorkflowStepConfig (line 7316) | type WorkflowStepConfig = {
type WorkflowEvent (line 7324) | type WorkflowEvent<T> = {
type WorkflowStepEvent (line 7329) | type WorkflowStepEvent<T> = {
type SecretsStoreSecret (line 7358) | interface SecretsStoreSecret {
type Header (line 7370) | interface Header {
type FetchEventInfo (line 7374) | interface FetchEventInfo {
type JsRpcEventInfo (line 7381) | interface JsRpcEventInfo {
type ScheduledEventInfo (line 7385) | interface ScheduledEventInfo {
type AlarmEventInfo (line 7390) | interface AlarmEventInfo {
type QueueEventInfo (line 7394) | interface QueueEventInfo {
type EmailEventInfo (line 7399) | interface EmailEventInfo {
type TraceEventInfo (line 7405) | interface TraceEventInfo {
type HibernatableWebSocketEventInfoMessage (line 7409) | interface HibernatableWebSocketEventInfoMessage {
type HibernatableWebSocketEventInfoError (line 7412) | interface HibernatableWebSocketEventInfoError {
type HibernatableWebSocketEventInfoClose (line 7415) | interface HibernatableWebSocketEventInfoClose {
type HibernatableWebSocketEventInfo (line 7420) | interface HibernatableWebSocketEventInfo {
type Resume (line 7424) | interface Resume {
type CustomEventInfo (line 7428) | interface CustomEventInfo {
type FetchResponseInfo (line 7431) | interface FetchResponseInfo {
type EventOutcome (line 7435) | type EventOutcome =
type ScriptVersion (line 7447) | interface ScriptVersion {
type Trigger (line 7452) | interface Trigger {
type Onset (line 7457) | interface Onset {
type Outcome (line 7478) | interface Outcome {
type Hibernate (line 7484) | interface Hibernate {
type SpanOpen (line 7487) | interface SpanOpen {
type SpanClose (line 7492) | interface SpanClose {
type DiagnosticChannelEvent (line 7496) | interface DiagnosticChannelEvent {
type Exception (line 7501) | interface Exception {
type Log (line 7507) | interface Log {
type Return (line 7512) | interface Return {
type Link (line 7516) | interface Link {
type Attribute (line 7523) | interface Attribute {
type Attributes (line 7527) | interface Attributes {
type TailEvent (line 7531) | interface TailEvent {
type TailEventHandler (line 7550) | type TailEventHandler = (event: TailEvent) => void | Promise<void>;
type TailEventHandlerName (line 7551) | type TailEventHandlerName =
type TailEventHandlerObject (line 7562) | type TailEventHandlerObject = Record<TailEventHandlerName, TailEventHand...
type TailEventHandlerType (line 7563) | type TailEventHandlerType = TailEventHandler | TailEventHandlerObject;
type VectorizeVectorMetadataValue (line 7571) | type VectorizeVectorMetadataValue = string | number | boolean | string[];
type VectorizeVectorMetadata (line 7575) | type VectorizeVectorMetadata = VectorizeVectorMetadataValue | Record<str...
type VectorFloatArray (line 7576) | type VectorFloatArray = Float32Array | Float64Array;
type VectorizeError (line 7577) | interface VectorizeError {
type VectorizeVectorMetadataFilterOp (line 7586) | type VectorizeVectorMetadataFilterOp = '$eq' | '$ne';
type VectorizeVectorMetadataFilter (line 7590) | type VectorizeVectorMetadataFilter = {
type VectorizeDistanceMetric (line 7602) | type VectorizeDistanceMetric = 'euclidean' | 'cosine' | 'dot-product';
type VectorizeMetadataRetrievalLevel (line 7612) | type VectorizeMetadataRetrievalLevel = 'all' | 'indexed' | 'none';
type VectorizeQueryOptions (line 7613) | interface VectorizeQueryOptions {
type VectorizeIndexConfig (line 7623) | type VectorizeIndexConfig =
type VectorizeIndexDetails (line 7637) | interface VectorizeIndexDetails {
type VectorizeIndexInfo (line 7652) | interface VectorizeIndexInfo {
type VectorizeVector (line 7665) | interface VectorizeVector {
type VectorizeMatch (line 7678) | type VectorizeMatch = Pick<Partial<VectorizeVector>, 'values'> &
type VectorizeMatches (line 7686) | interface VectorizeMatches {
type VectorizeVectorMutation (line 7697) | interface VectorizeVectorMutation {
type VectorizeAsyncMutation (line 7707) | interface VectorizeAsyncMutation {
type WorkerVersionMetadata (line 7809) | type WorkerVersionMetadata = {
type DynamicDispatchLimits (line 7817) | interface DynamicDispatchLimits {
type DynamicDispatchOptions (line 7827) | interface DynamicDispatchOptions {
type DispatchNamespace (line 7839) | interface DispatchNamespace {
class NonRetryableError (line 7860) | class NonRetryableError extends Error {
type WorkflowDurationLabel (line 7885) | type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'wee...
type WorkflowSleepDuration (line 7886) | type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ...
type WorkflowRetentionDuration (line 7887) | type WorkflowRetentionDuration = WorkflowSleepDuration;
type WorkflowInstanceCreateOptions (line 7888) | interface WorkflowInstanceCreateOptions<PARAMS = unknown> {
type InstanceStatus (line 7906) | type InstanceStatus = {
type WorkflowError (line 7920) | interface WorkflowError {
FILE: scripts/lint-markdown.js
constant DOCS_DIR (line 7) | const DOCS_DIR = path.resolve(__dirname, '../docs');
function getDocsFiles (line 10) | function getDocsFiles(dir) {
Condensed preview — 286 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,535K chars).
[
{
"path": ".devcontainer/devcontainer.json",
"chars": 198,
"preview": "{\n \"appPort\": 5173,\n \"features\": {},\n \"image\": \"mcr.microsoft.com/devcontainers/universal:2\",\n \"name\": \"FMHYedit\",\n "
},
{
"path": ".dockerignore",
"chars": 40,
"preview": "node_modules\n.git\n.gitignore\ndist\ncache\n"
},
{
"path": ".gitattributes",
"chars": 7346,
"preview": "# Common settings that generally should always be used with your language specific settings\n\n# Auto detect text files an"
},
{
"path": ".github/CODEOWNERS",
"chars": 79,
"preview": "docs/.vitepress/**/* @taskylizard\napi/**/* @taskylizard\nindex.md @taskylizard\n\n"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 6737,
"preview": "# Contribute Guide\n\n> [!INFO] NOTE\n> Some of these steps are easier if you're in our [Discord](https://github.com/fmhy/F"
},
{
"path": ".github/ISSUE_TEMPLATE/wiki.yml",
"chars": 1715,
"preview": "name: Create Issue\ndescription: 'Help us improve FMHY for everyone'\ntitle: 'Issue form title'\nbody:\n - type: markdown\n "
},
{
"path": ".github/POST_TEMPLATE.md",
"chars": 1177,
"preview": "### Post Template\n\n> [!NOTE]\n> This is a template for creating a post for the freemediaheckyeah blog, and is meant for *"
},
{
"path": ".github/README.md",
"chars": 1635,
"preview": "# FMHY\n\n\n\n<p align=\"cente"
},
{
"path": ".github/assets/nginx.conf",
"chars": 768,
"preview": "server {\n listen 80;\n server_name _;\n\n root /usr/share/nginx/html;\n index index.html;\n\n locatio"
},
{
"path": ".github/workflows/deploy-api.yml",
"chars": 1082,
"preview": "name: Deploy API\n\n# Only run this workflow when the API directory changes\non:\n push:\n paths:\n - 'api/**'\n br"
},
{
"path": ".github/workflows/deploy-gh-pages.yml",
"chars": 1162,
"preview": "name: Deploy to GitHub Pages\n\non:\n workflow_dispatch:\n\npermissions:\n contents: read\n pages: write\n id-token: write\n\n"
},
{
"path": ".gitignore",
"chars": 133,
"preview": "**/.vitepress/dist\n**/.vitepress/cache\nnode_modules\n*.log*\n.nitro\n.cache\n.output\n.env\ndist\n.eslintcache\ndocs/.vitepress/"
},
{
"path": ".gitpod.yml",
"chars": 109,
"preview": "tasks:\n - init: pnpm install\n command: pnpm run docs:dev\n\nports:\n - port: 5173\n onOpen: open-preview\n"
},
{
"path": ".licenserc.json",
"chars": 1208,
"preview": "{\n \"**/*.ts\": [\n \"/**\",\n \"* Copyright (c) 2025 taskylizard. Apache License 2.0.\",\n \"*\",\n \"* Licensed unde"
},
{
"path": ".mise.toml",
"chars": 221,
"preview": "# https://github.com/vitejs/vite/issues/17291\n[tools]\nnode = \"latest\"\npnpm = \"latest\"\n\n[tasks]\nd = \"nrr docs:dev --host\""
},
{
"path": ".npmrc",
"chars": 74,
"preview": "package-manager-strict=false\nshell-emulator=true\nauto-install-peers=false\n"
},
{
"path": ".prettierignore",
"chars": 52,
"preview": "**/*.md\n!docs/index.md\npnpm-lock.yaml\n.turbo\n.cache\n"
},
{
"path": ".prettierrc.yaml",
"chars": 422,
"preview": "proseWrap: always\nsemi: false\nsingleQuote: true\nprintWidth: 80\ntrailingComma: none\nhtmlWhitespaceSensitivity: ignore\nplu"
},
{
"path": "Dockerfile",
"chars": 524,
"preview": "FROM node:25.7-alpine AS base\nENV PNPM_HOME=\"/pnpm\"\nENV PATH=\"$PNPM_HOME:$PATH\"\nRUN npm install -g pnpm@10.30.3\nWORKDIR "
},
{
"path": "api/README.md",
"chars": 165,
"preview": "This is the API for the website and other related services.\n\nLicensed under the Apache License v2.0, see [LICENSE](../do"
},
{
"path": "api/middleware/cors.ts",
"chars": 790,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "api/middleware/ratelimit.ts",
"chars": 1193,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "api/routes/feedback.post.ts",
"chars": 2020,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "api/routes/index.ts",
"chars": 694,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "api/routes/single-page.ts",
"chars": 1909,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "api/tsconfig.json",
"chars": 120,
"preview": "{\n \"extends\": \"../.nitro/types/tsconfig.json\",\n \"compilerOptions\": {\n \"types\": [\"@cloudflare/workers-types\"]\n }\n}\n"
},
{
"path": "api/worker-configuration.d.ts",
"chars": 778,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docker-compose.yaml",
"chars": 218,
"preview": "services:\n docs:\n networks: [fmhy]\n build:\n context: .\n dockerfile: Dockerfile\n image: fmhy-docs\n "
},
{
"path": "docs/.vitepress/LICENSE",
"chars": 568,
"preview": "Copyright (c) taskylizard. Apache License 2.0\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may n"
},
{
"path": "docs/.vitepress/README.md",
"chars": 173,
"preview": "This is the website source code to be used with [VitePress](https://vitepress.dev/).\n\nLicensed under the Apache License "
},
{
"path": "docs/.vitepress/config.mts",
"chars": 6987,
"preview": "import { fileURLToPath } from 'node:url'\nimport consola from 'consola'\nimport UnoCSS from 'unocss/vite'\nimport AutoImpor"
},
{
"path": "docs/.vitepress/constants.ts",
"chars": 3619,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/hooks/Template.vue",
"chars": 863,
"preview": "<script setup lang=\"ts\">\ndefineProps<{ title: string; description?: string; image?: string }>()\n</script>\n\n<template>\n "
},
{
"path": "docs/.vitepress/hooks/index.ts",
"chars": 787,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/hooks/meta.ts",
"chars": 3644,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/hooks/opengraph.ts",
"chars": 3939,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/hooks/rss.ts",
"chars": 2049,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/hooks/satoriConfig.ts",
"chars": 1869,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/markdown/base64.ts",
"chars": 1804,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/markdown/emoji.ts",
"chars": 1855,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/markdown/headers.ts",
"chars": 2946,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/markdown/toggleStarred.ts",
"chars": 1607,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/notes/1337x-ranks.md",
"chars": 184,
"preview": "#### 1337x Ranks\n\n* ⬛ Black - Administrators\n* 🟩 Green - Moderators\n* 🟦 Blue - VIP Uploaders (Very Trusted)\n* 🟨 Yellow -"
},
{
"path": "docs/.vitepress/notes/CodeRabbit.md",
"chars": 172,
"preview": "Note you can get free pro by installing code rabbit on a public github/gitlab repo, as they offer a free open source pla"
},
{
"path": "docs/.vitepress/notes/advanced-logic-calculators.md",
"chars": 425,
"preview": "#### Advanced Logic Calculators\n\n* Analytic tableaux generator: https://www.umsu.de/trees/\n* Natural deduction proof che"
},
{
"path": "docs/.vitepress/notes/affine-note.md",
"chars": 125,
"preview": "#### AFFiNE Note\r\n\r\nDownloads for Windows, Mac, Linux are available on their [GitHub](https://github.com/toeverything/AF"
},
{
"path": "docs/.vitepress/notes/alt-twitch-player-extensions.md",
"chars": 205,
"preview": "#### Alternative Twitch Player Extensions\n\n* https://addons.mozilla.org/en-US/firefox/addon/twitch_5/\n* https://chrome.g"
},
{
"path": "docs/.vitepress/notes/alt-warp-clients.md",
"chars": 246,
"preview": "#### Alternative Warp Clients\n\nIf you can't connect, try `Scanner Settings` -> `Endpoint` -> `Suggested` -> then try dif"
},
{
"path": "docs/.vitepress/notes/android-spotify-note.md",
"chars": 127,
"preview": "#### Android Spotify Note\n\nNone of Spotify apks (for rooted and non-rooted users) works for now due to server side restr"
},
{
"path": "docs/.vitepress/notes/apkmirror-extensions.md",
"chars": 289,
"preview": "#### APKMirror Extensions\n\n* https://addons.mozilla.org/en-US/firefox/addon/toolbox-google-play-store/ \n* https://chrome"
},
{
"path": "docs/.vitepress/notes/audiobookbay-warning.md",
"chars": 163,
"preview": "#### AudiobookBay Warning\n\nAvoid fake download links, use [Torrents / Magnets](https://i.ibb.co/8sV2061/0fa8159b11bb.png"
},
{
"path": "docs/.vitepress/notes/aurora-note.md",
"chars": 190,
"preview": "#### Aurora Note\n\nKeep in mind that some apps will not work unless you installed them from the Google Play Store. This i"
},
{
"path": "docs/.vitepress/notes/better-reasoning.md",
"chars": 79,
"preview": "#### Better Reasoning\n\nTo get better reasoning, switch to \"Think Deeper\" mode.\n"
},
{
"path": "docs/.vitepress/notes/bookmarkeddit.md",
"chars": 111,
"preview": "#### Bookmarkeddit\n\nThis also extends the amount of saved posts you can view (Reddit caps at 1000 by default).\n"
},
{
"path": "docs/.vitepress/notes/buster-note.md",
"chars": 293,
"preview": "#### Buster Note\n\nThe client app simulates user interactions which greatly improves the success rate of buster. You can "
},
{
"path": "docs/.vitepress/notes/buzzheavier-warning.md",
"chars": 303,
"preview": "#### Buzzheavier Warning\n\nMake sure you have an [adblocker](https://fmhy.net/adblockvpnguide#adblocking) when using Buzz"
},
{
"path": "docs/.vitepress/notes/bypass-freedlink.md",
"chars": 226,
"preview": "#### Bypass FREEdlink\n\nYou still need to bypass Cloudflare captcha by yourself. This only bypasses timer on single downl"
},
{
"path": "docs/.vitepress/notes/captcha-4pda.md",
"chars": 64,
"preview": "#### Captcha 4PDA \n\nUse Google Gemini to translate the captcha.\n"
},
{
"path": "docs/.vitepress/notes/chatgpt-limits.md",
"chars": 112,
"preview": "#### ChatGPT Limits\n\n GPT-5.2 Instant (no reasoning; 16K context) / 10 messages every 5 hours, then GPT-5-mini.\n"
},
{
"path": "docs/.vitepress/notes/clipboard2file-addons.md",
"chars": 124,
"preview": "#### Clipboard2File Addons\n\n* https://github.com/vord1080/clipboard2file/\n* https://github.com/daijro/Clipboard2File-Chr"
},
{
"path": "docs/.vitepress/notes/cofi-note.md",
"chars": 149,
"preview": "#### Cofi Note\n\nUseful if you're a coffee enthusiast. The methods are created by James Hoffmann, he's a world champion b"
},
{
"path": "docs/.vitepress/notes/crystaldiskinfo.md",
"chars": 53,
"preview": "#### CrystalDiskInfo \n\nAvoid versions labeled \"Ads\".\n"
},
{
"path": "docs/.vitepress/notes/csrin-search.md",
"chars": 291,
"preview": "#### CS.RIN Search\n\nIf your initial search doesn't work, trying searching the same term again within the \"search these r"
},
{
"path": "docs/.vitepress/notes/cute-save-button-icon.md",
"chars": 265,
"preview": "#### Changing the Cute Save Button Icon\n\nYou can change the icon of the save button in the extension's settings. The set"
},
{
"path": "docs/.vitepress/notes/dodi-warning.md",
"chars": 236,
"preview": "#### DODI Warning\n\nIt is highly recommended to stick to DODI's 1337x page or main website, as sites they linked to have "
},
{
"path": "docs/.vitepress/notes/dolby-access-atmos-note.md",
"chars": 314,
"preview": "#### Dolby Access / Atmos Note\n\nMany headsets come with Dolby Access for free without letting users know. You can check "
},
{
"path": "docs/.vitepress/notes/driver-note.md",
"chars": 165,
"preview": "#### Driver Note\n\nOnly install the drivers you actually need. Don't install new drivers all at once, as this could lead "
},
{
"path": "docs/.vitepress/notes/eaglercraft-note.md",
"chars": 81,
"preview": "#### Eaglercraft Note\n\nPlay on Chromium-based browsers for the best performance.\n"
},
{
"path": "docs/.vitepress/notes/eruda.md",
"chars": 312,
"preview": "#### Eruda \n\nEruda Console for mobile browsers [bookmarklet](https://wikipedia.org/wiki/Bookmarklet):\n```\njavascript:(fu"
},
{
"path": "docs/.vitepress/notes/filebin-warning.md",
"chars": 128,
"preview": "#### Filebin Warning\n\n Anyone with a link to a \"bin\" has full access to it. They can add new files, delete existing file"
},
{
"path": "docs/.vitepress/notes/filelu-warning.md",
"chars": 159,
"preview": "#### Filelu Warning\n\nAccording to their FAQ, you must login to your account at least once every 180 days to prevent your"
},
{
"path": "docs/.vitepress/notes/filezilla-warning.md",
"chars": 332,
"preview": "#### FileZilla Warning\n\nThe version of FileZilla on FileZilla's front page has adware, but the non-adware version is the"
},
{
"path": "docs/.vitepress/notes/flicker-proxy.md",
"chars": 118,
"preview": "#### Flicker Proxy\n\nNote that the proxy may be slower, but it can be used in cases where the site or TMDb is blocked.\n"
},
{
"path": "docs/.vitepress/notes/fluxy-repacks.md",
"chars": 99,
"preview": "#### Fluxy Repacks\n\nNote that although it has repacks in the name, its not actually a repack site.\n"
},
{
"path": "docs/.vitepress/notes/forest-extensions.md",
"chars": 208,
"preview": "#### Forest Extensions\n\n* https://addons.mozilla.org/en-US/firefox/addon/forest-stay-focused-be-present/\n* https://chrom"
},
{
"path": "docs/.vitepress/notes/foxit-warning.md",
"chars": 146,
"preview": "#### Foxit Warning\n\nThe installer tries to install McAfee WebAdvisor + PhantomPDF Business. They can be skipped by click"
},
{
"path": "docs/.vitepress/notes/freegogpcgames-note.md",
"chars": 701,
"preview": "#### FreeGOGPCGames Note\n\nThe file checksum may not match with the original GOG installer. This is because many titles o"
},
{
"path": "docs/.vitepress/notes/gemai.md",
"chars": 131,
"preview": "#### Nano Banana Pro Note\n\nNano Banana Pro is a bit glitchy as of now, but it is being worked on according to their Disc"
},
{
"path": "docs/.vitepress/notes/general-tweak-warning.md",
"chars": 181,
"preview": "#### General Tweak Warning\n\nMake sure you know what you're doing before you apply these tweaks. Always research first, n"
},
{
"path": "docs/.vitepress/notes/glitchwave-note.md",
"chars": 279,
"preview": "#### Glitchwave Note\n\nFor charts you can specify months and days using URLs like the following examples:\n\nJanuary 2006:\n"
},
{
"path": "docs/.vitepress/notes/google-song-identification.md",
"chars": 126,
"preview": "#### Google Song Identification\n\nGoogle and YouTube Music mobile apps have song identification button next to the search"
},
{
"path": "docs/.vitepress/notes/google-translate-note.md",
"chars": 318,
"preview": "#### Google Translate Note\n\nGoogle Translate can be used as a web proxy. Simply paste your URL into the translate field "
},
{
"path": "docs/.vitepress/notes/hdo-box-note.md",
"chars": 214,
"preview": "#### HDO Box Note\n\nTo use the app, HDO Box may ask you to install a third-party video player which contains ads. To bloc"
},
{
"path": "docs/.vitepress/notes/hugging-face-warning.md",
"chars": 589,
"preview": "#### Hugging Face Warning\n\nHuggingFace uses a system called ZeroGPU to manage access to their high-end GPUs. To make sur"
},
{
"path": "docs/.vitepress/notes/instaeclipse-note.md",
"chars": 169,
"preview": "#### InstaEclipse Note\n\nUse [this guide](https://wispydocs.pages.dev/revanced-morphe-obtainium/#advanced) to build clean"
},
{
"path": "docs/.vitepress/notes/irc-highway-note.md",
"chars": 184,
"preview": "#### IRC Highway Note\n\nTo request a book run: @request [author] [title] - Requests without both [author] and [title] are"
},
{
"path": "docs/.vitepress/notes/jdownloader-warning.md",
"chars": 167,
"preview": "#### JDownloader Warning \n\nThe version of JDownloader linked on JDownloader's front page has adware. The version linked "
},
{
"path": "docs/.vitepress/notes/limit-bypass-note.md",
"chars": 117,
"preview": "#### Limit Bypass Note\n\n- SparseBox: iOS 17.0 - 18.1 Beta 4 (not including 17.7.1, 17.7.2)\n- Live container: iOS 16+\n"
},
{
"path": "docs/.vitepress/notes/liteapk-modyolo-note.md",
"chars": 216,
"preview": "#### LiteAPK + Modyolo Note\n\nThe site is safe, but they are known for mislabeling things like RockMods releases as their"
},
{
"path": "docs/.vitepress/notes/malware-removal-forums.md",
"chars": 175,
"preview": "#### Malware Removal Forums\n\nNote that many of these will suggest removing pirated software, but if you got everything f"
},
{
"path": "docs/.vitepress/notes/megabasterd-note.md",
"chars": 73,
"preview": "#### Megabasterd Note\n\nFree proxies work but they are very hit and miss.\n"
},
{
"path": "docs/.vitepress/notes/mobilism-ranks.md",
"chars": 105,
"preview": "#### Mobilism Ranks\n\nSee what the different Mobilism Ranks mean [here](https://i.imgur.com/WpShSFp.png).\n"
},
{
"path": "docs/.vitepress/notes/modelscope.md",
"chars": 394,
"preview": "### ModelScope Note\n\nThis site uses credits (called *magicubes*) to generate images and videos, you get 100 daily. It co"
},
{
"path": "docs/.vitepress/notes/mori-note.md",
"chars": 487,
"preview": "#### Māori Note\n\nMāori is the indigenous language of mainland New Zealand. Due to the [Native Schools Act](https://en.wi"
},
{
"path": "docs/.vitepress/notes/movie-web-sources.md",
"chars": 578,
"preview": "#### Adding sources to P-Stream (and all movie-web forks)\n\nYou can [enable an extension](https://pstream.net/onboarding/"
},
{
"path": "docs/.vitepress/notes/movieparadise-code.md",
"chars": 403,
"preview": "#### MovieParadise Code\n\nIn order to unlock the better host (1fichier) you need a signup code. This is important as with"
},
{
"path": "docs/.vitepress/notes/mp-opensubs.md",
"chars": 286,
"preview": "### OpenSubtitles with MPC-HC\n\nYou can create an OpenSubtitles account and link it in MPC-HC to bypass quota limits.\n\nYo"
},
{
"path": "docs/.vitepress/notes/mvsep-note.md",
"chars": 79,
"preview": "#### MVSEP Note\n\nRegister to get .wav and .flac output, and lower queue times.\n"
},
{
"path": "docs/.vitepress/notes/oneclick-note.md",
"chars": 243,
"preview": "#### OneClick Note\n\nMain features include:\n- Download links straight to Google Drive.\n- Torrent to Google Drive.\n- Googl"
},
{
"path": "docs/.vitepress/notes/openasar.md",
"chars": 170,
"preview": "#### OpenAsar Note\n\nThe Vencord installer has an option to install OpenAsar, but you may need to click the install butto"
},
{
"path": "docs/.vitepress/notes/openrgb-beta.md",
"chars": 621,
"preview": "#### OpenRGB Beta\n\nThe latest stable release (0.9) is from July, 2023. It is lacking support for many devices, so you ma"
},
{
"path": "docs/.vitepress/notes/pollinations-limits.md",
"chars": 854,
"preview": "#### Pollinations Limits\n\nFor chat.pollinations.ai (and the underlying API), the rate limits depend on how you're using "
},
{
"path": "docs/.vitepress/notes/printeditwe-addons.md",
"chars": 180,
"preview": "#### PrintEditWe Addons\n\n* https://addons.mozilla.org/en-US/firefox/addon/print-edit-we/\n* https://chrome.google.com/web"
},
{
"path": "docs/.vitepress/notes/proton-torrenting.md",
"chars": 343,
"preview": "#### Proton Torrenting\n\nTorrenting on Proton VPN's free plan is only possible when using an OpenVPN configuration / [Gui"
},
{
"path": "docs/.vitepress/notes/reaper-note.md",
"chars": 120,
"preview": "#### Reaper Note\n\nReaper asks you to buy it after 60 days, but you can just close the popup and keep using it for free.\n"
},
{
"path": "docs/.vitepress/notes/redditfilter-note.md",
"chars": 158,
"preview": "#### RedditFilter Note\n\nGo to `Settings` -> `Feed Filter` and untoggle `Promoted` to not see ads. You can also untoggle "
},
{
"path": "docs/.vitepress/notes/rgshows-autoplay.md",
"chars": 260,
"preview": "#### RGShows Autoplay\n\nTo enable autoplay on Firefox:\n* Click the permissions button located to the left of your search "
},
{
"path": "docs/.vitepress/notes/sanet-warning.md",
"chars": 311,
"preview": "#### Sanet Warning\n\nNote that Sanet has been known to host unsafe things like KMS Matrix, so it's best to avoid it for s"
},
{
"path": "docs/.vitepress/notes/savepagewe.md",
"chars": 170,
"preview": "#### SavePageWe\n\n* https://addons.mozilla.org/en-US/firefox/addon/save-page-we/\n* https://chrome.google.com/webstore/det"
},
{
"path": "docs/.vitepress/notes/scrollanywhere-addons.md",
"chars": 262,
"preview": "#### ScrollAnywhere Addons\n\n* https://addons.mozilla.org/en-US/firefox/addon/scroll_anywhere/\n* https://chrome.google.co"
},
{
"path": "docs/.vitepress/notes/sd-maid.md",
"chars": 199,
"preview": "#### SD Maid Note\n\nThe Google Play Store version is paid only. On the F-Droid and GitHub versions, however, you can use "
},
{
"path": "docs/.vitepress/notes/sh-note.md",
"chars": 204,
"preview": "#### SH Note\n\nBased on [this](https://wikipedia.org/wiki/Secret_Hitler) popular card game which was created by a co-foun"
},
{
"path": "docs/.vitepress/notes/site-favicon-dl.md",
"chars": 195,
"preview": "#### Site Favicon Downloading\n\nYou can also go to `https://www.google.com/s2/favicons?domain=URL&sz=64` where `URL` is t"
},
{
"path": "docs/.vitepress/notes/soft98-note.md",
"chars": 409,
"preview": "#### Soft98 Note\n\nEnable the `AdGuard - Ads` filter list in uBlock to allow downloads to work. To remove all ads, you ca"
},
{
"path": "docs/.vitepress/notes/sora.md",
"chars": 97,
"preview": "#### Sora\n\nBypass the need for a invite code by installing Sora Mobile, and logging into OpenAI.\n"
},
{
"path": "docs/.vitepress/notes/spacewar.md",
"chars": 198,
"preview": "Spacewar! is a [1962 multiplayer game](https://wikipedia.org/wiki/Spacewar!) made for the DEC PDP-1 minicomputer. It was"
},
{
"path": "docs/.vitepress/notes/spicetify-note.md",
"chars": 183,
"preview": "#### Spicetify Note\n\nJoin their [Discord](https://discord.gg/VnevqPp2Rr) for version compatibility.\n\nNote that you can u"
},
{
"path": "docs/.vitepress/notes/sport7.md",
"chars": 66,
"preview": "#### Sport7\n\nMany sites use this player but this was the original."
},
{
"path": "docs/.vitepress/notes/steam-controller-support.md",
"chars": 166,
"preview": "#### Steam Controller Support\n\nSteam has built in support for most controller types, just add your games to Steam, right"
},
{
"path": "docs/.vitepress/notes/steam-currency-converter-note.md",
"chars": 228,
"preview": "#### Steam Currency Converter Note\n\nFor instant currency conversion:\n\nGo to Firefox's add-on settings (or the link `abou"
},
{
"path": "docs/.vitepress/notes/tabiverse-extensions.md",
"chars": 157,
"preview": "#### Tabiverse Extensions\n\n* https://addons.mozilla.org/firefox/addon/tabiverse/\n* https://chromewebstore.google.com/det"
},
{
"path": "docs/.vitepress/notes/tautulli-note.md",
"chars": 146,
"preview": "#### Tautulli Note\n\nThis will sometimes get falsely flagged by Windows Defender and removed automatically, so it may nee"
},
{
"path": "docs/.vitepress/notes/teamspeak-warning.md",
"chars": 118,
"preview": "#### TeamSpeak Warning\n\nNote that TeamSpeak server admins can view user IP addresses, so only join servers you trust.\n"
},
{
"path": "docs/.vitepress/notes/thunderbird.md",
"chars": 299,
"preview": "#### Thunderbird Notifications\n\nTo get real-time notifications:\n\nPress the three lines in the top left corner -> select "
},
{
"path": "docs/.vitepress/notes/tinyurl-note.md",
"chars": 138,
"preview": "#### TinyURL Note\n\nTo reveal the destination URL, replace \"www\" with \"preview\" in the URL like so:\n \nhttps://preview.tin"
},
{
"path": "docs/.vitepress/notes/video-downloadhelper.md",
"chars": 170,
"preview": "#### Video DownloadHelper\n\nNote that some versions of this extension give a watermark on sites that need conversion. It "
},
{
"path": "docs/.vitepress/notes/welib-note.md",
"chars": 273,
"preview": "#### WeLib Note\n\nWeLib is *not* connected to Anna's Archive, they simply mirror Anna's content onto their own site that "
},
{
"path": "docs/.vitepress/notes/winrar.md",
"chars": 195,
"preview": "#### WinRAR Note\n\nWinRAR does not auto-update, and because it had a remote code execution vulnerability in the past, you"
},
{
"path": "docs/.vitepress/notes/yet-another-call-blocker-note.md",
"chars": 370,
"preview": "#### Yet Another Call Blocker Note\n\nThe app itself isn't updated, but the blocklists are. It has a main local blocklist "
},
{
"path": "docs/.vitepress/notes/youtube-tweaks.md",
"chars": 172,
"preview": "#### YouTube Tweaks\n\n* https://addons.mozilla.org/firefox/addon/youtube-tweaks/\n* https://chrome.google.com/webstore/det"
},
{
"path": "docs/.vitepress/notes/yts-yify-note.md",
"chars": 322,
"preview": "#### YTS / Yify Note\n\nYTS / Yify has many fake copycat sites out there, make sure you're on one of the official domains "
},
{
"path": "docs/.vitepress/shared.ts",
"chars": 8259,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/theme/Appearance.vue",
"chars": 1618,
"preview": "<script setup lang=\"ts\">\nimport VPIconMoon from 'vitepress/dist/client/theme-default/components/icons/VPIconMoon.vue'\nim"
},
{
"path": "docs/.vitepress/theme/Layout.vue",
"chars": 3407,
"preview": "<script setup lang=\"ts\">\nimport { useData } from 'vitepress'\nimport { ref, onMounted, onUnmounted, provide, nextTick } f"
},
{
"path": "docs/.vitepress/theme/PostLayout.vue",
"chars": 564,
"preview": "<script setup lang=\"ts\">\nimport Authors from './components/Authors.vue'\n\nconst props = defineProps<{\n authors: string\n}"
},
{
"path": "docs/.vitepress/theme/Posts.vue",
"chars": 1741,
"preview": "<!-- eslint-disable vue/require-v-for-key -->\n<script setup lang=\"ts\">\nimport { data as posts } from './posts.data'\n\ncon"
},
{
"path": "docs/.vitepress/theme/components/Announcement.vue",
"chars": 358,
"preview": "<script setup lang=\"ts\">\nconst { frontmatter } = useData()\n</script>\n\n<template>\n <a\n v-if=\"frontmatter.hero.announc"
},
{
"path": "docs/.vitepress/theme/components/Authors.vue",
"chars": 981,
"preview": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst props = defineProps<{\n authors: string[]\n}>()\n\ninterface"
},
{
"path": "docs/.vitepress/theme/components/Base64Dialog.vue",
"chars": 2996,
"preview": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\nconst props = defineProps<{\n show: boolean\n url: string\n}>()\n\ncons"
},
{
"path": "docs/.vitepress/theme/components/CardField.vue",
"chars": 337,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n icon: string\n}>()\n</script>\n\n<template>\n <div class=\"g-[12px] mb-[8px] flex it"
},
{
"path": "docs/.vitepress/theme/components/ColorPicker.vue",
"chars": 8166,
"preview": "<script setup lang=\"ts\">\nimport { colors } from '@fmhy/colors'\nimport { useStorage } from '@vueuse/core'\nimport { watch,"
},
{
"path": "docs/.vitepress/theme/components/Feedback.vue",
"chars": 9544,
"preview": "<script setup lang=\"ts\">\nimport type { FeedbackType } from '../../types/Feedback'\nimport { useRouter } from 'vitepress'\n"
},
{
"path": "docs/.vitepress/theme/components/InputField.vue",
"chars": 769,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n label: string\n id: string\n}>()\n</script>\n\n<template>\n <div class=\"input-field"
},
{
"path": "docs/.vitepress/theme/components/SidebarCard.vue",
"chars": 1472,
"preview": "<script setup lang=\"ts\">\nimport Field from './CardField.vue'\nimport ColorPicker from './ColorPicker.vue'\nimport ThemeSel"
},
{
"path": "docs/.vitepress/theme/components/Switch.vue",
"chars": 1667,
"preview": "<script setup lang=\"ts\">\nimport { Switch as HeadlessSwitch } from '@headlessui/vue'\n\nconst props = defineProps<{\n model"
},
{
"path": "docs/.vitepress/theme/components/ThemeDropdown.vue",
"chars": 5369,
"preview": "<script setup lang=\"ts\">\nimport { computed, ref, onMounted, onUnmounted } from 'vue'\nimport { useTheme } from '../themes"
},
{
"path": "docs/.vitepress/theme/components/ThemeSelector.vue",
"chars": 1595,
"preview": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTheme } from '../themes/themeHandler'\nimport { theme"
},
{
"path": "docs/.vitepress/theme/components/ToggleIndexes.vue",
"chars": 1534,
"preview": "<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref } from 'vue'\nimport Switch from './Switch.vue'\n\nconst "
},
{
"path": "docs/.vitepress/theme/components/ToggleStarred.vue",
"chars": 1205,
"preview": "<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref } from 'vue'\nimport Switch from './Switch.vue'\n\nconst "
},
{
"path": "docs/.vitepress/theme/components/Tooltip.vue",
"chars": 1989,
"preview": "<script setup lang=\"ts\">\nimport { withBase } from 'vitepress'\nimport { computed } from 'vue'\nimport { useMediaQuery } fr"
},
{
"path": "docs/.vitepress/theme/components/VPLocalSearchBox.vue",
"chars": 39086,
"preview": "<script lang=\"ts\" setup>\n/**\n * VPLocalSearchBox - Enhanced Local Search Modal Component\n * \n * Base: VitePress default "
},
{
"path": "docs/.vitepress/theme/components/VPNav.vue",
"chars": 4252,
"preview": "<script setup lang=\"ts\">\nimport { useData } from 'vitepress'\nimport { inBrowser } from 'vitepress'\nimport { computed, pr"
},
{
"path": "docs/.vitepress/theme/components/WallpaperCard.vue",
"chars": 949,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n title: string\n description: string\n mobile: string\n desktop: string\n}>()\n</s"
},
{
"path": "docs/.vitepress/theme/components/startpage/Bookmarks.vue",
"chars": 22965,
"preview": "<script setup lang=\"ts\">\nimport {\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogOverlay,\n DialogPortal,\n"
},
{
"path": "docs/.vitepress/theme/components/startpage/Clock.vue",
"chars": 689,
"preview": "<template>\n <div class=\"text-6xl font-bold text-text\">\n {{ timeString }}\n </div>\n</template>\n\n<script setup lang=\"t"
},
{
"path": "docs/.vitepress/theme/components/startpage/SearchBar.vue",
"chars": 5466,
"preview": "<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, ref } from 'vue'\nimport Clock from './Clock.vue'\n\nexport inter"
},
{
"path": "docs/.vitepress/theme/components/startpage/Startpage.vue",
"chars": 1323,
"preview": "<script setup lang=\"ts\">\nimport Bookmarks from './Bookmarks.vue'\nimport Searchbar from './SearchBar.vue'\n\nconst isFocuse"
},
{
"path": "docs/.vitepress/theme/composables/nprogress.ts",
"chars": 1331,
"preview": "import type { NProgress } from 'nprogress'\nimport type { EnhanceAppContext } from 'vitepress'\nimport nprogress from 'npr"
},
{
"path": "docs/.vitepress/theme/index.ts",
"chars": 2510,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/theme/posts.data.ts",
"chars": 1424,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/theme/style.scss",
"chars": 13304,
"preview": "body {\n transition: background-color 0.5s cubic-bezier(0.25, 0.8, 0.25, 1), color 0.5s cubic-bezier(0.25, 0.8, 0.25, 1)"
},
{
"path": "docs/.vitepress/theme/themes/README.md",
"chars": 4785,
"preview": "# Theme System\n\nThis document explains the updated theme architecture, display modes, and integration components in the "
},
{
"path": "docs/.vitepress/theme/themes/configs/catppuccin.ts",
"chars": 4180,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/theme/themes/configs/christmas.ts",
"chars": 4325,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/theme/themes/configs/index.ts",
"chars": 923,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/theme/themes/configs/monochrome.ts",
"chars": 4643,
"preview": "import type { Theme } from '../types'\n\nexport const monochromeTheme: Theme = {\n name: 'monochrome',\n displayName: "
},
{
"path": "docs/.vitepress/theme/themes/index.ts",
"chars": 709,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/theme/themes/themeHandler.ts",
"chars": 15078,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/theme/themes/types.ts",
"chars": 2678,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/transformer/constants.ts",
"chars": 4718,
"preview": "import { meta } from '../constants'\n\n/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under t"
},
{
"path": "docs/.vitepress/transformer/core.ts",
"chars": 2096,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/transformer.ts",
"chars": 10826,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/types/Feedback.ts",
"chars": 1433,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/utils/markdown.ts",
"chars": 1522,
"preview": "import type { MarkdownRenderer } from 'vitepress'\nimport { getTooltip } from './tooltips'\n\nconst NOTE_MATCH_RE = /\\.vite"
},
{
"path": "docs/.vitepress/utils/tooltips.ts",
"chars": 569,
"preview": "import { existsSync, readFileSync } from 'node:fs'\nimport { join, resolve } from 'pathe'\n\nexport interface TooltipData {"
},
{
"path": "docs/.vitepress/utils.ts",
"chars": 950,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/.vitepress/vue-shim.d.ts",
"chars": 1316,
"preview": "/**\n * Copyright (c) 2025 taskylizard. Apache License 2.0.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "docs/ai.md",
"chars": 53192,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n# ► AI Chatbots\n\n* *"
},
{
"path": "docs/audio.md",
"chars": 82391,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n# ► Audio Streaming\n"
},
{
"path": "docs/beginners-guide.md",
"chars": 12242,
"preview": "### Adblocking\n\n> How important is using an adblocker?\n\nSites generally contain ads, some of which can be harmful, often"
},
{
"path": "docs/developer-tools.md",
"chars": 103300,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/tools-index)**\n***\n***\n\n# ► Dev Commun"
},
{
"path": "docs/downloading.md",
"chars": 17745,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n# ► Download Directo"
},
{
"path": "docs/educational.md",
"chars": 155898,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n# ► Documentaries\n\n*"
},
{
"path": "docs/feedback.md",
"chars": 7505,
"preview": "---\ntitle: Feedback\ndescription: Anonymous comments taken from Reddit, Discord, X.com and our feedback system.\nnext: fal"
},
{
"path": "docs/file-tools.md",
"chars": 37647,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/tools-index)**\n***\n***\n\n# ► File Tools"
},
{
"path": "docs/gaming-tools.md",
"chars": 107799,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n# ► Gaming Tools\n\n* "
},
{
"path": "docs/gaming.md",
"chars": 94202,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n* **Note** - Use [re"
},
{
"path": "docs/image-tools.md",
"chars": 72846,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/tools-index)**\n***\n***\n\n# ► Image Edit"
},
{
"path": "docs/index.md",
"chars": 9941,
"preview": "---\ntitle: Welcome\nlayout: home\ndescription: The largest collection of free stuff on the internet!\n\nhero:\n name: freeme"
},
{
"path": "docs/internet-tools.md",
"chars": 79040,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/tools-index)**\n***\n***\n\n# ► Internet T"
},
{
"path": "docs/linux-macos.md",
"chars": 77853,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n# ► Linux Guides\n\n* "
},
{
"path": "docs/misc.md",
"chars": 148726,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n# ► Indexes\n\n* 🌐 **["
},
{
"path": "docs/mobile.md",
"chars": 137228,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n# ► Android APKs\n\n##"
},
{
"path": "docs/non-english.md",
"chars": 113522,
"preview": "***\n***\n**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)**\n***\n***\n\n* **Note** - Use thi"
},
{
"path": "docs/other/FAQ.md",
"chars": 2070,
"preview": "---\ntitle: FAQ\ndescription: A List Of Questions And Answers Related To FMHY.\n---\n\n# FAQs\n\n**Q**: Why are there so few su"
},
{
"path": "docs/other/backups.md",
"chars": 2492,
"preview": "**Official Website / Source**\n\nOfficial website, mirrors, GitHub, markdown, and a selfhosting guide.\n\n* **[FMHY.net](htt"
},
{
"path": "docs/other/contributing.md",
"chars": 149,
"preview": "---\ntitle: Contributing\ndescription: How to contribute to the project.\nnext: false\nprev: false\n---\n\n<!-- @include: ../.."
},
{
"path": "docs/other/selfhosting.md",
"chars": 5724,
"preview": "---\ntitle: Selfhosting FMHY\ndescription: This guide will help you set up and run your own instance of FMHY locally.\n---\n"
},
{
"path": "docs/other/wallpapers.md",
"chars": 1254,
"preview": "---\ntitle: Wallpapers\ndescription: Elevate yourself to the next level with our beautiful wallpapers.\nnext: false\nprev: f"
},
{
"path": "docs/posts/FCC.md",
"chars": 1124,
"preview": "---\ntitle: Fight Chat Control 🔒\ndescription: Protect EU Digital Privacy\ndate: 2025-09-04\nnext: false\n\nprev: false\n\nfoote"
}
]
// ... and 86 more files (download for full content)
About this extraction
This page contains the full source code of the fmhy/edit GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 286 files (5.0 MB), approximately 1.3M tokens, and a symbol index with 571 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.