Slim the phone stage to icons, chevrons, and a More sheet.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-28 22:21:13 -05:00
co-authored by Cursor
parent d61f99d6fd
commit fe560d7e40
+118 -63
View File
@@ -47,35 +47,46 @@
</div>
<div v-else class="m-chips"><span class="m-chip on">YuE2</span></div>
<div class="m-stage">
<div
class="m-stage"
@touchstart.passive="onStageTouchStart"
@touchend.passive="onStageTouchEnd"
>
<div class="m-stage-chrome" aria-label="Stage controls">
<div class="m-stage-chrome-top">
<button
type="button"
class="m-icon-btn m-library"
:aria-expanded="ctx.libraryOpen ? 'true' : 'false'"
aria-label="Library"
@click="ctx.openLibrary()"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<rect x="3" y="4" width="7" height="7" rx="1.5" />
<rect x="14" y="4" width="7" height="7" rx="1.5" />
<rect x="3" y="13" width="7" height="7" rx="1.5" />
<rect x="14" y="13" width="7" height="7" rx="1.5" />
</svg>
<span v-if="ctx.familyStillCount" class="m-library-badge">{{ ctx.familyStillCount > 99 ? '99+' : ctx.familyStillCount }}</span>
</button>
<div class="m-stage-chrome-end">
<button
v-if="ctx.selected && ctx.selected.kind!=='track'"
type="button"
class="m-library"
:aria-expanded="ctx.libraryOpen ? 'true' : 'false'"
@click="ctx.openLibrary()"
>Library<span v-if="ctx.familyStillCount" class="m-library-count">{{ ctx.familyStillCount }}</span></button>
class="m-icon-btn"
aria-label="More"
:aria-expanded="sheet==='actions' ? 'true' : 'false'"
@click.stop="toggleSheet('actions')"
>•••</button>
<button
v-if="ctx.selected"
type="button"
class="m-eye"
class="m-icon-btn m-eye"
:aria-label="ctx.eyeOpen ? 'Hide output' : 'Show output'"
@click.stop="ctx.toggleReveal()"
>
<img :src="ctx.eyeOpen ? '/icons/display/eye.svg' : '/icons/display/eye-off.svg'" alt="" width="20" height="20">
</button>
</div>
<div
v-if="ctx.selected && ctx.selected.kind!=='track'"
class="m-stage-actions"
aria-label="Selected still actions"
>
<button type="button" @click.stop="ctx.applyUseAsSource">Use as photo</button>
<button type="button" @click.stop="ctx.copySelectedPrompt">Copy prompt</button>
<button type="button" @click.stop="ctx.loadSelectedJob">Load</button>
<button type="button" class="m-danger" @click.stop="ctx.deleteOutput">Delete</button>
</div>
</div>
<button
v-if="(ctx.family || []).length > 1"
@@ -92,7 +103,7 @@
@click.stop="ctx.stepFamily(1)"
>›</button>
<template v-if="ctx.selected && ctx.isHidden(ctx.selected)">
<p class="m-muted">{{ ctx.selected.kind==='track' ? 'Track loaded · preview hidden' : 'Output hidden' }}</p>
<p class="m-hidden-note">{{ ctx.selected.kind==='track' ? 'Track hidden' : 'Output hidden' }}</p>
</template>
<template v-else-if="ctx.selected?.kind==='track'">
<div class="m-audio">
@@ -135,6 +146,16 @@
<!-- Sheets -->
<div v-if="sheet" class="m-sheet-backdrop" @click="sheet=''" />
<aside v-if="sheet==='actions'" class="m-sheet m-sheet-actions" role="dialog" aria-label="Still actions">
<button type="button" class="m-handle" aria-label="Close" @click="sheet=''" />
<div class="m-sheet-head"><strong>Actions</strong><button type="button" @click="sheet=''">Done</button></div>
<div class="m-sheet-body m-actions-list">
<button type="button" @click="runAction('use')">Use as photo</button>
<button type="button" @click="runAction('copy')">Copy prompt</button>
<button type="button" @click="runAction('load')">Load</button>
<button type="button" class="m-danger" @click="runAction('delete')">Delete</button>
</div>
</aside>
<aside v-if="sheet==='slots'" class="m-sheet" role="dialog" aria-label="Photos">
<button type="button" class="m-handle" aria-label="Close" @click="sheet=''" />
<div class="m-sheet-head"><strong>Photos</strong><button type="button" @click="sheet=''">Done</button></div>
@@ -331,6 +352,7 @@ export type Studio2MobileCtx = Record<string, any>
const props = defineProps<{ ctx: Studio2MobileCtx }>()
const sheet = ref('')
const touchStart = ref<{ x: number; y: number } | null>(null)
function toggleSheet(id: string) {
sheet.value = sheet.value === id ? '' : id
@@ -338,6 +360,28 @@ function toggleSheet(id: string) {
function closeMenus() {
sheet.value = ''
}
function runAction(kind: 'use' | 'copy' | 'load' | 'delete') {
sheet.value = ''
if (kind === 'use') props.ctx.applyUseAsSource?.()
else if (kind === 'copy') props.ctx.copySelectedPrompt?.()
else if (kind === 'load') props.ctx.loadSelectedJob?.()
else if (kind === 'delete') props.ctx.deleteOutput?.()
}
function onStageTouchStart(e: TouchEvent) {
const t = e.changedTouches[0]
if (!t) return
touchStart.value = { x: t.clientX, y: t.clientY }
}
function onStageTouchEnd(e: TouchEvent) {
const start = touchStart.value
touchStart.value = null
const t = e.changedTouches[0]
if (!start || !t || (props.ctx.family || []).length < 2) return
const dx = t.clientX - start.x
const dy = t.clientY - start.y
if (Math.abs(dx) < 48 || Math.abs(dx) < Math.abs(dy) * 1.4) return
props.ctx.stepFamily?.(dx < 0 ? 1 : -1)
}
watch(() => props.ctx.form.mode, () => { sheet.value = '' })
</script>
@@ -447,76 +491,67 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
.m-audio { width: 100%; max-width: 100%; padding: 12px; }
.m-stage-chrome {
position: absolute;
top: 8px;
top: calc(8px + env(safe-area-inset-top, 0px));
right: 8px;
left: 8px;
z-index: 5;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
pointer-events: none;
}
.m-stage-chrome-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.m-stage-chrome-top > *,
.m-stage-actions {
.m-stage-chrome > *,
.m-stage-chrome-end {
pointer-events: auto;
}
.m-library {
display: inline-flex;
min-height: 48px;
.m-stage-chrome-end {
display: flex;
align-items: center;
gap: 6px;
border: 1px solid var(--line);
border-radius: 8px;
background: #000a;
padding: 0 14px;
color: var(--text);
font: inherit;
font-weight: 600;
gap: 8px;
}
.m-library-count {
color: var(--muted);
font-weight: 500;
}
.m-eye {
.m-icon-btn {
position: relative;
display: grid;
width: 48px;
height: 48px;
width: 44px;
height: 44px;
flex-shrink: 0;
place-items: center;
border: 1px solid var(--line);
border-radius: 8px;
background: #000a;
}
.m-stage-actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
max-width: calc(100% - 56px);
}
.m-stage-actions button {
min-height: 44px;
border: 1px solid var(--line);
border-radius: 8px;
background: #000a;
color: var(--text);
padding: 0 10px;
font: inherit;
font-size: 12px;
font-size: 16px;
line-height: 1;
}
.m-library-badge {
position: absolute;
top: -4px;
right: -4px;
min-width: 18px;
border-radius: 999px;
background: var(--amber);
color: var(--amber-on);
font-size: 10px;
font-weight: 700;
line-height: 16px;
padding: 0 4px;
text-align: center;
}
.m-hidden-note {
margin: 0;
color: var(--muted);
font-size: 13px;
text-align: center;
}
.m-stage-actions .m-danger { color: #fca5a5; }
.m-nav {
position: absolute;
top: 50%;
z-index: 4;
display: grid;
width: 48px;
height: 48px;
width: 44px;
height: 44px;
place-items: center;
border: 1px solid var(--line);
border-radius: 8px;
@@ -524,11 +559,31 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
color: var(--text);
font-size: 28px;
line-height: 1;
opacity: 0.7;
transform: translateY(-50%);
}
.m-nav-prev { left: 8px; }
.m-nav-next { right: 8px; }
.m-photos-head { margin-bottom: 8px; }
.m-sheet-actions {
max-height: min(50vh, 100%);
}
.m-actions-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.m-actions-list button {
min-height: 48px;
border: 1px solid var(--line);
border-radius: 8px;
background: #1a1a1d;
color: var(--text);
font: inherit;
text-align: left;
padding: 0 14px;
}
.m-actions-list .m-danger { color: #fca5a5; }
.m-dock {
display: grid;
grid-template-columns: repeat(4, 1fr) 120px;