Files
RUWIO/src/components/HelpDialog.vue
T
2026-06-07 12:10:35 +02:00

931 lines
25 KiB
Vue

<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import type { Component } from 'vue'
import {
PhCode,
PhCursorClick,
PhExport,
PhFilmStrip,
PhFrameCorners,
PhImageSquare,
PhListBullets,
PhMusicNote,
PhPencilSimpleLine,
PhQuestion,
PhSlidersHorizontal,
PhSparkle,
} from '@phosphor-icons/vue'
import { Modal } from '@/components/ui'
import { t } from '@/i18n'
import { hasDesktopRender } from '@/lib/desktopRenderExport'
const props = defineProps<{ modelValue: boolean }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
type ChapterId =
| 'getting-started'
| 'stage'
| 'assets'
| 'layers-timeline'
| 'clips'
| 'buttons'
| 'scenes'
| 'audio'
| 'markers'
| 'scripts-actions'
| 'script-language'
| 'export'
| 'shortcuts'
interface HelpListItem {
bodyKey: string
icon?: Component
labelKey?: string
}
interface HelpTableRow {
effectKey: string
syntaxKey: string
}
type HelpBlock =
| { kind: 'paragraph'; bodyKey: string; titleKey?: string }
| { items: HelpListItem[]; kind: 'list'; titleKey?: string }
| { items: string[]; kind: 'steps'; titleKey: string }
| { kind: 'table'; rows: HelpTableRow[]; titleKey: string }
| { bodyKey: string; kind: 'code'; titleKey: string }
| { kind: 'shortcuts' }
interface Chapter {
blocks: HelpBlock[]
icon: Component
id: ChapterId
introKey: string
titleKey: string
}
const isDesktop = !!window.ruwioDesktop
const hasDesktopMp4 = hasDesktopRender()
const CHAPTERS: Chapter[] = [
{
id: 'getting-started',
titleKey: 'help.chapters.gettingStarted',
icon: PhSparkle,
introKey: 'help.content.gettingStarted.intro',
blocks: [
{
kind: 'paragraph',
titleKey: 'help.content.gettingStarted.modelTitle',
bodyKey: 'help.content.gettingStarted.modelBody',
},
{
kind: 'list',
titleKey: 'help.content.gettingStarted.whereTitle',
items: [
{
icon: PhQuestion,
labelKey: 'help.content.gettingStarted.where.topBar.label',
bodyKey: 'help.content.gettingStarted.where.topBar.body',
},
{
icon: PhImageSquare,
labelKey: 'help.content.gettingStarted.where.assets.label',
bodyKey: 'help.content.gettingStarted.where.assets.body',
},
{
icon: PhFrameCorners,
labelKey: 'help.content.gettingStarted.where.stage.label',
bodyKey: 'help.content.gettingStarted.where.stage.body',
},
{
icon: PhSlidersHorizontal,
labelKey: 'help.content.gettingStarted.where.properties.label',
bodyKey: 'help.content.gettingStarted.where.properties.body',
},
{
icon: PhFilmStrip,
labelKey: 'help.content.gettingStarted.where.timeline.label',
bodyKey: 'help.content.gettingStarted.where.timeline.body',
},
{
icon: PhListBullets,
labelKey: 'help.content.gettingStarted.where.scenes.label',
bodyKey: 'help.content.gettingStarted.where.scenes.body',
},
],
},
{
kind: 'steps',
titleKey: 'help.content.gettingStarted.quickStartTitle',
items: [
'help.content.gettingStarted.quickStart.step1',
'help.content.gettingStarted.quickStart.step2',
'help.content.gettingStarted.quickStart.step3',
'help.content.gettingStarted.quickStart.step4',
],
},
],
},
{
id: 'stage',
titleKey: 'help.chapters.stage',
icon: PhFrameCorners,
introKey: 'help.content.stage.intro',
blocks: [
{
kind: 'list',
titleKey: 'help.content.stage.viewTitle',
items: [
{ bodyKey: 'help.content.stage.view.zoom' },
{ bodyKey: 'help.content.stage.view.pan' },
{ bodyKey: 'help.content.stage.view.fit' },
],
},
{
kind: 'list',
titleKey: 'help.content.stage.selectionTitle',
items: [
{ bodyKey: 'help.content.stage.selection.pick' },
{ bodyKey: 'help.content.stage.selection.clear' },
{ bodyKey: 'help.content.stage.selection.handles' },
],
},
{
kind: 'paragraph',
titleKey: 'help.content.stage.lockingTitle',
bodyKey: 'help.content.stage.lockingBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.stage.drawingTitle',
bodyKey: 'help.content.stage.drawingBody',
},
],
},
{
id: 'assets',
titleKey: 'help.chapters.assets',
icon: PhImageSquare,
introKey: 'help.content.assets.intro',
blocks: [
{
kind: 'list',
titleKey: 'help.content.assets.sectionsTitle',
items: [
{ bodyKey: 'help.content.assets.sections.shapes', icon: PhSparkle },
{ bodyKey: 'help.content.assets.sections.images', icon: PhImageSquare },
{ bodyKey: 'help.content.assets.sections.clips', icon: PhFilmStrip },
{ bodyKey: 'help.content.assets.sections.buttons', icon: PhCursorClick },
{ bodyKey: 'help.content.assets.sections.audio', icon: PhMusicNote },
],
},
{
kind: 'paragraph',
titleKey: 'help.content.assets.thumbsTitle',
bodyKey: 'help.content.assets.thumbsBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.assets.foldTitle',
bodyKey: 'help.content.assets.foldBody',
},
],
},
{
id: 'layers-timeline',
titleKey: 'help.chapters.layersTimeline',
icon: PhFilmStrip,
introKey: 'help.content.layersTimeline.intro',
blocks: [
{
kind: 'list',
titleKey: 'help.content.layersTimeline.keyframesTitle',
items: [
{ bodyKey: 'help.content.layersTimeline.keyframes.add' },
{ bodyKey: 'help.content.layersTimeline.keyframes.drag' },
{ bodyKey: 'help.content.layersTimeline.keyframes.jump' },
{ bodyKey: 'help.content.layersTimeline.keyframes.menu' },
],
},
{
kind: 'paragraph',
titleKey: 'help.content.layersTimeline.holdsTitle',
bodyKey: 'help.content.layersTimeline.holdsBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.layersTimeline.separateTitle',
bodyKey: 'help.content.layersTimeline.separateBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.layersTimeline.groupsTitle',
bodyKey: 'help.content.layersTimeline.groupsBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.layersTimeline.snappingTitle',
bodyKey: 'help.content.layersTimeline.snappingBody',
},
],
},
{
id: 'clips',
titleKey: 'help.chapters.clips',
icon: PhFilmStrip,
introKey: 'help.content.clips.intro',
blocks: [
{
kind: 'steps',
titleKey: 'help.content.clips.createTitle',
items: [
'help.content.clips.create.step1',
'help.content.clips.create.step2',
'help.content.clips.create.step3',
],
},
{
kind: 'paragraph',
titleKey: 'help.content.clips.editTitle',
bodyKey: 'help.content.clips.editBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.clips.trimTitle',
bodyKey: 'help.content.clips.trimBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.clips.transformTitle',
bodyKey: 'help.content.clips.transformBody',
},
],
},
{
id: 'buttons',
titleKey: 'help.chapters.buttons',
icon: PhCursorClick,
introKey: 'help.content.buttons.intro',
blocks: [
{
kind: 'paragraph',
titleKey: 'help.content.buttons.editingTitle',
bodyKey: 'help.content.buttons.editingBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.buttons.stateKeyframesTitle',
bodyKey: 'help.content.buttons.stateKeyframesBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.buttons.stillFramesTitle',
bodyKey: 'help.content.buttons.stillFramesBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.buttons.hitShapesTitle',
bodyKey: 'help.content.buttons.hitShapesBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.buttons.actionsTitle',
bodyKey: 'help.content.buttons.actionsBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.buttons.disabledTitle',
bodyKey: 'help.content.buttons.disabledBody',
},
],
},
{
id: 'scenes',
titleKey: 'help.chapters.scenes',
icon: PhListBullets,
introKey: 'help.content.scenes.intro',
blocks: [
{
kind: 'paragraph',
titleKey: 'help.content.scenes.drawerTitle',
bodyKey: 'help.content.scenes.drawerBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.scenes.switchingTitle',
bodyKey: 'help.content.scenes.switchingBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.scenes.referencesTitle',
bodyKey: 'help.content.scenes.referencesBody',
},
],
},
{
id: 'audio',
titleKey: 'help.chapters.audio',
icon: PhMusicNote,
introKey: 'help.content.audio.intro',
blocks: [
{
kind: 'steps',
titleKey: 'help.content.audio.addTitle',
items: [
'help.content.audio.add.step1',
'help.content.audio.add.step2',
'help.content.audio.add.step3',
],
},
{
kind: 'paragraph',
titleKey: 'help.content.audio.tracksTitle',
bodyKey: 'help.content.audio.tracksBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.audio.movingTitle',
bodyKey: 'help.content.audio.movingBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.audio.waveformsTitle',
bodyKey: 'help.content.audio.waveformsBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.audio.urlsTitle',
bodyKey: 'help.content.audio.urlsBody',
},
],
},
{
id: 'markers',
titleKey: 'help.chapters.markers',
icon: PhPencilSimpleLine,
introKey: 'help.content.markers.intro',
blocks: [
{
kind: 'list',
titleKey: 'help.content.markers.editingTitle',
items: [
{ bodyKey: 'help.content.markers.editing.add' },
{ bodyKey: 'help.content.markers.editing.drag' },
{ bodyKey: 'help.content.markers.editing.rename' },
{ bodyKey: 'help.content.markers.editing.delete' },
{ bodyKey: 'help.content.markers.editing.clear' },
],
},
{
kind: 'paragraph',
titleKey: 'help.content.markers.sceneTitle',
bodyKey: 'help.content.markers.sceneBody',
},
],
},
{
id: 'scripts-actions',
titleKey: 'help.chapters.scriptsActions',
icon: PhCode,
introKey: 'help.content.scriptsActions.intro',
blocks: [
{
kind: 'steps',
titleKey: 'help.content.scriptsActions.addTitle',
items: [
'help.content.scriptsActions.add.step1',
'help.content.scriptsActions.add.step2',
'help.content.scriptsActions.add.step3',
],
},
{
kind: 'list',
titleKey: 'help.content.scriptsActions.firingTitle',
items: [
{ bodyKey: 'help.content.scriptsActions.firing.forward' },
{ bodyKey: 'help.content.scriptsActions.firing.exact' },
{ bodyKey: 'help.content.scriptsActions.firing.order' },
{ bodyKey: 'help.content.scriptsActions.firing.replay' },
],
},
{
kind: 'paragraph',
titleKey: 'help.content.scriptsActions.menuTitle',
bodyKey: 'help.content.scriptsActions.menuBody',
},
{
kind: 'list',
titleKey: 'help.content.scriptsActions.typesTitle',
items: [
{ bodyKey: 'help.content.scriptsActions.types.transport' },
{ bodyKey: 'help.content.scriptsActions.types.jump' },
{ bodyKey: 'help.content.scriptsActions.types.scene' },
{ bodyKey: 'help.content.scriptsActions.types.button' },
{ bodyKey: 'help.content.scriptsActions.types.script' },
],
},
],
},
{
id: 'script-language',
titleKey: 'help.chapters.scriptLanguage',
icon: PhCode,
introKey: 'help.content.scriptLanguage.intro',
blocks: [
{
kind: 'list',
titleKey: 'help.content.scriptLanguage.syntaxTitle',
items: [
{ bodyKey: 'help.content.scriptLanguage.syntax.onePerLine' },
{ bodyKey: 'help.content.scriptLanguage.syntax.comments' },
{ bodyKey: 'help.content.scriptLanguage.syntax.tokens' },
{ bodyKey: 'help.content.scriptLanguage.syntax.case' },
],
},
{
kind: 'table',
titleKey: 'help.content.scriptLanguage.commandsTitle',
rows: [
{
syntaxKey: 'help.content.scriptLanguage.commands.play.syntax',
effectKey: 'help.content.scriptLanguage.commands.play.effect',
},
{
syntaxKey: 'help.content.scriptLanguage.commands.pause.syntax',
effectKey: 'help.content.scriptLanguage.commands.pause.effect',
},
{
syntaxKey: 'help.content.scriptLanguage.commands.rewind.syntax',
effectKey: 'help.content.scriptLanguage.commands.rewind.effect',
},
{
syntaxKey: 'help.content.scriptLanguage.commands.jumpSeconds.syntax',
effectKey: 'help.content.scriptLanguage.commands.jumpSeconds.effect',
},
{
syntaxKey: 'help.content.scriptLanguage.commands.jumpFrame.syntax',
effectKey: 'help.content.scriptLanguage.commands.jumpFrame.effect',
},
{
syntaxKey: 'help.content.scriptLanguage.commands.jumpMarker.syntax',
effectKey: 'help.content.scriptLanguage.commands.jumpMarker.effect',
},
],
},
{
kind: 'code',
titleKey: 'help.content.scriptLanguage.exampleTitle',
bodyKey: 'help.content.scriptLanguage.exampleBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.scriptLanguage.errorsTitle',
bodyKey: 'help.content.scriptLanguage.errorsBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.scriptLanguage.notInDslTitle',
bodyKey: 'help.content.scriptLanguage.notInDslBody',
},
],
},
{
id: 'export',
titleKey: 'help.chapters.export',
icon: PhExport,
introKey: 'help.content.export.intro',
blocks: [
{
kind: 'paragraph',
titleKey: 'help.content.export.editorPreviewTitle',
bodyKey: 'help.content.export.editorPreviewBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.export.fullscreenPreviewTitle',
bodyKey: 'help.content.export.fullscreenPreviewBody',
},
{
kind: 'list',
titleKey: 'help.content.export.formatsTitle',
items: [
{ bodyKey: 'help.content.export.formats.runtime' },
{ bodyKey: 'help.content.export.formats.scene' },
{ bodyKey: 'help.content.export.formats.html' },
{
bodyKey: hasDesktopMp4
? 'help.content.export.formats.mp4Desktop'
: 'help.content.export.formats.mp4',
},
{
bodyKey: isDesktop
? 'help.content.export.formats.webmDesktop'
: 'help.content.export.formats.webm',
},
],
},
{
kind: 'paragraph',
titleKey: 'help.content.export.skipHiddenTitle',
bodyKey: 'help.content.export.skipHiddenBody',
},
{
kind: 'paragraph',
titleKey: 'help.content.export.assetsTitle',
bodyKey: 'help.content.export.assetsBody',
},
],
},
{
id: 'shortcuts',
titleKey: 'help.chapters.shortcuts',
icon: PhQuestion,
introKey: 'help.content.shortcuts.intro',
blocks: [{ kind: 'shortcuts' }],
},
]
const activeChapter = ref<ChapterId>('getting-started')
const contentRef = ref<HTMLElement | null>(null)
const activeChapterDef = computed<Chapter>(
() => CHAPTERS.find((chapter) => chapter.id === activeChapter.value) ?? CHAPTERS[0],
)
watch(
() => props.modelValue,
async (open) => {
if (!open) return
activeChapter.value = 'getting-started'
await nextTick()
if (contentRef.value) contentRef.value.scrollTop = 0
},
)
function selectChapter(id: ChapterId) {
activeChapter.value = id
if (contentRef.value) contentRef.value.scrollTop = 0
}
const APP_SHORTCUTS = [
{ labelKey: 'help.shortcut.playPause', keys: 'Space' },
{ labelKey: 'help.shortcut.fullscreen', keys: 'Shift+Space' },
{ labelKey: 'help.shortcut.undo', keys: 'Ctrl/Cmd+Z' },
{ labelKey: 'help.shortcut.redo', keys: 'Ctrl/Cmd+Shift+Z' },
{ labelKey: 'help.shortcut.save', keys: 'Ctrl/Cmd+S' },
{ labelKey: 'help.shortcut.inPoint', keys: 'I' },
{ labelKey: 'help.shortcut.outPoint', keys: 'O' },
{ labelKey: 'help.shortcut.rewind', keys: 'R' },
{ labelKey: 'help.shortcut.addMarker', keys: 'M' },
{ labelKey: 'help.shortcut.deleteSelected', keys: 'Backspace / Delete' },
{ labelKey: 'help.shortcut.clearSelection', keys: 'Escape' },
{ labelKey: 'help.shortcut.panCanvas', keys: 'Middle-click / Ctrl+drag' },
{ labelKey: 'help.shortcut.zoomCanvas', keys: 'Ctrl/Cmd+wheel' },
{ labelKey: 'help.shortcut.pasteImage', keys: 'Ctrl/Cmd+V' },
{ labelKey: 'help.shortcut.tests', keys: 'Ctrl+Shift+Alt+T' },
]
</script>
<template>
<Modal
:model-value="modelValue"
size="xl"
:title="t('help.title')"
:dialog-style="{
width: 'min(1100px, calc(100% - 32px))',
height: 'min(760px, calc(100vh - 48px))',
}"
:body-style="{
display: 'flex',
overflow: 'hidden',
minHeight: 0,
}"
@update:model-value="emit('update:modelValue', $event)"
>
<div class="help">
<aside class="toc" :aria-label="t('help.tocLabel')">
<button
v-for="chapter in CHAPTERS"
:key="chapter.id"
class="toc-item"
:class="{ active: activeChapter === chapter.id }"
@click="selectChapter(chapter.id)"
>
<component :is="chapter.icon" :size="15" class="toc-icon" />
<span>{{ t(chapter.titleKey) }}</span>
</button>
</aside>
<div ref="contentRef" class="content">
<section class="chapter">
<h3 class="chapter-title">{{ t(activeChapterDef.titleKey) }}</h3>
<p class="chapter-intro" v-html="t(activeChapterDef.introKey)" />
<template v-for="block in activeChapterDef.blocks" :key="JSON.stringify(block)">
<div v-if="block.kind === 'paragraph'" class="chapter-block">
<h4 v-if="block.titleKey">{{ t(block.titleKey) }}</h4>
<p v-html="t(block.bodyKey)" />
</div>
<div v-else-if="block.kind === 'list'" class="chapter-block">
<h4 v-if="block.titleKey">{{ t(block.titleKey) }}</h4>
<ul
class="bullets"
:class="block.items.some((i) => i.icon) ? 'bullets-icon' : 'bullets-disc'"
>
<li v-for="item in block.items" :key="item.bodyKey" class="bullet-item">
<span v-if="item.icon" class="bullet-icon">
<component :is="item.icon" :size="14" />
</span>
<div class="bullet-copy">
<strong v-if="item.labelKey">{{ t(item.labelKey) }}:</strong>
<span v-html="t(item.bodyKey)" />
</div>
</li>
</ul>
</div>
<div v-else-if="block.kind === 'steps'" class="chapter-block">
<h4>{{ t(block.titleKey) }}</h4>
<ol class="bullets bullets-steps">
<li v-for="item in block.items" :key="item" v-html="t(item)" />
</ol>
</div>
<div v-else-if="block.kind === 'table'" class="chapter-block">
<h4>{{ t(block.titleKey) }}</h4>
<table class="cmd-table">
<thead>
<tr>
<th>{{ t('help.table.syntax') }}</th>
<th>{{ t('help.table.effect') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in block.rows" :key="row.syntaxKey">
<td v-html="t(row.syntaxKey)" />
<td v-html="t(row.effectKey)" />
</tr>
</tbody>
</table>
</div>
<div v-else-if="block.kind === 'code'" class="chapter-block">
<h4>{{ t(block.titleKey) }}</h4>
<pre class="code">{{ t(block.bodyKey) }}</pre>
</div>
<div v-else-if="block.kind === 'shortcuts'" class="chapter-block">
<ul class="shortcuts">
<li v-for="shortcut in APP_SHORTCUTS" :key="shortcut.keys + shortcut.labelKey">
<span class="shortcut-label">{{ t(shortcut.labelKey) }}</span>
<kbd>{{ shortcut.keys }}</kbd>
</li>
</ul>
</div>
</template>
</section>
</div>
</div>
</Modal>
</template>
<style scoped>
.help {
display: flex;
flex: 1 1 auto;
min-height: 0;
}
.toc {
flex: 0 0 220px;
border-right: 1px solid var(--border);
padding: 10px 8px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
background: var(--panel-alt);
min-height: 0;
}
.toc-item {
display: flex;
align-items: center;
gap: 8px;
text-align: left;
padding: 8px 10px;
font-size: 12px;
color: var(--text-dim);
background: transparent;
border: 1px solid transparent;
border-radius: var(--radius-sm);
cursor: pointer;
}
.toc-icon {
flex-shrink: 0;
}
.toc-item:hover:not(.active) {
background: var(--panel-hover);
color: var(--text);
}
.toc-item.active {
background: var(--accent);
color: #fff;
font-weight: 600;
}
.content {
flex: 1;
overflow-y: auto;
padding: 18px 22px 22px;
min-width: 0;
min-height: 0;
}
.chapter {
display: flex;
flex-direction: column;
gap: 14px;
}
.chapter-title {
margin: 0;
font-size: 16px;
}
.chapter-intro {
margin: 0;
font-size: 13px;
line-height: 1.6;
color: var(--text);
}
.chapter-block {
display: flex;
flex-direction: column;
gap: 8px;
}
.chapter h4 {
margin: 0;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-dim);
}
.chapter p {
margin: 0;
font-size: 13px;
line-height: 1.55;
color: var(--text);
}
.bullets {
margin: 0;
font-size: 13px;
line-height: 1.55;
}
.bullets-disc {
list-style: disc;
padding-left: 22px;
display: flex;
flex-direction: column;
gap: 4px;
}
.bullets-disc::marker,
.bullets-disc li::marker {
color: var(--text-dim);
}
.bullets-icon {
list-style: none;
padding-left: 4px;
display: flex;
flex-direction: column;
gap: 6px;
}
.bullets-steps {
padding-left: 22px;
display: flex;
flex-direction: column;
gap: 4px;
}
.bullets-icon .bullet-item {
display: flex;
gap: 10px;
align-items: flex-start;
}
.bullet-icon {
width: 18px;
color: var(--text-dim);
display: inline-flex;
align-items: center;
justify-content: flex-start;
flex-shrink: 0;
margin-top: 2px;
}
.bullet-copy {
min-width: 0;
}
.bullet-copy strong {
color: var(--text);
margin-right: 4px;
}
.chapter code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
background: var(--panel-alt);
padding: 1px 5px;
border-radius: 3px;
color: var(--text);
}
.chapter em {
font-style: italic;
color: var(--text);
}
.code {
margin: 0;
padding: 10px 12px;
background: var(--panel-alt);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
line-height: 1.5;
white-space: pre-wrap;
color: var(--text);
}
.cmd-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.cmd-table th,
.cmd-table td {
text-align: left;
padding: 6px 8px;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
.cmd-table th {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-dim);
}
.shortcuts {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px 12px;
}
.shortcuts li {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 5px 8px;
background: var(--panel-alt);
border-radius: var(--radius-sm);
font-size: 12px;
}
.shortcut-label {
color: var(--text);
}
kbd {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 4px;
padding: 2px 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 10px;
color: var(--text-dim-strong);
}
</style>