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
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 726 KiB |
|
After Width: | Height: | Size: 618 KiB |
|
Before Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 770 KiB |
|
After Width: | Height: | Size: 655 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 386 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
Before Width: | Height: | Size: 331 KiB |
|
After Width: | Height: | Size: 210 KiB |
@@ -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',
|
||||
|
||||