mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 20:21:39 +03:00
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:
@@ -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
@@ -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/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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}`)
|
||||
@@ -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" },
|
||||
})
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user