bak
This commit is contained in:
1 parent
e851d52aef
commit
fcefd258eb
6 files changed
+533
-91
No files matched your search
+305
-83
@@ -5,6 +5,8 @@ import {
|
||||
PhCheck,
|
||||
PhCopy,
|
||||
PhDotsSixVertical,
|
||||
PhLock,
|
||||
PhLockOpen,
|
||||
PhMagnet,
|
||||
PhPauseCircle,
|
||||
PhTrash,
|
||||
@@ -206,7 +208,7 @@ function onLaneDblClick(e: MouseEvent, track: TimelineLayerTrack) {
|
||||
const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
|
||||
const time = snapTime(ratio * duration.value)
|
||||
const layer = store.activeLayers.find((l) => l.id === track.layerId)
|
||||
if (!layer) return
|
||||
if (!layer || layer.locked) return
|
||||
const sampled = sampleLayer(layer, time)
|
||||
const props: Partial<AnimatableProps> = {}
|
||||
if (track.kind === 'position') {
|
||||
@@ -247,6 +249,8 @@ function onKeyframeClick(e: MouseEvent, layerId: string, time: number) {
|
||||
function onKeyframeDblClick(e: MouseEvent, layerId: string, keyframeId: string) {
|
||||
e.stopPropagation()
|
||||
if (kfDrag.value?.moved) return
|
||||
const layer = store.activeLayers.find((l) => l.id === layerId)
|
||||
if (layer?.locked) return
|
||||
store.removeKeyframe(layerId, keyframeId)
|
||||
}
|
||||
|
||||
@@ -325,6 +329,8 @@ const kfDrag = ref<KfDrag | null>(null)
|
||||
|
||||
function onKeyframePointerDown(e: PointerEvent, layerId: string, keyframeId: string) {
|
||||
if (e.button !== 0) return
|
||||
const layer = store.activeLayers.find((l) => l.id === layerId)
|
||||
if (layer?.locked) return
|
||||
const kfEl = e.currentTarget as HTMLElement
|
||||
const lane = kfEl.parentElement
|
||||
if (!lane) return
|
||||
@@ -482,12 +488,28 @@ function duplicateFromMenu() {
|
||||
}
|
||||
|
||||
interface TrackMenuState {
|
||||
layerId: string
|
||||
kind: 'layer' | 'clip'
|
||||
trackId: string
|
||||
x: number
|
||||
y: number
|
||||
separated: boolean
|
||||
currentColor: string | null
|
||||
locked: boolean
|
||||
}
|
||||
|
||||
const TRACK_COLORS: Array<{ value: string | null; label: string }> = [
|
||||
{ value: null, label: 'Keine' },
|
||||
{ value: '#ff6b6b', label: 'Rot' },
|
||||
{ value: '#ffa94d', label: 'Orange' },
|
||||
{ value: '#ffd43b', label: 'Gelb' },
|
||||
{ value: '#51cf66', label: 'Grün' },
|
||||
{ value: '#4dc4ff', label: 'Türkis' },
|
||||
{ value: '#6ea8ff', label: 'Blau' },
|
||||
{ value: '#b197fc', label: 'Lila' },
|
||||
{ value: '#ff8cc6', label: 'Pink' },
|
||||
{ value: '#adb5bd', label: 'Grau' },
|
||||
]
|
||||
|
||||
const dragTrackId = ref<string | null>(null)
|
||||
const dropIndex = ref<number | null>(null)
|
||||
|
||||
@@ -537,16 +559,7 @@ function onTrackDragEnd() {
|
||||
dropIndex.value = null
|
||||
}
|
||||
|
||||
interface ClipDrag {
|
||||
kind: 'new' | 'move'
|
||||
clipId: string
|
||||
instanceId?: string
|
||||
grabOffsetSec: number
|
||||
}
|
||||
|
||||
const clipDrag = ref<ClipDrag | null>(null)
|
||||
|
||||
function pixelTimeFromEvent(e: DragEvent, el: HTMLElement): number {
|
||||
function pixelTimeFromEvent(e: DragEvent | PointerEvent, el: HTMLElement): number {
|
||||
const rect = el.getBoundingClientRect()
|
||||
if (rect.width <= 0) return 0
|
||||
const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
|
||||
@@ -554,60 +567,76 @@ function pixelTimeFromEvent(e: DragEvent, el: HTMLElement): number {
|
||||
}
|
||||
|
||||
function onClipLaneDragOver(e: DragEvent, _clipTrackId: string) {
|
||||
if (!clipDrag.value) {
|
||||
if (e.dataTransfer?.types.includes('application/x-animatr-clip')) {
|
||||
e.preventDefault()
|
||||
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'
|
||||
}
|
||||
return
|
||||
if (e.dataTransfer?.types.includes('application/x-animatr-clip')) {
|
||||
e.preventDefault()
|
||||
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'
|
||||
}
|
||||
e.preventDefault()
|
||||
if (e.dataTransfer) e.dataTransfer.dropEffect = clipDrag.value.kind === 'new' ? 'copy' : 'move'
|
||||
}
|
||||
|
||||
function onClipLaneDrop(e: DragEvent, clipTrackId: string) {
|
||||
e.preventDefault()
|
||||
const track = store.scene.clipTracks?.find((t) => t.id === clipTrackId)
|
||||
if (track?.locked) return
|
||||
const el = e.currentTarget as HTMLElement
|
||||
const time = pixelTimeFromEvent(e, el)
|
||||
const cd = clipDrag.value
|
||||
if (cd) {
|
||||
const start = Math.max(0, time - cd.grabOffsetSec)
|
||||
if (cd.kind === 'move' && cd.instanceId) {
|
||||
store.moveClipInstance(cd.instanceId, start, clipTrackId)
|
||||
} else {
|
||||
store.addClipInstance(cd.clipId, start, clipTrackId)
|
||||
}
|
||||
clipDrag.value = null
|
||||
return
|
||||
}
|
||||
const clipId = e.dataTransfer?.getData('application/x-animatr-clip')
|
||||
if (clipId) {
|
||||
store.addClipInstance(clipId, time, clipTrackId)
|
||||
}
|
||||
}
|
||||
|
||||
function onClipBlockDragStart(e: DragEvent, ci: ClipInstance) {
|
||||
if (clipTrim.value) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
if (!e.dataTransfer) return
|
||||
interface ClipMoveState {
|
||||
instanceId: string
|
||||
pointerId: number
|
||||
startX: number
|
||||
initialStartTime: number
|
||||
moved: boolean
|
||||
}
|
||||
|
||||
const clipMove = ref<ClipMoveState | null>(null)
|
||||
|
||||
function onClipBlockPointerDown(e: PointerEvent, ci: ClipInstance) {
|
||||
if (e.button !== 0) return
|
||||
const track = store.scene.clipTracks?.find((t) => t.id === ci.clipTrackId)
|
||||
if (track?.locked) return
|
||||
// Let handles handle their own pointerdowns
|
||||
const target = e.target as HTMLElement | null
|
||||
if (target?.classList.contains('clip-handle')) return
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const el = e.currentTarget as HTMLElement
|
||||
const rect = el.getBoundingClientRect()
|
||||
const visible = visibleDurationOf(ci)
|
||||
const grabOffsetSec =
|
||||
rect.width > 0 ? ((e.clientX - rect.left) / rect.width) * visible : 0
|
||||
clipDrag.value = { kind: 'move', clipId: ci.clipId, instanceId: ci.id, grabOffsetSec }
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
try {
|
||||
e.dataTransfer.setData('application/x-animatr-clip-move', ci.id)
|
||||
} catch {
|
||||
/* ignore */
|
||||
el.setPointerCapture(e.pointerId)
|
||||
clipMove.value = {
|
||||
instanceId: ci.id,
|
||||
pointerId: e.pointerId,
|
||||
startX: e.clientX,
|
||||
initialStartTime: ci.startTime,
|
||||
moved: false,
|
||||
}
|
||||
}
|
||||
|
||||
function onClipBlockDragEnd() {
|
||||
clipDrag.value = null
|
||||
function onClipBlockPointerMove(e: PointerEvent) {
|
||||
const m = clipMove.value
|
||||
if (!m || m.pointerId !== e.pointerId) return
|
||||
const deltaPx = e.clientX - m.startX
|
||||
if (!m.moved && Math.abs(deltaPx) < 2) return
|
||||
m.moved = true
|
||||
const pps = effectivePxPerSec.value
|
||||
if (pps <= 0) return
|
||||
const deltaSec = deltaPx / pps
|
||||
const newStart = Math.max(0, m.initialStartTime + deltaSec)
|
||||
store.moveClipInstance(m.instanceId, newStart)
|
||||
}
|
||||
|
||||
function onClipBlockPointerUp(e: PointerEvent) {
|
||||
const m = clipMove.value
|
||||
if (!m || m.pointerId !== e.pointerId) return
|
||||
const el = e.currentTarget as HTMLElement
|
||||
if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId)
|
||||
const wasMoved = m.moved
|
||||
clipMove.value = null
|
||||
// suppress click/dblclick if we actually dragged
|
||||
if (wasMoved) e.preventDefault()
|
||||
}
|
||||
|
||||
function addClipTrack() {
|
||||
@@ -656,6 +685,8 @@ const clipTrim = ref<ClipTrimState | null>(null)
|
||||
|
||||
function onClipHandlePointerDown(e: PointerEvent, ci: ClipInstance, side: 'start' | 'end') {
|
||||
if (e.button !== 0) return
|
||||
const track = store.scene.clipTracks?.find((t) => t.id === ci.clipTrackId)
|
||||
if (track?.locked) return
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const el = e.currentTarget as HTMLElement
|
||||
@@ -715,26 +746,66 @@ const trackMenuRef = ref<HTMLDivElement | null>(null)
|
||||
function onTrackContextMenu(e: MouseEvent, layerId: string) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const layer = store.scene.layers.find((l) => l.id === layerId)
|
||||
const layer = store.activeLayers.find((l) => l.id === layerId)
|
||||
if (!layer) return
|
||||
store.selectLayer(layerId)
|
||||
trackMenu.value = {
|
||||
layerId,
|
||||
kind: 'layer',
|
||||
trackId: layerId,
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
separated: !!layer.separated,
|
||||
currentColor: layer.color ?? null,
|
||||
locked: !!layer.locked,
|
||||
}
|
||||
}
|
||||
|
||||
function onClipTrackContextMenu(e: MouseEvent, clipTrackId: string) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const track = store.scene.clipTracks?.find((t) => t.id === clipTrackId)
|
||||
if (!track) return
|
||||
trackMenu.value = {
|
||||
kind: 'clip',
|
||||
trackId: clipTrackId,
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
separated: false,
|
||||
currentColor: track.color ?? null,
|
||||
locked: !!track.locked,
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLocked() {
|
||||
if (!trackMenu.value) return
|
||||
store.setTrackLocked(trackMenu.value.trackId, !trackMenu.value.locked)
|
||||
trackMenu.value = null
|
||||
}
|
||||
|
||||
function applyTrackColor(color: string | null) {
|
||||
if (!trackMenu.value) return
|
||||
store.setTrackColor(trackMenu.value.trackId, color)
|
||||
trackMenu.value = null
|
||||
}
|
||||
|
||||
function trackColorStyle(color: string | null | undefined): Record<string, string> | undefined {
|
||||
if (!color) return undefined
|
||||
return { '--track-color': color }
|
||||
}
|
||||
|
||||
function toggleSeparated() {
|
||||
if (!trackMenu.value) return
|
||||
store.setLayerSeparated(trackMenu.value.layerId, !trackMenu.value.separated)
|
||||
store.setLayerSeparated(trackMenu.value.trackId, !trackMenu.value.separated)
|
||||
trackMenu.value = null
|
||||
}
|
||||
|
||||
function deleteLayer() {
|
||||
function deleteTrack() {
|
||||
if (!trackMenu.value) return
|
||||
store.removeLayer(trackMenu.value.layerId)
|
||||
if (trackMenu.value.kind === 'layer') {
|
||||
store.removeLayer(trackMenu.value.trackId)
|
||||
} else {
|
||||
store.removeClipTrack(trackMenu.value.trackId)
|
||||
}
|
||||
trackMenu.value = null
|
||||
}
|
||||
|
||||
@@ -876,7 +947,10 @@ onBeforeUnmount(() => {
|
||||
'drop-before': isDropBefore(track),
|
||||
'drop-after': isDropAfter(track, tracks[idx + 1]),
|
||||
dragging: track.isParent && dragTrackId === track.layerId,
|
||||
colored: !!track.layer.color,
|
||||
locked: !!track.layer.locked,
|
||||
}"
|
||||
:style="trackColorStyle(track.layer.color)"
|
||||
@dragover="track.isParent ? onTrackDragOver($event, track.orderIndex) : null"
|
||||
@drop="track.isParent ? onTrackDrop($event) : null"
|
||||
@dragleave="onTrackDragLeave"
|
||||
@@ -895,6 +969,11 @@ onBeforeUnmount(() => {
|
||||
@dragend="onTrackDragEnd"
|
||||
>
|
||||
<PhDotsSixVertical v-if="track.isParent" :size="12" class="drag-handle" />
|
||||
<PhLock
|
||||
v-if="track.isParent && track.layer.locked"
|
||||
:size="11"
|
||||
class="lock-badge"
|
||||
/>
|
||||
<PhArrowsSplit
|
||||
v-if="track.isParent && track.layer.separated"
|
||||
:size="11"
|
||||
@@ -947,7 +1026,10 @@ onBeforeUnmount(() => {
|
||||
'drop-before': isDropBefore(track),
|
||||
'drop-after': isDropAfter(track, tracks[idx + 1]),
|
||||
dragging: dragTrackId === track.clipTrackId,
|
||||
colored: !!clipTrackById.get(track.clipTrackId)?.color,
|
||||
locked: !!clipTrackById.get(track.clipTrackId)?.locked,
|
||||
}"
|
||||
:style="trackColorStyle(clipTrackById.get(track.clipTrackId)?.color)"
|
||||
@dragover="onTrackDragOver($event, track.orderIndex)"
|
||||
@drop="onTrackDrop($event)"
|
||||
@dragleave="onTrackDragLeave"
|
||||
@@ -955,10 +1037,16 @@ onBeforeUnmount(() => {
|
||||
<span
|
||||
class="track-name clip-track-name"
|
||||
draggable="true"
|
||||
@contextmenu="onClipTrackContextMenu($event, track.clipTrackId)"
|
||||
@dragstart="onTrackDragStart($event, track.clipTrackId)"
|
||||
@dragend="onTrackDragEnd"
|
||||
>
|
||||
<PhDotsSixVertical :size="12" class="drag-handle" />
|
||||
<PhLock
|
||||
v-if="clipTrackById.get(track.clipTrackId)?.locked"
|
||||
:size="11"
|
||||
class="lock-badge"
|
||||
/>
|
||||
<PhFilmStrip :size="11" class="name-badge" />
|
||||
{{ track.name }}
|
||||
</span>
|
||||
@@ -976,12 +1064,15 @@ onBeforeUnmount(() => {
|
||||
'trimmed-start': (ci.trimStart ?? 0) > 0,
|
||||
'trimmed-end': (ci.trimEnd ?? 0) > 0,
|
||||
trimming: clipTrim?.instanceId === ci.id,
|
||||
moving: clipMove?.instanceId === ci.id,
|
||||
}"
|
||||
:style="clipBlockStyle(ci)"
|
||||
:title="trimTooltip(ci)"
|
||||
draggable="true"
|
||||
@dragstart.stop="onClipBlockDragStart($event, ci)"
|
||||
@dragend="onClipBlockDragEnd"
|
||||
@pointerdown="onClipBlockPointerDown($event, ci)"
|
||||
@pointermove="onClipBlockPointerMove"
|
||||
@pointerup="onClipBlockPointerUp"
|
||||
@pointercancel="onClipBlockPointerUp"
|
||||
@dragstart.prevent.stop
|
||||
@dblclick="onClipBlockDblClick(ci)"
|
||||
@contextmenu="onClipBlockContextMenu($event, ci)"
|
||||
>
|
||||
@@ -1084,16 +1175,51 @@ onBeforeUnmount(() => {
|
||||
class="kf-menu"
|
||||
:style="{ left: trackMenu.x + 'px', top: trackMenu.y + 'px' }"
|
||||
>
|
||||
<button class="kf-menu-item" @click="toggleSeparated">
|
||||
<span class="check">
|
||||
<PhCheck v-if="trackMenu.separated" :size="12" weight="bold" />
|
||||
</span>
|
||||
Separate Position and Rotation
|
||||
</button>
|
||||
<div class="kf-menu-section">
|
||||
<div class="kf-menu-header">Farbe</div>
|
||||
<div class="color-tiles">
|
||||
<button
|
||||
v-for="c in TRACK_COLORS"
|
||||
:key="c.value ?? 'none'"
|
||||
class="color-tile"
|
||||
:class="{ selected: trackMenu.currentColor === c.value, none: c.value === null }"
|
||||
:style="c.value ? { background: c.value } : undefined"
|
||||
:title="c.label"
|
||||
@click="applyTrackColor(c.value)"
|
||||
>
|
||||
<PhCheck
|
||||
v-if="trackMenu.currentColor === c.value && c.value !== null"
|
||||
:size="10"
|
||||
weight="bold"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kf-menu-divider"></div>
|
||||
<button class="kf-menu-item danger" @click="deleteLayer">
|
||||
<button class="kf-menu-item" @click="toggleLocked">
|
||||
<span class="check">
|
||||
<PhCheck v-if="trackMenu.locked" :size="12" weight="bold" />
|
||||
</span>
|
||||
<component :is="trackMenu.locked ? PhLockOpen : PhLock" :size="12" />
|
||||
{{ trackMenu.locked ? 'Entsperren' : 'Sperren' }}
|
||||
</button>
|
||||
<template v-if="trackMenu.kind === 'layer'">
|
||||
<div class="kf-menu-divider"></div>
|
||||
<button
|
||||
class="kf-menu-item"
|
||||
:disabled="trackMenu.locked"
|
||||
@click="toggleSeparated"
|
||||
>
|
||||
<span class="check">
|
||||
<PhCheck v-if="trackMenu.separated" :size="12" weight="bold" />
|
||||
</span>
|
||||
Separate Position and Rotation
|
||||
</button>
|
||||
</template>
|
||||
<div class="kf-menu-divider"></div>
|
||||
<button class="kf-menu-item danger" @click="deleteTrack">
|
||||
<PhTrash :size="12" />
|
||||
Layer löschen
|
||||
{{ trackMenu.kind === 'layer' ? 'Layer löschen' : 'Clip-Spur löschen' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1324,6 +1450,51 @@ onBeforeUnmount(() => {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.track.locked {
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.track.clip-track.locked {
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
.track.sub-track.locked {
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.track.locked .track-name {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.track.locked .track-lane {
|
||||
background-image: repeating-linear-gradient(
|
||||
45deg,
|
||||
rgba(255, 255, 255, 0.04) 0,
|
||||
rgba(255, 255, 255, 0.04) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.track.locked .kf,
|
||||
.track.locked .kf-seg,
|
||||
.track.locked .clip-block {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.track.locked .drag-handle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lock-badge {
|
||||
color: var(--text-dim);
|
||||
margin-right: 4px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.track-name {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
@@ -1357,6 +1528,10 @@ onBeforeUnmount(() => {
|
||||
var(--panel);
|
||||
}
|
||||
|
||||
.track.colored > .track-name {
|
||||
box-shadow: inset 3px 0 0 0 var(--track-color);
|
||||
}
|
||||
|
||||
.track.sub-track .track-name {
|
||||
padding-left: 24px;
|
||||
font-size: 11px;
|
||||
@@ -1441,20 +1616,20 @@ onBeforeUnmount(() => {
|
||||
margin-top: -1.5px;
|
||||
border-radius: 999px;
|
||||
pointer-events: none;
|
||||
background: var(--accent);
|
||||
background: var(--track-color, var(--accent));
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.kf-seg.easing-linear {
|
||||
background: var(--accent);
|
||||
background: var(--track-color, var(--accent));
|
||||
}
|
||||
|
||||
.kf-seg.easing-easeIn {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(110, 168, 255, 0.15) 0%,
|
||||
rgba(110, 168, 255, 0.25) 30%,
|
||||
rgba(110, 168, 255, 1) 100%
|
||||
color-mix(in srgb, var(--track-color, var(--accent)) 15%, transparent) 0%,
|
||||
color-mix(in srgb, var(--track-color, var(--accent)) 25%, transparent) 30%,
|
||||
var(--track-color, var(--accent)) 100%
|
||||
);
|
||||
opacity: 0.9;
|
||||
}
|
||||
@@ -1462,9 +1637,9 @@ onBeforeUnmount(() => {
|
||||
.kf-seg.easing-easeOut {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(110, 168, 255, 1) 0%,
|
||||
rgba(110, 168, 255, 0.25) 70%,
|
||||
rgba(110, 168, 255, 0.15) 100%
|
||||
var(--track-color, var(--accent)) 0%,
|
||||
color-mix(in srgb, var(--track-color, var(--accent)) 25%, transparent) 70%,
|
||||
color-mix(in srgb, var(--track-color, var(--accent)) 15%, transparent) 100%
|
||||
);
|
||||
opacity: 0.9;
|
||||
}
|
||||
@@ -1472,9 +1647,9 @@ onBeforeUnmount(() => {
|
||||
.kf-seg.easing-easeInOut {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(110, 168, 255, 0.15) 0%,
|
||||
rgba(110, 168, 255, 1) 50%,
|
||||
rgba(110, 168, 255, 0.15) 100%
|
||||
color-mix(in srgb, var(--track-color, var(--accent)) 15%, transparent) 0%,
|
||||
var(--track-color, var(--accent)) 50%,
|
||||
color-mix(in srgb, var(--track-color, var(--accent)) 15%, transparent) 100%
|
||||
);
|
||||
opacity: 0.95;
|
||||
}
|
||||
@@ -1497,7 +1672,7 @@ onBeforeUnmount(() => {
|
||||
height: 11px;
|
||||
margin-left: -5.5px;
|
||||
margin-top: -5.5px;
|
||||
background: var(--accent);
|
||||
background: var(--track-color, var(--accent));
|
||||
transform: rotate(45deg);
|
||||
border-radius: 2px;
|
||||
cursor: grab;
|
||||
@@ -1524,7 +1699,7 @@ onBeforeUnmount(() => {
|
||||
transform: none;
|
||||
border-radius: 50%;
|
||||
background: var(--panel);
|
||||
border: 2px solid var(--accent);
|
||||
border: 2px solid var(--track-color, var(--accent));
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -1574,10 +1749,10 @@ onBeforeUnmount(() => {
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0 8px;
|
||||
background: var(--accent-soft);
|
||||
border: 1px solid var(--accent);
|
||||
background: color-mix(in srgb, var(--track-color, var(--accent)) 14%, transparent);
|
||||
border: 1px solid var(--track-color, var(--accent));
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--accent);
|
||||
color: var(--track-color, var(--accent));
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
@@ -1589,7 +1764,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.clip-block:hover {
|
||||
background: var(--accent-soft-strong);
|
||||
background: color-mix(in srgb, var(--track-color, var(--accent)) 22%, transparent);
|
||||
}
|
||||
|
||||
.clip-block:active {
|
||||
@@ -1647,7 +1822,7 @@ onBeforeUnmount(() => {
|
||||
bottom: 20%;
|
||||
width: 2px;
|
||||
border-radius: 2px;
|
||||
background: var(--accent);
|
||||
background: var(--track-color, var(--accent));
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
@@ -1682,6 +1857,12 @@ onBeforeUnmount(() => {
|
||||
box-shadow: 0 0 0 1px #ffc857;
|
||||
}
|
||||
|
||||
.clip-block.moving {
|
||||
box-shadow: 0 0 0 1px var(--track-color, var(--accent)), 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
cursor: grabbing;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.clip-handle:hover,
|
||||
.clip-handle:active {
|
||||
background: rgba(255, 255, 255, 0.25) !important;
|
||||
@@ -1792,4 +1973,45 @@ onBeforeUnmount(() => {
|
||||
background: var(--border);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.color-tiles {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 4px;
|
||||
padding: 4px 10px 6px;
|
||||
}
|
||||
|
||||
.color-tile {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
transition: transform 100ms var(--ease), box-shadow 100ms var(--ease);
|
||||
}
|
||||
|
||||
.color-tile:hover {
|
||||
transform: scale(1.1);
|
||||
border-color: var(--text-dim);
|
||||
}
|
||||
|
||||
.color-tile.selected {
|
||||
border-color: var(--text);
|
||||
box-shadow: 0 0 0 2px var(--panel), 0 0 0 3px var(--accent);
|
||||
}
|
||||
|
||||
.color-tile.none {
|
||||
background:
|
||||
linear-gradient(45deg, transparent calc(50% - 1px), var(--danger) calc(50% - 1px), var(--danger) calc(50% + 1px), transparent calc(50% + 1px)),
|
||||
var(--panel-alt);
|
||||
}
|
||||
|
||||
.color-tile.none.selected {
|
||||
box-shadow: 0 0 0 2px var(--panel), 0 0 0 3px var(--accent);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user