chore(console): add the media CDN plumbing, the server-bundle guard and the site docs

Heavy media (about 148 MB) is not committed. src/lib/media.ts holds the one CDN base URL and the media
file types, script/upload-media.ts uploads the files (not run), media-manifest.txt lists them and MEDIA.md is
the plan; .gitignore keeps them out of git. script/check-server-bundle.ts fails the build when three.js reaches
the Worker's server bundle (wired into the build in the next commit). Also
the site's AGENTS.md, DESIGN.md and README.md, and the translator agent.
This commit is contained in:
David Hill
2026-11-05 03:42:01 -06:00
Unverified
parent 32ab1f3a9e
commit 229476779a
12 changed files with 1576 additions and 4 deletions
+918
View File
@@ -0,0 +1,918 @@
---
description: Translate content for a specified locale while preserving technical terms
mode: subagent
model: opencode/gpt-5.6-sol
---
You are a professional translator and localization specialist.
Translate the user's content into the requested target locale (language + region, e.g. fr-FR, de-DE).
Requirements:
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks.
- Also preserve every term listed in the Do-Not-Translate glossary below.
- Also apply locale-specific guidance from `.opencode/glossary/<locale>.md` when available (for example, `zh-cn.md`).
- Do not modify fenced code blocks.
- Output ONLY the translation (no commentary).
If the target locale is missing, ask the user to provide it.
If no locale-specific glossary exists, use the global glossary only.
---
# Website Copy (opencode.ai)
When translating the marketing site (`packages/console/app/src/i18n/<locale>.ts` and `src/i18n/content/<locale>.ts`):
- Keep every `{{param}}`, every link id in `[text](id)` (translate the text, keep the id) and every `` `code` `` span exactly.
- Keep these names as written: OpenCode, OpenCode Models, OpenCode Go, OpenCode Go Plus, Claude Opus 5.5, Anomaly, Console, and every company, model and app name.
- Never describe OpenCode as an AI coding agent: it is an "open source coding agent". Do not add "AI" to that phrase in any language. "AI models" and "AI providers" are fine.
- Prefer natural phrasing over literal translation. Use statements, not questions. Avoid colons where the language allows another construction.
- Keep strings about as long as the English. Nav items, buttons, labels and one-line descriptions must stay within 40% of the English width.
- Validate with `bun i18n/check.ts <locale>` from the worktree parent folder before you finish.
---
# Locale-Specific Glossaries
When a locale glossary exists, use it to:
- Apply preferred wording for recurring UI/docs terms in that locale
- Preserve locale-specific do-not-translate terms and casing decisions
- Prefer natural phrasing over literal translation when the locale file calls it out
- If the repo uses a locale alias slug, apply that file too (for example, `pt-BR` maps to `br.md` in this repo)
Locale guidance does not override code/command preservation rules or the global Do-Not-Translate glossary below.
---
# Do-Not-Translate Terms (OpenCode Docs)
Generated from: `packages/web/src/content/docs/*.mdx` (default English docs)
Generated on: 2026-02-10
Use this as a translation QA checklist / glossary. Preserve listed terms exactly (spelling, casing, punctuation).
General rules (verbatim, even if not listed below):
- Anything inside inline code (single backticks) or fenced code blocks (triple backticks)
- MDX/JS code in docs: `import ... from "..."`, component tags, identifiers
- CLI commands, flags, config keys/values, file paths, URLs/domains, and env vars
## Proper nouns and product names
- OpenCode Models
- OpenCode Go
- OpenCode Go Plus
- Claude Opus 5.5
- Anomaly
Additional (not reliably captured via link text):
```text
Astro
Bun
Chocolatey
Cursor
Docker
Git
GitHub Actions
GitLab CI
GNOME Terminal
Homebrew
Mise
Neovim
Node.js
npm
Obsidian
opencode
opencode-ai
Paru
pnpm
ripgrep
Scoop
SST
Starlight
Visual Studio Code
VS Code
VSCodium
Windsurf
Windows Terminal
Yarn
Zellij
Zed
anomalyco
```
Extracted from link labels in the English docs (review and prune as desired):
```text
@openspoon/subtask2
302.AI console
ACP progress report
Agent Client Protocol
Agent Skills
Agentic
AGENTS.md
AI SDK
Alacritty
Anthropic
Anthropic's Data Policies
Atom One
Avante.nvim
Ayu
Azure AI Foundry
Azure portal
Baseten
built-in GITHUB_TOKEN
Bun.$
Catppuccin
Cerebras console
ChatGPT Plus or Pro
Cloudflare dashboard
CodeCompanion.nvim
CodeNomad
Configuring Adapters: Environment Variables
Context7 MCP server
Cortecs console
Deep Infra dashboard
DeepSeek console
Duo Agent Platform
Everforest
Fireworks AI console
Firmware dashboard
Ghostty
GitLab CLI agents docs
GitLab docs
GitLab User Settings > Access Tokens
Granular Rules (Object Syntax)
Grep by Vercel
Groq console
Gruvbox
Helicone
Helicone documentation
Helicone Header Directory
Helicone's Model Directory
Hugging Face Inference Providers
Hugging Face settings
install WSL
IO.NET console
JetBrains IDE
Kanagawa
Kitty
MiniMax API Console
Models.dev
Moonshot AI console
Nebius Token Factory console
Nord
OAuth
Ollama integration docs
OpenAI's Data Policies
OpenChamber
OpenCode
OpenCode config
OpenCode Config
OpenCode TUI with the opencode theme
OpenCode Web - Active Session
OpenCode Web - New Session
OpenCode Web - See Servers
OpenCode Zen
OpenCode-Obsidian
OpenRouter dashboard
OpenWork
OVHcloud panel
Pro+ subscription
SAP BTP Cockpit
Scaleway Console IAM settings
Scaleway Generative APIs
SDK documentation
Sentry MCP server
shell API
Together AI console
Tokyonight
Unified Billing
Venice AI console
Vercel dashboard
WezTerm
Windows Subsystem for Linux (WSL)
WSL
WSL (Windows Subsystem for Linux)
WSL extension
xAI console
Z.AI API console
Zed
ZenMux dashboard
Zod
```
## Acronyms and initialisms
```text
ACP
AGENTS
AI
AI21
ANSI
API
AST
AWS
BTP
CD
CDN
CI
CLI
CMD
CORS
DEBUG
EKS
ERROR
FAQ
GLM
GNOME
GPT
HTML
HTTP
HTTPS
IAM
ID
IDE
INFO
IO
IP
IRSA
JS
JSON
JSONC
K2
LLM
LM
LSP
M2
MCP
MR
NET
NPM
NTLM
OIDC
OS
PAT
PATH
PHP
PR
PTY
README
RFC
RPC
SAP
SDK
SKILL
SSE
SSO
TS
TTY
TUI
UI
URL
US
UX
VCS
VPC
VPN
VS
WARN
WSL
X11
YAML
```
## Code identifiers used in prose (CamelCase, mixedCase)
```text
apiKey
AppleScript
AssistantMessage
baseURL
BurntSushi
ChatGPT
ClangFormat
CodeCompanion
CodeNomad
DeepSeek
DefaultV2
FileContent
FileDiff
FileNode
fineGrained
FormatterStatus
GitHub
GitLab
iTerm2
JavaScript
JetBrains
macOS
mDNS
MiniMax
NeuralNomadsAI
NickvanDyke
NoeFabris
OpenAI
OpenAPI
OpenChamber
OpenCode
OpenRouter
OpenTUI
OpenWork
ownUserPermissions
PowerShell
ProviderAuthAuthorization
ProviderAuthMethod
ProviderInitError
SessionStatus
TabItem
tokenType
ToolIDs
ToolList
TypeScript
typesUrl
UserMessage
VcsInfo
WebView2
WezTerm
xAI
ZenMux
```
## OpenCode CLI commands (as shown in docs)
```text
opencode
opencode [project]
opencode /path/to/project
opencode acp
opencode agent [command]
opencode agent create
opencode agent list
opencode attach [url]
opencode attach http://10.20.30.40:4096
opencode attach http://localhost:4096
opencode auth [command]
opencode auth list
opencode auth login
opencode auth logout
opencode auth ls
opencode export [sessionID]
opencode github [command]
opencode github install
opencode github run
opencode import <file>
opencode import https://opncd.ai/s/abc123
opencode import session.json
opencode mcp [command]
opencode mcp add
opencode mcp auth [name]
opencode mcp auth list
opencode mcp auth ls
opencode mcp auth my-oauth-server
opencode mcp auth sentry
opencode mcp debug <name>
opencode mcp debug my-oauth-server
opencode mcp list
opencode mcp logout [name]
opencode mcp logout my-oauth-server
opencode mcp ls
opencode models --refresh
opencode models [provider]
opencode models anthropic
opencode run [message..]
opencode run Explain the use of context in Go
opencode serve
opencode serve --cors http://localhost:5173 --cors https://app.example.com
opencode serve --hostname 0.0.0.0 --port 4096
opencode serve [--port <number>] [--hostname <string>] [--cors <origin>]
opencode session [command]
opencode session list
opencode session delete <sessionID>
opencode stats
opencode uninstall
opencode upgrade
opencode upgrade [target]
opencode upgrade v0.1.48
opencode web
opencode web --cors https://example.com
opencode web --hostname 0.0.0.0
opencode web --mdns
opencode web --mdns --mdns-domain myproject.local
opencode web --port 4096
opencode web --port 4096 --hostname 0.0.0.0
opencode.server.close()
```
## Slash commands and routes
```text
/agent
/auth/:id
/clear
/command
/config
/config/providers
/connect
/continue
/doc
/editor
/event
/experimental/tool?provider=<p>&model=<m>
/experimental/tool/ids
/export
/file?path=<path>
/file/content?path=<p>
/file/status
/find?pattern=<pat>
/find/file
/find/file?query=<q>
/find/symbol?query=<q>
/formatter
/global/event
/global/health
/help
/init
/instance/dispose
/log
/lsp
/mcp
/mnt/
/mnt/c/
/mnt/d/
/models
/oc
/opencode
/path
/project
/project/current
/provider
/provider/{id}/oauth/authorize
/provider/{id}/oauth/callback
/provider/auth
/q
/quit
/redo
/resume
/session
/session/:id
/session/:id/abort
/session/:id/children
/session/:id/command
/session/:id/diff
/session/:id/fork
/session/:id/init
/session/:id/message
/session/:id/message/:messageID
/session/:id/permissions/:permissionID
/session/:id/prompt_async
/session/:id/revert
/session/:id/share
/session/:id/shell
/session/:id/summarize
/session/:id/todo
/session/:id/unrevert
/session/status
/share
/summarize
/theme
/tui
/tui/append-prompt
/tui/clear-prompt
/tui/control/next
/tui/control/response
/tui/execute-command
/tui/open-help
/tui/open-models
/tui/open-sessions
/tui/open-themes
/tui/show-toast
/tui/submit-prompt
/undo
/Users/username
/Users/username/projects/*
/vcs
```
## CLI flags and short options
```text
--agent
--attach
--command
--continue
--cors
--cwd
--days
--dir
--dry-run
--event
--file
--force
--fork
--format
--help
--hostname
--hostname 0.0.0.0
--keep-config
--keep-data
--log-level
--max-count
--mdns
--mdns-domain
--method
--model
--models
--port
--print-logs
--project
--prompt
--refresh
--session
--share
--title
--token
--tools
--verbose
--version
--wait
-c
-d
-f
-h
-m
-n
-s
-v
```
## Environment variables
```text
AI_API_URL
AI_FLOW_CONTEXT
AI_FLOW_EVENT
AI_FLOW_INPUT
AICORE_DEPLOYMENT_ID
AICORE_RESOURCE_GROUP
AICORE_SERVICE_KEY
ANTHROPIC_API_KEY
AWS_ACCESS_KEY_ID
AWS_BEARER_TOKEN_BEDROCK
AWS_PROFILE
AWS_REGION
AWS_ROLE_ARN
AWS_SECRET_ACCESS_KEY
AWS_WEB_IDENTITY_TOKEN_FILE
AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
AZURE_RESOURCE_NAME
CI_PROJECT_DIR
CI_SERVER_FQDN
CI_WORKLOAD_REF
CLOUDFLARE_ACCOUNT_ID
CLOUDFLARE_API_TOKEN
CLOUDFLARE_GATEWAY_ID
CONTEXT7_API_KEY
GITHUB_TOKEN
GITLAB_AI_GATEWAY_URL
GITLAB_HOST
GITLAB_INSTANCE_URL
GITLAB_OAUTH_CLIENT_ID
GITLAB_TOKEN
GITLAB_TOKEN_OPENCODE
GOOGLE_APPLICATION_CREDENTIALS
GOOGLE_CLOUD_PROJECT
HTTP_PROXY
HTTPS_PROXY
K2_
MY_API_KEY
MY_ENV_VAR
MY_MCP_CLIENT_ID
MY_MCP_CLIENT_SECRET
NO_PROXY
NODE_ENV
NODE_EXTRA_CA_CERTS
NPM_AUTH_TOKEN
OC_ALLOW_WAYLAND
OPENCODE_API_KEY
OPENCODE_AUTH_JSON
OPENCODE_AUTO_SHARE
OPENCODE_CLIENT
OPENCODE_CONFIG
OPENCODE_CONFIG_CONTENT
OPENCODE_CONFIG_DIR
OPENCODE_DISABLE_AUTOCOMPACT
OPENCODE_DISABLE_AUTOUPDATE
OPENCODE_DISABLE_CLAUDE_CODE
OPENCODE_DISABLE_CLAUDE_CODE_PROMPT
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS
OPENCODE_DISABLE_DEFAULT_PLUGINS
OPENCODE_DISABLE_LSP_DOWNLOAD
OPENCODE_DISABLE_MODELS_FETCH
OPENCODE_DISABLE_PRUNE
OPENCODE_DISABLE_TERMINAL_TITLE
OPENCODE_ENABLE_EXA
OPENCODE_ENABLE_EXPERIMENTAL_MODELS
OPENCODE_EXPERIMENTAL
OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS
OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER
OPENCODE_EXPERIMENTAL_EXA
OPENCODE_EXPERIMENTAL_FILEWATCHER
OPENCODE_EXPERIMENTAL_ICON_DISCOVERY
OPENCODE_EXPERIMENTAL_LSP_TOOL
OPENCODE_EXPERIMENTAL_LSP_TY
OPENCODE_EXPERIMENTAL_MARKDOWN
OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX
OPENCODE_EXPERIMENTAL_OXFMT
OPENCODE_EXPERIMENTAL_PLAN_MODE
OPENCODE_ENABLE_QUESTION_TOOL
OPENCODE_FAKE_VCS
OPENCODE_GIT_BASH_PATH
OPENCODE_MODEL
OPENCODE_MODELS_URL
OPENCODE_PERMISSION
OPENCODE_PORT
OPENCODE_SERVER_PASSWORD
OPENCODE_SERVER_USERNAME
PROJECT_ROOT
RESOURCE_NAME
RUST_LOG
VARIABLE_NAME
VERTEX_LOCATION
XDG_CONFIG_HOME
```
## Package/module identifiers
```text
../../../config.mjs
@astrojs/starlight/components
@opencode-ai/plugin
@opencode-ai/sdk
path
shescape
zod
@
@ai-sdk/anthropic
@ai-sdk/cerebras
@ai-sdk/google
@ai-sdk/openai
@ai-sdk/openai-compatible
@File#L37-42
@modelcontextprotocol/server-everything
@opencode
```
## GitHub owner/repo slugs referenced in docs
```text
24601/opencode-zellij-namer
angristan/opencode-wakatime
anomalyco/opencode
apps/opencode-agent
athal7/opencode-devcontainers
awesome-opencode/awesome-opencode
backnotprop/plannotator
ben-vargas/ai-sdk-provider-opencode-sdk
btriapitsyn/openchamber
BurntSushi/ripgrep
Cluster444/agentic
code-yeongyu/oh-my-opencode
darrenhinde/opencode-agents
different-ai/opencode-scheduler
different-ai/openwork
features/copilot
folke/tokyonight.nvim
franlol/opencode-md-table-formatter
ggml-org/llama.cpp
ghoulr/opencode-websearch-cited.git
H2Shami/opencode-helicone-session
hosenur/portal
jamesmurdza/daytona
jenslys/opencode-gemini-auth
JRedeker/opencode-morph-fast-apply
JRedeker/opencode-shell-strategy
kdcokenny/ocx
kdcokenny/opencode-background-agents
kdcokenny/opencode-notify
kdcokenny/opencode-workspace
kdcokenny/opencode-worktree
login/device
mohak34/opencode-notifier
morhetz/gruvbox
mtymek/opencode-obsidian
NeuralNomadsAI/CodeNomad
nick-vi/opencode-type-inject
NickvanDyke/opencode.nvim
NoeFabris/opencode-antigravity-auth
nordtheme/nord
numman-ali/opencode-openai-codex-auth
olimorris/codecompanion.nvim
panta82/opencode-notificator
rebelot/kanagawa.nvim
remorses/kimaki
sainnhe/everforest
shekohex/opencode-google-antigravity-auth
shekohex/opencode-pty.git
spoons-and-mirrors/subtask2
sudo-tee/opencode.nvim
supermemoryai/opencode-supermemory
Tarquinen/opencode-dynamic-context-pruning
Th3Whit3Wolf/one-nvim
upstash/context7
vtemian/micode
vtemian/octto
yetone/avante.nvim
zenobi-us/opencode-plugin-template
zenobi-us/opencode-skillful
```
## Paths, filenames, globs, and URLs
```text
./.opencode/themes/*.json
./<project-slug>/storage/
./config/#custom-directory
./global/storage/
.agents/skills/*/SKILL.md
.agents/skills/<name>/SKILL.md
.clang-format
.claude
.claude/skills
.claude/skills/*/SKILL.md
.claude/skills/<name>/SKILL.md
.env
.github/workflows/opencode.yml
.gitignore
.gitlab-ci.yml
.ignore
.NET SDK
.npmrc
.ocamlformat
.opencode
.opencode/
.opencode/agents/
.opencode/commands/
.opencode/commands/test.md
.opencode/modes/
.opencode/plans/*.md
.opencode/plugins/
.opencode/skills/<name>/SKILL.md
.opencode/skills/git-release/SKILL.md
.opencode/tools/
.well-known/opencode
{ type: "raw" \| "patch", content: string }
{file:path/to/file}
**/*.js
%USERPROFILE%/intelephense/license.txt
%USERPROFILE%\.cache\opencode
%USERPROFILE%\.config\opencode\opencode.jsonc
%USERPROFILE%\.config\opencode\plugins
%USERPROFILE%\.local\share\opencode
%USERPROFILE%\.local\share\opencode\log
<project-root>/.opencode/themes/*.json
<providerId>/<modelId>
<your-project>/.opencode/plugins/
~
~/...
~/.agents/skills/*/SKILL.md
~/.agents/skills/<name>/SKILL.md
~/.aws/credentials
~/.bashrc
~/.cache/opencode
~/.cache/opencode/node_modules/
~/.claude/CLAUDE.md
~/.claude/skills/
~/.claude/skills/*/SKILL.md
~/.claude/skills/<name>/SKILL.md
~/.config/opencode
~/.config/opencode/AGENTS.md
~/.config/opencode/agents/
~/.config/opencode/commands/
~/.config/opencode/modes/
~/.config/opencode/opencode.json
~/.config/opencode/opencode.jsonc
~/.config/opencode/plugins/
~/.config/opencode/skills/*/SKILL.md
~/.config/opencode/skills/<name>/SKILL.md
~/.config/opencode/themes/*.json
~/.config/opencode/tools/
~/.config/zed/settings.json
~/.local/share
~/.local/share/opencode/
~/.local/share/opencode/auth.json
~/.local/share/opencode/log/
~/.local/share/opencode/mcp-auth.json
~/.local/share/opencode/opencode.jsonc
~/.npmrc
~/.zshrc
~/code/
~/Library/Application Support
~/projects/*
~/projects/personal/
${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts
$HOME/intelephense/license.txt
$HOME/projects/*
$XDG_CONFIG_HOME/opencode/themes/*.json
agent/
agents/
build/
commands/
dist/
http://<wsl-ip>:4096
http://127.0.0.1:8080/callback
http://localhost:<port>
http://localhost:4096
http://localhost:4096/doc
https://app.example.com
https://AZURE_COGNITIVE_SERVICES_RESOURCE_NAME.cognitiveservices.azure.com/
https://opencode.ai/zen/v1/chat/completions
https://opencode.ai/zen/v1/messages
https://opencode.ai/zen/v1/models/gemini-3-flash
https://opencode.ai/zen/v1/models/gemini-3-pro
https://opencode.ai/zen/v1/responses
https://RESOURCE_NAME.openai.azure.com/
laravel/pint
log/
model: "anthropic/claude-sonnet-4-5"
modes/
node_modules/
openai/gpt-4.1
opencode.ai/config.json
opencode/<model-id>
opencode/gpt-5.1-codex
opencode/gpt-5.2-codex
opencode/kimi-k2
openrouter/google/gemini-2.5-flash
opncd.ai/s/<share-id>
packages/*/AGENTS.md
plugins/
project/
provider_id/model_id
provider/model
provider/model-id
rm -rf ~/.cache/opencode
skills/
skills/*/SKILL.md
src/**/*.ts
themes/
tools/
```
## Keybind strings
```text
alt+b
Alt+Ctrl+K
alt+d
alt+f
Cmd+Esc
Cmd+Option+K
Cmd+Shift+Esc
Cmd+Shift+G
Cmd+Shift+P
ctrl+a
ctrl+b
ctrl+d
ctrl+e
Ctrl+Esc
ctrl+f
ctrl+g
ctrl+k
Ctrl+Shift+Esc
Ctrl+Shift+P
ctrl+t
ctrl+u
ctrl+w
ctrl+x
DELETE
Shift+Enter
WIN+R
```
## Model ID strings referenced
```text
{env:OPENCODE_MODEL}
anthropic/claude-3-5-sonnet-20241022
anthropic/claude-haiku-4-20250514
anthropic/claude-haiku-4-5
anthropic/claude-sonnet-4-20250514
anthropic/claude-sonnet-4-5
gitlab/duo-chat-haiku-4-5
lmstudio/google/gemma-3n-e4b
openai/gpt-4.1
openai/gpt-5
opencode/gpt-5.1-codex
opencode/gpt-5.2-codex
opencode/kimi-k2
openrouter/google/gemini-2.5-flash
```
+3 -1
View File
@@ -193,6 +193,8 @@
".pi/**",
".roo/**",
".windsurf/**",
"script/oxlint/anti-slop/**"
"script/oxlint/anti-slop/**",
"packages/console/app/public/arcade3d/basis/**",
"packages/console/app/public/arcade3d/draco/**"
]
}
+23 -3
View File
@@ -22,9 +22,29 @@ app.config.timestamp_*.js
# Temp
gitignore
# Generated files
public/sitemap.xml
# System Files
.DS_Store
Thumbs.db
# Built by packages/console/textjs (bun run build)
/public/textjs
# Written by script/build-stamp.ts (bun run build / dev)
/src/lib/build-stamp.json
# Written by script/og.ts (bun run build / dev)
/public/og
/src/lib/og-images.json
# Heavy media lives in the opencode.ai files bucket, not in git (MEDIA.md, src/lib/media.ts,
# script/upload-media.ts). Files dev already tracks stay tracked.
/public/**/*.mp4
/public/**/*.webm
/public/**/*.mov
/public/**/*.mp3
/public/**/*.wav
/public/**/*.ogg
/public/**/*.m4a
/public/**/*.zip
/public/**/*.glb
/public/**/*.gltf
+20
View File
@@ -0,0 +1,20 @@
# Console app (opencode.ai)
Read the design-system rules in `DESIGN.md` (also shown at `/dev/components#rules`) before you change UI or copy.
## Public pages
- When you add, remove or rename a public page, keep `/sitemap.xml`, `llms.txt`, search and the page's `.md` version in sync; run the sitemap test (`bun test test/sitemap.test.ts` from this directory).
- `/sitemap.xml` is the only sitemap in `robots.txt`. It is an index of `sitemapFiles` in `src/lib/sitemap.ts`: this app's `/sitemap-pages.xml`, the docs sitemap `/docs/sitemap.xml`, and the Data and Console sitemaps.
- `/sitemap-pages.xml` (`src/routes/sitemap-pages.xml.ts`) lists every page route automatically. To leave a page out, add it to `sitemapExclusions` in `src/lib/sitemap.ts` with a reason. A route with parameters needs an entry in `dynamicRoutes` there, or an exclusion.
- Do not add a static `public/sitemap.xml`, because it would hide the generated one.
- The view-source comment's FOR HUMANS rows start with the main nav in the same order; they're generated from the nav data, so update the nav, not the comment.
## Copy
- Do not use colons in site copy (headings, leads, body text, buttons, captions, alt text). Rewrite the sentence instead, for example "The easiest way to start is to download OpenCode", not "The easiest way to start: download OpenCode". Colons in code, commands, URLs, times and data labels (such as "HEX") are fine.
- Write statements, not rhetorical questions. Use "For more, bring your own subscription", not "Want more? Bring your own subscription". Real questions are fine where the reader is asking them, such as FAQ entries.
## UI states
- A button that opens a menu, popover, dropdown, sheet or dialog sets `aria-expanded="true"` while it is open and is styled from that attribute, not hover: filled and split buttons fill with the link blue (like the product cards' split chevron), neutral buttons keep their pressed grey, and text or nav triggers take the current-item colour with the chevron flipped; Escape closes and returns focus to the trigger.
+157
View File
@@ -0,0 +1,157 @@
# Rules for agents
Design-system rules for the opencode.ai site (`packages/console/app`), compiled from David's reviews and decisions. Read them before you change UI or copy. Every component they mention is live at [/dev/components](/dev/components) on the dev server.
Version 2026-11-05. Last changed 2026-11-05, OpenTUI Mono as the site's mono font, disabled splits share one line at the join, focus rings drawn inside adjacent and split buttons, and split buttons shown only as a pair.
To add a rule, add one line under its group. Mark a rule that is still being decided with _(provisional)_.
## Look
- Use the neutral grey tokens (`--bg`, `--text-*`, `--border*`), never raw colours in components. → [Colours](/dev/components#colours)
- Use square corners. The radius scale is `0`, `50%` (avatars, orbs, dots), `999px` (pills) and `22.5%` (app icons), nothing else. → [Radii](/dev/components#radii)
- Do not add 2px, 3px, 4px, 6px or 8px radii. They remain only on /zen's old header (2px, 8px), the 404 arcade's drawn panel (4px) and one known exception (the sheet grabber). → [Radii](/dev/components#radii)
- Write every colour as a `light-dark()` pair, so a subtree's `color-scheme` flips it. Set `color` again where a pane changes scheme, because it inherits as a resolved value. → [Colours](/dev/components#colours)
- Give each SVG `<pattern>` or gradient a per-instance id (`createUniqueId`), never a fixed id. Why — with two instances every one paints the first one's colours, as the footer wordmark hatch did in dark. → [Logo and wordmark](/dev/components#marks)
- Keep hatching and hairlines subtle, about 1.3 to 1.5 to 1 against the background in both themes (`--border` on `--bg`). → [Logo and wordmark](/dev/components#marks)
- Keep the link blue (`--link`) for links, focus rings, hover rings and open states.
- Read the page background from `--bg`; the light background follows Settings › Light background, so never assume white.
- Take every icon from `component/site/icons.tsx`, drawn in `currentColor`; no local copies in routes. → [Icons](/dev/components#icons)
- Draw icons at 16px with a 1.5px stroke, and 12px with 1.25px for chevrons and inline links. _(provisional)_ → [Icons](/dev/components#icons)
## Type
- Set every font size from the `--fs-*` tokens. Nothing is smaller than 12px, and on phones nothing that carries meaning is smaller than 14px. → [Type scale](/dev/components#type-scale)
- Use OpenTUI Mono (`--font-mono`), Anomaly's own font, for nav, labels, titles and code, and Inter (`--font-sans`) for body and reading text. → [Fonts](/dev/components#fonts)
- Serve only fonts we own or that are openly licensed (OpenTUI Mono, the OpenCode font, Inter), never a commercial face. `test/fonts.test.ts` enforces it.
- Step text once, at 960px. Only page titles (`--fs-h1`) and display titles are fluid. → [Type scale](/dev/components#type-scale)
- Set inputs with `--fs-input`, 16px on touch, so iOS doesn't zoom. → [Signup form](/dev/components#signup)
- Give section titles `--title-size` with the `--title-stroke` faux medium; the current nav item gets the same stroke.
- Keep reading text within `--measure` (about 70 characters a line).
- Let hero and product leads run as two long lines, with no max-width cap. Why — the cap was tried and removed by David.
- Give each page exactly one h1. → [Page title](/dev/components#hero)
- Wrap h2 and h3 with `text-wrap: pretty` (site.css) so headings don't end on one orphan word; keep `balance` only where a component sets it on purpose (changelog titles).
## Copy
- Use no colons in site copy (headings, leads, body, buttons, captions, alt text). Good "The easiest way to start is to download OpenCode". Bad "The easiest way to start: download OpenCode". Colons in code, commands, URLs, times and data labels are fine.
- Write statements, not rhetorical questions. Good "For more, bring your own subscription". Bad "Want more? Bring your own subscription". Real reader questions, such as FAQ entries, are fine. One deliberate exception: the footer bar's "Like letters?" (`footer.wordmark.font`), which is David's chosen copy for the font teaser.
- Call OpenCode an "open source coding agent", never an "AI coding agent". "AI models" and "AI providers" are fine.
- Say "OpenCode Models", never "Zen" or "OpenCode Zen".
- Write prices per month as "/month", for example "$10/month".
- Make any model named in copy, screenshots or footage read "Claude Opus 5.5".
- Keep list descriptions to one line at desktop width. → [Product list](/dev/components#product-list)
- Keep feature descriptions to exactly two lines at 1728px. → [Feature grid](/dev/components#feature-grid)
- Make only true claims, and check numbers against their source (stats, pricing, press facts).
- Keep the main nav as Docs, Products, Data, Console, Models, Subscriptions, Enterprise. → [Desktop sidebar nav](/dev/components#sidebar-nav)
## Layout
- Centre wide screens; the layout stops growing at 1728px (`--page-max`). → [Spacing and layout](/dev/components#spacing)
- Build on the four-column grid (`--gutter`, `--edge`, `--sidebar-width`); a section is `data-component="block"` with its title in the first column. → [Section layouts](/dev/components#block)
- Make the space below an accordion's open content match the space above it. → [Accordion](/dev/components#faq)
- Keep text aligned when you add a row background or hover fill, with negative margins and matching padding.
- Make icon tiles as tall as their text block. → [Product list](/dev/components#product-list)
- Make action buttons in product headers equal width and right-aligned.
- Keep product and companion tile sizes as they are until the size is decided. _(provisional)_ → [Companion shelf](/dev/components#companions)
- Cause no layout shift (CLS 0). Reserve the space, and use skeletons where content streams in.
- Decide when a layout stacks by the width of its own container (container queries) where the sidebar or the Contents rail narrows the column, not by the screen width alone. _(provisional)_
## Components
- Use the shared components instead of copying their markup; this page renders the real ones. → [Components](/dev/components)
- Use `data-component="site-button"` for buttons, the ink primary or `data-variant="neutral"` grey. → [Button](/dev/components#button)
- Use a split button when there is a default choice plus alternatives, and a plain disclosure button only when there is no sensible default. → [Split button](/dev/components#split-button)
- Show a split button only as its joined pair; never render the chevron half on its own. → [Button](/dev/components#button)
- When a split button's card or column is too narrow for "Download for macOS" (under about 264px), drop the " for <OS>" text and keep the platform icon, as the inline card layouts do; never let the chevron leave its card. _(provisional)_
- Give a disabled split one shared 1px line at the join, at exactly the enabled split's size and positions. → [Button](/dev/components#button)
- Place icons by meaning. Action verbs (Download, Copy) lead, disclosure chevrons trail and flip when open, and ↗ trails anything that leaves the site, always visible. _(provisional)_ → [Button](/dev/components#button)
- Allow icon-only buttons only for universal actions (close, play, theme, copy, prev and next), each with an `aria-label` and a `data-tip` tooltip. → [Tooltips](/dev/components#toasts)
- End every `target="_blank"` link with `<NewTab />` for screen readers. → [Icons](/dev/components#icons)
- Use `component/faq.tsx` for every accordion. → [Accordion](/dev/components#faq)
- Every dropdown uses the shared Dropdown component; no one-off menus. On the site that is `SiteMenu` (`component/site/site-menu.tsx`), which brings the shared menu look, keyboard support and the phone sheet.
- Turn dropdowns into the shared bottom sheet (`sheet.tsx`) on touch phones. → [Bottom sheet](/dev/components#sheet)
- Give long pages the Contents rail (`OnThisPage`) and its phone bar (`ContentsMenu`). → [Contents](/dev/components#contents)
- Draw breadcrumbs with muted ancestors, `|` separators and the current page in ink. → [Breadcrumbs](/dev/components#breadcrumb)
- Confirm copy actions in place ("Copied") through a live region; the site has no toasts. → [Copy buttons](/dev/components#copy-buttons)
- On docs pages, the left nav's first item, the content's first item and the right rail's first item share one top line (text tops for the nav and the title, the box top for the search box).
- A sidebar that scrolls under fixed chrome (the docs nav under the logo and lockup) fades out at its edges with a mask, only while items are under that edge; phones keep hard cuts on sticky bars.
- No flat greys inside callouts or other tinted surfaces; grey layers blend with the surface (mix-blend-mode), except code and table bodies, which stay solid. → [Code inside callouts](/dev/docs-components#blend)
- Code blocks and tables inside a tinted surface blend their header and borders with mix-blend-mode, so they take the surface's hue; code and table bodies stay solid, on the code body colour. → [Code inside callouts](/dev/docs-components#blend)
- Docs code blocks copy with a text-only button in the header: "Copy" turns to "Copied" (or "Failed"), right-aligned in the width of the longest label so nothing moves. → [Code blocks](/dev/docs-components#code-blocks)
- Size every asset button (PNG, SVG, Download) from `asset-buttons.css`. → [Asset tiles](/dev/components#asset-tiles)
- Keep code blocks in today's docs style until the style is chosen. _(provisional)_ → [Kbd and code](/dev/components#typography)
## Interaction
- Keep a button in its open look while its popup is open (`aria-expanded="true"`), with the split button's blue chevron as the reference; Escape closes and returns focus to the trigger. → [Split button](/dev/components#split-button)
- Show disabled as an outline with grey text, no fill and no hover or press, on `:disabled` and `aria-disabled="true"`. → [Button](/dev/components#button)
- Keep the focus ring on `aria-disabled` buttons, which stay focusable, and check `aria-disabled` in their click handlers. → [Button](/dev/components#button)
- Show loading with `aria-busy="true"`, which runs a bar along the button's bottom edge. → [Button](/dev/components#button)
- Put no UI behind hover alone on touch; anything hover reveals is also reachable by tap and keyboard.
- Decide device-specific UI by device (platform or coarse pointer, `prefersSheet()` and `lib/platform.ts`), not by width. → [ProductAction](/dev/components#product-action)
- Make tap targets at least 44px on coarse pointers (`tap-targets.css`).
- Close menus on Escape, an outside click and a choice, and return focus to the trigger. → [Language picker](/dev/components#language-picker)
- Test platform UI with the device preview's `_device` parameter, not a narrow window. → [ProductAction](/dev/components#product-action)
## Motion
Motion decisions follow Emil Kowalski's skills ([emilkowalski/skills](https://github.com/emilkowalski/skills)). Install them with `npx skills add emilkowalski/skills`, then use `animate` to build motion, `review-animations` to critique it, `animation-performance` for frame budget, `apple-design` for springs and gestures, and `emil-design-eng` for polish.
- Animate only transform and opacity (composite-only); never animate layout. → [Motion](/dev/components#motion)
- Honour `prefers-reduced-motion`; fade or stop instead of moving. → [Motion](/dev/components#motion)
- Ease colour changes for 150ms, move with `--ease-out`, and scale presses to 0.985. → [Motion](/dev/components#motion)
- Use the sheet springs (open 1.0 at 0.38s, release 0.86 at 0.32s, close 1.0 at 0.28s). → [Bottom sheet](/dev/components#sheet)
- End sticky bars in a hard cut at their line, with no scroll fade. → [Sticky bars](/dev/components#sticky-bar)
- Pause dot leaders, loops and swaps off screen, in background tabs and with reduced motion. → [Logo row](/dev/components#logo-wall)
## Media
- Show real product footage and screenshots only.
- Ship no placeholders; hide a media slot until its real content exists, as on the changelog. → [Timeline](/dev/components#changelog-timeline)
- Put heavy media (video, audio, zip, glb, wasm, anything over 256KB) on the CDN, not in git; SVGs, favicons, Open Graph images and small posters stay in git.
- Give every video a poster; card videos rest on a still and play on hover. → [Product carousel](/dev/components#carousel)
- Caption videos that have speech.
## Accessibility
- Give text at least 4.5 to 1 contrast and meaningful marks and controls at least 3 to 1, in light and dark. → [Colours](/dev/components#colours)
- Keep disabled text legible at about 2.5 to 3 to 1, though WCAG exempts it. → [Button](/dev/components#button)
- Draw keyboard focus as a 2px link-blue ring, 2px outside a standalone button. → [Button](/dev/components#button)
- Draw focus rings on adjacent or split buttons inside, the ring along the button's edge and a 2px page-colour band inside it, so it never crosses into the neighbour. → [Button](/dev/components#button)
- Keep the focus ring at 3 to 1 or more against whatever it touches, in light and dark. → [Button](/dev/components#button)
- Mark decorative SVGs `aria-hidden="true"` and give icon-only controls an accessible name.
- Trap focus in dialogs and sheets, and restore it on close. → [Bottom sheet](/dev/components#sheet)
- Let single-key shortcuts skip text fields and modified keys.
## i18n and RTL
- Put new copy in `src/i18n/en.ts` (or `src/i18n/content`), never hard-coded in a component.
- Follow the translators' rules in `i18n/packets.md` (keep names, params, code and prices; stay within 140% in tight places).
- Lay out with logical properties (`padding-inline-start`, `inset-inline-end`), never left and right; the site ships 17 locales with right to left.
- Keep the locale prefix on internal links with `language.route()`.
## Options and flags
- Make every design option a Design mode or Settings toggle (`settings.ts`), applied before first paint, with undo, export and reset.
- Hide unfinished features behind a flag instead of deleting them (Isocast, SSH, WSL, Android "Coming soon").
- Keep the dev tools (Settings, Design mode, device preview) off for visitors in production.
- Treat the Settings defaults as the shipped design; change a default only when David decides.
## SEO and text files
- Keep `/sitemap.xml`, `llms.txt`, search and the page's `.md` version in sync when you add, remove or rename a public page, and run `bun test test/sitemap.test.ts`.
- Leave a page out of the sitemap through `sitemapExclusions` with a reason; never add a static `public/sitemap.xml`.
- Update the nav data, not the view-source comment; its FOR HUMANS rows come from the nav.
- Give every page a meta description, Open Graph image, canonical and hreflang.
- Do not ship `/dev/*`, `/data-demo` or `/console-demo`.
## Process
- Never restart the dev server (:3011), and never commit unless asked.
- Take real screenshots before and after every visual change.
- Check light and dark at 1440 and 390.
- Pass typecheck and prettier, with zero console errors.
- Re-read a file before you edit it, because other agents edit the site at the same time.
- Add each new decision here as one line, and keep the evidence in the local review notes, outside the repo.
- Review hubs, launch lists and screenshots are local dev only; never commit or link them from shipped code.
+44
View File
@@ -0,0 +1,44 @@
# Media files
The site's heavy media (product videos, arcade sounds, 3D models and download zips; `media-manifest.txt` lists every file) is not in git and not in the Worker's static assets. It is served from the opencode.ai files bucket, the one the desktop downloads already use, under `https://opencode.ai/files/site/2026-11/`, at the same path it has under `public/`.
## How it works
- `src/lib/media.ts` holds the one base URL, `mediaBase` (default `https://opencode.ai/files/site/2026-11`, override with `VITE_MEDIA_URL`), and the file types that live in the bucket (`mediaFile`: `mp4`, `webm`, `mov`, `mp3`, `wav`, `ogg`, `m4a`, `zip`, `glb`, `gltf`).
- Pages keep linking the site path, for example `/product-videos/heroes/sdk-landscape-dark.mp4`.
- In development the file is in `public/` (git-ignored, see `.gitignore`), and the dev server serves it.
- In production the request misses the static assets and reaches the Worker. `middleware.ts` sends a 302 to `mediaBase` plus the path. `/files/*` itself is never redirected.
- Same origin (opencode.ai), so no CORS rule is needed: the arcade's `fetch` of its models and sounds works as is.
- Files that dev already tracked in git (for example `public/opencode-brand-assets.zip`) stay in git and in the static assets.
- Images (`png`, `jpg`, `webp`, `avif`, `svg`), posters, fonts and the 3D decoders (`arcade3d/draco`, `arcade3d/basis`) stay in git.
## Files
`media-manifest.txt` lists only the files the site requests in production (default settings, voice mode off), with SHA-256 and size. `bun ./script/upload-media.ts --manifest` lists every media file in `public/`; remove the ones no page requests before uploading.
| Folder | Files |
|---|---|
| `media/` | AV1 and HEVC copies of the product videos (`media.tsx`) |
| `product-videos/` | hero, card, card loop and feature videos (the originals, the last fallback) |
| `arcade404/audio/` | 404 arcade sounds (WebM Opus and MP3) |
| `arcade3d/` | cabinet models (`*.glb`) |
| `font/film/` | font film videos |
| `press-media/zips/`, `opencode-press-media.zip`, `press/anomaly/*.zip` | press kit downloads |
| `brand/badges/*.zip` | badge downloads |
## Serving
- Videos need HTTP range requests (`206`, `Accept-Ranges: bytes`); the files route supports them.
- Content types: `mp4` `video/mp4`, `webm` `audio/webm`, `mp3` `audio/mpeg`, `zip` `application/zip`, `glb` `model/gltf-binary`.
- `Cache-Control: public, max-age=31536000, immutable`. A changed file goes under a new version prefix (for example `site/2026-12`) and a new `mediaBase`.
## Upload
```sh
cd packages/console/app
bun ./script/upload-media.ts # dry run: lists the uploads
MEDIA_BUCKET=<files bucket> MEDIA_PREFIX=site/2026-11 bun ./script/upload-media.ts --run
curl -sI https://opencode.ai/files/site/2026-11/arcade3d/cabinet.glb # expect 200 and model/gltf-binary
```
The upload needs the media files in `public/`; they are not in git.
+14
View File
@@ -23,6 +23,20 @@ npm run dev
npm run dev -- --open
```
### GitHub API
The site reads the repo's stars, contributors and releases from the GitHub API on the server
(`src/lib/github-api.ts`). Without a token GitHub allows 60 calls an hour per IP, which a dev
machine can use up; then the pages show the last good data or the config fallbacks. Start the dev
server with your GitHub CLI token for 5,000 calls an hour:
```bash
GITHUB_TOKEN=$(gh auth token) bun run dev
```
Never commit a token. In dev the last good GitHub data is also kept in
`$TMPDIR/opencode-site-github/`, so a restart while rate limited still has it.
## Building
Solid apps are built with _presets_, which optimise your project for deployment to different environments.
+242
View File
@@ -0,0 +1,242 @@
# sha256 bytes path: the files the site requests, uploaded to https://opencode.ai/files/site/2026-11/<path>
75724f8c8bea73cd6adaab7240af737f5ec1dc8421a7ecfba3f1dd9f7c0d5b63 445544 /arcade3d/cabinet-lite.glb
a3216464f3880365226c34f6dc0f1550faeda16e2897f2966124fee6abe6bfd4 943492 /arcade3d/cabinet.glb
7759efc19d3a87b2805cac61142f4b64c1d2fe7919ba409618e396480eed9461 1765920 /arcade3d/cabinet.ktx2.glb
dcec7af411a2b98bad88f5f8aae1cef460564fdbf2c274f937250930653c5ea5 392828 /arcade404/audio/ambience.mp3
c4a2d02d8617d5a9646eab18f091dd12eaf42094b04ae43001c0d5bec922da68 205851 /arcade404/audio/ambience.webm
f7f59b7dcdeef8bea017cf69e6f7dfef3767126a9dd0e21e07134af5ed13c07d 42668 /arcade404/audio/boot-post.mp3
06e025ed0b93732ef3fe0dcdb7d87c57f90d1cb749d1d1be4185f44e0da8b7dd 32331 /arcade404/audio/boot-post.webm
4fa497eba3702f14ca5c9761ba4a95bb532b1f4e44ee90ff79a71263704a4d95 11180 /arcade404/audio/coin-insert.mp3
ab2b3a5f570e665b2bdd96c41099f0863eab89bd15e4b52541ca9704dd42bf6f 9180 /arcade404/audio/coin-insert.webm
a50962db2a4e8bf89b25d442607ea725df9dfbbf73962ea350b71b3e85843f9d 10796 /arcade404/audio/coin-pickup-slide.mp3
db58b246e4535d8826d57bab8bd043f81562f463e4f08a959a80687416761bea 9519 /arcade404/audio/coin-pickup-slide.webm
95fd4d884d6239b29c6a18586b330950ee691f38e9dab1348dda224e81c9bc0c 12524 /arcade404/audio/coin-reject.mp3
e4b0fd38bef9b5baf53d4ab6378dc116df77ff5fce4ece68e6bc4eff59a561d7 12537 /arcade404/audio/coin-reject.webm
9a2e134d8efab87d9b49491ce2322a5ae09708598f25a3402d7c2f2c78a99c1f 8300 /arcade404/audio/glitch.mp3
04f643758e5102d0cea4c3d7b878514541ce07897187ce0a6e6978c140c4a30e 7117 /arcade404/audio/glitch.webm
65f404029c9a63d7f239c954271fb94d172ed2fc8f9f8e28415eb285f6836b4f 1004 /arcade404/audio/menu-move.mp3
3198cc102b8b5fad31517e103cd16efeccdd973cb73c2fe1cd7458b407bd17b0 1494 /arcade404/audio/menu-move.webm
d20116ce5c3ca54f8f3a3e7a6377bbdddce7b405c27cb2528105f4795494725c 1964 /arcade404/audio/menu-select.mp3
0e35fa50bcd07d3baa8928df00a965fb41e9d07b2ccaff1f312b7d8355a78563 1753 /arcade404/audio/menu-select.webm
ea0dd3cd6841370f8cb2c013b5eb5df96d558d6add5f1cbc3039ed5f3a477b8e 29036 /arcade404/audio/power-up.mp3
4aa04411478e4dddf96410b48f5a21e7d59f4bbd89216308b4dadc15024363a4 26617 /arcade404/audio/power-up.webm
3149ac56a70bca632dfa0d921986dfdb71bdd1672fced4adc99dc1975d1ae652 19436 /arcade404/audio/start.mp3
4d57e6547a49565565480270078ee3556f8028bb72a6c3c696d2193b1d10bdcd 19707 /arcade404/audio/start.webm
f56d4722ba190c2061715aee13972aad957f66e3ab56cabf6f36355439773063 18791 /brand/badges/opencode-built-with-a-dark-mono-caps.zip
0b70387bfe05c7fe411b30a0376de94af58e417129cfe23b34915f10ae8e87c9 24980 /brand/badges/opencode-built-with-a-dark-mono.zip
9780bb138d244acbd997f802f93135c47b90f1922ffda9981336660255edaef5 16909 /brand/badges/opencode-built-with-a-dark.zip
2d5a93c9004a359c2bb6392f02eeee6411745fd4300422ea7944a7db66da3db9 18804 /brand/badges/opencode-built-with-a-light-mono-caps.zip
6dd39fae4ea22df4f0fa706cccdf87f29ee263a2170c21570a50c2fa9b6af728 25001 /brand/badges/opencode-built-with-a-light-mono.zip
54202790d13130705fe8aaf46c5e4553a497a5ed2a98e5010d0c96ff820872e5 16726 /brand/badges/opencode-built-with-a-light.zip
3df3a2bf9fc4b0dc015cd39c099b0fb52859816164d203a3f5b862c901031215 33613 /brand/badges/opencode-built-with-a.zip
44bfb4df48f54ef6461da3e4d28b3b05bb82bd4a205ce34e3a7382b7010f028e 18753 /brand/badges/opencode-built-with-c-dark-mono-caps.zip
24bcc8b1595c17e03853e86595bbc2d2a0c8d5503d07967af7057414c24f0388 24905 /brand/badges/opencode-built-with-c-dark-mono.zip
f0212de116aa96541cf676b94f82987d26cb2ef231552cc227b9dd7929b4ef2d 28138 /brand/badges/opencode-built-with-c-dark.zip
e18e842cffc215f2faed10d576ab2be17b24d034f93f3b6411ced777a47df8fa 18710 /brand/badges/opencode-built-with-c-light-mono-caps.zip
f0372e670947c706100aa168f32523e55d780284bb3ad3da51a04e229675622c 24885 /brand/badges/opencode-built-with-c-light-mono.zip
d253a691dc3c0d9d997d4ed2c4720292373d3a1eb4792cb514476895a343c115 28163 /brand/badges/opencode-built-with-c-light.zip
a1603c3f92874f6ddd97b4e7020beb039df0dfab780862db172d06c5757d9fde 56279 /brand/badges/opencode-built-with-c.zip
55408feb0db7a848f8b6b587a0eacded7301782ac8a0157ade27a0b80b224565 18550 /brand/badges/opencode-built-with-f-dark-mono-caps.zip
5eb515b69d6e2865e4a785a1b12f203e7b2e6eb633cc972dc411b2223cea1bb3 24724 /brand/badges/opencode-built-with-f-dark-mono.zip
e1fe209f031d3f22b1e0e7443cf6d7ab7507189b9398d0826e73e0f6c55ccbea 16570 /brand/badges/opencode-built-with-f-dark.zip
ac32bc88ed6cb275ce7d2cdd958f91e7e5eff6f229a355efd9081b37b8677722 18657 /brand/badges/opencode-built-with-f-light-mono-caps.zip
4d7a1ee711686b4c64d965ed39e0ff885142c09313cee6b01555a08eb3343b2d 24786 /brand/badges/opencode-built-with-f-light-mono.zip
a60f4aca1d0d4272c5d71bdabb7d3d1c861ebc323474f0bba7fe13d3c6544a84 16820 /brand/badges/opencode-built-with-f-light.zip
9e84487c7c4c792c61339452476853c85207e202a5d627067ba67a2002c4fe03 33368 /brand/badges/opencode-built-with-f.zip
1da5af08d2cf3725c177bf1abfa37f61ef4cac6e689bb7fdd74e518a75899e43 5458 /brand/badges/opencode-built-with-g-dark-mono-caps.zip
d3e5e7983a4dfbe159ac22d713e415bc850b3715613dd227202f798bb4255cae 7783 /brand/badges/opencode-built-with-g-dark-mono.zip
378337bcf6c98e098ef497ac317dd28e0d942348eb5647efd3c2b651b974891e 5068 /brand/badges/opencode-built-with-g-dark.zip
e9f334effa5132f729ceafa0e94b7408f89c96a202fc1960c34d5a4dbe4338ef 5412 /brand/badges/opencode-built-with-g-light-mono-caps.zip
1824a3d599cdcdb2e5b2788e8074450ea74557b830538675022b6f8d86330a16 7708 /brand/badges/opencode-built-with-g-light-mono.zip
ba0c37d4c857f48c725a53f3a37170da2900222515cdee117d3f840e8ac44b46 5105 /brand/badges/opencode-built-with-g-light.zip
0b1b630332f19f7061b947812ddfe8d3322b547d1ffa960db8e09ec499c38b19 10151 /brand/badges/opencode-built-with-g.zip
6a2fdefff73d3a1d3b4f44286c9f2a4f48f6bf09d1221090e4e28cf112cc73bf 22931 /brand/badges/opencode-works-with-a-dark-mono-caps.zip
471ef5cfd514fd7e6f9706f7274130fdbd3d18476c83d6ed2191c599b509f045 28331 /brand/badges/opencode-works-with-a-dark-mono.zip
97b841b3629bc301bf0f5dc10752ada9bdccd7de1e12321e7d6235dc5b72801c 18711 /brand/badges/opencode-works-with-a-dark.zip
fe200f1014aba285e6b131ccd07381b56b8ade2befcc20eb7b219f103d1c24aa 22875 /brand/badges/opencode-works-with-a-light-mono-caps.zip
6db1a0aafef199bfdfa4a1fc312543d2ff073c76e77cefef2b79814d7f8efb58 28329 /brand/badges/opencode-works-with-a-light-mono.zip
bc36ecb7a7d6ead89a6240c245efedd65f16ce984c3c3802a94b5cb0e41f4ff2 18432 /brand/badges/opencode-works-with-a-light.zip
f6e17b480bacad5dd8e3d95d9640c96c5fe7cbebf01dc00d06a54116faf19045 37121 /brand/badges/opencode-works-with-a.zip
13c8ed9d140843d16f710d5b679acabcde0e2059287d7319204742542c5673ec 22731 /brand/badges/opencode-works-with-c-dark-mono-caps.zip
0837ce1ad69ad07b0a1f6b660704ab8055003670e1d7a4ac3ffab792fba1c6b9 28186 /brand/badges/opencode-works-with-c-dark-mono.zip
0613124e714164d74c205c9d1e6125c114ada003d8efbebfdf8ed0babffe411b 32066 /brand/badges/opencode-works-with-c-dark.zip
b20301e60b4043cd1ef93945797a1e7df1c2aa3e15e8839a81e0ae9418248db4 22757 /brand/badges/opencode-works-with-c-light-mono-caps.zip
e92e19611a5a63514c75a5b4c5610a8bea302538a958bf42f44f09f3c080edc5 28234 /brand/badges/opencode-works-with-c-light-mono.zip
8436b43b542e72c6779909f74fb0db90be18071aecb0d62c9b4b1516c2aa66a2 31953 /brand/badges/opencode-works-with-c-light.zip
6e7535680fb57570a2fe1fc5a33322155cc97fdfaa050d995c303491a09ea056 63997 /brand/badges/opencode-works-with-c.zip
9a977f617217e89e46aff6249a81a68c324337f900e055b0cdd3c7e9962e34c5 22649 /brand/badges/opencode-works-with-f-dark-mono-caps.zip
19a997b31ee043f6ba80f580388a708b53939ccf4cd828a32f76aadd4d3abf20 28090 /brand/badges/opencode-works-with-f-dark-mono.zip
f2b005d660d26e42b64503f182f40021701e8bfd304ee228735eb27b476204f7 18318 /brand/badges/opencode-works-with-f-dark.zip
4f28ed282a9d6b48492b9cfb3f640bafe2a3490f592c0ec62194f73fedddda73 22667 /brand/badges/opencode-works-with-f-light-mono-caps.zip
50950ed383d2efc2bf68fa9a0399c2671837c134aa0373dbba307a705441fe4a 28088 /brand/badges/opencode-works-with-f-light-mono.zip
5d4b71b8f96a6b968ef4a6f6000ec2e17fca0babdb964e40b9912b9538b45e20 18547 /brand/badges/opencode-works-with-f-light.zip
70d5cf1c66395510828129fb123757e994206d81e5a747209cc244e0ecc8fc40 36843 /brand/badges/opencode-works-with-f.zip
2eab9d21b569e11319ffd937720b71db768a3e5861f85234d20aec6192eea17b 6708 /brand/badges/opencode-works-with-g-dark-mono-caps.zip
90fb2533d777a27f8fe34ec9ea13e142bf3d046f0911510928f0762892a63b6c 8787 /brand/badges/opencode-works-with-g-dark-mono.zip
c10492d5b3749f4d5adafb08cf26d2effb47e6a00b5c8f284c758fe0b6d1def7 5603 /brand/badges/opencode-works-with-g-dark.zip
128060389b2c8d200d460c00bd1ea39650b0829b393a007c45dc8836a6fab80c 6629 /brand/badges/opencode-works-with-g-light-mono-caps.zip
a33b96efa61e2d859864280c7fbba5918463359f8fa46f54e3d13385de3c4503 8737 /brand/badges/opencode-works-with-g-light-mono.zip
24a9c282b5a74a054ca88069092e513e551d9bc1b80c8aa9d1136c026187474b 5609 /brand/badges/opencode-works-with-g-light.zip
20fe723ec0a2818f6afe7df2140d717e029c37271852c85dd87b55a4f48a2471 11190 /brand/badges/opencode-works-with-g.zip
140f2c17fa7235f2199a3db22f1eda3eccbfb8ce98da39ee7b1e3e9eb9387846 6801928 /font/film/opencode-font-film-v3-1080p.mp4
c0d7932b3c2f5d27368e2039e580c169ac94802b8317260e5e679551c41e8461 6300093 /font/film/opencode-font-film-v3-4x5.mp4
000848bba8ee1cebeb5851b2db241758b157fad37ed4e036cfa1d7e524a4d83c 649602 /media/card-hover/angled/desktop.1080.av1.000848bb.mp4
531a933722ae7eb0321c56757922ea232972c67e3de366d01b76600822ab76b1 840910 /media/card-hover/angled/desktop.1080.hevc.531a9337.mp4
7a8ced27447988a0cc549e829b620e4c84b26d84a84fba4b86597369d7f529c9 428113 /media/card-hover/angled/desktop.720.av1.7a8ced27.mp4
e0be8abe5e8177ec91262076642712987bd9b09044465635b47e4ed5c007b214 522742 /media/card-hover/angled/desktop.720.hevc.e0be8abe.mp4
89dae645b11c75925c8004bcadf219d55293aff4a78b3ae3610cc7239731ced6 895609 /media/card-hover/angled/mobile.1080.av1.89dae645.mp4
5435e16522a8a05bbd6acaff5e279f2128b77f7ab6e0a6b3003b4616684f6096 1503532 /media/card-hover/angled/mobile.1080.hevc.5435e165.mp4
e3206614bf4d0b5059ba414180396d2483d5172928221a1b0c959e88dc0fcdd9 528811 /media/card-hover/angled/mobile.720.av1.e3206614.mp4
ed3aec1b69751b520c6310c0c0cc7b665778e1890037cf9e3bb1289c2d6f4832 865333 /media/card-hover/angled/mobile.720.hevc.ed3aec1b.mp4
55343564b0f97973d981fc9c95a92788837cb4625e590ed539e1fd79e3552f71 1140576 /media/card-hover/angled/sdk.1080.av1.55343564.mp4
5416f02029e612cf014beae948b47e44855002350cc64b74b29baf69b8b7f41b 1511028 /media/card-hover/angled/sdk.1080.hevc.5416f020.mp4
f06221b2a1064c8e3d53189acf5d09f8159c6a373dd7b5d0b73fe677028bcac5 486779 /media/card-hover/angled/sdk.720.av1.f06221b2.mp4
3bff7eae334be7c7c75e7f8fe36f903387a48d4dbe5732c3eb3e7bcf4283d802 1265113 /media/card-hover/angled/sdk.720.hevc.3bff7eae.mp4
6a2adb8e127261761f0ed60d18c631e19194c3891774994e7d20185dd49bec28 583158 /media/card-hover/angled/terminal.1080.av1.6a2adb8e.mp4
5802fcd0f8fa8d33badcec0210464944d7a495aa7adba7eeed845adfaf244c20 609030 /media/card-hover/angled/terminal.1080.hevc.5802fcd0.mp4
a45f7b7438349baa340e6ebff38bf898d1337d17aff03256b460453d803edf8b 347520 /media/card-hover/angled/terminal.720.av1.a45f7b74.mp4
33b9daa24215844d7adef07d4fc5fa5ea2b35428c4cd7d279dba9d95e1e0bbfd 386936 /media/card-hover/angled/terminal.720.hevc.33b9daa2.mp4
f04e65f4b645ad9c2f67a4303e2cf04341853e44179d972116322356c2ab47a5 21368 /media/card-loops/desktop.1064.av1.f04e65f4.mp4
c6be66a25e0b5b334b808425bc21782e86ea7c50ef241b753655629d1fce8de8 26456 /media/card-loops/desktop.1064.hevc.c6be66a2.mp4
76c673393db8151aa68ed838d012619f660e5dc441d4f9b87afb38a76aa7e831 15215 /media/card-loops/desktop.720.av1.76c67339.mp4
0e1d10302c20f0e3f225945476e59a99427adf06b4726d4d10bc4b2f16ebb46a 21261 /media/card-loops/desktop.720.hevc.0e1d1030.mp4
8d53f4c4cbfb2db22b6e5a64d6b73d80ece4c4e1f574938b6983e2a353571510 18097 /media/card-loops/desktop.ja.1064.av1.8d53f4c4.mp4
03051f4d568660708e1c7d6ede2c8949ee57ff0ecde88883b6d8025f2adfd2dd 13853 /media/card-loops/desktop.ja.720.av1.03051f4d.mp4
fcde1a61d3414adb1d5f1442c69e4be0d2d35b2e1e02518ffeae906ab182b206 19694 /media/card-loops/desktop.ja.720.hevc.fcde1a61.mp4
39fbf89d50cd987cfd5c34e71e5f8d415215c8268cf934637c766521217928ca 13638 /media/card-loops/mini.1064.av1.39fbf89d.mp4
de5e01b63a2a76ccce2f7019d792e6d5cb4e9451d6e35d5b825a690d96f93af9 19371 /media/card-loops/mini.1064.hevc.de5e01b6.mp4
52c28525a8af0756a80ad7192d26b6c384ca85ef1fefa39e075e8c08c1f76003 13790 /media/card-loops/mini.720.av1.52c28525.mp4
2adfbc82ff8cf57bf2c0dd3029a4f44e9be6d50774add274e0868d3e9d71080e 17342 /media/card-loops/mini.720.hevc.2adfbc82.mp4
57dfbb5d577bbe0be80103e7ab2c2a3f8be8137cdd418f86db3704c40c8ccf0e 20657 /media/card-loops/sdk.1064.av1.57dfbb5d.mp4
2eb9fa5fd126735746464774019051f962152b7dc97b8c570e0af399a108d727 29030 /media/card-loops/sdk.1064.hevc.2eb9fa5f.mp4
72327b63ce99e9f50a21673b38caa5427c34608f94e9da1237b3b3e4af6456ee 24394 /media/card-loops/sdk.720.av1.72327b63.mp4
bcf15f91918f98548aeee7e51d3c2ccc48b1d49b3f69dc259c8c2814768777c4 26813 /media/card-loops/sdk.720.hevc.bcf15f91.mp4
98000a3f6111d31f17ba3df1d5f66cfbb1a854b3b04daa9124d722f140effb66 14297 /media/card-loops/terminal.1064.av1.98000a3f.mp4
cb91d9913bb3453d211d79733b16e77ab8ef6c6754a867882ba31f3d5c8734f7 17714 /media/card-loops/terminal.1064.hevc.cb91d991.mp4
adf275c513f83ad7ad38d5de18657e34d0a5a539e3b720b8c124d1ceea416782 12742 /media/card-loops/terminal.720.av1.adf275c5.mp4
e8322dc49fa91e2c71fdd9f7b3e7d3376e7ecc770add89c19eab1e95e35f0fc1 14915 /media/card-loops/terminal.720.hevc.e8322dc4.mp4
a446478285608c75fa6bdaedff560da15a6b881a530a6ebb20730ed63f4db106 351234 /media/features/focused/desktop-1.1200.av1.a4464782.mp4
50b4baf75e1406a97d723d9f6b95ec5f68ae69a37063411f2caf3a5df34a0d35 378128 /media/features/focused/desktop-1.1200.hevc.50b4baf7.mp4
ff6b7fc87ffd72ecf8c82a102c711bf06396744650585e8d0b362de32e853bfb 209777 /media/features/focused/desktop-1.800.av1.ff6b7fc8.mp4
799d1bf0e704d81ace092c66beab4e1c69ade761de4a8cf25ac3ef7159da507e 231337 /media/features/focused/desktop-1.800.hevc.799d1bf0.mp4
e251cdbe7cb87ff644d865f667d4f51c65c7bc4cc902adc1d096280628d291d7 261632 /media/features/focused/desktop-2.1200.av1.e251cdbe.mp4
71d6859de874f3e96b9e0ba8f49d3cb7c79b0c63f4200ea81bbacb2c1255f231 290907 /media/features/focused/desktop-2.1200.hevc.71d6859d.mp4
89d0540878e06262fe47110b323c03dd1d5c1205a815014a06068e853e1bc7e6 159228 /media/features/focused/desktop-2.800.av1.89d05408.mp4
8bac7e6dde6725ec7aac7e78c95a62d3b61f4569e0e0ae56f1cea2af183ceeb9 168004 /media/features/focused/desktop-2.800.hevc.8bac7e6d.mp4
b919e8f621cfe6bba8a0ea859db90a1814186c8655f17c25c7fc9d1aab986a42 397140 /media/features/focused/desktop-3.1200.av1.b919e8f6.mp4
c8952b21cae85fcbc863e5061a83aa394e95ac27bfac2ced8fa755500ca9402a 363545 /media/features/focused/desktop-3.1200.hevc.c8952b21.mp4
6b677bb14d31ce02d38d7857b96cada2aeacb5e8dbe7bd9b08ef2d144dba316f 239123 /media/features/focused/desktop-3.800.av1.6b677bb1.mp4
728a37b0a7b8a488406e20ba146b2cb3f383f5f22e9148f0fb93d67a55c122b6 226894 /media/features/focused/desktop-3.800.hevc.728a37b0.mp4
8c6b59f155f3ca925ea4444fe023680ec0a443c1c5701a90cfad385bd9bfbfe7 180538 /media/features/focused/mobile-1.1200.av1.8c6b59f1.mp4
e421e4366f416612c733f0ca932163217a9f2917e1a77d0a2139f06a5daa3413 292303 /media/features/focused/mobile-1.1200.hevc.e421e436.mp4
4890e570244a55556b589d1c7a6400b425a87704f4e75391609b2fa1bd4dc10f 127395 /media/features/focused/mobile-1.800.av1.4890e570.mp4
4efad18e8f77a0bc61352042b778a05359fcf8766147fcfcbf80aa4f1a28e76b 178589 /media/features/focused/mobile-1.800.hevc.4efad18e.mp4
958b9f7efa69189d14ba6b7b58b344cafd9c263efce0c62f8cb67aa46efe6454 187025 /media/features/focused/mobile-2.1200.av1.958b9f7e.mp4
8f9ede874fa50c4827ebb05a886bccd2c0a488245d7d0e89759b08ace78c312e 309781 /media/features/focused/mobile-2.1200.hevc.8f9ede87.mp4
b6c90cc7500a4976c523b6ec9aaffaf6f863bc9ca0cab5c1e31514f256529126 124663 /media/features/focused/mobile-2.800.av1.b6c90cc7.mp4
31ef973ee040bc2deb2a3946b93ef9d667948cd079955157650a72d6b3086610 216498 /media/features/focused/mobile-2.800.hevc.31ef973e.mp4
24da9d95ed1c7d25a9c2c8d3b2ce46fde74912605486ec69fc1206e13edd30a0 173331 /media/features/focused/mobile-3.1200.av1.24da9d95.mp4
9d0a71a3f2c5b629d43e079e6ed8fc0d0ec36b41cb7a84eca0dd851d704ff9aa 242649 /media/features/focused/mobile-3.1200.hevc.9d0a71a3.mp4
711178888f562800d650b59e3b975be3c5f705fc4668effa0defd3e6dc1290e1 120504 /media/features/focused/mobile-3.800.av1.71117888.mp4
c51688d9012d49cf96609c2192687b2a7d033e2a6f99101b7dc4483e48d04f9a 175875 /media/features/focused/mobile-3.800.hevc.c51688d9.mp4
05f321e66f07ef867fac627686eee3518b414a58869cca13948c3512d38f8b57 153459 /media/features/focused/sdk-1.1200.av1.05f321e6.mp4
8099f20c8501ae9cd198197d6a1e52d033f1d59ef38345e157279f4c97c7dd18 273198 /media/features/focused/sdk-1.1200.hevc.8099f20c.mp4
e0de2928c35760348616d34165e2cfbf4e2dfd958fdb8fc2ee814e49523c12b7 98617 /media/features/focused/sdk-1.800.av1.e0de2928.mp4
732b34de8404a3eb3965cc772966d83285b7680930091d6b95f801b4b514564e 168028 /media/features/focused/sdk-1.800.hevc.732b34de.mp4
7ab84c55de2cc117c3bf7fbe941bf50e9bf8115b4ee758f8043e402c98e6ada7 191903 /media/features/focused/sdk-2.1200.av1.7ab84c55.mp4
6ca8a2eea6cc57706741fcb8b73cde17c18250016f99c4e350c420f56787904c 403063 /media/features/focused/sdk-2.1200.hevc.6ca8a2ee.mp4
bc734ce414b2088cb3ba58143a5abdddbdff0de9e785a1dce4b17f718f8fc6d0 123247 /media/features/focused/sdk-2.800.av1.bc734ce4.mp4
28ab05ce7bbd77fa70d1526d4301c350073845dfb2850c3850c1612e4671627c 237185 /media/features/focused/sdk-2.800.hevc.28ab05ce.mp4
dd8e2cb853ca96a1716da2129aa25b8304f6f3ce1c90588ee5abdb786dd84158 437702 /media/features/focused/sdk-3.1200.av1.dd8e2cb8.mp4
812d79d4beddb6e355aed30742199bfceb962e68178afdac1f15fdd2ea2a40b7 707475 /media/features/focused/sdk-3.1200.hevc.812d79d4.mp4
0d36392214ab7491f4fc7fe0d4509b8ba4ab4b91ac884e018baaaf09dd895ab8 264352 /media/features/focused/sdk-3.800.av1.0d363922.mp4
1c339153fd872fb9c7764fc6cc5fd130f9c4846b64fb086e3d842570c27d557f 437827 /media/features/focused/sdk-3.800.hevc.1c339153.mp4
f84ed97c655327c20d9b5a32995a17070098a30f458a54730d4270fc5838d615 65178 /media/features/focused/terminal-1.1200.av1.f84ed97c.mp4
789b0b0c7ad4fb336cc3e5fc15af5c5e40b5004aab392679f3585881888b33a1 74831 /media/features/focused/terminal-1.1200.hevc.789b0b0c.mp4
8e73a023a2caf3d7cc031b80744a548b3d858862f078002cb4789bd3e873e258 69930 /media/features/focused/terminal-1.800.av1.8e73a023.mp4
5ede6f33d5367472c4aa34f1064ff33a526c6cb69ec7869b7f06af0796bcf758 70652 /media/features/focused/terminal-1.800.hevc.5ede6f33.mp4
34880af73bf44dc8c40c258d4c395916d1c65b19b9ac820686b8d8afa2a4ea67 111203 /media/features/focused/terminal-2.1200.av1.34880af7.mp4
cd7f01de011fefacad2f45660d0466bcf0911e9edfbd2ae5f2f00ff457a95061 101952 /media/features/focused/terminal-2.1200.hevc.cd7f01de.mp4
39c336ab05b7adda33f400411a46a99aa073e2a59bf34ee425ddbbc3c3291a57 118972 /media/features/focused/terminal-2.800.av1.39c336ab.mp4
40a6b8c9a42ed413eb76d4763c6fc08381f513130b66eb1e0d8f0f1ac70faa74 103544 /media/features/focused/terminal-2.800.hevc.40a6b8c9.mp4
e1cf1a5755395679bdfcd0b886e151b2fba15694dd705b7521bb1ff17dd34888 342864 /media/features/focused/terminal-3.1200.av1.e1cf1a57.mp4
ddc881b8a90280a112d76cc8591231fc356a12859326bcb8f26f2506d5f4be0e 261860 /media/features/focused/terminal-3.1200.hevc.ddc881b8.mp4
934e672bfd1587aca365857d6bbcb2eee094b976116875b3f0a8f7bec46ddd92 200602 /media/features/focused/terminal-3.800.av1.934e672b.mp4
1bfc3877f607df6d0321bb6ccd6b5adf6328805937825c5775d87a153fd84b02 177738 /media/features/focused/terminal-3.800.hevc.1bfc3877.mp4
f4aac9d05337732c4132fd590e6493619ce4b8e549a2700caabbe289d5fe9c37 5047995 /media/font/film/opencode-font-film-v3-1080p.1920.av1.f4aac9d0.mp4
9a8fdd1e4484be6e842980dfc56778c7d0d4d44ff106314a49364a259173e9d2 6133108 /media/font/film/opencode-font-film-v3-1080p.1920.hevc.9a8fdd1e.mp4
874884626031b9c656615a1a626b4ca8fe753e827d08eab83343e7d52d2ef492 370158 /media/heroes/desktop-landscape-dark.1280.av1.87488462.mp4
f9408e0f9e65a3798c3e026d16923fd311d1e4d3824d5a6502b394c445e69476 346860 /media/heroes/desktop-landscape-dark.1280.hevc.f9408e0f.mp4
476dd57a0a73a619e13cd10add5cf9089f87cc98ba29908276c0e398f067bcd5 623805 /media/heroes/desktop-landscape-dark.1920.av1.476dd57a.mp4
4a955f68335a0f414ccb83972a6de613b257b2ddd9c3a48e12ad25539027b0ed 596683 /media/heroes/desktop-landscape-dark.1920.hevc.4a955f68.mp4
166905a1a0b90755d16ea5c149c5c35006e8b751cac624452444b2257ea6acfe 370108 /media/heroes/desktop-landscape-light.1280.av1.166905a1.mp4
27a6e47e11145d51d5c12e760175e442e93e7a36349e95108698a1099bc4b64d 347610 /media/heroes/desktop-landscape-light.1280.hevc.27a6e47e.mp4
461fb5087cfda4464eb950fa72e0dcda30865e4a6e53228e721758e1e34bc47f 620944 /media/heroes/desktop-landscape-light.1920.av1.461fb508.mp4
d0bae749c218b748f26dcb5a6d61032a8e9c8e25d32f8bb78141b1d1d3472912 598880 /media/heroes/desktop-landscape-light.1920.hevc.d0bae749.mp4
47ac0990e6637db0801f00b7e467976141ef9c1f074fd2f273866db318cd315a 113626 /media/heroes/mini-landscape-dark.1280.av1.47ac0990.mp4
a0d620214c951c9270073570ce896f97012d76fdbd573039f894090b3aa5a9d1 127653 /media/heroes/mini-landscape-dark.1280.hevc.a0d62021.mp4
d1ec2aa0bcf2211dafde14afbdd5fe1e7206f4da463d91ffc364a17d89106d3c 113771 /media/heroes/mini-landscape-light.1280.av1.d1ec2aa0.mp4
cf388476a80519260a998fc273fe969e94a8b68c18669543fd269cb5ddb73578 125406 /media/heroes/mini-landscape-light.1280.hevc.cf388476.mp4
968f138e5d576119c7a9a289e58da5560f1d59f97ec407bb208544fa8a037dc2 351325 /media/heroes/mobile-landscape-dark.1280.av1.968f138e.mp4
dd010af3cdbe8b61c8440c4ff2c167b7a3502ad9bda5bb0756e126ea97a2da64 529806 /media/heroes/mobile-landscape-dark.1280.hevc.dd010af3.mp4
2d4e37e8c3bd0d2329936f018bd3eed47cfc2c35f6e20fb454c07b33dda96bd3 552673 /media/heroes/mobile-landscape-dark.1920.av1.2d4e37e8.mp4
731d098be325dda29348964702c1ff4f7a91426d0e3cf4c578b2e0afba7fa4e3 963963 /media/heroes/mobile-landscape-dark.1920.hevc.731d098b.mp4
968f138e5d576119c7a9a289e58da5560f1d59f97ec407bb208544fa8a037dc2 351325 /media/heroes/mobile-landscape-light.1280.av1.968f138e.mp4
dd010af3cdbe8b61c8440c4ff2c167b7a3502ad9bda5bb0756e126ea97a2da64 529806 /media/heroes/mobile-landscape-light.1280.hevc.dd010af3.mp4
2d4e37e8c3bd0d2329936f018bd3eed47cfc2c35f6e20fb454c07b33dda96bd3 552673 /media/heroes/mobile-landscape-light.1920.av1.2d4e37e8.mp4
731d098be325dda29348964702c1ff4f7a91426d0e3cf4c578b2e0afba7fa4e3 963963 /media/heroes/mobile-landscape-light.1920.hevc.731d098b.mp4
034290dfc73122fea2f8a833634e460a0aded8aa7d4f109e6ab27cad843e69cc 517877 /media/heroes/sdk-landscape-dark.1280.av1.034290df.mp4
05b6000457df6da2aa1c0481e8ea564539cadf2a3bd7b54dde5e582064268347 865568 /media/heroes/sdk-landscape-dark.1280.hevc.05b60004.mp4
2901574673dd03a87098c2a8ff4e705bef89dc6225acc05060f71bc250a69121 851631 /media/heroes/sdk-landscape-dark.1920.av1.29015746.mp4
d644d9f8626724a3e404f82a3379450b33b9eaf867a6ee85e9dbac0a90d82490 1635522 /media/heroes/sdk-landscape-dark.1920.hevc.d644d9f8.mp4
e99560233e85d11af2045e2d4d6c0e14df529db6e763ec6a60fae163168d5e26 517709 /media/heroes/sdk-landscape-light.1280.av1.e9956023.mp4
39c3c112d98f3ec0f9cc0d950e25323da13f87c728646ca50efe446a8b59172f 852412 /media/heroes/sdk-landscape-light.1280.hevc.39c3c112.mp4
8bbc435da253a6febb58d4ea584ba21e927c34ad7e3b9afc6bd061c5dfd98656 847991 /media/heroes/sdk-landscape-light.1920.av1.8bbc435d.mp4
2b6ebccab815809d38ddb57b99ff60fcd681701f008d0d990367e86bd98b48c1 1641009 /media/heroes/sdk-landscape-light.1920.hevc.2b6ebcca.mp4
c5372903d706fc458231b2fadb7036bd134b0ae822078eb8b763e09717070427 284759 /media/heroes/terminal-landscape-dark.1280.av1.c5372903.mp4
d717c7ac9f6040a1f353ea42943165f8b5f1da90024802b7070456c9478b64c8 294714 /media/heroes/terminal-landscape-dark.1280.hevc.d717c7ac.mp4
2b3718178fdfb4eee98dbcb1aacbab566481218227d6b909e91c137d92b5215f 266082 /media/heroes/terminal-landscape-dark.1920.av1.2b371817.mp4
0f4dba504210190c7545f300488ba5baf8933d6ba6063c5d55c5841c73a70e5d 339529 /media/heroes/terminal-landscape-dark.1920.hevc.0f4dba50.mp4
ea1f17d7b55be8f141ee954109610496164dec0bcefc2d18724d4ab3cf2059e5 281679 /media/heroes/terminal-landscape-light.1280.av1.ea1f17d7.mp4
61386db5459ab334bc0d4e574980b4dda1c0d217d7a3d5ec790869a03c7b4ea4 295267 /media/heroes/terminal-landscape-light.1280.hevc.61386db5.mp4
51bebb60c49680d69876972f34cb662bad98ea7dd37e8e8c6c6e95e694ef2fad 267267 /media/heroes/terminal-landscape-light.1920.av1.51bebb60.mp4
3e2fb1e72e8e4ce8ef817ed0cb51edcdbf56bac37dcec579235e9a39c34e7814 342007 /media/heroes/terminal-landscape-light.1920.hevc.3e2fb1e7.mp4
1bdc53ec037dcdb8500d0197b1b2928e7acc26073a178e5f431d984b9df46982 169500 /media/more/mini-portrait.720.av1.1bdc53ec.mp4
9cba0d1d2108c8af4e22cc54cafcd8532101d6bbd2809ac18dd26ff1aaf615b3 5999115 /press-media/zips/opencode-desktop-media.zip
abb8006a7c78f52db1f5536c0f99a25bfe9e29703eb09a336d753475576f3b30 7454565 /press-media/zips/opencode-mobile-media.zip
a8a6aaa627d1ceeb57ff3f63c4b2e468027714719916d3460b37a6a838447fdd 9723829 /press-media/zips/opencode-sdk-media.zip
d516cc84c44ff1dcba1cc81e627e0c7406887c744fb804bdac553acc0e8735f7 2293579 /press-media/zips/opencode-terminal-media.zip
0f1d6dfc747f2d88650b2073bb04cd41380241adfa4de3e2f070bdc4bb367579 272426 /press/anomaly/anomaly-logo.zip
3e1b6629f92f154a4df48cc6a6a8d5028635260a29fe95033c8607174f0ee425 2128144 /product-videos/card-hover/angled/desktop.mp4
6e052a5f7615ce99c693d6aa6b31682d0dd111c03eb7c7a2050c4385ba154a65 2163077 /product-videos/card-hover/angled/mobile.mp4
4a255544f41c9f32754db1dcef99b2b6630bac3291aa4d145d169ea199990d93 2859662 /product-videos/card-hover/angled/sdk.mp4
1702d5c5898729a7a3cd27ba8057f63806f838795c6c912aa859fa53ba2976c8 1143236 /product-videos/card-hover/angled/terminal.mp4
e4ef1a568e4e1e96d8baa98d3dfc025a7f336065a7d48615045dbc51a1b94bc0 24081 /product-videos/card-loops/desktop.ja.mp4
73360275449cf5cbd5ba9f790180aa2ad751fcb568ebef2653c97e5eecfff9d2 30712 /product-videos/card-loops/desktop.mp4
2d9db52537cb6bd316257a303da7724b2aa985113667b0f0a122970065fc6d3e 22231 /product-videos/card-loops/mini.mp4
41351d6aaa74c958c475d0a158200661e39e43f63100cedebcbd078aa14e4f9d 578026 /product-videos/card-loops/mobile-orb-75.mp4
0c8e5c8c94bc24d0b92ea9cbe520c6ee5b68d182c58525e8ef4e1529d84e422a 35974 /product-videos/card-loops/sdk.mp4
46be1bab5bb02fb591fc3418c40b259abb72cfdfb1904a93d7fe4beb73323d9b 19352 /product-videos/card-loops/terminal.mp4
faf7bec9937d951bc84e093062de8ed61358d9e7d192e9a1c0be0d961160ae96 546600 /product-videos/features/focused/desktop-1.mp4
f919db7ee375155c634a78f11e6cea12ec9703d6ef5be3af633f70bd10ed0fdb 409561 /product-videos/features/focused/desktop-2.mp4
c30b70e0ce12862c6b0dd35a4a6070966552824a0d307c6dff3c603f46341432 930510 /product-videos/features/focused/desktop-3.mp4
600bf1e4ac09aba48e2d1288fca6328024608b1cb0899c7680a228d29ea45343 904903 /product-videos/features/focused/mobile-1.mp4
9f93bc89acee4859472624a40218dd80aa2ea3bc488d7b4668cf2cd22cec40ed 812821 /product-videos/features/focused/mobile-2.mp4
72b5c8f3435b1c6fe4452aabdadc19a08498a886ef0c402c7a5d36ee61bfce16 605731 /product-videos/features/focused/mobile-3.mp4
28e772578382ea800cacd6f8770fde67f4e842f3375a50448d0e89c91b02bc4f 728390 /product-videos/features/focused/sdk-1.mp4
e01dc4384d8376b7c2f9e047a960c9963ff07b48d11a3baf969b5b1ce591def6 1187535 /product-videos/features/focused/sdk-2.mp4
d1d6fb872ca6d2d380604484ec2ec6c875122779e87933d4777ca0aba17fd942 2066978 /product-videos/features/focused/sdk-3.mp4
d854e556997d1666a82baed884b6f07949c079c08b8f9eec2003ea6586f38cfa 115370 /product-videos/features/focused/terminal-1.mp4
0a6879113c2e61c0d0216075cbdc839e871d07d14f09f2639969df134d8feb03 171948 /product-videos/features/focused/terminal-2.mp4
45c498c69ab5f42059454e3d25dd4d6715997f04551d3e348c87027967280076 504028 /product-videos/features/focused/terminal-3.mp4
77ec07701c0a2ede6e6030a772098dabae6ad8987fde119f9a8708d83be6763a 814227 /product-videos/heroes/desktop-landscape-dark.mp4
2777797089a72f459348161a03694fcf5fdf3066bcd8793a960af68a51d2be1b 813486 /product-videos/heroes/desktop-landscape-light.mp4
5177beef16b8f798c2ff9c7183c80a890a644993631051899e4254556543763c 147855 /product-videos/heroes/mini-landscape-dark.mp4
c2c07ad1af9ce6589f569e647cd3746ccea6b6291c50a3fd731f59e3d60f495a 146633 /product-videos/heroes/mini-landscape-light.mp4
9adfd3db5ebcde87d77ad02c0cab6cc4ee5c5419ba778ce65f7cc920f701e941 1778110 /product-videos/heroes/mobile-landscape-dark.mp4
9adfd3db5ebcde87d77ad02c0cab6cc4ee5c5419ba778ce65f7cc920f701e941 1778110 /product-videos/heroes/mobile-landscape-light.mp4
c42b8b6c3e9bc7d8402bef0b850c496f78828e8ab8afdcc47dc8b58c17d68bf6 2804053 /product-videos/heroes/sdk-landscape-dark.mp4
5139edbaa3f4136a536ecf413dc31c7f3462b1c3c11fbe80e4031fadedfdbb5c 2804501 /product-videos/heroes/sdk-landscape-light.mp4
c6270673e37042b8dd3a55645f4b8ba1c5ab258123b1eb17572c457b51d85f61 415873 /product-videos/heroes/terminal-landscape-dark.mp4
b0a91978490a2f967c89e87216f78e99dab18e1d89ea70e9b1da7356040461bb 416388 /product-videos/heroes/terminal-landscape-light.mp4
a395767cd238750a2899482022223035e6cc2a93c165b130d9f9dcbdef70af6e 172193 /product-videos/more/mini-portrait.mp4
@@ -0,0 +1,30 @@
// Fails the build when three.js (or any WebGL module) reaches the Worker's server bundle. Its module
// start-up creates an AbortController, which Cloudflare Workers forbid during global start-up, so
// every page would answer 500. Run after `vite build`; browser-only modules must be loaded with
// `import.meta.env.SSR ? undefined : () => import(...)` (see test/server-bundle.test.ts).
import path from "node:path"
import { Glob } from "bun"
const root = path.resolve(import.meta.dir, "..", ".output", "server")
// Declarations only three.js's own code contains (the client manifest still names its asset files,
// so file names are not evidence).
const markers = ["class WebGLRenderer", "class PMREMGenerator", "class BufferGeometry", "class Object3D"]
const files = await Array.fromAsync(new Glob("**/*.{mjs,js}").scan({ cwd: root }))
if (!files.length) {
console.error(`check-server-bundle: no server output in ${root}; run vite build first`)
process.exit(1)
}
const found = (
await Promise.all(
files.map(async (file) => {
const text = await Bun.file(path.join(root, file)).text()
return markers.filter((marker) => text.includes(marker)).map((marker) => `${file}: ${marker}`)
}),
)
).flat()
if (found.length) {
console.error("check-server-bundle: three.js/WebGL code is in the server bundle:\n " + found.join("\n "))
process.exit(1)
}
console.log(`check-server-bundle: ${files.length} server files, no three.js/WebGL code`)
@@ -0,0 +1,74 @@
// Uploads the heavy media in public/ (src/lib/media.ts `mediaFile`) to the opencode.ai files bucket
// (served at https://opencode.ai/files/), at the same path under the version prefix site/2026-11,
// and writes media-manifest.txt. Not part of the build. See MEDIA.md.
//
// bun ./script/upload-media.ts --manifest rewrite media-manifest.txt from public/
// bun ./script/upload-media.ts print the uploads (dry run)
// MEDIA_BUCKET=<files bucket> bun ./script/upload-media.ts --run upload with `wrangler r2 object put`
//
// MEDIA_PREFIX (default site/2026-11) must match mediaBase in src/lib/media.ts. Uploads only the files
// media-manifest.txt lists (what the site uses); files that git tracks stay in the static assets.
import { createHash } from "node:crypto"
import path from "node:path"
import { Glob } from "bun"
import { mediaFile } from "../src/lib/media"
const root = path.resolve(import.meta.dir, "..")
const publicDir = path.join(root, "public")
const bucket = process.env.MEDIA_BUCKET ?? "<files bucket>"
const prefix = process.env.MEDIA_PREFIX ?? "site/2026-11"
const run = process.argv.includes("--run")
const writeManifest = process.argv.includes("--manifest")
const tracked = new Set(
(await Bun.$`git ls-files public`.cwd(root).quiet().text()).split("\n").map((file) => file.replace(/^public\//, "")),
)
const files = (await Array.fromAsync(new Glob("**/*").scan({ cwd: publicDir })))
.filter((file) => mediaFile.test(file) && !tracked.has(file))
.sort()
const types: Record<string, string> = {
mp4: "video/mp4",
webm: "audio/webm",
mov: "video/quicktime",
mp3: "audio/mpeg",
wav: "audio/wav",
ogg: "audio/ogg",
m4a: "audio/mp4",
zip: "application/zip",
glb: "model/gltf-binary",
gltf: "model/gltf+json",
}
if (writeManifest) {
const lines = await Promise.all(
files.map(async (file) => {
const bytes = await Bun.file(path.join(publicDir, file)).arrayBuffer()
return `${createHash("sha256").update(new Uint8Array(bytes)).digest("hex")} ${bytes.byteLength} /${file}`
}),
)
await Bun.write(
path.join(root, "media-manifest.txt"),
`# sha256 bytes path: the files the site requests, uploaded to https://opencode.ai/files/${prefix}/<path>\n${lines.join("\n")}\n`,
)
console.log(`media-manifest.txt: ${files.length} files; remove the ones the site does not request (MEDIA.md)`)
process.exit(0)
}
// Upload only what media-manifest.txt lists: the files the site requests.
const listed = (await Bun.file(path.join(root, "media-manifest.txt")).text())
.split("\n")
.filter((line) => line && !line.startsWith("#"))
.map((line) => line.split(/\s+/)[2].slice(1))
for (const file of listed) {
const key = `${bucket}/${prefix}/${file}`
const type = types[path.extname(file).slice(1).toLowerCase()] ?? "application/octet-stream"
if (!run) {
console.log(`would upload ${key} (${type})`)
continue
}
await Bun.$`bunx wrangler r2 object put ${key} --file ${path.join(publicDir, file)} --content-type ${type} --cache-control ${"public, max-age=31536000, immutable"} --remote`.cwd(
root,
)
}
console.log(`${run ? "uploaded" : "dry run:"} ${listed.length} files to ${bucket}/${prefix}`)
+28
View File
@@ -0,0 +1,28 @@
// Heavy media (video, audio, download zips and 3D models) is not in git and not in the Worker's
// static assets: it is served from the opencode.ai files bucket (the one the downloads use), under
// /files/site/<version>/, at the same path it has under public/. See MEDIA.md.
//
// Pages keep linking the site path (for example /product-videos/heroes/sdk-landscape-dark.mp4).
// In development the file is in public/ and the dev server serves it. In production the request
// misses the static assets, reaches the Worker, and middleware.ts redirects it here.
/** The one base URL of the site's media files. VITE_MEDIA_URL overrides it (for example for staging). */
export const mediaBase = import.meta.env.VITE_MEDIA_URL || "https://opencode.ai/files/site/2026-11"
/** File types that live in the files bucket. script/upload-media.ts uploads the same set. */
export const mediaFile = /\.(mp4|webm|mov|mp3|wav|ogg|m4a|zip|glb|gltf)$/i
/** The files URL for a site path, for example `mediaUrl("/font/film/opencode-font-film-v3-4x5.mp4")`. */
export function mediaUrl(path: string) {
return `${mediaBase}${path}`
}
/** A redirect to the files bucket for a media path that the static assets did not serve, or undefined. */
export function mediaRedirect(pathname: string, search: string) {
// /files/* is the files bucket's own route; never redirect it to itself.
if (!mediaFile.test(pathname) || pathname.startsWith("/_build/") || pathname.startsWith("/files/")) return
return new Response(null, {
status: 302,
headers: { Location: `${mediaUrl(pathname)}${search}`, "Cache-Control": "public, max-age=3600" },
})
}
+23
View File
@@ -0,0 +1,23 @@
import { describe, expect, test } from "bun:test"
import { mediaBase, mediaRedirect } from "../src/lib/media"
describe("media redirect", () => {
test("sends heavy media to opencode.ai/files/site/<version> at the same path", () => {
expect(mediaBase).toBe("https://opencode.ai/files/site/2026-11")
const response = mediaRedirect("/product-videos/heroes/sdk-landscape-dark.mp4", "?v=2")
expect(response?.status).toBe(302)
expect(response?.headers.get("Location")).toBe(`${mediaBase}/product-videos/heroes/sdk-landscape-dark.mp4?v=2`)
})
test("leaves pages, images, build files, download routes and the files route alone", () => {
for (const path of [
"/",
"/product",
"/brand/files/opencode-logo-dark.png",
"/_build/assets/hero.mp4",
"/download/stable/darwin-aarch64-dmg",
"/files/site/2026-11/product-videos/heroes/sdk-landscape-dark.mp4",
])
expect(mediaRedirect(path, "")).toBeUndefined()
})
})