Shrink feature tour animations without changing pixels or timing (#31012)

* Shrink feature tour animations without changing pixels or timing

* Update README media links for lossless animation assets
This commit is contained in:
Neil
2026-10-19 22:11:56 -07:00
committed by GitHub
parent 09fd0bec5a
commit acc6354363
33 changed files with 308 additions and 82 deletions
+4 -4
View File
@@ -68,7 +68,7 @@ Ghostty-class terminals with WebGL rendering, infinite splits, and scrollback th
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="resources/onboarding/feature-wall/tile-02.gif" type="image/gif"><img src="resources/onboarding/feature-wall/tile-02.poster.jpg" alt="Terminal splits" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="resources/onboarding/feature-wall/tile-02.webp" type="image/webp"><img src="resources/onboarding/feature-wall/tile-02.poster.jpg" alt="Terminal splits" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -96,7 +96,7 @@ Browse PRs, issues, and project boards in-app — open a worktree from any task
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="resources/onboarding/feature-wall/tile-03.gif" type="image/gif"><img src="resources/onboarding/feature-wall/tile-03.poster.jpg" alt="GitHub and Linear task workflows in Orca" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="resources/onboarding/feature-wall/tile-03.webp" type="image/webp"><img src="resources/onboarding/feature-wall/tile-03.poster.jpg" alt="GitHub and Linear task workflows in Orca" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -110,7 +110,7 @@ Run agents on a beefy remote box with full file editing, git, and terminals —
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="resources/onboarding/feature-wall/tile-06.gif" type="image/gif"><img src="resources/onboarding/feature-wall/tile-06.poster.jpg" alt="Remote worktrees over SSH" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="resources/onboarding/feature-wall/tile-06.webp" type="image/webp"><img src="resources/onboarding/feature-wall/tile-06.poster.jpg" alt="Remote worktrees over SSH" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -152,7 +152,7 @@ Agents drive Orca too — script every workflow with `orca worktree create`, `sn
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="resources/onboarding/feature-wall/tile-09.gif" type="image/gif"><img src="resources/onboarding/feature-wall/tile-09.poster.jpg" alt="Script Orca from the CLI" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="resources/onboarding/feature-wall/tile-09.webp" type="image/webp"><img src="resources/onboarding/feature-wall/tile-09.poster.jpg" alt="Script Orca from the CLI" width="100%" /></picture></a>
</td>
</tr>
</table>
+5 -18
View File
@@ -1,29 +1,16 @@
#!/usr/bin/env node
import { readdir, stat } from 'node:fs/promises'
import path from 'node:path'
import { FEATURE_WALL_TILES, isFeatureWallMediaTile } from '../../src/shared/feature-wall-tiles.ts'
const __dirname = import.meta.dirname
const ROOT = path.join(__dirname, '..', '..')
const FEATURE_WALL_ASSET_DIR = path.join(ROOT, 'resources', 'onboarding', 'feature-wall')
const MAX_BYTES = 11 * 1024 * 1024
const MEDIA_TILE_IDS = [
'tile-01',
'tile-02',
'tile-03',
'tile-04',
'tile-05',
'tile-06',
'tile-07',
'tile-08',
'tile-09',
'tile-10',
'tile-11',
'tile-12'
]
const EXPECTED_FILES = MEDIA_TILE_IDS.flatMap((id) => [
`${id}.gif`,
`${id}.poster.jpg`,
`${id}.recorded-at.json`
const EXPECTED_FILES = FEATURE_WALL_TILES.filter(isFeatureWallMediaTile).flatMap((tile) => [
tile.gifPath,
tile.posterPath,
tile.recordedAtPath
])
async function collectFiles(dir) {
+65 -25
View File
@@ -1,8 +1,11 @@
#!/usr/bin/env node
import { copyFile, mkdir, writeFile } from 'node:fs/promises'
import { copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { spawnSync } from 'node:child_process'
import { homedir } from 'node:os'
import { homedir, tmpdir } from 'node:os'
import path from 'node:path'
import { runProcessSync } from '@orca/process-host'
import { FEATURE_WALL_TILES } from '../../src/shared/feature-wall-tiles.ts'
import { verifyLosslessFeatureWallMedia } from './verify-feature-wall-lossless-media.mjs'
const __dirname = import.meta.dirname
const ROOT = path.join(__dirname, '..', '..')
@@ -101,29 +104,66 @@ function gitRecordedAtSeconds(tile, relativePath) {
await mkdir(DEST_ROOT, { recursive: true })
for (const tile of TILES) {
const sourceRoot = sourceRootForTile(tile)
const sourceGif = path.join(sourceRoot, ...tile.gifRelativePath.split('/'))
const sourcePoster = path.join(sourceRoot, ...tile.posterRelativePath.split('/'))
const destGif = path.join(DEST_ROOT, `${tile.id}.gif`)
const destPoster = path.join(DEST_ROOT, `${tile.id}.poster.jpg`)
const recordedAtSeconds = gitRecordedAtSeconds(tile, tile.gifRelativePath)
const originals = new Map()
const stagingRoot = await mkdtemp(path.join(tmpdir(), 'orca-feature-wall-media-'))
try {
for (const tile of TILES) {
const sourceRoot = sourceRootForTile(tile)
const sourceGif = path.join(sourceRoot, ...tile.gifRelativePath.split('/'))
const sourcePoster = path.join(sourceRoot, ...tile.posterRelativePath.split('/'))
const media = FEATURE_WALL_TILES.find((item) => item.id === tile.id)
if (!media || media.kind !== 'media') {
throw new Error(`Unknown media tile: ${tile.id}`)
}
const destAnimation = path.join(stagingRoot, media.gifPath)
const destPoster = path.join(stagingRoot, `${tile.id}.poster.jpg`)
const recordedAtSeconds = gitRecordedAtSeconds(tile, tile.gifRelativePath)
await copyFile(sourceGif, destGif)
await copyFile(sourcePoster, destPoster)
await writeFile(
path.join(DEST_ROOT, `${tile.id}.recorded-at.json`),
`${JSON.stringify(
{
recordedAtUnixSeconds: recordedAtSeconds,
recordedAtIso: new Date(recordedAtSeconds * 1000).toISOString(),
sourceGif: tile.gifRelativePath,
sourcePoster: tile.posterRelativePath
},
null,
2
)}\n`
)
originals.set(tile.id, await readFile(sourceGif))
if (media.gifPath.endsWith('.gif')) {
await copyFile(sourceGif, destAnimation)
} else {
// gif2webp defaults to lossless; the verifier rejects changed pixels or timing.
const result = runProcessSync({
program: 'gif2webp',
args: ['-m', '6', '-min_size', '-metadata', 'all', sourceGif, '-o', destAnimation],
timeoutMs: 120_000
})
if (result.code !== 0) {
throw new Error(`gif2webp failed for ${tile.id}: ${result.stderr}`)
}
}
await copyFile(sourcePoster, destPoster)
await writeFile(
path.join(stagingRoot, `${tile.id}.recorded-at.json`),
`${JSON.stringify(
{
recordedAtUnixSeconds: recordedAtSeconds,
recordedAtIso: new Date(recordedAtSeconds * 1000).toISOString(),
sourceGif: tile.gifRelativePath,
sourcePoster: tile.posterRelativePath
},
null,
2
)}\n`
)
console.log(`Vendored ${tile.gifRelativePath} -> ${tile.id}`)
console.log(`Vendored ${tile.gifRelativePath} -> ${tile.id}`)
}
await verifyLosslessFeatureWallMedia(originals, stagingRoot)
for (const tile of TILES) {
const media = FEATURE_WALL_TILES.find((item) => item.id === tile.id)
if (media?.kind !== 'media') {
throw new Error(`Unknown media tile: ${tile.id}`)
}
for (const file of [media.gifPath, media.posterPath, media.recordedAtPath]) {
await copyFile(path.join(stagingRoot, file), path.join(DEST_ROOT, file))
}
if (media.gifPath.endsWith('.webp')) {
await rm(path.join(DEST_ROOT, `${tile.id}.gif`), { force: true })
}
}
} finally {
await rm(stagingRoot, { recursive: true, force: true })
}
@@ -0,0 +1,198 @@
#!/usr/bin/env node
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { createServer } from 'node:http'
import path from 'node:path'
import { chromium } from 'playwright-core'
import { runProcessSync } from '@orca/process-host'
import { FEATURE_WALL_TILES, isFeatureWallMediaTile } from '../../src/shared/feature-wall-tiles.ts'
const ROOT = path.resolve(import.meta.dirname, '..', '..')
const ASSET_DIR = 'resources/onboarding/feature-wall'
function skipGifSubBlocks(bytes, offset) {
while (offset < bytes.length && bytes[offset] !== 0) {
offset += 1 + bytes[offset]
}
assert.ok(offset < bytes.length, 'Truncated GIF sub-block')
return offset + 1
}
function gifDurations(bytes) {
assert.match(bytes.subarray(0, 6).toString(), /^GIF8[79]a$/)
let offset = 13 + (bytes[10] & 128 ? 3 * 2 ** ((bytes[10] & 7) + 1) : 0)
let duration = 0
const durations = []
while (offset < bytes.length) {
const block = bytes[offset++]
if (block === 0x3b) {
return durations
}
if (block === 0x21) {
const label = bytes[offset++]
if (label === 0xf9) {
duration = bytes.readUInt16LE(offset + 2) * 10
}
offset = skipGifSubBlocks(bytes, offset)
} else {
assert.equal(block, 0x2c, 'Invalid GIF image descriptor')
const packed = bytes[offset + 8]
offset += 9 + (packed & 128 ? 3 * 2 ** ((packed & 7) + 1) : 0)
offset = skipGifSubBlocks(bytes, offset + 1)
durations.push(duration)
duration = 0
}
}
throw new Error('Missing GIF trailer')
}
function animationDurations(bytes) {
if (bytes.subarray(0, 3).toString() === 'GIF') {
return gifDurations(bytes)
}
assert.equal(bytes.subarray(0, 4).toString(), 'RIFF')
assert.equal(bytes.subarray(8, 12).toString(), 'WEBP')
const durations = []
for (let offset = 12; offset < bytes.length;) {
const length = bytes.readUInt32LE(offset + 4)
if (bytes.subarray(offset, offset + 4).toString() === 'ANMF') {
durations.push(bytes.readUIntLE(offset + 20, 3))
}
offset += 8 + length + (length % 2)
}
return durations
}
export async function verifyLosslessFeatureWallMedia(
originals,
candidateDirectory = path.join(ROOT, ASSET_DIR)
) {
const candidates = new Map()
for (const tile of FEATURE_WALL_TILES.filter(isFeatureWallMediaTile)) {
const candidate = await readFile(path.join(candidateDirectory, tile.gifPath))
const original = originals.get(tile.id)
assert.ok(original, `${tile.id}: original GIF missing`)
assert.deepEqual(
animationDurations(candidate),
animationDurations(original),
`${tile.id}: encoded per-frame timing changed`
)
candidates.set(tile.id, candidate)
}
const server = createServer((request, response) => {
response.setHeader('Cache-Control', 'no-store')
if (request.url === '/') {
return response.end('<!doctype html><title>Animation equality</title>')
}
const [kind, id] = request.url.slice(1).split('/')
const bytes = (kind === 'original' ? originals : candidates).get(id)
response.statusCode = bytes ? 200 : 404
response.end(bytes)
})
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
let browser
try {
browser = await chromium.launch({
headless: true,
executablePath: process.env.ORCA_ANIMATION_CHROMIUM_EXECUTABLE || undefined
})
const page = await browser.newPage()
await page.goto(`http://127.0.0.1:${server.address().port}`)
const results = []
for (const tile of FEATURE_WALL_TILES.filter(isFeatureWallMediaTile)) {
const result = await page.evaluate(async ({ id, gifPath }) => {
const makeDecoder = async (kind, type) =>
new ImageDecoder({
data: await (await fetch(`/${kind}/${id}`)).arrayBuffer(),
type,
preferAnimation: true
})
const original = await makeDecoder('original', 'image/gif')
const candidate = await makeDecoder(
'candidate',
gifPath.endsWith('.gif') ? 'image/gif' : 'image/webp'
)
try {
await Promise.all([original.tracks.ready, candidate.tracks.ready])
const a = original.tracks.selectedTrack
const b = candidate.tracks.selectedTrack
if (a.frameCount !== b.frameCount || a.repetitionCount !== b.repetitionCount) {
throw new Error(`${id}: frame count or loop changed`)
}
let canvas, context
let durationUs = 0
for (let frameIndex = 0; frameIndex < a.frameCount; frameIndex++) {
const first = (await original.decode({ frameIndex })).image
const second = (await candidate.decode({ frameIndex })).image
try {
if (
first.displayWidth !== second.displayWidth ||
first.displayHeight !== second.displayHeight ||
first.duration !== second.duration ||
first.timestamp !== second.timestamp
) {
throw new Error(`${id}: dimensions or playback timing changed at ${frameIndex}`)
}
if (!canvas) {
canvas = new OffscreenCanvas(first.displayWidth, first.displayHeight)
context = canvas.getContext('2d', { willReadFrequently: true })
}
context.clearRect(0, 0, canvas.width, canvas.height)
context.drawImage(first, 0, 0)
const left = context.getImageData(0, 0, canvas.width, canvas.height).data
context.clearRect(0, 0, canvas.width, canvas.height)
context.drawImage(second, 0, 0)
const right = context.getImageData(0, 0, canvas.width, canvas.height).data
const leftPixels = new Uint32Array(left.buffer)
const rightPixels = new Uint32Array(right.buffer)
for (let index = 0; index < leftPixels.length; index++) {
if (leftPixels[index] !== rightPixels[index]) {
throw new Error(`${id}: RGBA pixel ${index} changed at frame ${frameIndex}`)
}
}
durationUs += first.duration
} finally {
first.close()
second.close()
}
}
return { id, frames: a.frameCount, durationUs, exactRGBA: true }
} finally {
original.close()
candidate.close()
}
}, tile)
results.push(result)
console.log(JSON.stringify(result))
}
return results
} finally {
await browser?.close()
await new Promise((resolve) => server.close(resolve))
}
}
function readOriginalGif(baseRef, id) {
const result = runProcessSync({
program: 'git',
args: ['show', `${baseRef}:${ASSET_DIR}/${id}.gif`],
cwd: ROOT,
maxOutputBytes: 16 * 1024 * 1024,
captureStdoutAsBytes: true
})
assert.equal(result.code, 0, result.stderr)
assert.ok(result.stdoutBytes)
return result.stdoutBytes
}
if (process.argv[1] && path.resolve(process.argv[1]) === import.meta.filename) {
const baseRef = process.argv[2]
assert.ok(baseRef, 'Usage: node config/scripts/verify-feature-wall-lossless-media.mjs <base-ref>')
const originals = new Map(
FEATURE_WALL_TILES.filter(isFeatureWallMediaTile).map((tile) => [
tile.id,
readOriginalGif(baseRef, tile.id)
])
)
await verifyLosslessFeatureWallMedia(originals)
}
+4 -4
View File
@@ -68,7 +68,7 @@ Terminales de nivel Ghostty con renderizado WebGL, divisiones infinitas y un scr
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="Terminales divididas" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="Terminales divididas" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -96,7 +96,7 @@ Explora PRs, issues y tableros de proyecto dentro de la app — abre un worktree
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Flujos de trabajo de GitHub y Linear en Orca" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Flujos de trabajo de GitHub y Linear en Orca" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -110,7 +110,7 @@ Ejecuta agentes en una máquina remota potente con edición completa de archivos
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="Worktrees remotos por SSH" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="Worktrees remotos por SSH" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -152,7 +152,7 @@ Los agentes también manejan Orca — automatiza cualquier flujo de trabajo con
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="Automatiza Orca desde la CLI" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="Automatiza Orca desde la CLI" width="100%" /></picture></a>
</td>
</tr>
</table>
+4 -4
View File
@@ -72,7 +72,7 @@ Terminaux de niveau Ghostty avec rendu WebGL, splits infinis et un scrollback qu
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="Splits de terminal" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="Splits de terminal" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -100,7 +100,7 @@ Parcourez PRs, issues et boards de projet dans l'app — ouvrez un worktree depu
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Workflows GitHub et Linear dans Orca" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Workflows GitHub et Linear dans Orca" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -114,7 +114,7 @@ Faites tourner des agents sur une machine distante costaude, avec édition de fi
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="Worktrees distants via SSH" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="Worktrees distants via SSH" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -156,7 +156,7 @@ Les agents pilotent aussi Orca — scriptez n'importe quel workflow avec `orca w
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="Scripter Orca depuis la CLI" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="Scripter Orca depuis la CLI" width="100%" /></picture></a>
</td>
</tr>
</table>
+4 -4
View File
@@ -68,7 +68,7 @@ WebGL レンダリング、無制限の分割、再起動後も残るスクロ
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="ターミナル分割" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="ターミナル分割" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -96,7 +96,7 @@ PR、Issue、プロジェクトボードをアプリ内で閲覧 — 任意の
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Orca の GitHub と Linear タスクワークフロー" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Orca の GitHub と Linear タスクワークフロー" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -110,7 +110,7 @@ PR、Issue、プロジェクトボードをアプリ内で閲覧 — 任意の
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="SSH 経由のリモートワークツリー" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="SSH 経由のリモートワークツリー" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -152,7 +152,7 @@ PR、Issue、プロジェクトボードをアプリ内で閲覧 — 任意の
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="CLI から Orca をスクリプト操作" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="CLI から Orca をスクリプト操作" width="100%" /></picture></a>
</td>
</tr>
</table>
+4 -4
View File
@@ -68,7 +68,7 @@ WebGL 렌더링, 무한 분할, 재시작 후에도 유지되는 스크롤백을
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="터미널 분할" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="터미널 분할" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -96,7 +96,7 @@ PR, issue, 프로젝트 보드를 앱 안에서 탐색하세요 — 어떤 작
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Orca의 GitHub 및 Linear 작업 워크플로" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Orca의 GitHub 및 Linear 작업 워크플로" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -110,7 +110,7 @@ PR, issue, 프로젝트 보드를 앱 안에서 탐색하세요 — 어떤 작
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="SSH를 통한 원격 worktree" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="SSH를 통한 원격 worktree" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -152,7 +152,7 @@ diff의 어느 줄에든 코멘트를 남기고 에이전트에게 바로 보내
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="CLI에서 Orca 스크립팅" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="CLI에서 Orca 스크립팅" width="100%" /></picture></a>
</td>
</tr>
</table>
+4 -4
View File
@@ -68,7 +68,7 @@ Terminais no nível do Ghostty com renderização WebGL, divisões infinitas e s
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="Terminais divididos" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="Terminais divididos" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -96,7 +96,7 @@ Navegue por PRs, issues e quadros de projeto dentro do app — abra um worktree
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Fluxos de trabalho de tarefas do GitHub e Linear no Orca" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Fluxos de trabalho de tarefas do GitHub e Linear no Orca" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -110,7 +110,7 @@ Execute agentes em uma máquina remota potente com edição completa de arquivos
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="Worktrees remotos por SSH" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="Worktrees remotos por SSH" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -152,7 +152,7 @@ Agentes também controlam o Orca — automatize qualquer fluxo de trabalho com `
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="Automatize o Orca pela CLI" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="Automatize o Orca pela CLI" width="100%" /></picture></a>
</td>
</tr>
</table>
+4 -4
View File
@@ -68,7 +68,7 @@ Ghostty 级终端,支持 WebGL 渲染、无限分屏,以及重启后依然
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="终端分屏" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/terminal"><picture><source srcset="../../resources/onboarding/feature-wall/tile-02.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-02.poster.jpg" alt="终端分屏" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -96,7 +96,7 @@ Ghostty 级终端,支持 WebGL 渲染、无限分屏,以及重启后依然
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Orca 中的 GitHub 与 Linear 任务工作流" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="../../resources/onboarding/feature-wall/tile-03.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-03.poster.jpg" alt="Orca 中的 GitHub 与 Linear 任务工作流" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -110,7 +110,7 @@ Ghostty 级终端,支持 WebGL 渲染、无限分屏,以及重启后依然
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="通过 SSH 使用远程 worktree" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/ssh"><picture><source srcset="../../resources/onboarding/feature-wall/tile-06.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-06.poster.jpg" alt="通过 SSH 使用远程 worktree" width="100%" /></picture></a>
</td>
</tr>
<tr>
@@ -152,7 +152,7 @@ VS Code 的编辑器,处处自动保存 — 把文件或图片直接拖入智
</td>
<td width="50%">
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.gif" type="image/gif"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="从 CLI 脚本化 Orca" width="100%" /></picture></a>
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="../../resources/onboarding/feature-wall/tile-09.webp" type="image/webp"><img src="../../resources/onboarding/feature-wall/tile-09.poster.jpg" alt="从 CLI 脚本化 Orca" width="100%" /></picture></a>
</td>
</tr>
</table>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 726 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 770 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

+12 -11
View File
@@ -61,7 +61,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'Parallel workspace orchestration',
caption:
'Give each task its own workspace - no stashing, no branch juggling. Fan work across agents, compare, and continue with the best result.',
gifPath: 'tile-01.gif',
gifPath: 'tile-01.webp',
posterPath: 'tile-01.poster.jpg',
recordedAtPath: 'tile-01.recorded-at.json',
owner: 'worktree-orchestration',
@@ -73,7 +73,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'Ghostty-class terminal',
caption:
'WebGL rendering, infinite splits, scrollback restored on restart, full scrollback search.',
gifPath: 'tile-02.gif',
gifPath: 'tile-02.webp',
posterPath: 'tile-02.poster.jpg',
recordedAtPath: 'tile-02.recorded-at.json',
owner: 'terminal',
@@ -85,7 +85,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'GitHub & Linear, native',
caption:
'Find connected GitHub or Linear work in Tasks, open its context, and start workspaces without switching tools.',
gifPath: 'tile-03.gif',
gifPath: 'tile-03.webp',
posterPath: 'tile-03.poster.jpg',
recordedAtPath: 'tile-03.recorded-at.json',
owner: 'task-integrations',
@@ -96,7 +96,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
kind: 'media',
title: 'Supported CLI agents',
caption: 'Claude Code, Codex, Cursor CLI, Gemini, Copilot, OpenCode, and Pi are preconfigured.',
gifPath: 'tile-04.gif',
gifPath: 'tile-04.webp',
posterPath: 'tile-04.poster.jpg',
recordedAtPath: 'tile-04.recorded-at.json',
owner: 'agent-integrations',
@@ -108,7 +108,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'Embedded browser + Design Mode',
caption:
'A real Chromium window per workspace. Click any UI element to send its HTML, CSS, and a cropped screenshot into your agent.',
gifPath: 'tile-05.gif',
gifPath: 'tile-05.webp',
posterPath: 'tile-05.poster.jpg',
recordedAtPath: 'tile-05.recorded-at.json',
owner: 'browser-experience',
@@ -120,7 +120,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'Remote workspaces',
caption:
'Run agents on a remote machine with the same Orca editing, git, and terminal workflow.',
gifPath: 'tile-06.gif',
gifPath: 'tile-06.webp',
posterPath: 'tile-06.poster.jpg',
recordedAtPath: 'tile-06.recorded-at.json',
owner: 'ssh-workspaces',
@@ -132,6 +132,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'Monaco editor, drag-to-agent',
caption:
"VS Code's editor, autosave everywhere, quick-open with hidden files, drag-drop files or Finder images into an agent prompt.",
// gif2webp changes this recording's 10 ms frame delays.
gifPath: 'tile-07.gif',
posterPath: 'tile-07.poster.jpg',
recordedAtPath: 'tile-07.recorded-at.json',
@@ -144,7 +145,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'Inline review, back to the agent',
caption:
'Drop markdown comments on any diff line, batch them, ship them back to the agent. Inspect CI, resolve conflicts, open PRs - all in-app.',
gifPath: 'tile-08.gif',
gifPath: 'tile-08.webp',
posterPath: 'tile-08.poster.jpg',
recordedAtPath: 'tile-08.recorded-at.json',
owner: 'diff-review',
@@ -155,7 +156,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
kind: 'media',
title: 'Orca CLI',
caption: 'Agents can drive Orca too: create workspaces, snapshot screens, click, and fill.',
gifPath: 'tile-09.gif',
gifPath: 'tile-09.webp',
posterPath: 'tile-09.poster.jpg',
recordedAtPath: 'tile-09.recorded-at.json',
owner: 'orca-cli',
@@ -167,7 +168,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'Keyboard-native',
caption:
'Jump across workspaces, open files, and remap every shortcut. Move at the speed of your fingers.',
gifPath: 'tile-10.gif',
gifPath: 'tile-10.webp',
posterPath: 'tile-10.poster.jpg',
recordedAtPath: 'tile-10.recorded-at.json',
owner: 'keyboard-ux',
@@ -179,7 +180,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'Usage & rate-limit aware',
caption:
'See Claude and Codex usage, rate-limit resets, and hot-swap Codex accounts without re-logging in.',
gifPath: 'tile-11.gif',
gifPath: 'tile-11.webp',
posterPath: 'tile-11.poster.jpg',
recordedAtPath: 'tile-11.recorded-at.json',
owner: 'usage-rate-limits',
@@ -191,7 +192,7 @@ export const FEATURE_WALL_TILES: readonly FeatureWallTile[] = [
title: 'PDFs, images, CSV, Markdown',
caption:
'Preview everything your repo carries: PDFs, image diff modes, CSV tables, wiki-linked Markdown with search.',
gifPath: 'tile-12.gif',
gifPath: 'tile-12.webp',
posterPath: 'tile-12.poster.jpg',
recordedAtPath: 'tile-12.recorded-at.json',
owner: 'file-preview',