@designbasekorea/ui-wc 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.17.0 - 2026-11-08
6
+
7
+ - Timeline layers now expand from their track-name chevrons; source transform channels are separate from indented applied effects.
8
+ - Applied effects have independent move/trim ranges with draft/commit, cancellation and undo support.
9
+ - Added layerGap (0–32px) to separate multiple parent layers while keeping their properties/effects grouped.
10
+
5
11
  ## 0.16.0 - 2026-11-07
6
12
 
7
13
  - Timeline: resizable 120–480px label panel with persisted resize events and one-line property ellipsis.
package/README.md CHANGED
@@ -533,6 +533,10 @@ AI 대화·작업 과정·출처·도구 결과는 [AI Elements](./docs/AI-ELEME
533
533
 
534
534
  ## 컴포넌트 패키지만 배포
535
535
 
536
+ 화면을 처음 구성할 때는 [페이지 유형별 컴포넌트 선택과 사용법](docs/COMPONENT-USAGE.md)을 먼저 참고하세요. 소개 페이지·SaaS 앱의 셸, Sidebar의 app/navigation/category 구분, Dropdown과 Select의 차이, FilterBar 조합과 Modal 저장 흐름을 다룹니다.
537
+
538
+ 편집기·유틸리티는 [ToolLayout·ToolPanel 상세 사용법](docs/TOOL-UI.md)을 참고하세요. 영역 배치와 패널 구성, 탭·접기·닫기·복원, 리사이즈와 도구 상태 연결을 다룹니다.
539
+
536
540
  저장소 루트에서 `npm run ui-wc:build`로 공유 계약과 ui-wc만 빌드하고, `npm run publish:ui-wc`로 `@designbasekorea/ui-wc`만 npm에 배포합니다. 이 명령은 blocks·템플릿·갤러리를 배포하지 않습니다. 갤러리 전체 빌드는 `npm run example:gallery:build`가 blocks 빌드를 별도로 실행합니다.
537
541
 
538
542
  npm 배포물에는 ui-wc의 `dist`, README·변경 이력과 결제 로고 라이선스 고지만 포함됩니다. 신규 컴포넌트는 전체 진입점·React 래퍼·개별 import에서 사용할 수 있습니다. 결제·초대·AI 요청·저장 같은 실제 서버 동작은 소비 앱에서 연결합니다.
@@ -4,7 +4,7 @@ export { VideoClipEffect, VideoEffectProperty } from './media-editor-model.js';
4
4
 
5
5
  interface TimelineEditDetail {
6
6
  clips: VideoClip[];
7
- action: 'undo' | 'redo' | 'keyframe' | 'move' | 'trim-start' | 'trim-end' | 'fade-in' | 'fade-out' | 'delete' | 'split';
7
+ action: 'undo' | 'redo' | 'effect-range' | 'keyframe' | 'move' | 'trim-start' | 'trim-end' | 'fade-in' | 'fade-out' | 'delete' | 'split';
8
8
  clipId: string;
9
9
  effectId?: string;
10
10
  propertyId?: string;
@@ -37,6 +37,12 @@ declare class DbVideoTimeline extends HTMLElement {
37
37
  private resize?;
38
38
  private resizeDrag?;
39
39
  private paneGrip?;
40
+ private rangeDrag?;
41
+ get layerGap(): number;
42
+ set layerGap(value: number);
43
+ private isSource;
44
+ private rangeFinish;
45
+ private renderRange;
40
46
  private labelDrag?;
41
47
  get labelWidth(): number;
42
48
  set labelWidth(value: number);
@@ -1 +1 @@
1
- import"./db-button.js";import{DbContextMenu as t}from"./db-context-menu.js";import{DbKeyframeEditor as e}from"./db-keyframe-editor.js";import{t as i,subscribeDbLocale as s}from"../core/i18n.js";import{frameTime as r,emitEditor as a,clamp as d,editorEditable as n,finite as h,editorButton as o,evaluateKeyframes as c,editorTimecode as l,applyKeyframeEasing as p}from"./media-editor-model.js";class DbVideoTimeline extends HTMLElement{constructor(){super(...arguments),this.effectTreeId=`effects-${crypto.randomUUID()}`,this.effectKeySelection=new Map,this.expandedEffects=new Set,this.activeProperty=new Map,this.effectEditing=!1,this.undoClips=[],this.redoClips=[],this.committedClips=[],this.selectedEffectKeys=new Set,this._tracks=[],this._clips=[],this._markers=[],this.raf=0,this.down=t=>{if(this.drag||this.resizeDrag)return;if(this.disabled||0!==t.button||!(t.target instanceof Element)||t.target.closest("db-button,db-keyframe-editor,[data-effect-clip]"))return;const e=t.target.closest("[data-track-resize]"),i=t.target.closest("[data-pane-resize]");if(e||i){t.preventDefault(),this.viewport?.focus({preventScroll:!0});const i=this._tracks.find(t=>t.id===e?.dataset.trackResize);return this.resizeDrag={id:t.pointerId,trackId:i?.id,startY:t.clientY,initial:i?this.trackHeight(i):this.viewport.getBoundingClientRect().height,beforeTracks:this.tracks,beforeHeight:this.height},void this.setPointerCapture(t.pointerId)}const s=t.target.closest("[data-marker]");if(s)return void this.seek(Number(s.dataset.marker));const r=t.target.closest("[data-clip]"),a=this._clips.find(t=>t.id===r?.dataset.clip);if(a){if(this.selectedEffectKeys.clear(),this.select(a.id),this._tracks.find(t=>t.id===a.trackId)?.locked)return;this.viewport?.focus({preventScroll:!0})}else if(!t.target.closest("[data-seek],[data-playhead]"))return;if("touch"===t.pointerType&&!a&&!t.target.closest(".db-vt__ruler,[data-playhead]"))return;t.preventDefault();const d=t.target.closest("[data-trim]")?.dataset.trim,n=t.target.closest("[data-fade]")?.dataset.fade;this.drag={kind:a?"in"===n?"fade-in":"out"===n?"fade-out":"start"===d?"trim-start":"end"===d?"trim-end":"move":"seek",id:t.pointerId,clipId:a?.id,originX:t.clientX,originY:t.clientY,scroll:this.viewport.scrollLeft,before:this.clips,x:t.clientX,y:t.clientY,changed:!1,shift:t.shiftKey,ripple:t.metaKey||t.ctrlKey,duplicate:a&&!d&&!n&&t.altKey?`clip-${crypto.randomUUID()}`:void 0},this.setPointerCapture(t.pointerId),a||this.seek(this.timeAt(t.clientX)),this.raf=requestAnimationFrame(this.autoScroll)},this.move=t=>{if(this.labelDrag?.id!==t.pointerId){if(this.marquee?.pointer===t.pointerId){const e=this.marquee,i=this.getBoundingClientRect(),s=Math.min(e.x,t.clientX),r=Math.min(e.y,t.clientY),a=Math.max(e.x,t.clientX),d=Math.max(e.y,t.clientY);Object.assign(e.box.style,{left:s-i.left+"px",top:r-i.top+"px",width:a-s+"px",height:d-r+"px"}),this.selectedEffectKeys=new Set(e.before);for(const t of this.querySelectorAll("[data-key-token]")){if(t.closest("[data-effect-clip]")?.dataset.effectClip!==e.clipId||t.hasAttribute("disabled"))continue;const i=t.getBoundingClientRect(),n=i.x+i.width/2,h=i.y+i.height/2;n>=s&&n<=a&&h>=r&&h<=d&&this.selectedEffectKeys.add(t.dataset.keyToken)}return void this.syncEffectSelection()}if(this.effectDrag?.pointer===t.pointerId){const e=this.effectDrag,i=e.before.find(t=>t.id===e.clipId);if(t.altKey&&!e.copies&&(e.copies=this.makeKeyCopyIds()),e.copies){const s=r((t.clientX-e.x)/this.scale,this.fps);if(0===s){this._clips=e.before,e.changed=!1;for(const t of this.querySelectorAll("[data-duplicate-preview]"))t.remove();return void this.syncEffectSelection()}const d=this.copyKeyData(e.before,e.clipId,s,e.copies);if(!d)return;this._clips=d,e.changed=!0;for(const[t,s]of e.copies){const[e,r,a]=JSON.parse(t),n=this.effectToken(e,r,a,s),h=Array.from(this.querySelectorAll("[data-key-token]")).find(e=>e.dataset.keyToken===t);if(!h)continue;let o=Array.from(this.querySelectorAll("[data-key-token]")).find(t=>t.dataset.keyToken===n);o||(o=h.cloneNode(!0),o.dataset.keyToken=n,o.dataset.propertyKey=s,o.dataset.duplicatePreview="true",h.parentElement.append(o));const c=d.find(t=>t.id===e)?.effects?.find(t=>t.id===r)?.properties?.find(t=>t.id===a)?.keyframes.find(t=>t.id===s);c&&(o.style.left=(i.start+c.time)*this.scale+"px"),o.setAttribute("aria-pressed","true"),h.setAttribute("aria-pressed","false")}return void a(this,"db-input",{clips:this.clips,clipId:e.clipId,action:"keyframe"})}let s=-1/0,n=1/0;for(const t of i.effects||[])for(const e of t.properties||[]){const r=[...e.keyframes].sort((t,e)=>t.time-e.time);r.forEach((a,d)=>{if(!this.selectedEffectKeys.has(this.effectToken(i.id,t.id,e.id,a.id)))return;const h=r.slice(0,d).reverse().find(s=>!this.selectedEffectKeys.has(this.effectToken(i.id,t.id,e.id,s.id))),o=r.slice(d+1).find(s=>!this.selectedEffectKeys.has(this.effectToken(i.id,t.id,e.id,s.id)));s=Math.max(s,(h?h.time+1/this.fps:0)-a.time),n=Math.min(n,(o?o.time-1/this.fps:i.duration)-a.time)})}const h=r(d((t.clientX-e.x)/this.scale,s,n),this.fps);this._clips=e.before.map(t=>t.id!==e.clipId?t:{...t,effects:t.effects?.map(e=>({...e,properties:e.properties?.map(i=>({...i,keyframes:i.keyframes.map(s=>this.selectedEffectKeys.has(this.effectToken(t.id,e.id,i.id,s.id))?{...s,time:r(s.time+h,this.fps)}:s)}))}))}),e.changed=0!==h;for(const t of this.querySelectorAll("[data-key-token]")){if(!this.selectedEffectKeys.has(t.dataset.keyToken))continue;const[e,i,s,r]=JSON.parse(t.dataset.keyToken),a=this._clips.find(t=>t.id===e),d=a?.effects?.find(t=>t.id===i)?.properties?.find(t=>t.id===s)?.keyframes.find(t=>t.id===r);a&&d&&(t.style.left=(a.start+d.time)*this.scale+"px")}return void a(this,"db-input",{clips:this.clips,clipId:e.clipId,action:"keyframe",effectId:e.effectId,propertyId:e.propertyId})}if(this.resizeDrag?.id===t.pointerId){const e=this.resizeDrag,i=Math.round(e.initial+t.clientY-e.startY);return void(e.trackId?(this._tracks=e.beforeTracks.map(t=>t.id===e.trackId?{...t,height:d(i,32,120)}:t),this.render(),a(this,"db-track-input",{tracks:this.tracks})):(this.height=d(i,120,720),a(this,"db-resize-input",{height:this.height})))}this.drag?.id===t.pointerId&&(this.drag.x=t.clientX,this.drag.y=t.clientY,this.drag.shift=t.shiftKey,this.drag.ripple=t.metaKey||t.ctrlKey,"move"===this.drag.kind&&t.altKey&&!this.drag.duplicate&&(this.drag.duplicate=`clip-${crypto.randomUUID()}`),this.updateDrag())}else this.labelWidth=this.labelDrag.width+t.clientX-this.labelDrag.x},this.autoScroll=()=>{if(!this.drag||!this.viewport)return;const t=this.viewport.getBoundingClientRect(),e=this.drag.x,i=e>t.right-48?d((e-t.right+48)/3,0,18):e<t.left+this.labelWidth+32?-d((t.left+176-e)/3,0,18):0,s=this.viewport.scrollLeft;this.viewport.scrollLeft+=i,s!==this.viewport.scrollLeft&&this.updateDrag(),this.raf=requestAnimationFrame(this.autoScroll)},this.recordTimelineHistory=t=>{if(t.target!==this)return;const e=t.detail;e?.clips&&"undo"!==e.action&&"redo"!==e.action&&JSON.stringify(e.clips)!==JSON.stringify(this.committedClips)&&(this.undoClips.push(structuredClone(this.committedClips)),this.undoClips.length>100&&this.undoClips.shift(),this.redoClips=[],this.committedClips=structuredClone(e.clips))},this.effectContextMenu=e=>{const s=e.target,r=s.closest("[data-effect-clip]");if(!r)return;const a=this._clips.find(t=>t.id===r.dataset.effectClip);if(!a)return;e.preventDefault(),e.stopPropagation(),this.viewport?.focus({preventScroll:!0});const d=s.closest("[data-key-token]");d&&!this.selectedEffectKeys.has(d.dataset.keyToken)&&(this.selectedEffectKeys.clear(),this.selectedEffectKeys.add(d.dataset.keyToken),this.syncEffectSelection());const n=this.disabled||!!this._tracks.find(t=>t.id===a.trackId)?.locked||!this.selectedEffectKeys.size||!!d?.hasAttribute("disabled");this.keyMenu||(this.keyMenu=new t,this.append(this.keyMenu)),this.keyMenu.items=[{id:"duplicate",label:i("media.duplicateKeys"),icon:"copy",disabled:n},{id:"easing",label:i("media.keyEasing"),disabled:n,subItems:["linear","hold","ease-in","ease-out","ease-in-out"].map(t=>({id:`ease:${t}`,label:i("media."+t)}))},{id:"divider",divider:!0},{id:"delete",label:i("media.deleteKey"),icon:"trash",variant:"danger",disabled:n},{id:"undo",label:i("media.undo"),disabled:!this.undoClips.length}],this.keyMenu.oncontextmenu=t=>t.preventDefault(),this.keyMenu.addEventListener("db-select",this.keyMenuSelect),this.keyMenu.dataset.clipId=a.id,this.keyMenu.openAt(e.clientX,e.clientY)},this.keyMenuSelect=t=>{t.stopPropagation();const e=t.detail.item.id,i=this.keyMenu.dataset.clipId;"duplicate"===e?this.duplicateSelectedKeys(i):"delete"===e?this.editSelectedKeys(i):e.startsWith("ease:")?this.editSelectedKeys(i,e.slice(5)):"undo"===e&&this.restoreTimelineHistory()},this.up=t=>{if(this.labelDrag?.id===t.pointerId)return this.labelDrag=void 0,this.hasPointerCapture(t.pointerId)&&this.releasePointerCapture(t.pointerId),void a(this,"db-label-resize",{width:this.labelWidth});this.marquee?.pointer!==t.pointerId?this.effectDrag?.pointer!==t.pointerId?this.resizeDrag?.id!==t.pointerId?this.drag?.id===t.pointerId&&(this.drag.changed&&(this.drag.x=t.clientX,this.drag.y=t.clientY,this.updateDrag()),this.finish()):this.finishResize():this.finishEffectDrag():this.finishMarquee()},this.cancel=t=>{if(this.labelDrag?.id===t.pointerId){if("lostpointercapture"===t.type&&this.hasPointerCapture(t.pointerId))return;const e=this.labelDrag;return this.labelDrag=void 0,void(this.labelWidth=e.width)}[this.drag?.id,this.resizeDrag?.id,this.effectDrag?.pointer,this.marquee?.pointer].includes(t.pointerId)&&("lostpointercapture"!==t.type||t.target===this&&!this.hasPointerCapture(t.pointerId))&&(this.finishMarquee(!0),this.finishEffectDrag(!0),this.finish(!0),this.finishResize(!0))},this.onActionClick=t=>{const e=t.target,i=e.dataset.action;if(!this.disabled&&("scroll"===i&&this.viewport?.scrollBy({left:Math.max(160,this.viewport.clientWidth-this.labelWidth),behavior:matchMedia("(prefers-reduced-motion: reduce)").matches?"instant":"smooth"}),"lock"===i||"mute"===i)){const t=e.dataset.track;this._tracks=this._tracks.map(e=>e.id===t?{...e,["lock"===i?"locked":"muted"]:!e["lock"===i?"locked":"muted"]}:e),this.render(),a(this,"db-track-change",{tracks:this.tracks})}},this.doubleResize=t=>{if(this.disabled||!(t.target instanceof Element))return;const e=t.target.closest("[data-track-resize],[data-pane-resize]");e&&(e.dataset.trackResize?(this._tracks=this._tracks.map(t=>t.id===e.dataset.trackResize?{...t,height:this.rowHeight}:t),this.render(),a(this,"db-track-change",{tracks:this.tracks})):(this.height=0,a(this,"db-resize",{height:0})))},this.wheel=t=>{if(this.disabled||!this.shortcuts||this.drag||this.resizeDrag||!t.metaKey&&!t.ctrlKey)return;t.preventDefault(),t.stopPropagation();const e=Math.exp(-t.deltaY*(1===t.deltaMode?.04:.002));this.changeZoom(this.zoom*e,t.clientX-this.viewport.getBoundingClientRect().left,"wheel")},this.key=t=>{if(!this.disabled&&!n(t.target)&&(t.metaKey||t.ctrlKey)&&"z"===t.key.toLowerCase()&&!this.drag&&!this.effectDrag&&!this.effectEditing)return void((t.shiftKey?this.redoClips:this.undoClips).length&&(t.preventDefault(),t.stopPropagation(),this.restoreTimelineHistory(t.shiftKey)));if(!(this.disabled||n(t.target)||t.metaKey||t.ctrlKey||t.altKey||"u"!==t.key.toLowerCase())){const e=this._clips.find(t=>t.id===this.selectedClipId&&t.effects?.some(t=>t.properties?.length))||this._clips.find(t=>t.effects?.some(t=>t.properties?.length));if(e){t.preventDefault(),t.stopPropagation();const i=!this.expandedEffects.has(e.id);for(const t of[e.id,...(e.effects||[]).map(t=>`${e.id}/${t.id}`)])i?this.expandedEffects.add(t):this.expandedEffects.delete(t);this.render(),this.viewport?.focus({preventScroll:!0})}return}if("Escape"===t.key&&this.labelDrag){t.preventDefault();const e=this.labelDrag;return this.labelDrag=void 0,this.labelWidth=e.width,void(this.hasPointerCapture(e.id)&&this.releasePointerCapture(e.id))}if("Escape"===t.key&&this.marquee)return t.preventDefault(),void this.finishMarquee(!0);if(!this.disabled&&!n(t.target)&&!t.target?.closest("db-keyframe-editor")&&["Delete","Backspace"].includes(t.key)&&this.selectedEffectKeys.size){t.preventDefault();const e=new Set(Array.from(this.selectedEffectKeys).map(t=>JSON.parse(t)[0]));for(const t of e)this.editSelectedKeys(t);return}if(this.disabled||n(t.target)||t.isComposing||t.target?.closest("db-keyframe-editor"))return;if("Escape"===t.key&&this.effectDrag)return t.preventDefault(),void this.finishEffectDrag(!0);if("Escape"===t.key&&this.drag)return t.preventDefault(),void this.finish(!0);if("Escape"===t.key&&this.resizeDrag)return t.preventDefault(),void this.finishResize(!0);const e=t.target.closest("[data-track-resize],[data-pane-resize]");if(e&&["ArrowUp","ArrowDown","Home"].includes(t.key)){t.preventDefault();const i="ArrowUp"===t.key?-1:1;return void(e.dataset.trackResize?(this._tracks=this._tracks.map(s=>s.id===e.dataset.trackResize?{...s,height:"Home"===t.key?this.rowHeight:d(this.trackHeight(s)+i*(t.shiftKey?8:2),32,120)}:s),this.render(),a(this,"db-track-change",{tracks:this.tracks}),this.querySelector(`[data-track-resize="${CSS.escape(e.dataset.trackResize)}"]`)?.focus()):(this.height="Home"===t.key?0:d((this.height||this.viewport.clientHeight)+i*(t.shiftKey?24:8),120,720),a(this,"db-resize",{height:this.height}),this.paneGrip?.focus()))}if(this.shortcuts&&!t.altKey&&["+","=","-","0"].includes(t.key))return t.preventDefault(),t.stopPropagation(),void("0"===t.key?this.changeZoom((this.viewport.clientWidth-this.labelWidth)/this.duration/.8,void 0,"fit"):this.changeZoom(this.zoom*("-"===t.key?1/1.2:1.2)));if(this.disabled||n(t.target)||t.ctrlKey||t.metaKey||t.altKey)return;const i=t.target.closest("[data-trim],[data-fade]");if(i&&["ArrowLeft","ArrowRight"].includes(t.key)){const e=i.closest("[data-clip]"),s=this._clips.find(t=>t.id===e?.dataset.clip);if(!s||this._tracks.find(t=>t.id===s.trackId)?.locked)return;t.preventDefault();const r="start"===i.dataset.trim?"trim-start":"end"===i.dataset.trim?"trim-end":"in"===i.dataset.fade?"fade-in":"fade-out";this.drag={kind:r,id:-1,clipId:s.id,originX:0,originY:0,scroll:this.viewport.scrollLeft,before:this.clips,x:("ArrowLeft"===t.key?-1:1)*(t.shiftKey?10:1)/this.fps*this.scale,y:0,changed:!1,shift:!0,ripple:t.metaKey||t.ctrlKey},this.updateDrag(),this.finish();const a=i.dataset.focus;return void Array.from(this.querySelectorAll("[data-focus]")).find(t=>t.dataset.focus===a)?.focus({preventScroll:!0})}if(["ArrowLeft","ArrowRight","Home","End"].includes(t.key))t.preventDefault(),this.seek("Home"===t.key?0:"End"===t.key?this.duration:this.currentTime+("ArrowLeft"===t.key?-1:1)*(t.shiftKey?10:1)/this.fps),this.scrollToTime(this.currentTime);else if("s"===t.key.toLowerCase())t.preventDefault(),this.splitSelected();else if("Delete"===t.key||"Backspace"===t.key){const e=this._clips.find(t=>t.id===this.selectedClipId);if(!e||this._tracks.find(t=>t.id===e.trackId)?.locked)return;t.preventDefault(),this._clips=this._clips.filter(t=>t.id!==e.id),this.select(""),this.render(),a(this,"db-change",{clips:this.clips,clipId:e.id,action:"delete"})}else"Enter"===t.key&&t.target instanceof HTMLElement&&t.target.dataset.clip&&(t.preventDefault(),this.select(t.target.dataset.clip))}}static get observedAttributes(){return["duration","fps","current-time","zoom","snap","disabled","selected-clip-id","row-height","label-width","height","min-clip-duration","shortcuts"]}get labelWidth(){return d(this.number("label-width",144),120,480)}set labelWidth(t){this.setAttribute("label-width",String(d(t,120,480)))}get contentDuration(){const t=Math.max(this.duration,...this._clips.map(t=>t.start+t.duration));return t>this.duration?t+Math.max(1,.15*this.duration):t}labelGrip(t){const e=this.node("span","db-vt__label-resize");e.tabIndex=this.disabled?-1:0,e.setAttribute("aria-disabled",String(this.disabled)),e.setAttribute("role","separator"),e.setAttribute("aria-orientation","vertical"),e.setAttribute("aria-label",i("media.resizeLabel")),e.setAttribute("aria-valuemin","120"),e.setAttribute("aria-valuemax","480"),e.setAttribute("aria-valuenow",String(this.labelWidth)),e.addEventListener("pointerdown",t=>{this.disabled||0!==t.button||(t.preventDefault(),t.stopPropagation(),this.labelDrag={id:t.pointerId,x:t.clientX,width:this.labelWidth},this.setPointerCapture(t.pointerId))}),e.addEventListener("keydown",t=>{this.disabled||["ArrowLeft","ArrowRight","Home"].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.labelWidth="Home"===t.key?144:this.labelWidth+("ArrowLeft"===t.key?-1:1)*(t.shiftKey?24:8),a(this,"db-label-resize",{width:this.labelWidth}))}),t.append(e)}get rowHeight(){return d(this.number("row-height",42),32,120)}set rowHeight(t){this.setAttribute("row-height",String(t))}get height(){const t=this.number("height",0);return t>0?d(t,120,720):0}set height(t){t>0?this.setAttribute("height",String(t)):this.removeAttribute("height")}get minClipDuration(){return Math.min(this.duration,Math.max(1/this.fps,Math.ceil(this.number("min-clip-duration",.5)*this.fps)/this.fps))}set minClipDuration(t){this.setAttribute("min-clip-duration",String(t))}get shortcuts(){return"false"!==this.getAttribute("shortcuts")}set shortcuts(t){this.setAttribute("shortcuts",String(t))}trackHeight(t){return d(h(t.height,this.rowHeight),32,120)}number(t,e){const i=this.getAttribute(t);return i?.trim()&&Number.isFinite(Number(i))?Number(i):e}get duration(){return d(this.number("duration",30),1/this.fps,86400)}set duration(t){this.setAttribute("duration",String(t))}get fps(){return d(Math.round(this.number("fps",30)),1,120)}set fps(t){this.setAttribute("fps",String(t))}get zoom(){return d(this.number("zoom",100),10,800)}set zoom(t){this.setAttribute("zoom",String(t))}get currentTime(){return r(d(this.number("current-time",0),0,this.duration),this.fps)}set currentTime(t){this.setAttribute("current-time",String(t))}get snap(){return"false"!==this.getAttribute("snap")}set snap(t){this.setAttribute("snap",String(t))}get disabled(){return this.hasAttribute("disabled")}set disabled(t){this.toggleAttribute("disabled",t)}get selectedClipId(){return this.getAttribute("selected-clip-id")||""}set selectedClipId(t){this.setAttribute("selected-clip-id",t)}get tracks(){return this._tracks.map(t=>({...t}))}set tracks(t){const e=new Set;this._tracks=(Array.isArray(t)?t:[]).filter(t=>t&&"string"==typeof t.id&&!e.has(t.id)&&!!e.add(t.id)).map(t=>({...t,label:String(t.label||t.id),kind:["video","audio","title","mixed"].includes(t.kind)?t.kind:"video"})),this.render()}get clips(){return this._clips.map(t=>({...t,...t.effects?{effects:t.effects.map(t=>structuredClone(t))}:{}}))}set clips(t){if(this.drag||this.effectEditing)return;const e=new Set;this._clips=(Array.isArray(t)?t:[]).filter(t=>t&&"string"==typeof t.id&&!e.has(t.id)&&!!e.add(t.id)).map(t=>{const e=r(Math.max(0,h(t.start,0)),this.fps),i=Math.max(1/this.fps,r(h(t.duration,1),this.fps)),s=r(d(h(t.fadeIn,0),0,i),this.fps),a=r(d(h(t.fadeOut,0),0,Math.max(0,i-s)),this.fps);return{...t,label:String(t.label||t.id),start:e,duration:i,sourceOffset:Math.max(0,h(t.sourceOffset,0)),...void 0!==t.fadeIn?{fadeIn:s}:{},...void 0!==t.fadeOut?{fadeOut:a}:{},...t.effects?{effects:Array.isArray(t.effects)?t.effects.filter(t=>t&&"string"==typeof t.id).map(t=>({...t,label:String(t.label||t.id)})):[]}:{}}}),JSON.stringify(this._clips)!==JSON.stringify(this.committedClips)&&(this.committedClips=this.clips,this.undoClips=[],this.redoClips=[]),this.render()}get markers(){return this._markers.map(t=>({...t}))}set markers(t){this._markers=(Array.isArray(t)?t:[]).filter(t=>t&&"string"==typeof t.id&&Number.isFinite(t.time)).map(t=>({...t,label:String(t.label),time:d(t.time,0,this.duration)})),this.render()}get scale(){return.8*this.zoom}connectedCallback(){if(this.classList.add("designbase-wc-video-timeline"),this.setAttribute("role","region"),!this.viewport){this.viewport=document.createElement("div"),this.viewport.className="db-vt__viewport",this.viewport.tabIndex=0,this.canvas=document.createElement("div"),this.canvas.className="db-vt__canvas",this.viewport.append(this.canvas),this.hint=document.createElement("div"),this.hint.className="db-vt__hint";const t=o(i("media.more"),"chevron-right","scroll");this.hint.append(t),this.paneGrip=document.createElement("div"),this.paneGrip.className="db-vt__pane-resize",this.paneGrip.dataset.paneResize="",this.paneGrip.tabIndex=0,this.paneGrip.setAttribute("role","separator"),this.paneGrip.setAttribute("aria-orientation","horizontal"),this.append(this.viewport,this.hint,this.paneGrip),this.viewport.addEventListener("scroll",()=>this.syncHint())}this.viewport.addEventListener("wheel",this.wheel,{passive:!1}),this.addEventListener("dblclick",this.doubleResize),this.addEventListener("db-change",this.recordTimelineHistory,!0),this.addEventListener("contextmenu",this.effectContextMenu),this.addEventListener("pointerdown",this.down),this.addEventListener("pointermove",this.move),this.addEventListener("pointerup",this.up),this.addEventListener("pointercancel",this.cancel),this.addEventListener("lostpointercapture",this.cancel),this.addEventListener("keydown",this.key),this.addEventListener("db-click",this.onActionClick),this.unsubscribe=s(()=>this.render()),this.resize=new ResizeObserver(()=>this.syncHint()),this.resize.observe(this),this.render()}disconnectedCallback(){if(this.labelDrag){const t=this.labelDrag;this.labelDrag=void 0,this.labelWidth=t.width}this.rollback(),this.resize?.disconnect(),this.unsubscribe?.(),this.viewport?.removeEventListener("wheel",this.wheel),this.removeEventListener("dblclick",this.doubleResize),this.finishResize(!0),this.removeEventListener("db-change",this.recordTimelineHistory,!0),this.removeEventListener("contextmenu",this.effectContextMenu),this.keyMenu?.remove(),this.keyMenu=void 0,this.removeEventListener("pointerdown",this.down),this.removeEventListener("pointermove",this.move),this.removeEventListener("pointerup",this.up),this.removeEventListener("pointercancel",this.cancel),this.removeEventListener("lostpointercapture",this.cancel),this.removeEventListener("keydown",this.key),this.removeEventListener("db-click",this.onActionClick)}attributeChangedCallback(t){"disabled"===t&&this.disabled&&(this.finishEffectDrag(!0),this.finish(!0),this.finishResize(!0)),"current-time"===t?this.syncCursor():this.render()}syncHint(){for(const t of this.querySelectorAll(".db-vt__selection-tools"))t.style.width=`${this.viewport?.clientWidth||240}px`;if(this.syncCursor(),this.viewport)for(const t of this.querySelectorAll("[data-marker]"))t.style.visibility=Number(t.dataset.marker)*this.scale<this.viewport.scrollLeft?"hidden":"";this.viewport&&this.hint&&(this.hint.hidden=this.viewport.scrollLeft+this.viewport.clientWidth>=this.viewport.scrollWidth-2)}syncCursor(){for(const t of this.querySelectorAll("[data-effect-property]")){const e=this._clips.find(e=>e.id===t.closest("[data-effect-clip]")?.dataset.effectClip),i=e?.effects?.find(e=>e.id===t.dataset.effectId)?.properties?.find(e=>e.id===t.dataset.effectProperty),s=t.querySelector(".db-vt__property-value");e&&i&&s&&(s.textContent=String(Math.round(100*c(i.keyframes,this.currentTime-e.start))/100))}if(!this.effectEditing)for(const t of this.querySelectorAll("db-keyframe-editor[data-effect-editor]")){const e=this._clips.find(e=>e.id===t.closest("[data-effect-clip]")?.dataset.effectClip);e&&(t.currentTime=d(this.currentTime-e.start,0,e.duration))}this.cursor&&(this.cursor.style.left=`${this.labelWidth+this.currentTime*this.scale}px`,this.cursor.style.visibility=this.currentTime*this.scale<(this.viewport?.scrollLeft||0)?"hidden":"",this.cursor.setAttribute("aria-valuenow",String(this.currentTime)),this.cursor.setAttribute("aria-valuetext",l(this.currentTime,this.fps)))}seek(t){this.currentTime=r(d(t,0,this.duration),this.fps),a(this,"db-seek",{time:this.currentTime})}scrollToTime(t){this.viewport&&(this.viewport.scrollLeft=Math.max(0,d(t,0,this.duration)*this.scale-(this.viewport.clientWidth-this.labelWidth)/2))}setZoom(t){const e=this.currentTime;this.zoom=t,this.scrollToTime(e)}node(t,e,i){const s=document.createElement(t);return s.className=e,void 0!==i&&(s.textContent=i),s}render(){if(this.effectEditing)return;const t=new Set(this._clips.flatMap(t=>(t.effects||[]).flatMap(e=>(e.properties||[]).flatMap(i=>i.keyframes.map(s=>this.effectToken(t.id,e.id,i.id,s.id))))));if(this.selectedEffectKeys=new Set(Array.from(this.selectedEffectKeys).filter(e=>t.has(e))),!this.canvas||!this.viewport)return;this.querySelector(":scope > .db-vt__readout")?.remove(),this.viewport.style.height=this.height?`${this.height}px`:"",this.viewport.style.maxHeight=this.height?"720px":"360px",this.paneGrip&&(this.paneGrip.setAttribute("aria-label",i("media.resizeTimeline")),this.paneGrip.setAttribute("title",i("media.resizeTimelineHint")),this.paneGrip.setAttribute("aria-valuemin","120"),this.paneGrip.setAttribute("aria-valuemax","720"),this.paneGrip.setAttribute("aria-valuenow",String(this.height||this.viewport.clientHeight)));const e=this.hint?.querySelector("db-button");e?.setAttribute("aria-label",i("media.more")),e?.setAttribute("title",i("media.more"));const s=this.ownerDocument.activeElement?.dataset.focus;this.style.setProperty("--db-vt-label-width",`${this.labelWidth}px`);const r=document.createDocumentFragment(),a=this.node("div","db-vt__ruler"),n=this.node("span","db-vt__corner",i("media.tracks"));this.labelGrip(n),a.append(n);const h=[1/this.fps,.1,.25,.5,1,2,5,10,30,60,300,600,3600].find(t=>t*this.scale>=64&&this.duration/t<=200)||3600;for(let t=0;t<=this.contentDuration;t+=h){const e=this.node("span","db-vt__tick",l(t,this.fps));e.style.left=`${this.labelWidth+t*this.scale}px`,a.append(e)}a.dataset.seek="",r.append(a),this._tracks.forEach((t,e)=>{const s=this.node("div","db-vt__row");s.dataset.track=t.id,s.dataset.kind=t.kind,s.style.setProperty("--db-vt-track-height",`${this.trackHeight(t)}px`),s.dataset.condensed=String(this.trackHeight(t)<44),s.dataset.locked=String(!!t.locked),s.dataset.muted=String(!!t.muted);const a=this.node("div","db-vt__track");a.append(this.node("span","db-vt__track-name",t.label));for(const e of["lock","mute"]){const s=i("lock"===e?t.locked?"media.trackLocked":"media.trackUnlocked":t.muted?"media.soundOff":"media.soundOn"),r=o(`${s} · ${t.label}`,"lock"===e?t.locked?"lock":"unlock":t.muted?"mute":"volume-up",e);r.setAttribute("variant","ghost"),r.dataset.track=t.id,r.setAttribute("aria-pressed",String("lock"===e?!!t.locked:!!t.muted)),r.toggleAttribute("disabled",this.disabled),a.append(r)}this.labelGrip(a),s.append(a);const n=this.node("div","db-vt__lane");n.dataset.seek="",n.style.width=this.contentDuration*this.scale+"px",n.style.setProperty("--tick",h*this.scale+"px"),this._clips.filter(e=>e.trackId===t.id).forEach(e=>{const s=this.node("div","db-vt__clip");s.dataset.clip=e.id,s.dataset.focus=e.id,s.dataset.selected=String(this.selectedClipId===e.id),s.setAttribute("role","group"),s.tabIndex=0,s.setAttribute("aria-label",`${e.label}, ${l(e.start,this.fps)}, ${i("media.duration")} ${l(e.duration,this.fps)}`),s.setAttribute("aria-disabled",String(this.disabled||!!t.locked));const r=this.selectedClipId===e.id;if(s.dataset.appearance=e.appearance||("audio"===(e.kind||t.kind)?"waveform":"standard"),s.dataset.narrow=String(e.duration*this.scale<100),s.dataset.editing=String(this.drag?.clipId===e.id),s.style.left=e.start*this.scale+"px",e.start+e.duration>this.duration){const t=Math.max(0,this.duration-e.start)*this.scale;s.style.maskImage=`linear-gradient(to right, black ${t}px, rgba(0,0,0,.35) ${t}px)`,s.dataset.outOfRange="true"}s.style.width=`${Math.max(24,e.duration*this.scale)}px`,s.dataset.hitProxy=String(e.duration*this.scale<24),s.style.setProperty("--db-vt-actual-width",e.duration*this.scale+"px");for(const a of["start","end"]){const d=this.node("span","db-vt__trim");d.dataset.trim=a,d.dataset.focus=`${e.id}-trim-${a}`,d.tabIndex=!r||this.disabled||t.locked?-1:0,d.setAttribute("role","slider"),d.setAttribute("aria-label",`${i("start"===a?"media.trimStart":"media.trimEnd")} · ${e.label}`),d.setAttribute("aria-valuemin","0"),d.setAttribute("aria-valuemax",String(this.duration)),d.setAttribute("aria-valuenow",String("start"===a?e.start:e.start+e.duration)),d.setAttribute("aria-valuetext",l("start"===a?e.start:e.start+e.duration,this.fps)),d.setAttribute("title",i("start"===a?"media.trimStart":"media.trimEnd")),d.append(this.node("i","db-vt__grip")),s.append(d)}if("filmstrip"===e.appearance&&e.thumbnail){const t=this.node("div","db-vt__filmstrip");t.setAttribute("aria-hidden","true");for(let i=0;i<Math.min(24,Math.ceil(e.duration*this.scale/64));i++){const i=document.createElement("img");i.src=e.thumbnail,i.alt="",i.draggable=!1,t.append(i)}s.append(t)}for(const a of["in","out"]){const n=d("in"===a?e.fadeIn||0:e.fadeOut||0,0,e.duration),h=this.node("span","db-vt__fade");if(h.dataset.fadeArea=a,h.style.width=n*this.scale+"px",h.setAttribute("aria-hidden","true"),s.append(h),r){const r=this.node("span","db-vt__fade-handle");r.dataset.fade=a,r.dataset.focus=`${e.id}-fade-${a}`,r.style["in"===a?"left":"right"]=n*this.scale+"px",r.tabIndex=this.disabled||t.locked?-1:0,r.setAttribute("role","slider"),r.setAttribute("aria-label",`${i("in"===a?"media.fadeIn":"media.fadeOut")} · ${e.label}`),r.setAttribute("aria-valuemin","0"),r.setAttribute("aria-valuemax",String(e.duration)),r.setAttribute("aria-valuenow",String(n)),r.setAttribute("title",i("in"===a?"media.fadeIn":"media.fadeOut")),s.append(r)}}if(s.append(this.node("span","db-vt__clip-label",e.label)),"audio"===(e.kind||t.kind)||"waveform"===e.appearance){const t=this.node("span","db-vt__wave");t.setAttribute("aria-hidden","true");for(let i=0;i<Math.min(160,Math.ceil(e.duration*this.scale/7));i++){const e=this.node("i","");e.style.height=20+75*Math.abs(Math.sin(1.7*i)*Math.cos(.4*i))+"%",t.append(e)}s.append(t)}if(e.effects?.length){const t=this.node("span","db-vt__effects");t.setAttribute("aria-label",i("media.effects"));for(const i of e.effects){const e=this.node("span","db-vt__effect",i.label);e.dataset.enabled=String(!1!==i.enabled),e.dataset.kind=i.kind,e.setAttribute("title",i.label),t.append(e)}s.append(t)}n.append(s)}),s.append(n);const c=this.node("div","db-vt__track-resize");c.dataset.trackResize=t.id,c.tabIndex=0,c.setAttribute("role","separator"),c.setAttribute("aria-orientation","horizontal"),c.setAttribute("aria-label",`${i("media.resizeTrack")} · ${t.label}`),c.setAttribute("aria-valuemin","32"),c.setAttribute("aria-valuemax","120"),c.setAttribute("aria-valuenow",String(this.trackHeight(t))),s.append(c),r.append(s),this.renderEffects(r,t)});for(const t of this._markers){const e=this.node("button","db-vt__marker");e.type="button",e.dataset.marker=String(t.time),e.style.left=`${this.labelWidth+t.time*this.scale}px`;const s=`${i("media.marker")} · ${t.label} · ${l(t.time,this.fps)}`;e.setAttribute("aria-label",s),e.setAttribute("title",s),r.append(e)}if(this.cursor=this.node("div","db-vt__playhead"),this.cursor.dataset.playhead="",this.cursor.tabIndex=0,this.cursor.setAttribute("role","slider"),this.cursor.dataset.focus="playhead",this.cursor.setAttribute("aria-label",i("media.playhead")),this.cursor.setAttribute("aria-valuemin","0"),this.cursor.setAttribute("aria-valuemax",String(this.duration)),this.cursor.setAttribute("aria-disabled",String(this.disabled)),r.append(this.cursor),this._tracks.length||r.append(this.node("p","db-vt__empty",i("media.emptyTracks"))),this.canvas.style.width=`${this.labelWidth+this.contentDuration*this.scale}px`,this.drag?.clipId){const t=this._clips.find(t=>t.id===this.drag?.clipId);if(t){const e="fade-in"===this.drag.kind?t.fadeIn||0:"fade-out"===this.drag.kind?t.fadeOut||0:"trim-end"===this.drag.kind?t.start+t.duration:t.start,s=this.node("div","db-vt__readout",`${i("media."+("trim-start"===this.drag.kind?"trimStart":"trim-end"===this.drag.kind?"trimEnd":"fade-in"===this.drag.kind?"fadeIn":"fade-out"===this.drag.kind?"fadeOut":"time"))} ${l(e,this.fps)} / ${i("media.duration")} ${l(t.duration,this.fps)}`);this.append(s)}}this.canvas.replaceChildren(r),this.viewport.setAttribute("aria-label",i("media.timeline")),this.syncCursor(),this.syncHint(),s&&!this.drag&&Array.from(this.querySelectorAll("[data-focus]")).find(t=>t.dataset.focus===s)?.focus({preventScroll:!0})}renderEffects(t,s){for(const r of this._clips.filter(t=>t.trackId===s.id&&t.effects?.some(t=>t.properties?.length))){const n=this.node("section","db-vt__effect-tree");n.dataset.effectClip=r.id;const toggle=(t,e,i,s)=>{const r=o(t,i?"chevron-down":"chevron-right","effect-disclosure");return r.removeAttribute("icon-only"),r.setAttribute("variant","ghost"),r.textContent=t,r.setAttribute("aria-expanded",String(i)),r.setAttribute("aria-controls",e),r.dataset.focus=e,r.addEventListener("db-click",t=>{t.stopPropagation(),s(),this.render()}),r},h=`${this.effectTreeId}-${r.id}`,p=1===(r.effects||[]).filter(t=>t.properties?.length).length;n.append(toggle(p?i("media.effects"):`${r.label} · ${i("media.effects")}`,h,this.expandedEffects.has(r.id),()=>{this.expandedEffects.has(r.id)?this.expandedEffects.delete(r.id):this.expandedEffects.add(r.id)}));const f=this.node("div","db-vt__effect-content");if(f.id=h,f.hidden=!this.expandedEffects.has(r.id),!f.hidden)for(const t of r.effects||[]){if(!t.properties?.length)continue;const i=`${r.id}/${t.id}`,n=`${h}-${t.id}`;p||f.append(toggle(t.label,n,this.expandedEffects.has(i),()=>{this.expandedEffects.has(i)?this.expandedEffects.delete(i):this.expandedEffects.add(i)}));const o=this.node("div","db-vt__property-group");if(o.id=n,o.hidden=!p&&!this.expandedEffects.has(i),!o.hidden)for(const n of t.properties){const h=`${i}/${n.id}`,p=this.node("div","db-vt__property-row");p.dataset.effectProperty=n.id,p.dataset.effectId=t.id;const f=this.node("div","db-vt__property-label"),u=document.createElement("db-button");u.setAttribute("size","s"),u.setAttribute("variant","ghost"),u.dataset.action="effect-property",u.setAttribute("aria-label",n.label),u.append(this.node("span","db-vt__property-name",n.label)),u.setAttribute("aria-expanded",String(this.activeProperty.get(r.id)===h)),u.dataset.focus=h,u.addEventListener("db-click",t=>{t.stopPropagation(),this.activeProperty.set(r.id,this.activeProperty.get(r.id)===h?"":h),this.render()}),u.title=n.label,f.append(u,this.node("span","db-vt__property-value",String(Math.round(100*c(n.keyframes,this.currentTime-r.start))/100))),this.labelGrip(f),p.append(f);const m=this.node("div","db-vt__property-lane");m.style.width=this.contentDuration*this.scale+"px";for(const e of n.keyframes){const i=this.node("button","db-vt__property-key");i.dataset.propertyKey=e.id,i.dataset.keyToken=this.effectToken(r.id,t.id,n.id,e.id),i.setAttribute("aria-pressed",String(this.selectedEffectKeys.has(i.dataset.keyToken))),i.addEventListener("pointerdown",s=>{if(i.disabled||0!==s.button)return;s.preventDefault(),s.stopPropagation(),this.viewport?.focus({preventScroll:!0});const a=i.dataset.keyToken;Array.from(this.selectedEffectKeys).some(t=>JSON.parse(t)[0]!==r.id)&&this.selectedEffectKeys.clear(),s.shiftKey?this.selectedEffectKeys.has(a)?this.selectedEffectKeys.delete(a):this.selectedEffectKeys.add(a):this.selectedEffectKeys.has(a)||(this.selectedEffectKeys.clear(),this.selectedEffectKeys.add(a)),this.syncEffectSelection(),this.selectedEffectKeys.has(a)?(this.effectDrag={pointer:s.pointerId,clipId:r.id,effectId:t.id,propertyId:n.id,keyId:e.id,x:s.clientX,time:e.time,before:this.clips,changed:!1,copies:s.altKey?this.makeKeyCopyIds():void 0},this.effectEditing=!0,this.setPointerCapture(s.pointerId)):this.render()}),i.type="button",i.style.left=(r.start+e.time)*this.scale+"px",i.setAttribute("aria-label",`${n.label} · ${l(e.time,this.fps)} · ${e.value}`),i.disabled=this.disabled||!!s.locked||!1===t.enabled,i.addEventListener("click",t=>{t.stopPropagation(),t.shiftKey||(this.selectedEffectKeys.has(i.dataset.keyToken)||(this.selectedEffectKeys.clear(),this.selectedEffectKeys.add(i.dataset.keyToken)),this.activeProperty.set(r.id,h),this.effectKeySelection.set(h,e.id),this.seek(r.start+e.time),this.render())}),m.append(i)}if(p.append(m),o.append(p),this.activeProperty.get(r.id)===h){const i=new e;i.dataset.effectEditor=n.id,i.duration=r.duration,i.fps=this.fps,i.min=n.min,i.max=n.max,i.setAttribute("label",n.label),i.keyframes=n.keyframes,i.selectedKeyframeId=this.effectKeySelection.get(h)||n.keyframes[0]?.id||"",i.currentTime=d(this.currentTime-r.start,0,r.duration),i.disabled=this.disabled||!!s.locked||!1===t.enabled;const edit=e=>{e.stopPropagation();const i=e.detail;this.effectEditing="db-input"===e.type&&!i.cancelled;const s=i.keyframes;this._clips=this._clips.map(e=>e.id!==r.id?e:{...e,effects:e.effects?.map(e=>e.id!==t.id?e:{...e,properties:e.properties?.map(t=>t.id!==n.id?t:{...t,keyframes:s})})}),a(this,e.type,{clips:this.clips,clipId:r.id,action:"keyframe",effectId:t.id,propertyId:n.id,...i.cancelled?{cancelled:!0}:{}}),("db-change"===e.type||i.cancelled)&&(this.effectEditing=!1,this.render())};i.addEventListener("db-input",edit),i.addEventListener("db-change",edit),i.addEventListener("db-selection-change",t=>{t.stopPropagation(),this.effectKeySelection.set(h,t.detail.keyframeId)}),i.addEventListener("db-seek",t=>{t.stopPropagation(),this.seek(r.start+t.detail.time)});const c=this.node("div","db-vt__effect-editor");c.style.width=`${Math.max(240,(this.viewport?.clientWidth||800)-16)}px`,c.append(i),o.append(c)}}f.append(o)}n.addEventListener("pointerdown",t=>{if(0!==t.button||this.disabled||s.locked||t.target.closest("db-button,db-keyframe-editor,db-input,db-select,.db-vt__property-label,[data-property-key]"))return;t.preventDefault(),t.stopPropagation(),this.viewport?.focus({preventScroll:!0});const e=this.node("div","db-vt__marquee");this.append(e),this.marquee={pointer:t.pointerId,x:t.clientX,y:t.clientY,clipId:r.id,before:t.shiftKey?new Set(Array.from(this.selectedEffectKeys).filter(t=>JSON.parse(t)[0]===r.id)):new Set,box:e},t.shiftKey||this.selectedEffectKeys.clear(),this.syncEffectSelection(),this.setPointerCapture(t.pointerId)}),n.append(f),t.append(n)}}select(t){this.selectedClipId!==t&&(this.selectedClipId=t,a(this,"db-selection-change",{clipId:t}))}timeAt(t){return(t-this.viewport.getBoundingClientRect().left+this.viewport.scrollLeft-this.labelWidth)/this.scale}snapped(t,e){let i=r(t,this.fps);if(this.snap&&!this.drag?.shift){const t=[0,this.currentTime,...this._markers.map(t=>t.time),...this._clips.filter(t=>t.id!==e).flatMap(t=>[t.start,t.start+t.duration])].reduce((t,e)=>Math.abs(e-i)<Math.abs(t-i)?e:t,1/0);Math.abs(t-i)*this.scale<=8&&(i=r(t,this.fps))}return i}updateDrag(){const t=this.drag;if(!t)return;if("seek"===t.kind)return void this.seek(this.timeAt(t.x));const e=t.before.find(e=>e.id===t.clipId);if(!e)return;const i=(t.x-t.originX+this.viewport.scrollLeft-t.scroll)/this.scale;let s=e.start,n=e.duration,h=e.sourceOffset||0,o=e.trackId;if("fade-in"===t.kind||"fade-out"===t.kind){const s="fade-in"===t.kind?"fadeIn":"fadeOut",n="fade-in"===t.kind?e.fadeOut||0:e.fadeIn||0,h=r(d((e[s]||0)+("fade-in"===t.kind?i:-i),0,Math.max(0,e.duration-n)),this.fps);return this._clips=t.before.map(t=>t.id===e.id?{...t,[s]:h}:t),t.changed=JSON.stringify(this._clips)!==JSON.stringify(t.before),this.render(),void a(this,"db-input",{clips:this.clips,clipId:e.id,action:t.kind})}if("move"===t.kind){s=this.snapped(s+i,e.id),this.canvas.getBoundingClientRect();const r=this.trackIndexAt(t.y),a=this._tracks[d(r,0,this._tracks.length-1)],n=this._tracks.find(t=>t.id===e.trackId);!a||a.locked||"mixed"!==a.kind&&a.kind!==(e.kind||n?.kind)||(o=a.id),s=Math.max(0,s)}else if("trim-start"===t.kind)s=d(this.snapped(s+i,e.id),Math.max(0,e.start-h),e.start+n-Math.min(this.minClipDuration,e.start+n-Math.max(0,e.start-h))),n=e.start+e.duration-s,h+=s-e.start;else{const t=e.sourceDuration?Math.min(this.duration-s,e.sourceDuration-h):this.duration-s;n=d(this.snapped(s+n+i,e.id)-s,Math.min(this.minClipDuration,t),t)}const c=t.ripple&&("trim-start"===t.kind||"trim-end"===t.kind),l=t.before.filter(t=>t.id!==e.id&&t.trackId===e.trackId&&t.start>=e.start+e.duration-1e-6);let p=0;if(c){"trim-start"===t.kind&&(s=e.start);const i=Math.max(e.start+e.duration,...l.map(t=>t.start+t.duration));n=Math.min(n,e.duration+this.duration-i),p=r(n-e.duration,this.fps)}let f=t.before.map(t=>l.some(e=>e.id===t.id)&&c?{...t,start:r(t.start+p,this.fps)}:t);if("move"===t.kind&&t.ripple){const i=t.before.filter(i=>i.trackId===o&&(t.duplicate||i.id!==e.id)).sort((t,e)=>t.start-e.start),a=i.find(t=>s>t.start+1e-6&&s<t.start+t.duration-1e-6);a&&(s=a.start);let d=s+n;const h=new Map;for(const t of i){if(t.start<s-1e-6)continue;const e=Math.max(t.start,d);h.set(t.id,e),d=e+t.duration}f=t.before.map(t=>h.has(t.id)?{...t,start:r(h.get(t.id),this.fps)}:t)}if(f.some(i=>(t.duplicate||i.id!==e.id)&&i.trackId===o&&s<i.start+i.duration-1e-6&&s+n>i.start+1e-6))return;const u=t.duplicate||e.id;t.duplicate&&(f=[...f,{...e,id:u}]),this._clips=f.map(t=>t.id===u?{...t,kind:t.kind||("mixed"===this._tracks.find(t=>t.id===e.trackId)?.kind?"video":this._tracks.find(t=>t.id===e.trackId)?.kind),start:r(s,this.fps),duration:r(n,this.fps),sourceOffset:r(h,this.fps),trackId:o,...void 0!==t.fadeIn?{fadeIn:Math.min(t.fadeIn,n)}:{},...void 0!==t.fadeOut?{fadeOut:Math.min(t.fadeOut,Math.max(0,n-(t.fadeIn||0)))}:{}}:t),t.changed=JSON.stringify(this._clips)!==JSON.stringify(t.before),this.render(),a(this,"db-input",{clips:this.clips,clipId:e.id,action:t.kind})}restoreTimelineHistory(t=!1){const e=t?this.redoClips:this.undoClips,i=t?this.undoClips:this.redoClips,s=e.pop();s&&(i.push(this.clips),this._clips=structuredClone(s),this.committedClips=this.clips,this.selectedEffectKeys.clear(),this.render(),a(this,"db-change",{clips:this.clips,clipId:this.selectedClipId,action:t?"redo":"undo"}))}makeKeyCopyIds(){return new Map(Array.from(this.selectedEffectKeys,t=>[t,`key-${crypto.randomUUID()}`]))}copyKeyData(t,e,i,s){let a=!0,d=0;const n=t.map(t=>t.id!==e?t:{...t,effects:t.effects?.map(e=>({...e,properties:e.properties?.map(n=>{const h=n.keyframes.filter(i=>s.has(this.effectToken(t.id,e.id,n.id,i.id))).map(h=>{const o=r(h.time+i,this.fps);return d++,(o<0||o>t.duration||n.keyframes.some(t=>Math.abs(t.time-o)<.5/this.fps))&&(a=!1),{...structuredClone(h),id:s.get(this.effectToken(t.id,e.id,n.id,h.id)),time:o}});return{...n,keyframes:[...n.keyframes,...h].sort((t,e)=>t.time-e.time)}})}))});return a&&d?n:void 0}duplicateSelectedKeys(t){const e=this.makeKeyCopyIds(),i=this.clips;for(let s=1;s<=Math.min(3e3,Math.ceil(this.duration*this.fps));s++)for(const r of[1,-1]){const d=this.copyKeyData(i,t,r*s/this.fps,e);if(d)return this._clips=d,this.selectedEffectKeys=new Set(Array.from(e,([t,e])=>{const[i,s,r]=JSON.parse(t);return this.effectToken(i,s,r,e)})),a(this,"db-change",{clips:this.clips,clipId:t,action:"keyframe"}),void this.render()}}effectToken(t,e,i,s){return JSON.stringify([t,e,i,s])}syncEffectSelection(){for(const t of this.querySelectorAll("[data-key-token]"))t.setAttribute("aria-pressed",String(this.selectedEffectKeys.has(t.dataset.keyToken)))}finishMarquee(t=!1){const e=this.marquee;e&&(this.marquee=void 0,e.box.remove(),t&&(this.selectedEffectKeys=e.before),this.hasPointerCapture(e.pointer)&&this.releasePointerCapture(e.pointer),this.render())}editSelectedKeys(t,e){const i=this._clips.find(e=>e.id===t);!i||this.disabled||this._tracks.find(t=>t.id===i.trackId)?.locked||(this._clips=this._clips.map(i=>i.id!==t?i:{...i,effects:i.effects?.map(t=>!1===t.enabled?t:{...t,properties:t.properties?.map(s=>{let r=s.keyframes;if(e)for(const a of s.keyframes)this.selectedEffectKeys.has(this.effectToken(i.id,t.id,s.id,a.id))&&(r=p(r,a.id,e));else r=r.filter(e=>!this.selectedEffectKeys.has(this.effectToken(i.id,t.id,s.id,e.id)));return{...s,keyframes:r}})})}),e||this.selectedEffectKeys.clear(),a(this,"db-change",{clips:this.clips,clipId:t,action:"keyframe"}),this.render(),this.viewport?.focus({preventScroll:!0}))}finishEffectDrag(t=!1){const e=this.effectDrag;e&&(this.effectDrag=void 0,this.effectEditing=!1,this.hasPointerCapture(e.pointer)&&this.releasePointerCapture(e.pointer),t?this._clips=e.before:e.copies&&e.changed&&(this.selectedEffectKeys=new Set(Array.from(e.copies,([t,e])=>{const[i,s,r]=JSON.parse(t);return this.effectToken(i,s,r,e)}))),(t||e.changed)&&a(this,t?"db-input":"db-change",{clips:this.clips,clipId:e.clipId,action:"keyframe",effectId:e.effectId,propertyId:e.propertyId,...t?{cancelled:!0}:{}}),this.render())}finish(t=!1){const e=this.drag;e&&(cancelAnimationFrame(this.raf),this.drag=void 0,this.hasPointerCapture(e.id)&&this.releasePointerCapture(e.id),t?(this._clips=e.before,this.render(),a(this,"db-input",{clips:this.clips,clipId:e.clipId,action:e.kind,cancelled:!0})):e.changed&&e.clipId&&"seek"!==e.kind&&a(this,"db-change",{clips:this.clips,clipId:e.clipId,action:e.kind}),this.render(),this.viewport?.focus({preventScroll:!0}))}rollback(){this.finishMarquee(!0),this.finishEffectDrag(!0),this.finish(!0)}splitSelected(){const t=this._clips.find(t=>t.id===this.selectedClipId);if(this.disabled||!t||this._tracks.find(e=>e.id===t.trackId)?.locked||t.duration<2*this.minClipDuration||this.currentTime<t.start+this.minClipDuration||this.currentTime>t.start+t.duration-this.minClipDuration)return;const e=this.currentTime;let i=t.id+"-split";for(;this._clips.some(t=>t.id===i);)i+="-1";this._clips=this._clips.flatMap(s=>s.id===t.id?[{...s,duration:e-s.start,fadeOut:0,fadeIn:Math.min(s.fadeIn||0,e-s.start)},{...s,id:i,fadeIn:0,fadeOut:Math.min(s.fadeOut||0,s.start+s.duration-e),start:e,duration:s.start+s.duration-e,sourceOffset:(s.sourceOffset||0)+e-s.start}]:[s]),this.render(),a(this,"db-change",{clips:this.clips,action:"split",clipId:t.id})}trackIndexAt(t){const e=Array.from(this.canvas.querySelectorAll(".db-vt__row"));let i=0;return e.forEach((e,s)=>{t>=e.getBoundingClientRect().top&&(i=s)}),i}finishResize(t=!1){const e=this.resizeDrag;e&&(this.resizeDrag=void 0,this.hasPointerCapture(e.id)&&this.releasePointerCapture(e.id),t?(this._tracks=e.beforeTracks,this.height=e.beforeHeight,this.render(),a(this,e.trackId?"db-track-input":"db-resize-input",e.trackId?{tracks:this.tracks,cancelled:!0}:{height:this.height,cancelled:!0})):a(this,e.trackId?"db-track-change":"db-resize",e.trackId?{tracks:this.tracks}:{height:this.height}))}changeZoom(t,e,i="keyboard"){if(!this.viewport)return;const s=this.zoom,r=e??Math.min(this.viewport.clientWidth-8,Math.max(152,this.labelWidth+this.currentTime*this.scale-this.viewport.scrollLeft)),n=(this.viewport.scrollLeft+r-this.labelWidth)/this.scale;this.zoom=Math.round(100*d(t,10,800))/100,this.viewport.scrollLeft="fit"===i?0:Math.max(0,this.labelWidth+n*this.scale-r),this.zoom!==s&&a(this,"db-zoom-change",{value:this.zoom,previousValue:s,anchorTime:n,source:i})}}customElements.get("db-video-timeline")||customElements.define("db-video-timeline",DbVideoTimeline);export{DbVideoTimeline};
1
+ import"./db-button.js";import{DbContextMenu as t}from"./db-context-menu.js";import{DbKeyframeEditor as e}from"./db-keyframe-editor.js";import{t as i,subscribeDbLocale as s}from"../core/i18n.js";import{frameTime as r,clamp as a,emitEditor as d,editorEditable as n,finite as o,editorButton as h,evaluateKeyframes as c,editorTimecode as l,applyKeyframeEasing as p}from"./media-editor-model.js";class DbVideoTimeline extends HTMLElement{constructor(){super(...arguments),this.effectTreeId=`effects-${crypto.randomUUID()}`,this.effectKeySelection=new Map,this.expandedEffects=new Set,this.activeProperty=new Map,this.effectEditing=!1,this.undoClips=[],this.redoClips=[],this.committedClips=[],this.selectedEffectKeys=new Set,this._tracks=[],this._clips=[],this._markers=[],this.raf=0,this.down=t=>{if(this.drag||this.resizeDrag)return;if(this.disabled||0!==t.button||!(t.target instanceof Element)||t.target.closest("db-button,db-keyframe-editor,[data-effect-clip]"))return;const e=t.target.closest("[data-track-resize]"),i=t.target.closest("[data-pane-resize]");if(e||i){t.preventDefault(),this.viewport?.focus({preventScroll:!0});const i=this._tracks.find(t=>t.id===e?.dataset.trackResize);return this.resizeDrag={id:t.pointerId,trackId:i?.id,startY:t.clientY,initial:i?this.trackHeight(i):this.viewport.getBoundingClientRect().height,beforeTracks:this.tracks,beforeHeight:this.height},void this.setPointerCapture(t.pointerId)}const s=t.target.closest("[data-marker]");if(s)return void this.seek(Number(s.dataset.marker));const r=t.target.closest("[data-clip]"),a=this._clips.find(t=>t.id===r?.dataset.clip);if(a){if(this.selectedEffectKeys.clear(),this.select(a.id),this._tracks.find(t=>t.id===a.trackId)?.locked)return;this.viewport?.focus({preventScroll:!0})}else if(!t.target.closest("[data-seek],[data-playhead]"))return;if("touch"===t.pointerType&&!a&&!t.target.closest(".db-vt__ruler,[data-playhead]"))return;t.preventDefault();const d=t.target.closest("[data-trim]")?.dataset.trim,n=t.target.closest("[data-fade]")?.dataset.fade;this.drag={kind:a?"in"===n?"fade-in":"out"===n?"fade-out":"start"===d?"trim-start":"end"===d?"trim-end":"move":"seek",id:t.pointerId,clipId:a?.id,originX:t.clientX,originY:t.clientY,scroll:this.viewport.scrollLeft,before:this.clips,x:t.clientX,y:t.clientY,changed:!1,shift:t.shiftKey,ripple:t.metaKey||t.ctrlKey,duplicate:a&&!d&&!n&&t.altKey?`clip-${crypto.randomUUID()}`:void 0},this.setPointerCapture(t.pointerId),a||this.seek(this.timeAt(t.clientX)),this.raf=requestAnimationFrame(this.autoScroll)},this.move=t=>{if(this.rangeDrag?.pointer===t.pointerId){const e=this.rangeDrag,i=e.before.find(t=>t.id===e.clipId),s=i.effects.find(t=>t.id===e.effectId),n=r((t.clientX-e.x+(this.viewport?.scrollLeft||0)-e.scroll)/this.scale,this.fps),o=Math.max(0,s.start||0),h=Math.max(1/this.fps,s.duration??i.duration);let c=o,l=h;"move"===e.kind?c=Math.max(0,c+n):"start"===e.kind?(c=a(c+n,0,c+l-1/this.fps),l=o+h-c):l=Math.max(1/this.fps,l+n),this._clips=e.before.map(t=>t.id!==i.id?t:{...t,effects:t.effects?.map(t=>t.id!==s.id?t:{...t,start:r(c,this.fps),duration:r(l,this.fps)})});const p=this.querySelector(`[data-effect-clip="${CSS.escape(i.id)}"] [data-range-effect="${CSS.escape(s.id)}"]`);return p&&(p.style.left=(i.start+c)*this.scale+"px",p.style.width=`${Math.max(12,l*this.scale)}px`),void d(this,"db-input",{clips:this.clips,clipId:i.id,effectId:s.id,action:"effect-range"})}if(this.labelDrag?.id!==t.pointerId){if(this.marquee?.pointer===t.pointerId){const e=this.marquee,i=this.getBoundingClientRect(),s=Math.min(e.x,t.clientX),r=Math.min(e.y,t.clientY),a=Math.max(e.x,t.clientX),d=Math.max(e.y,t.clientY);Object.assign(e.box.style,{left:s-i.left+"px",top:r-i.top+"px",width:a-s+"px",height:d-r+"px"}),this.selectedEffectKeys=new Set(e.before);for(const t of this.querySelectorAll("[data-key-token]")){if(t.closest("[data-effect-clip]")?.dataset.effectClip!==e.clipId||t.hasAttribute("disabled"))continue;const i=t.getBoundingClientRect(),n=i.x+i.width/2,o=i.y+i.height/2;n>=s&&n<=a&&o>=r&&o<=d&&this.selectedEffectKeys.add(t.dataset.keyToken)}return void this.syncEffectSelection()}if(this.effectDrag?.pointer===t.pointerId){const e=this.effectDrag,i=e.before.find(t=>t.id===e.clipId);if(t.altKey&&!e.copies&&(e.copies=this.makeKeyCopyIds()),e.copies){const s=r((t.clientX-e.x)/this.scale,this.fps);if(0===s){this._clips=e.before,e.changed=!1;for(const t of this.querySelectorAll("[data-duplicate-preview]"))t.remove();return void this.syncEffectSelection()}const a=this.copyKeyData(e.before,e.clipId,s,e.copies);if(!a)return;this._clips=a,e.changed=!0;for(const[t,s]of e.copies){const[e,r,d]=JSON.parse(t),n=this.effectToken(e,r,d,s),o=Array.from(this.querySelectorAll("[data-key-token]")).find(e=>e.dataset.keyToken===t);if(!o)continue;let h=Array.from(this.querySelectorAll("[data-key-token]")).find(t=>t.dataset.keyToken===n);h||(h=o.cloneNode(!0),h.dataset.keyToken=n,h.dataset.propertyKey=s,h.dataset.duplicatePreview="true",o.parentElement.append(h));const c=a.find(t=>t.id===e)?.effects?.find(t=>t.id===r)?.properties?.find(t=>t.id===d)?.keyframes.find(t=>t.id===s);c&&(h.style.left=(i.start+c.time)*this.scale+"px"),h.setAttribute("aria-pressed","true"),o.setAttribute("aria-pressed","false")}return void d(this,"db-input",{clips:this.clips,clipId:e.clipId,action:"keyframe"})}let s=-1/0,n=1/0;for(const t of i.effects||[])for(const e of t.properties||[]){const r=[...e.keyframes].sort((t,e)=>t.time-e.time);r.forEach((a,d)=>{if(!this.selectedEffectKeys.has(this.effectToken(i.id,t.id,e.id,a.id)))return;const o=r.slice(0,d).reverse().find(s=>!this.selectedEffectKeys.has(this.effectToken(i.id,t.id,e.id,s.id))),h=r.slice(d+1).find(s=>!this.selectedEffectKeys.has(this.effectToken(i.id,t.id,e.id,s.id)));s=Math.max(s,(o?o.time+1/this.fps:0)-a.time),n=Math.min(n,(h?h.time-1/this.fps:i.duration)-a.time)})}const o=r(a((t.clientX-e.x)/this.scale,s,n),this.fps);this._clips=e.before.map(t=>t.id!==e.clipId?t:{...t,effects:t.effects?.map(e=>({...e,properties:e.properties?.map(i=>({...i,keyframes:i.keyframes.map(s=>this.selectedEffectKeys.has(this.effectToken(t.id,e.id,i.id,s.id))?{...s,time:r(s.time+o,this.fps)}:s)}))}))}),e.changed=0!==o;for(const t of this.querySelectorAll("[data-key-token]")){if(!this.selectedEffectKeys.has(t.dataset.keyToken))continue;const[e,i,s,r]=JSON.parse(t.dataset.keyToken),a=this._clips.find(t=>t.id===e),d=a?.effects?.find(t=>t.id===i)?.properties?.find(t=>t.id===s)?.keyframes.find(t=>t.id===r);a&&d&&(t.style.left=(a.start+d.time)*this.scale+"px")}return void d(this,"db-input",{clips:this.clips,clipId:e.clipId,action:"keyframe",effectId:e.effectId,propertyId:e.propertyId})}if(this.resizeDrag?.id===t.pointerId){const e=this.resizeDrag,i=Math.round(e.initial+t.clientY-e.startY);return void(e.trackId?(this._tracks=e.beforeTracks.map(t=>t.id===e.trackId?{...t,height:a(i,32,120)}:t),this.render(),d(this,"db-track-input",{tracks:this.tracks})):(this.height=a(i,120,720),d(this,"db-resize-input",{height:this.height})))}this.drag?.id===t.pointerId&&(this.drag.x=t.clientX,this.drag.y=t.clientY,this.drag.shift=t.shiftKey,this.drag.ripple=t.metaKey||t.ctrlKey,"move"===this.drag.kind&&t.altKey&&!this.drag.duplicate&&(this.drag.duplicate=`clip-${crypto.randomUUID()}`),this.updateDrag())}else this.labelWidth=this.labelDrag.width+t.clientX-this.labelDrag.x},this.autoScroll=()=>{if(!this.drag||!this.viewport)return;const t=this.viewport.getBoundingClientRect(),e=this.drag.x,i=e>t.right-48?a((e-t.right+48)/3,0,18):e<t.left+this.labelWidth+32?-a((t.left+176-e)/3,0,18):0,s=this.viewport.scrollLeft;this.viewport.scrollLeft+=i,s!==this.viewport.scrollLeft&&this.updateDrag(),this.raf=requestAnimationFrame(this.autoScroll)},this.recordTimelineHistory=t=>{if(t.target!==this)return;const e=t.detail;e?.clips&&"undo"!==e.action&&"redo"!==e.action&&JSON.stringify(e.clips)!==JSON.stringify(this.committedClips)&&(this.undoClips.push(structuredClone(this.committedClips)),this.undoClips.length>100&&this.undoClips.shift(),this.redoClips=[],this.committedClips=structuredClone(e.clips))},this.effectContextMenu=e=>{const s=e.target,r=s.closest("[data-effect-clip]");if(!r)return;const a=this._clips.find(t=>t.id===r.dataset.effectClip);if(!a)return;e.preventDefault(),e.stopPropagation(),this.viewport?.focus({preventScroll:!0});const d=s.closest("[data-key-token]");d&&!this.selectedEffectKeys.has(d.dataset.keyToken)&&(this.selectedEffectKeys.clear(),this.selectedEffectKeys.add(d.dataset.keyToken),this.syncEffectSelection());const n=this.disabled||!!this._tracks.find(t=>t.id===a.trackId)?.locked||!this.selectedEffectKeys.size||!!d?.hasAttribute("disabled");this.keyMenu||(this.keyMenu=new t,this.append(this.keyMenu)),this.keyMenu.items=[{id:"duplicate",label:i("media.duplicateKeys"),icon:"copy",disabled:n},{id:"easing",label:i("media.keyEasing"),disabled:n,subItems:["linear","hold","ease-in","ease-out","ease-in-out"].map(t=>({id:`ease:${t}`,label:i("media."+t)}))},{id:"divider",divider:!0},{id:"delete",label:i("media.deleteKey"),icon:"trash",variant:"danger",disabled:n},{id:"undo",label:i("media.undo"),disabled:!this.undoClips.length}],this.keyMenu.oncontextmenu=t=>t.preventDefault(),this.keyMenu.addEventListener("db-select",this.keyMenuSelect),this.keyMenu.dataset.clipId=a.id,this.keyMenu.openAt(e.clientX,e.clientY)},this.keyMenuSelect=t=>{t.stopPropagation();const e=t.detail.item.id,i=this.keyMenu.dataset.clipId;"duplicate"===e?this.duplicateSelectedKeys(i):"delete"===e?this.editSelectedKeys(i):e.startsWith("ease:")?this.editSelectedKeys(i,e.slice(5)):"undo"===e&&this.restoreTimelineHistory()},this.up=t=>{if(this.rangeDrag?.pointer!==t.pointerId)return this.labelDrag?.id===t.pointerId?(this.labelDrag=void 0,this.hasPointerCapture(t.pointerId)&&this.releasePointerCapture(t.pointerId),void d(this,"db-label-resize",{width:this.labelWidth})):void(this.marquee?.pointer!==t.pointerId?this.effectDrag?.pointer!==t.pointerId?this.resizeDrag?.id!==t.pointerId?this.drag?.id===t.pointerId&&(this.drag.changed&&(this.drag.x=t.clientX,this.drag.y=t.clientY,this.updateDrag()),this.finish()):this.finishResize():this.finishEffectDrag():this.finishMarquee());this.rangeFinish()},this.cancel=t=>{if(this.labelDrag?.id===t.pointerId){if("lostpointercapture"===t.type&&this.hasPointerCapture(t.pointerId))return;const e=this.labelDrag;return this.labelDrag=void 0,void(this.labelWidth=e.width)}if(this.rangeDrag?.pointer===t.pointerId){if("lostpointercapture"===t.type&&(t.target!==this||this.hasPointerCapture(t.pointerId)))return;return void this.rangeFinish(!0)}[this.drag?.id,this.resizeDrag?.id,this.effectDrag?.pointer,this.marquee?.pointer].includes(t.pointerId)&&("lostpointercapture"!==t.type||t.target===this&&!this.hasPointerCapture(t.pointerId))&&(this.finishMarquee(!0),this.finishEffectDrag(!0),this.finish(!0),this.finishResize(!0))},this.onActionClick=t=>{const e=t.target,i=e.dataset.action;if(!this.disabled&&("scroll"===i&&this.viewport?.scrollBy({left:Math.max(160,this.viewport.clientWidth-this.labelWidth),behavior:matchMedia("(prefers-reduced-motion: reduce)").matches?"instant":"smooth"}),"lock"===i||"mute"===i)){const t=e.dataset.track;this._tracks=this._tracks.map(e=>e.id===t?{...e,["lock"===i?"locked":"muted"]:!e["lock"===i?"locked":"muted"]}:e),this.render(),d(this,"db-track-change",{tracks:this.tracks})}},this.doubleResize=t=>{if(this.disabled||!(t.target instanceof Element))return;const e=t.target.closest("[data-track-resize],[data-pane-resize]");e&&(e.dataset.trackResize?(this._tracks=this._tracks.map(t=>t.id===e.dataset.trackResize?{...t,height:this.rowHeight}:t),this.render(),d(this,"db-track-change",{tracks:this.tracks})):(this.height=0,d(this,"db-resize",{height:0})))},this.wheel=t=>{if(this.disabled||!this.shortcuts||this.drag||this.resizeDrag||!t.metaKey&&!t.ctrlKey)return;t.preventDefault(),t.stopPropagation();const e=Math.exp(-t.deltaY*(1===t.deltaMode?.04:.002));this.changeZoom(this.zoom*e,t.clientX-this.viewport.getBoundingClientRect().left,"wheel")},this.key=t=>{if(!this.disabled&&!n(t.target)&&(t.metaKey||t.ctrlKey)&&"z"===t.key.toLowerCase()&&!this.drag&&!this.effectDrag&&!this.effectEditing)return void((t.shiftKey?this.redoClips:this.undoClips).length&&(t.preventDefault(),t.stopPropagation(),this.restoreTimelineHistory(t.shiftKey)));if(!(this.disabled||n(t.target)||t.metaKey||t.ctrlKey||t.altKey||"u"!==t.key.toLowerCase())){const e=this._clips.find(t=>t.id===this.selectedClipId&&t.effects?.some(t=>t.properties?.length))||this._clips.find(t=>t.effects?.some(t=>t.properties?.length));if(e){t.preventDefault(),t.stopPropagation();const i=!this.expandedEffects.has(e.id);for(const t of[e.id,...(e.effects||[]).map(t=>`${e.id}/${t.id}`)])i?this.expandedEffects.add(t):this.expandedEffects.delete(t);this.render(),this.viewport?.focus({preventScroll:!0})}return}if("Escape"===t.key&&this.rangeDrag)return t.preventDefault(),void this.rangeFinish(!0);if("Escape"===t.key&&this.labelDrag){t.preventDefault();const e=this.labelDrag;return this.labelDrag=void 0,this.labelWidth=e.width,void(this.hasPointerCapture(e.id)&&this.releasePointerCapture(e.id))}if("Escape"===t.key&&this.marquee)return t.preventDefault(),void this.finishMarquee(!0);if(!this.disabled&&!n(t.target)&&!t.target?.closest("db-keyframe-editor")&&["Delete","Backspace"].includes(t.key)&&this.selectedEffectKeys.size){t.preventDefault();const e=new Set(Array.from(this.selectedEffectKeys).map(t=>JSON.parse(t)[0]));for(const t of e)this.editSelectedKeys(t);return}if(this.disabled||n(t.target)||t.isComposing||t.target?.closest("db-keyframe-editor"))return;if("Escape"===t.key&&this.effectDrag)return t.preventDefault(),void this.finishEffectDrag(!0);if("Escape"===t.key&&this.drag)return t.preventDefault(),void this.finish(!0);if("Escape"===t.key&&this.resizeDrag)return t.preventDefault(),void this.finishResize(!0);const e=t.target.closest("[data-track-resize],[data-pane-resize]");if(e&&["ArrowUp","ArrowDown","Home"].includes(t.key)){t.preventDefault();const i="ArrowUp"===t.key?-1:1;return void(e.dataset.trackResize?(this._tracks=this._tracks.map(s=>s.id===e.dataset.trackResize?{...s,height:"Home"===t.key?this.rowHeight:a(this.trackHeight(s)+i*(t.shiftKey?8:2),32,120)}:s),this.render(),d(this,"db-track-change",{tracks:this.tracks}),this.querySelector(`[data-track-resize="${CSS.escape(e.dataset.trackResize)}"]`)?.focus()):(this.height="Home"===t.key?0:a((this.height||this.viewport.clientHeight)+i*(t.shiftKey?24:8),120,720),d(this,"db-resize",{height:this.height}),this.paneGrip?.focus()))}if(this.shortcuts&&!t.altKey&&["+","=","-","0"].includes(t.key))return t.preventDefault(),t.stopPropagation(),void("0"===t.key?this.changeZoom((this.viewport.clientWidth-this.labelWidth)/this.duration/.8,void 0,"fit"):this.changeZoom(this.zoom*("-"===t.key?1/1.2:1.2)));if(this.disabled||n(t.target)||t.ctrlKey||t.metaKey||t.altKey)return;const i=t.target.closest("[data-trim],[data-fade]");if(i&&["ArrowLeft","ArrowRight"].includes(t.key)){const e=i.closest("[data-clip]"),s=this._clips.find(t=>t.id===e?.dataset.clip);if(!s||this._tracks.find(t=>t.id===s.trackId)?.locked)return;t.preventDefault();const r="start"===i.dataset.trim?"trim-start":"end"===i.dataset.trim?"trim-end":"in"===i.dataset.fade?"fade-in":"fade-out";this.drag={kind:r,id:-1,clipId:s.id,originX:0,originY:0,scroll:this.viewport.scrollLeft,before:this.clips,x:("ArrowLeft"===t.key?-1:1)*(t.shiftKey?10:1)/this.fps*this.scale,y:0,changed:!1,shift:!0,ripple:t.metaKey||t.ctrlKey},this.updateDrag(),this.finish();const a=i.dataset.focus;return void Array.from(this.querySelectorAll("[data-focus]")).find(t=>t.dataset.focus===a)?.focus({preventScroll:!0})}if(["ArrowLeft","ArrowRight","Home","End"].includes(t.key))t.preventDefault(),this.seek("Home"===t.key?0:"End"===t.key?this.duration:this.currentTime+("ArrowLeft"===t.key?-1:1)*(t.shiftKey?10:1)/this.fps),this.scrollToTime(this.currentTime);else if("s"===t.key.toLowerCase())t.preventDefault(),this.splitSelected();else if("Delete"===t.key||"Backspace"===t.key){const e=this._clips.find(t=>t.id===this.selectedClipId);if(!e||this._tracks.find(t=>t.id===e.trackId)?.locked)return;t.preventDefault(),this._clips=this._clips.filter(t=>t.id!==e.id),this.select(""),this.render(),d(this,"db-change",{clips:this.clips,clipId:e.id,action:"delete"})}else"Enter"===t.key&&t.target instanceof HTMLElement&&t.target.dataset.clip&&(t.preventDefault(),this.select(t.target.dataset.clip))}}static get observedAttributes(){return["duration","fps","current-time","zoom","snap","disabled","selected-clip-id","row-height","label-width","layer-gap","height","min-clip-duration","shortcuts"]}get layerGap(){return a(this.number("layer-gap",0),0,32)}set layerGap(t){this.setAttribute("layer-gap",String(t))}isSource(t){return"source"===t.scope||!t.scope&&"transform"===t.kind}rangeFinish(t=!1){const e=this.rangeDrag;if(!e)return;this.rangeDrag=void 0,this.effectEditing=!1;const i=JSON.stringify(this._clips)!==JSON.stringify(e.before);t&&(this._clips=e.before),this.hasPointerCapture(e.pointer)&&this.releasePointerCapture(e.pointer),i&&d(this,t?"db-input":"db-change",{clips:this.clips,clipId:e.clipId,effectId:e.effectId,action:"effect-range",...t?{cancelled:!0}:{}}),this.render()}renderRange(t,e,s,n){const o=this.node("div","db-vt__effect-range-lane");o.style.width=this.contentDuration*this.scale+"px";const h=this.node("div","db-vt__effect-range");h.dataset.rangeEffect=s.id,h.title=s.label,h.setAttribute("aria-label",s.label),h.append(this.node("span","db-vt__effect-range-name",s.label));const c=Math.max(0,s.start||0),l=Math.max(1/this.fps,s.duration??e.duration);h.style.left=(e.start+c)*this.scale+"px",h.style.width=`${Math.max(12,l*this.scale)}px`;for(const t of["start","end"]){const o=this.node("span","db-vt__effect-range-grip");o.dataset.rangeTrim=t,o.tabIndex=this.disabled||n.locked?-1:0,o.setAttribute("role","slider"),o.setAttribute("aria-label",`${i("start"===t?"media.trimStart":"media.trimEnd")} · ${s.label}`),o.setAttribute("aria-valuenow",String("start"===t?c:c+l)),o.setAttribute("aria-valuemin","0"),o.setAttribute("aria-valuemax",String(this.contentDuration)),o.addEventListener("keydown",i=>{if(this.disabled||n.locked||!["ArrowLeft","ArrowRight"].includes(i.key))return;i.preventDefault(),i.stopPropagation();const o=("ArrowLeft"===i.key?-1:1)*(i.shiftKey?10:1)/this.fps;let h=c,p=l;"start"===t?(h=a(h+o,0,h+p-1/this.fps),p=c+l-h):p=Math.max(1/this.fps,p+o),s.start=r(h,this.fps),s.duration=r(p,this.fps),d(this,"db-change",{clips:this.clips,clipId:e.id,effectId:s.id,action:"effect-range"}),this.render()}),h.append(o)}h.addEventListener("pointerdown",t=>{0!==t.button||this.disabled||n.locked||(t.preventDefault(),t.stopPropagation(),this.rangeDrag={pointer:t.pointerId,clipId:e.id,effectId:s.id,kind:t.target.dataset.rangeTrim||"move",x:t.clientX,scroll:this.viewport?.scrollLeft||0,before:this.clips},this.effectEditing=!0,this.setPointerCapture(t.pointerId))}),o.append(h),t.append(o)}get labelWidth(){return a(this.number("label-width",144),120,480)}set labelWidth(t){this.setAttribute("label-width",String(a(t,120,480)))}get contentDuration(){const t=Math.max(this.duration,...this._clips.flatMap(t=>[t.start+t.duration,...(t.effects||[]).filter(t=>!this.isSource(t)).map(e=>t.start+Math.max(0,e.start||0)+(e.duration??t.duration))]));return t>this.duration?t+Math.max(1,.15*this.duration):t}labelGrip(t){const e=this.node("span","db-vt__label-resize");e.tabIndex=this.disabled?-1:0,e.setAttribute("aria-disabled",String(this.disabled)),e.setAttribute("role","separator"),e.setAttribute("aria-orientation","vertical"),e.setAttribute("aria-label",i("media.resizeLabel")),e.setAttribute("aria-valuemin","120"),e.setAttribute("aria-valuemax","480"),e.setAttribute("aria-valuenow",String(this.labelWidth)),e.addEventListener("pointerdown",t=>{this.disabled||0!==t.button||(t.preventDefault(),t.stopPropagation(),this.labelDrag={id:t.pointerId,x:t.clientX,width:this.labelWidth},this.setPointerCapture(t.pointerId))}),e.addEventListener("keydown",t=>{this.disabled||["ArrowLeft","ArrowRight","Home"].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.labelWidth="Home"===t.key?144:this.labelWidth+("ArrowLeft"===t.key?-1:1)*(t.shiftKey?24:8),d(this,"db-label-resize",{width:this.labelWidth}))}),t.append(e)}get rowHeight(){return a(this.number("row-height",42),32,120)}set rowHeight(t){this.setAttribute("row-height",String(t))}get height(){const t=this.number("height",0);return t>0?a(t,120,720):0}set height(t){t>0?this.setAttribute("height",String(t)):this.removeAttribute("height")}get minClipDuration(){return Math.min(this.duration,Math.max(1/this.fps,Math.ceil(this.number("min-clip-duration",.5)*this.fps)/this.fps))}set minClipDuration(t){this.setAttribute("min-clip-duration",String(t))}get shortcuts(){return"false"!==this.getAttribute("shortcuts")}set shortcuts(t){this.setAttribute("shortcuts",String(t))}trackHeight(t){return a(o(t.height,this.rowHeight),32,120)}number(t,e){const i=this.getAttribute(t);return i?.trim()&&Number.isFinite(Number(i))?Number(i):e}get duration(){return a(this.number("duration",30),1/this.fps,86400)}set duration(t){this.setAttribute("duration",String(t))}get fps(){return a(Math.round(this.number("fps",30)),1,120)}set fps(t){this.setAttribute("fps",String(t))}get zoom(){return a(this.number("zoom",100),10,800)}set zoom(t){this.setAttribute("zoom",String(t))}get currentTime(){return r(a(this.number("current-time",0),0,this.duration),this.fps)}set currentTime(t){this.setAttribute("current-time",String(t))}get snap(){return"false"!==this.getAttribute("snap")}set snap(t){this.setAttribute("snap",String(t))}get disabled(){return this.hasAttribute("disabled")}set disabled(t){this.toggleAttribute("disabled",t)}get selectedClipId(){return this.getAttribute("selected-clip-id")||""}set selectedClipId(t){this.setAttribute("selected-clip-id",t)}get tracks(){return this._tracks.map(t=>({...t}))}set tracks(t){const e=new Set;this._tracks=(Array.isArray(t)?t:[]).filter(t=>t&&"string"==typeof t.id&&!e.has(t.id)&&!!e.add(t.id)).map(t=>({...t,label:String(t.label||t.id),kind:["video","audio","title","mixed"].includes(t.kind)?t.kind:"video"})),this.render()}get clips(){return this._clips.map(t=>({...t,...t.effects?{effects:t.effects.map(t=>structuredClone(t))}:{}}))}set clips(t){if(this.drag||this.effectEditing)return;const e=new Set;this._clips=(Array.isArray(t)?t:[]).filter(t=>t&&"string"==typeof t.id&&!e.has(t.id)&&!!e.add(t.id)).map(t=>{const e=r(Math.max(0,o(t.start,0)),this.fps),i=Math.max(1/this.fps,r(o(t.duration,1),this.fps)),s=r(a(o(t.fadeIn,0),0,i),this.fps),d=r(a(o(t.fadeOut,0),0,Math.max(0,i-s)),this.fps);return{...t,label:String(t.label||t.id),start:e,duration:i,sourceOffset:Math.max(0,o(t.sourceOffset,0)),...void 0!==t.fadeIn?{fadeIn:s}:{},...void 0!==t.fadeOut?{fadeOut:d}:{},...t.effects?{effects:Array.isArray(t.effects)?t.effects.filter(t=>t&&"string"==typeof t.id).map(t=>({...t,label:String(t.label||t.id)})):[]}:{}}});for(const t of this._clips)if(!t.effects?.some(t=>this.isSource(t))){let e="source-transform";for(;t.effects?.some(t=>t.id===e);)e+="-source";const s=[["x","X",-1e4,1e4,0],["y","Y",-1e4,1e4,0],["size",i("media.sourceSize"),0,1e4,100],["rotation",i("media.sourceRotation"),-36e3,36e3,0]].map(([t,e,i,s,r])=>({id:String(t),label:String(e),min:Number(i),max:Number(s),keyframes:[{id:`${t}-initial`,time:0,value:Number(r)}]}));t.effects=[{id:e,label:i("media.sourceTransform"),kind:"transform",scope:"source",properties:s},...t.effects||[]]}JSON.stringify(this._clips)!==JSON.stringify(this.committedClips)&&(this.committedClips=this.clips,this.undoClips=[],this.redoClips=[]),this.render()}get markers(){return this._markers.map(t=>({...t}))}set markers(t){this._markers=(Array.isArray(t)?t:[]).filter(t=>t&&"string"==typeof t.id&&Number.isFinite(t.time)).map(t=>({...t,label:String(t.label),time:a(t.time,0,this.duration)})),this.render()}get scale(){return.8*this.zoom}connectedCallback(){if(this.classList.add("designbase-wc-video-timeline"),this.setAttribute("role","region"),!this.viewport){this.viewport=document.createElement("div"),this.viewport.className="db-vt__viewport",this.viewport.tabIndex=0,this.canvas=document.createElement("div"),this.canvas.className="db-vt__canvas",this.viewport.append(this.canvas),this.hint=document.createElement("div"),this.hint.className="db-vt__hint";const t=h(i("media.more"),"chevron-right","scroll");this.hint.append(t),this.paneGrip=document.createElement("div"),this.paneGrip.className="db-vt__pane-resize",this.paneGrip.dataset.paneResize="",this.paneGrip.tabIndex=0,this.paneGrip.setAttribute("role","separator"),this.paneGrip.setAttribute("aria-orientation","horizontal"),this.append(this.viewport,this.hint,this.paneGrip),this.viewport.addEventListener("scroll",()=>this.syncHint())}this.viewport.addEventListener("wheel",this.wheel,{passive:!1}),this.addEventListener("dblclick",this.doubleResize),this.addEventListener("db-change",this.recordTimelineHistory,!0),this.addEventListener("contextmenu",this.effectContextMenu),this.addEventListener("pointerdown",this.down),this.addEventListener("pointermove",this.move),this.addEventListener("pointerup",this.up),this.addEventListener("pointercancel",this.cancel),this.addEventListener("lostpointercapture",this.cancel),this.addEventListener("keydown",this.key),this.addEventListener("db-click",this.onActionClick),this.unsubscribe=s(()=>this.render()),this.resize=new ResizeObserver(()=>this.syncHint()),this.resize.observe(this),this.render()}disconnectedCallback(){if(this.labelDrag){const t=this.labelDrag;this.labelDrag=void 0,this.labelWidth=t.width}this.rollback(),this.resize?.disconnect(),this.unsubscribe?.(),this.viewport?.removeEventListener("wheel",this.wheel),this.removeEventListener("dblclick",this.doubleResize),this.finishResize(!0),this.removeEventListener("db-change",this.recordTimelineHistory,!0),this.removeEventListener("contextmenu",this.effectContextMenu),this.keyMenu?.remove(),this.keyMenu=void 0,this.removeEventListener("pointerdown",this.down),this.removeEventListener("pointermove",this.move),this.removeEventListener("pointerup",this.up),this.removeEventListener("pointercancel",this.cancel),this.removeEventListener("lostpointercapture",this.cancel),this.removeEventListener("keydown",this.key),this.removeEventListener("db-click",this.onActionClick)}attributeChangedCallback(t){"disabled"===t&&this.disabled&&(this.finishEffectDrag(!0),this.finish(!0),this.finishResize(!0)),"current-time"===t?this.syncCursor():this.render()}syncHint(){for(const t of this.querySelectorAll(".db-vt__selection-tools"))t.style.width=`${this.viewport?.clientWidth||240}px`;if(this.syncCursor(),this.viewport)for(const t of this.querySelectorAll("[data-marker]"))t.style.visibility=Number(t.dataset.marker)*this.scale<this.viewport.scrollLeft?"hidden":"";this.viewport&&this.hint&&(this.hint.hidden=this.viewport.scrollLeft+this.viewport.clientWidth>=this.viewport.scrollWidth-2)}syncCursor(){for(const t of this.querySelectorAll("[data-effect-property]")){const e=this._clips.find(e=>e.id===t.closest("[data-effect-clip]")?.dataset.effectClip),i=e?.effects?.find(e=>e.id===t.dataset.effectId)?.properties?.find(e=>e.id===t.dataset.effectProperty),s=t.querySelector(".db-vt__property-value");e&&i&&s&&(s.textContent=String(Math.round(100*c(i.keyframes,this.currentTime-e.start))/100))}if(!this.effectEditing)for(const t of this.querySelectorAll("db-keyframe-editor[data-effect-editor]")){const e=this._clips.find(e=>e.id===t.closest("[data-effect-clip]")?.dataset.effectClip);e&&(t.currentTime=a(this.currentTime-e.start,0,e.duration))}this.cursor&&(this.cursor.style.left=`${this.labelWidth+this.currentTime*this.scale}px`,this.cursor.style.visibility=this.currentTime*this.scale<(this.viewport?.scrollLeft||0)?"hidden":"",this.cursor.setAttribute("aria-valuenow",String(this.currentTime)),this.cursor.setAttribute("aria-valuetext",l(this.currentTime,this.fps)))}seek(t){this.currentTime=r(a(t,0,this.duration),this.fps),d(this,"db-seek",{time:this.currentTime})}scrollToTime(t){this.viewport&&(this.viewport.scrollLeft=Math.max(0,a(t,0,this.duration)*this.scale-(this.viewport.clientWidth-this.labelWidth)/2))}setZoom(t){const e=this.currentTime;this.zoom=t,this.scrollToTime(e)}node(t,e,i){const s=document.createElement(t);return s.className=e,void 0!==i&&(s.textContent=i),s}render(){if(this.effectEditing)return;const t=new Set(this._clips.flatMap(t=>(t.effects||[]).flatMap(e=>(e.properties||[]).flatMap(i=>i.keyframes.map(s=>this.effectToken(t.id,e.id,i.id,s.id))))));if(this.selectedEffectKeys=new Set(Array.from(this.selectedEffectKeys).filter(e=>t.has(e))),!this.canvas||!this.viewport)return;this.querySelector(":scope > .db-vt__readout")?.remove(),this.viewport.style.height=this.height?`${this.height}px`:"",this.viewport.style.maxHeight=this.height?"720px":"360px",this.paneGrip&&(this.paneGrip.setAttribute("aria-label",i("media.resizeTimeline")),this.paneGrip.setAttribute("title",i("media.resizeTimelineHint")),this.paneGrip.setAttribute("aria-valuemin","120"),this.paneGrip.setAttribute("aria-valuemax","720"),this.paneGrip.setAttribute("aria-valuenow",String(this.height||this.viewport.clientHeight)));const e=this.hint?.querySelector("db-button");e?.setAttribute("aria-label",i("media.more")),e?.setAttribute("title",i("media.more"));const s=this.ownerDocument.activeElement?.dataset.focus;this.style.setProperty("--db-vt-layer-gap",`${this._tracks.length>1?this.layerGap:0}px`),this.style.setProperty("--db-vt-label-width",`${this.labelWidth}px`);const r=document.createDocumentFragment(),d=this.node("div","db-vt__ruler"),n=this.node("span","db-vt__corner",i("media.tracks"));this.labelGrip(n),d.append(n);const o=[1/this.fps,.1,.25,.5,1,2,5,10,30,60,300,600,3600].find(t=>t*this.scale>=64&&this.duration/t<=200)||3600;for(let t=0;t<=this.contentDuration;t+=o){const e=this.node("span","db-vt__tick",l(t,this.fps));e.style.left=`${this.labelWidth+t*this.scale}px`,d.append(e)}d.dataset.seek="",r.append(d),this._tracks.forEach((t,e)=>{const s=this.node("div","db-vt__row");s.dataset.track=t.id,s.dataset.layerIndex=String(e),s.dataset.kind=t.kind,s.style.setProperty("--db-vt-track-height",`${this.trackHeight(t)}px`),s.dataset.condensed=String(this.trackHeight(t)<44),s.dataset.locked=String(!!t.locked),s.dataset.muted=String(!!t.muted);const d=this.node("div","db-vt__track"),n=this._clips.filter(e=>e.trackId===t.id),c=n.length>0,p=document.createElement("db-button");if(p.setAttribute("size","s"),p.setAttribute("variant","ghost"),p.className="db-vt__layer-toggle",p.dataset.action="layer-disclosure",p.title=t.label,p.setAttribute("aria-label",t.label),p.append(this.node("span","db-vt__track-name",t.label)),c){const t=n.some(t=>this.expandedEffects.has(t.id));p.setAttribute("start-icon",t?"chevron-down":"chevron-right"),p.setAttribute("aria-expanded",String(t)),p.addEventListener("db-click",e=>{e.stopPropagation();for(const e of n)t?this.expandedEffects.delete(e.id):this.expandedEffects.add(e.id);this.render()})}d.append(p);for(const e of["lock","mute"]){const s=i("lock"===e?t.locked?"media.trackLocked":"media.trackUnlocked":t.muted?"media.soundOff":"media.soundOn"),r=h(`${s} · ${t.label}`,"lock"===e?t.locked?"lock":"unlock":t.muted?"mute":"volume-up",e);r.setAttribute("variant","ghost"),r.dataset.track=t.id,r.setAttribute("aria-pressed",String("lock"===e?!!t.locked:!!t.muted)),r.toggleAttribute("disabled",this.disabled),d.append(r)}this.labelGrip(d),s.append(d);const f=this.node("div","db-vt__lane");f.dataset.seek="",f.style.width=this.contentDuration*this.scale+"px",f.style.setProperty("--tick",o*this.scale+"px"),this._clips.filter(e=>e.trackId===t.id).forEach(e=>{const s=this.node("div","db-vt__clip");s.dataset.clip=e.id,s.dataset.focus=e.id,s.dataset.selected=String(this.selectedClipId===e.id),s.setAttribute("role","group"),s.tabIndex=0,s.setAttribute("aria-label",`${e.label}, ${l(e.start,this.fps)}, ${i("media.duration")} ${l(e.duration,this.fps)}`),s.setAttribute("aria-disabled",String(this.disabled||!!t.locked));const r=this.selectedClipId===e.id;if(s.dataset.appearance=e.appearance||("audio"===(e.kind||t.kind)?"waveform":"standard"),s.dataset.narrow=String(e.duration*this.scale<100),s.dataset.editing=String(this.drag?.clipId===e.id),s.style.left=e.start*this.scale+"px",e.start+e.duration>this.duration){const t=Math.max(0,this.duration-e.start)*this.scale;s.style.maskImage=`linear-gradient(to right, black ${t}px, rgba(0,0,0,.35) ${t}px)`,s.dataset.outOfRange="true"}s.style.width=`${Math.max(24,e.duration*this.scale)}px`,s.dataset.hitProxy=String(e.duration*this.scale<24),s.style.setProperty("--db-vt-actual-width",e.duration*this.scale+"px");for(const a of["start","end"]){const d=this.node("span","db-vt__trim");d.dataset.trim=a,d.dataset.focus=`${e.id}-trim-${a}`,d.tabIndex=!r||this.disabled||t.locked?-1:0,d.setAttribute("role","slider"),d.setAttribute("aria-label",`${i("start"===a?"media.trimStart":"media.trimEnd")} · ${e.label}`),d.setAttribute("aria-valuemin","0"),d.setAttribute("aria-valuemax",String(this.duration)),d.setAttribute("aria-valuenow",String("start"===a?e.start:e.start+e.duration)),d.setAttribute("aria-valuetext",l("start"===a?e.start:e.start+e.duration,this.fps)),d.setAttribute("title",i("start"===a?"media.trimStart":"media.trimEnd")),d.append(this.node("i","db-vt__grip")),s.append(d)}if("filmstrip"===e.appearance&&e.thumbnail){const t=this.node("div","db-vt__filmstrip");t.setAttribute("aria-hidden","true");for(let i=0;i<Math.min(24,Math.ceil(e.duration*this.scale/64));i++){const i=document.createElement("img");i.src=e.thumbnail,i.alt="",i.draggable=!1,t.append(i)}s.append(t)}for(const d of["in","out"]){const n=a("in"===d?e.fadeIn||0:e.fadeOut||0,0,e.duration),o=this.node("span","db-vt__fade");if(o.dataset.fadeArea=d,o.style.width=n*this.scale+"px",o.setAttribute("aria-hidden","true"),s.append(o),r){const r=this.node("span","db-vt__fade-handle");r.dataset.fade=d,r.dataset.focus=`${e.id}-fade-${d}`,r.style["in"===d?"left":"right"]=n*this.scale+"px",r.tabIndex=this.disabled||t.locked?-1:0,r.setAttribute("role","slider"),r.setAttribute("aria-label",`${i("in"===d?"media.fadeIn":"media.fadeOut")} · ${e.label}`),r.setAttribute("aria-valuemin","0"),r.setAttribute("aria-valuemax",String(e.duration)),r.setAttribute("aria-valuenow",String(n)),r.setAttribute("title",i("in"===d?"media.fadeIn":"media.fadeOut")),s.append(r)}}if(s.append(this.node("span","db-vt__clip-label",e.label)),"audio"===(e.kind||t.kind)||"waveform"===e.appearance){const t=this.node("span","db-vt__wave");t.setAttribute("aria-hidden","true");for(let i=0;i<Math.min(160,Math.ceil(e.duration*this.scale/7));i++){const e=this.node("i","");e.style.height=20+75*Math.abs(Math.sin(1.7*i)*Math.cos(.4*i))+"%",t.append(e)}s.append(t)}if(e.effects?.length){const t=this.node("span","db-vt__effects");t.setAttribute("aria-label",i("media.effects"));for(const i of e.effects){const e=this.node("span","db-vt__effect",i.label);e.dataset.enabled=String(!1!==i.enabled),e.dataset.kind=i.kind,e.setAttribute("title",i.label),t.append(e)}s.append(t)}f.append(s)}),s.append(f);const u=this.node("div","db-vt__track-resize");u.dataset.trackResize=t.id,u.tabIndex=0,u.setAttribute("role","separator"),u.setAttribute("aria-orientation","horizontal"),u.setAttribute("aria-label",`${i("media.resizeTrack")} · ${t.label}`),u.setAttribute("aria-valuemin","32"),u.setAttribute("aria-valuemax","120"),u.setAttribute("aria-valuenow",String(this.trackHeight(t))),s.append(u),r.append(s),this.renderEffects(r,t)});for(const t of this._markers){const e=this.node("button","db-vt__marker");e.type="button",e.dataset.marker=String(t.time),e.style.left=`${this.labelWidth+t.time*this.scale}px`;const s=`${i("media.marker")} · ${t.label} · ${l(t.time,this.fps)}`;e.setAttribute("aria-label",s),e.setAttribute("title",s),r.append(e)}if(this.cursor=this.node("div","db-vt__playhead"),this.cursor.dataset.playhead="",this.cursor.tabIndex=0,this.cursor.setAttribute("role","slider"),this.cursor.dataset.focus="playhead",this.cursor.setAttribute("aria-label",i("media.playhead")),this.cursor.setAttribute("aria-valuemin","0"),this.cursor.setAttribute("aria-valuemax",String(this.duration)),this.cursor.setAttribute("aria-disabled",String(this.disabled)),r.append(this.cursor),this._tracks.length||r.append(this.node("p","db-vt__empty",i("media.emptyTracks"))),this.canvas.style.width=`${this.labelWidth+this.contentDuration*this.scale}px`,this.drag?.clipId){const t=this._clips.find(t=>t.id===this.drag?.clipId);if(t){const e="fade-in"===this.drag.kind?t.fadeIn||0:"fade-out"===this.drag.kind?t.fadeOut||0:"trim-end"===this.drag.kind?t.start+t.duration:t.start,s=this.node("div","db-vt__readout",`${i("media."+("trim-start"===this.drag.kind?"trimStart":"trim-end"===this.drag.kind?"trimEnd":"fade-in"===this.drag.kind?"fadeIn":"fade-out"===this.drag.kind?"fadeOut":"time"))} ${l(e,this.fps)} / ${i("media.duration")} ${l(t.duration,this.fps)}`);this.append(s)}}this.canvas.replaceChildren(r),this.viewport.setAttribute("aria-label",i("media.timeline")),this.syncCursor(),this.syncHint(),s&&!this.drag&&Array.from(this.querySelectorAll("[data-focus]")).find(t=>t.dataset.focus===s)?.focus({preventScroll:!0})}renderEffects(t,i){for(const s of this._clips.filter(t=>t.trackId===i.id&&t.effects?.length)){if(!this.expandedEffects.has(s.id))continue;const r=this.node("section","db-vt__effect-tree");r.dataset.effectClip=s.id;const toggle=(t,e,i,s)=>{const r=h(t,i?"chevron-down":"chevron-right","effect-disclosure");return r.removeAttribute("icon-only"),r.setAttribute("variant","ghost"),r.textContent=t,r.setAttribute("aria-expanded",String(i)),r.setAttribute("aria-controls",e),r.dataset.focus=e,r.addEventListener("db-click",t=>{t.stopPropagation(),s(),this.render()}),r},n=`${this.effectTreeId}-${s.id}`,o=this.node("div","db-vt__effect-content");if(o.id=n,o.hidden=!this.expandedEffects.has(s.id),!o.hidden)for(const t of[...s.effects||[]].sort((t,e)=>Number(this.isSource(e))-Number(this.isSource(t)))){const r=this.isSource(t),h=`${s.id}/${t.id}`,p=`${n}-${t.id}`;if(!r){const e=this.node("div","db-vt__effect-range-row");e.dataset.effectId=t.id;const r=this.node("div","db-vt__effect-range-label");r.append(toggle(t.label,p,this.expandedEffects.has(h),()=>{this.expandedEffects.has(h)?this.expandedEffects.delete(h):this.expandedEffects.add(h)})),this.labelGrip(r),e.append(r),this.renderRange(e,s,t,i),o.append(e)}const f=this.node("div","db-vt__property-group");if(f.id=p,f.dataset.scope=r?"source":"effect",f.hidden=!r&&!this.expandedEffects.has(h),!f.hidden)for(const r of t.properties||[]){const n=`${h}/${r.id}`,o=this.node("div","db-vt__property-row");o.dataset.effectProperty=r.id,o.dataset.effectId=t.id;const p=this.node("div","db-vt__property-label"),u=document.createElement("db-button");u.setAttribute("size","s"),u.setAttribute("variant","ghost"),u.dataset.action="effect-property",u.setAttribute("aria-label",r.label),u.append(this.node("span","db-vt__property-name",r.label)),u.setAttribute("aria-expanded",String(this.activeProperty.get(s.id)===n)),u.dataset.focus=n,u.addEventListener("db-click",t=>{t.stopPropagation(),this.activeProperty.set(s.id,this.activeProperty.get(s.id)===n?"":n),this.render()}),u.title=r.label,p.append(u,this.node("span","db-vt__property-value",String(Math.round(100*c(r.keyframes,this.currentTime-s.start))/100))),this.labelGrip(p),o.append(p);const m=this.node("div","db-vt__property-lane");m.style.width=this.contentDuration*this.scale+"px";for(const e of r.keyframes){const a=this.node("button","db-vt__property-key");a.dataset.propertyKey=e.id,a.dataset.keyToken=this.effectToken(s.id,t.id,r.id,e.id),a.setAttribute("aria-pressed",String(this.selectedEffectKeys.has(a.dataset.keyToken))),a.addEventListener("pointerdown",i=>{if(a.disabled||0!==i.button)return;i.preventDefault(),i.stopPropagation(),this.viewport?.focus({preventScroll:!0});const d=a.dataset.keyToken;Array.from(this.selectedEffectKeys).some(t=>JSON.parse(t)[0]!==s.id)&&this.selectedEffectKeys.clear(),i.shiftKey?this.selectedEffectKeys.has(d)?this.selectedEffectKeys.delete(d):this.selectedEffectKeys.add(d):this.selectedEffectKeys.has(d)||(this.selectedEffectKeys.clear(),this.selectedEffectKeys.add(d)),this.syncEffectSelection(),this.selectedEffectKeys.has(d)?(this.effectDrag={pointer:i.pointerId,clipId:s.id,effectId:t.id,propertyId:r.id,keyId:e.id,x:i.clientX,time:e.time,before:this.clips,changed:!1,copies:i.altKey?this.makeKeyCopyIds():void 0},this.effectEditing=!0,this.setPointerCapture(i.pointerId)):this.render()}),a.type="button",a.style.left=(s.start+e.time)*this.scale+"px",a.setAttribute("aria-label",`${r.label} · ${l(e.time,this.fps)} · ${e.value}`),a.disabled=this.disabled||!!i.locked||!1===t.enabled,a.addEventListener("click",t=>{t.stopPropagation(),t.shiftKey||(this.selectedEffectKeys.has(a.dataset.keyToken)||(this.selectedEffectKeys.clear(),this.selectedEffectKeys.add(a.dataset.keyToken)),this.activeProperty.set(s.id,n),this.effectKeySelection.set(n,e.id),this.seek(s.start+e.time),this.render())}),m.append(a)}if(o.append(m),f.append(o),this.activeProperty.get(s.id)===n){const o=new e;o.dataset.effectEditor=r.id,o.duration=s.duration,o.fps=this.fps,o.min=r.min,o.max=r.max,o.setAttribute("label",r.label),o.keyframes=r.keyframes,o.selectedKeyframeId=this.effectKeySelection.get(n)||r.keyframes[0]?.id||"",o.currentTime=a(this.currentTime-s.start,0,s.duration),o.disabled=this.disabled||!!i.locked||!1===t.enabled;const edit=e=>{e.stopPropagation();const i=e.detail;this.effectEditing="db-input"===e.type&&!i.cancelled;const a=i.keyframes;this._clips=this._clips.map(e=>e.id!==s.id?e:{...e,effects:e.effects?.map(e=>e.id!==t.id?e:{...e,properties:e.properties?.map(t=>t.id!==r.id?t:{...t,keyframes:a})})}),d(this,e.type,{clips:this.clips,clipId:s.id,action:"keyframe",effectId:t.id,propertyId:r.id,...i.cancelled?{cancelled:!0}:{}}),("db-change"===e.type||i.cancelled)&&(this.effectEditing=!1,this.render())};o.addEventListener("db-input",edit),o.addEventListener("db-change",edit),o.addEventListener("db-selection-change",t=>{t.stopPropagation(),this.effectKeySelection.set(n,t.detail.keyframeId)}),o.addEventListener("db-seek",t=>{t.stopPropagation(),this.seek(s.start+t.detail.time)});const h=this.node("div","db-vt__effect-editor");h.style.width=`${Math.max(240,(this.viewport?.clientWidth||800)-16)}px`,h.append(o),f.append(h)}}o.append(f)}r.addEventListener("pointerdown",t=>{if(0!==t.button||this.disabled||i.locked||t.target.closest("db-button,db-keyframe-editor,db-input,db-select,.db-vt__property-label,[data-property-key]"))return;t.preventDefault(),t.stopPropagation(),this.viewport?.focus({preventScroll:!0});const e=this.node("div","db-vt__marquee");this.append(e),this.marquee={pointer:t.pointerId,x:t.clientX,y:t.clientY,clipId:s.id,before:t.shiftKey?new Set(Array.from(this.selectedEffectKeys).filter(t=>JSON.parse(t)[0]===s.id)):new Set,box:e},t.shiftKey||this.selectedEffectKeys.clear(),this.syncEffectSelection(),this.setPointerCapture(t.pointerId)}),r.append(o),t.append(r)}}select(t){this.selectedClipId!==t&&(this.selectedClipId=t,d(this,"db-selection-change",{clipId:t}))}timeAt(t){return(t-this.viewport.getBoundingClientRect().left+this.viewport.scrollLeft-this.labelWidth)/this.scale}snapped(t,e){let i=r(t,this.fps);if(this.snap&&!this.drag?.shift){const t=[0,this.currentTime,...this._markers.map(t=>t.time),...this._clips.filter(t=>t.id!==e).flatMap(t=>[t.start,t.start+t.duration])].reduce((t,e)=>Math.abs(e-i)<Math.abs(t-i)?e:t,1/0);Math.abs(t-i)*this.scale<=8&&(i=r(t,this.fps))}return i}updateDrag(){const t=this.drag;if(!t)return;if("seek"===t.kind)return void this.seek(this.timeAt(t.x));const e=t.before.find(e=>e.id===t.clipId);if(!e)return;const i=(t.x-t.originX+this.viewport.scrollLeft-t.scroll)/this.scale;let s=e.start,n=e.duration,o=e.sourceOffset||0,h=e.trackId;if("fade-in"===t.kind||"fade-out"===t.kind){const s="fade-in"===t.kind?"fadeIn":"fadeOut",n="fade-in"===t.kind?e.fadeOut||0:e.fadeIn||0,o=r(a((e[s]||0)+("fade-in"===t.kind?i:-i),0,Math.max(0,e.duration-n)),this.fps);return this._clips=t.before.map(t=>t.id===e.id?{...t,[s]:o}:t),t.changed=JSON.stringify(this._clips)!==JSON.stringify(t.before),this.render(),void d(this,"db-input",{clips:this.clips,clipId:e.id,action:t.kind})}if("move"===t.kind){s=this.snapped(s+i,e.id),this.canvas.getBoundingClientRect();const r=this.trackIndexAt(t.y),d=this._tracks[a(r,0,this._tracks.length-1)],n=this._tracks.find(t=>t.id===e.trackId);!d||d.locked||"mixed"!==d.kind&&d.kind!==(e.kind||n?.kind)||(h=d.id),s=Math.max(0,s)}else if("trim-start"===t.kind)s=a(this.snapped(s+i,e.id),Math.max(0,e.start-o),e.start+n-Math.min(this.minClipDuration,e.start+n-Math.max(0,e.start-o))),n=e.start+e.duration-s,o+=s-e.start;else{const t=e.sourceDuration?Math.min(this.duration-s,e.sourceDuration-o):this.duration-s;n=a(this.snapped(s+n+i,e.id)-s,Math.min(this.minClipDuration,t),t)}const c=t.ripple&&("trim-start"===t.kind||"trim-end"===t.kind),l=t.before.filter(t=>t.id!==e.id&&t.trackId===e.trackId&&t.start>=e.start+e.duration-1e-6);let p=0;if(c){"trim-start"===t.kind&&(s=e.start);const i=Math.max(e.start+e.duration,...l.map(t=>t.start+t.duration));n=Math.min(n,e.duration+this.duration-i),p=r(n-e.duration,this.fps)}let f=t.before.map(t=>l.some(e=>e.id===t.id)&&c?{...t,start:r(t.start+p,this.fps)}:t);if("move"===t.kind&&t.ripple){const i=t.before.filter(i=>i.trackId===h&&(t.duplicate||i.id!==e.id)).sort((t,e)=>t.start-e.start),a=i.find(t=>s>t.start+1e-6&&s<t.start+t.duration-1e-6);a&&(s=a.start);let d=s+n;const o=new Map;for(const t of i){if(t.start<s-1e-6)continue;const e=Math.max(t.start,d);o.set(t.id,e),d=e+t.duration}f=t.before.map(t=>o.has(t.id)?{...t,start:r(o.get(t.id),this.fps)}:t)}if(f.some(i=>(t.duplicate||i.id!==e.id)&&i.trackId===h&&s<i.start+i.duration-1e-6&&s+n>i.start+1e-6))return;const u=t.duplicate||e.id;t.duplicate&&(f=[...f,{...e,id:u}]),this._clips=f.map(t=>t.id===u?{...t,kind:t.kind||("mixed"===this._tracks.find(t=>t.id===e.trackId)?.kind?"video":this._tracks.find(t=>t.id===e.trackId)?.kind),start:r(s,this.fps),duration:r(n,this.fps),sourceOffset:r(o,this.fps),trackId:h,...void 0!==t.fadeIn?{fadeIn:Math.min(t.fadeIn,n)}:{},...void 0!==t.fadeOut?{fadeOut:Math.min(t.fadeOut,Math.max(0,n-(t.fadeIn||0)))}:{}}:t),t.changed=JSON.stringify(this._clips)!==JSON.stringify(t.before),this.render(),d(this,"db-input",{clips:this.clips,clipId:e.id,action:t.kind})}restoreTimelineHistory(t=!1){const e=t?this.redoClips:this.undoClips,i=t?this.undoClips:this.redoClips,s=e.pop();s&&(i.push(this.clips),this._clips=structuredClone(s),this.committedClips=this.clips,this.selectedEffectKeys.clear(),this.render(),d(this,"db-change",{clips:this.clips,clipId:this.selectedClipId,action:t?"redo":"undo"}))}makeKeyCopyIds(){return new Map(Array.from(this.selectedEffectKeys,t=>[t,`key-${crypto.randomUUID()}`]))}copyKeyData(t,e,i,s){let a=!0,d=0;const n=t.map(t=>t.id!==e?t:{...t,effects:t.effects?.map(e=>({...e,properties:e.properties?.map(n=>{const o=n.keyframes.filter(i=>s.has(this.effectToken(t.id,e.id,n.id,i.id))).map(o=>{const h=r(o.time+i,this.fps);return d++,(h<0||h>t.duration||n.keyframes.some(t=>Math.abs(t.time-h)<.5/this.fps))&&(a=!1),{...structuredClone(o),id:s.get(this.effectToken(t.id,e.id,n.id,o.id)),time:h}});return{...n,keyframes:[...n.keyframes,...o].sort((t,e)=>t.time-e.time)}})}))});return a&&d?n:void 0}duplicateSelectedKeys(t){const e=this.makeKeyCopyIds(),i=this.clips;for(let s=1;s<=Math.min(3e3,Math.ceil(this.duration*this.fps));s++)for(const r of[1,-1]){const a=this.copyKeyData(i,t,r*s/this.fps,e);if(a)return this._clips=a,this.selectedEffectKeys=new Set(Array.from(e,([t,e])=>{const[i,s,r]=JSON.parse(t);return this.effectToken(i,s,r,e)})),d(this,"db-change",{clips:this.clips,clipId:t,action:"keyframe"}),void this.render()}}effectToken(t,e,i,s){return JSON.stringify([t,e,i,s])}syncEffectSelection(){for(const t of this.querySelectorAll("[data-key-token]"))t.setAttribute("aria-pressed",String(this.selectedEffectKeys.has(t.dataset.keyToken)))}finishMarquee(t=!1){const e=this.marquee;e&&(this.marquee=void 0,e.box.remove(),t&&(this.selectedEffectKeys=e.before),this.hasPointerCapture(e.pointer)&&this.releasePointerCapture(e.pointer),this.render())}editSelectedKeys(t,e){const i=this._clips.find(e=>e.id===t);!i||this.disabled||this._tracks.find(t=>t.id===i.trackId)?.locked||(this._clips=this._clips.map(i=>i.id!==t?i:{...i,effects:i.effects?.map(t=>!1===t.enabled?t:{...t,properties:t.properties?.map(s=>{let r=s.keyframes;if(e)for(const a of s.keyframes)this.selectedEffectKeys.has(this.effectToken(i.id,t.id,s.id,a.id))&&(r=p(r,a.id,e));else r=r.filter(e=>!this.selectedEffectKeys.has(this.effectToken(i.id,t.id,s.id,e.id)));return{...s,keyframes:r}})})}),e||this.selectedEffectKeys.clear(),d(this,"db-change",{clips:this.clips,clipId:t,action:"keyframe"}),this.render(),this.viewport?.focus({preventScroll:!0}))}finishEffectDrag(t=!1){const e=this.effectDrag;e&&(this.effectDrag=void 0,this.effectEditing=!1,this.hasPointerCapture(e.pointer)&&this.releasePointerCapture(e.pointer),t?this._clips=e.before:e.copies&&e.changed&&(this.selectedEffectKeys=new Set(Array.from(e.copies,([t,e])=>{const[i,s,r]=JSON.parse(t);return this.effectToken(i,s,r,e)}))),(t||e.changed)&&d(this,t?"db-input":"db-change",{clips:this.clips,clipId:e.clipId,action:"keyframe",effectId:e.effectId,propertyId:e.propertyId,...t?{cancelled:!0}:{}}),this.render())}finish(t=!1){const e=this.drag;e&&(cancelAnimationFrame(this.raf),this.drag=void 0,this.hasPointerCapture(e.id)&&this.releasePointerCapture(e.id),t?(this._clips=e.before,this.render(),d(this,"db-input",{clips:this.clips,clipId:e.clipId,action:e.kind,cancelled:!0})):e.changed&&e.clipId&&"seek"!==e.kind&&d(this,"db-change",{clips:this.clips,clipId:e.clipId,action:e.kind}),this.render(),this.viewport?.focus({preventScroll:!0}))}rollback(){this.rangeFinish(!0),this.finishMarquee(!0),this.finishEffectDrag(!0),this.finish(!0)}splitSelected(){const t=this._clips.find(t=>t.id===this.selectedClipId);if(this.disabled||!t||this._tracks.find(e=>e.id===t.trackId)?.locked||t.duration<2*this.minClipDuration||this.currentTime<t.start+this.minClipDuration||this.currentTime>t.start+t.duration-this.minClipDuration)return;const e=this.currentTime;let i=t.id+"-split";for(;this._clips.some(t=>t.id===i);)i+="-1";this._clips=this._clips.flatMap(s=>s.id===t.id?[{...s,duration:e-s.start,fadeOut:0,fadeIn:Math.min(s.fadeIn||0,e-s.start)},{...s,id:i,fadeIn:0,fadeOut:Math.min(s.fadeOut||0,s.start+s.duration-e),start:e,duration:s.start+s.duration-e,sourceOffset:(s.sourceOffset||0)+e-s.start}]:[s]),this.render(),d(this,"db-change",{clips:this.clips,action:"split",clipId:t.id})}trackIndexAt(t){const e=Array.from(this.canvas.querySelectorAll(".db-vt__row"));let i=0;return e.forEach((e,s)=>{t>=e.getBoundingClientRect().top&&(i=s)}),i}finishResize(t=!1){const e=this.resizeDrag;e&&(this.resizeDrag=void 0,this.hasPointerCapture(e.id)&&this.releasePointerCapture(e.id),t?(this._tracks=e.beforeTracks,this.height=e.beforeHeight,this.render(),d(this,e.trackId?"db-track-input":"db-resize-input",e.trackId?{tracks:this.tracks,cancelled:!0}:{height:this.height,cancelled:!0})):d(this,e.trackId?"db-track-change":"db-resize",e.trackId?{tracks:this.tracks}:{height:this.height}))}changeZoom(t,e,i="keyboard"){if(!this.viewport)return;const s=this.zoom,r=e??Math.min(this.viewport.clientWidth-8,Math.max(152,this.labelWidth+this.currentTime*this.scale-this.viewport.scrollLeft)),n=(this.viewport.scrollLeft+r-this.labelWidth)/this.scale;this.zoom=Math.round(100*a(t,10,800))/100,this.viewport.scrollLeft="fit"===i?0:Math.max(0,this.labelWidth+n*this.scale-r),this.zoom!==s&&d(this,"db-zoom-change",{value:this.zoom,previousValue:s,anchorTime:n,source:i})}}customElements.get("db-video-timeline")||customElements.define("db-video-timeline",DbVideoTimeline);export{DbVideoTimeline};
@@ -18,6 +18,11 @@ interface VideoEffectProperty {
18
18
  keyframes: EditorKeyframe[];
19
19
  }
20
20
  interface VideoClipEffect {
21
+ /** Source transform channels or an applied effect. Legacy transform groups are source channels. */
22
+ scope?: 'source' | 'effect';
23
+ /** Effect range in clip-local seconds; defaults to the complete clip. Keys remain clip-local. */
24
+ start?: number;
25
+ duration?: number;
21
26
  properties?: VideoEffectProperty[];
22
27
  id: string;
23
28
  label: string;
@@ -1 +1 @@
1
- const e={"media.resizeLabel":"Resize track label width","layerList.label":"Layers","layerList.delete":"Delete {name}","layerList.lock":"Lock {name}","layerList.unlock":"Unlock {name}","layerList.show":"Show {name}","layerList.hide":"Hide {name}","layerList.empty":"No layers.","appUpdate.notice":"Update available","appUpdate.changes":"What’s new","appUpdate.automatic":"Restarts automatically when complete.","appUpdate.install":"Update","appUpdate.inProgress":"Updating","appUpdate.dialogTitle":"App update","appUpdate.toastComplete":"Updated to {version}.","appUpdate.checking.title":"Checking for updates","appUpdate.checking.description":"Looking for a new update.","appUpdate.up-to-date.title":"You’re up to date","appUpdate.up-to-date.description":"You’re using the latest available version.","appUpdate.available.title":"An update is available","appUpdate.available.description":"Download the latest improvements and stability updates.","appUpdate.downloading.title":"Downloading update","appUpdate.downloading.description":"We’ll let you know how to apply it when the download finishes.","appUpdate.ready.title":"Ready to update","appUpdate.ready.description":"Save your work, then restart the app to apply the update.","appUpdate.applying.title":"Applying update","appUpdate.applying.description":"Keep the app open and wait a moment.","appUpdate.complete.title":"Update complete","appUpdate.complete.description":"The new version has been applied successfully.","appUpdate.error.title":"Update could not be completed","appUpdate.error.description":"Check your connection and try again.","appUpdate.offline.title":"Internet connection required","appUpdate.offline.description":"Connect to the internet, then try again.","appUpdate.current":"Current","appUpdate.target":"New version","appUpdate.required":"Required update","appUpdate.later":"Later","appUpdate.download":"Download update","appUpdate.restart":"Restart to update","appUpdate.retry":"Try again","appUpdate.done":"Done","media.restoreHandles":"Restore handles (H)","media.tangentHelp":"Drag: symmetric · Cmd/Ctrl: independent · H: restore · dashed handles reveal collapsed controls.","media.resizeTimeline":"Resize timeline height","media.resizeTimelineHint":"Drag vertically to resize. Double-click or Home for automatic height.","media.resizeTrack":"Resize track height","easing.outVelocity":"Out velocity ×","easing.inVelocity":"In velocity ×","easing.label":"Easing & velocity","easing.graphMode":"Graph mode","easing.value":"Value","easing.speed":"Speed","easing.valueGraph":"Normalized progress curve","easing.speedGraph":"Normalized velocity graph","easing.custom":"Custom curve","easing.presets":"Easing presets","easing.linear":"Linear","easing.ease":"Ease","easing.ease-in":"Ease in","easing.ease-out":"Ease out","easing.ease-in-out":"Ease in/out","easing.smooth":"Smooth","easing.snappy":"Snappy","easing.swift":"Swift out","easing.gentle":"Gentle","easing.emphasized":"Emphasized","easing.outgoing":"Outgoing tangent","easing.incoming":"Incoming tangent","easing.preview":"Play easing preview","easing.velocityHint":"1× = constant speed","easing.speedHelp":"Drag tangent controls to change influence and endpoint velocity. Vertical scale is normalized; multiply by value change / duration for units per second.","easing.valueHelp":"Drag handles or enter exact coordinates. Arrow keys nudge .01; Shift nudges .1. Escape cancels dragging.","media.trimStart":"Trim start","media.trimEnd":"Trim end","media.fadeIn":"Fade in","media.fadeOut":"Fade out","media.duplicateKeys":"Duplicate keyframes","media.undo":"Undo","media.selectedKeys":"selected","media.keyEasing":"Selected key easing","media.effects":"Applied effects","media.timeline":"Video timeline","media.tracks":"Tracks","media.more":"Scroll to more tracks and clips","media.lock":"Lock track","media.trackLocked":"Locked","media.trackUnlocked":"Unlocked","media.soundOn":"Sound on","media.soundOff":"Muted","media.marker":"Timeline marker","media.trackMute":"Mute track","media.duration":"Duration","media.playhead":"Playhead","media.emptyTracks":"No tracks. Connect a tracks array to begin.","media.keyframes":"Keyframes","media.curve":"Value graph","media.previousKey":"Previous keyframe","media.nextKey":"Next keyframe","media.addKey":"Add keyframe (K)","media.deleteKey":"Delete keyframe","media.outHandle":"Outgoing Bezier handle","media.inHandle":"Incoming Bezier handle","media.keyframe":"Keyframe","media.emptyKeys":"Add a keyframe or double-click the graph.","media.selectKey":"Select a keyframe to edit its time, value and interpolation.","media.time":"Time (seconds)","media.value":"Value","media.interpolation":"Interpolation","media.linear":"Linear","media.hold":"Hold","media.ease-in":"Ease in","media.ease-out":"Ease out","media.ease-in-out":"Ease in / out","media.bezier":"Custom Bezier","selectionBar.count":"{count} selected","selectionBar.clear":"Clear","selectionBar.label":"Selection actions","zoom.label":"Zoom controls","zoom.out":"Zoom out","zoom.in":"Zoom in","zoom.value":"Zoom level","zoom.fit":"Fit to screen","zoom.fitShort":"Fit","zoom.fullscreen":"Enter fullscreen","zoom.exitFullscreen":"Exit fullscreen","zoom.fullscreenError":"Fullscreen is unavailable.","toolPanel.close":"Close panel","toolPanel.tabs":"Panel tabs","toolLayout.resize.sidebar":"Resize sidebar width","toolLayout.resize.inspector":"Resize inspector width","toolLayout.resize.bottom":"Resize bottom panel height","password.weak":"Weak","password.fair":"Fair","password.good":"Strong","password.veryStrong":"Very strong","rating.high":"Very good","rating.medium":"Average","rating.low":"Low","rating.label":"Rating {value}","rating.review":"{count} review","rating.reviews":"{count} reviews","countdown.days":"Days","countdown.hours":"Hours","countdown.minutes":"Minutes","countdown.seconds":"Seconds","upload.title":"Drag files here or click to choose","upload.choose":"Choose files","upload.allFormats":"All file types supported","upload.maximum":" (up to {size})","upload.files":"Uploaded files ({count})","upload.retry":"Retry","upload.remove":"Remove","upload.illustration":"Upload illustration","creditCard.security":"Security code {code}","creditCard.label":"{issuer} card{lastFour}, flip","creditCard.ending":", ending in {digits}","creditCard.front":"Front","creditCard.back":"Back","creditCard.showing":"Showing {side}","creditCard.expiry":"Expires {expiry}","creditCard.show":"Press to show {side}","flight.scheduled":"Scheduled","flight.boarding":"Boarding","flight.in-flight":"In flight","flight.landed":"Landed","flight.delayed":"Delayed","flight.cancelled":"Cancelled","flight.title":"Flight status","flight.progress":"Journey progress","boardingPass.origin":"From","boardingPass.destination":"To","boardingPass.passenger":"Passenger","boardingPass.service":"Flight","boardingPass.date":"Date","boardingPass.boarding":"Boarding","boardingPass.departure":"Departure","boardingPass.arrival":"Arrival","boardingPass.terminal":"Terminal","boardingPass.gate":"Gate","boardingPass.seat":"Seat","boardingPass.travelClass":"Class","boardingPass.title":"Boarding pass","wallet.title":"My boarding passes","wallet.empty":"No boarding passes yet.","wallet.journeys":"Choose a journey","receipt.quantity":"Qty","receipt.subtotal":"Subtotal","receipt.discount":"Discount","receipt.tax":"Tax","receipt.total":"Total","receipt.payment":"Payment","receipt.empty":"No items to display.","receipt.invalid":"Check the items and amounts.","receipt.title":"Receipt","ticket.title":"Ticket","paper.codeImage":"Code image","aiElement.emptyFolder":"Empty folder","aiElement.noFiles":"No files to display.","attachment.title":"Attachments","attachment.empty":"No attachments yet.","attachment.queued":"Queued","attachment.uploading":"Uploading","attachment.processing":"Processing","attachment.ready":"Ready","attachment.error":"Failed","attachment.cancelled":"Cancelled","attachment.unknown":"Checking progress…","attachment.cancel":"Cancel","attachment.retry":"Retry","attachment.remove":"Remove from list","attachment.open":"View details","attachment.reload":"Reload","workspace.members":"Workspace members","workspace.invite":"Invite members","workspace.email":"Email","workspace.role":"Role","workspace.chooseRole":"Choose a role","workspace.description":"Review the email address and role of the person you want to invite.","workspace.submit":"Send invitation","workspace.emailError":"Enter a valid email address of up to 254 characters.","workspace.roleError":"Choose a role for the invitation.","workspace.retry":"Reload","workspace.reinvite":"Invite again","workspace.active":"Active","workspace.invited":"Invitation pending","workspace.expired":"Invitation expired","workspace.empty":"No members to display yet.","aiElement.pending":"Pending","aiElement.running":"In progress","aiElement.complete":"Complete","aiElement.error":"Failed","aiElement.cancelled":"Cancelled","aiElement.approval":"Awaiting approval","aiElement.researching":"Reviewing sources","aiElement.researched":"View research","aiElement.thinking":"Thinking","aiElement.reasoning":"Response summary","aiElement.plan":"Plan","aiElement.queue":"Queued requests","aiElement.remove":"Remove","aiElement.queueEmpty":"No queued requests.","aiElement.sources":"Sources","aiElement.citation":"Source","aiElement.checkpoint":"Checkpoint saved","aiElement.restore":"Restore from here","aiElement.confirmation":"Proceed with this action?","aiElement.allow":"Allow","aiElement.reject":"Reject","aiElement.accepted":"Action approved.","aiElement.rejected":"Action rejected.","aiElement.context":"Context","aiElement.tokens":"Tokens","aiElement.unknown":"Usage is not available yet.","aiElement.files":"Files","aiElement.task":"Task progress","aiElement.subagent":"Subagent","aiElement.tool":"Tool call","aiElement.terminal":"Terminal","aiElement.stackTrace":"Error details","aiElement.tests":"Test results","aiElement.passed":"Passed","aiElement.failed":"Failed","aiElement.input":"Input","aiElement.output":"Output","aiElement.user":"User","aiElement.assistant":"AI response","aiElement.copy":"Copy","aiElement.regenerate":"Regenerate","aiElement.like":"Like","aiElement.dislike":"Dislike","aiElement.latest":"Latest response","aiElement.conversation":"AI conversation","notification.title":"Notifications","notification.filters":"Notification filters","notification.all":"All","notification.unread":"Unread","notification.readAll":"Mark displayed notifications as read","notification.markRead":"Mark as read","notification.retry":"Reload","notification.empty":"No notifications yet.","notification.caughtUp":"You’re all caught up.","notification.countUnknown":"Needs review","aiModel.label":"Model","aiModel.choose":"Choose a model","aiModel.empty":"No models are available right now.","aiModel.capabilities":"Capabilities","aiModel.usage":"Usage unit","aiQuote.expires":"Quote expires","aiQuote.title":"Execution estimate","aiQuote.reserved":"Maximum credit reservation","aiQuote.providerCost":"Estimated provider cost","aiQuote.pricingVersion":"Pricing version","aiQuote.unknown":"Unconfirmed","aiQuote.idle":"Choose a model and enter a request to get an estimate.","aiQuote.loading":"Checking the estimate…","aiQuote.executing":"Processing your request. Do not submit it again.","aiQuote.stale":"The model or input has changed. Refresh the estimate.","aiQuote.expired":"The estimate has expired. Request a new estimate.","aiQuote.balanceUnknown":"Your balance is unavailable. Refresh to try again.","aiQuote.insufficient":"Additional credits needed:","aiQuote.ready":"Credits are reserved when execution starts and settled against actual usage when it finishes.","aiQuote.refresh":"Refresh estimate","aiQuote.request":"Get estimate","aiQuote.execute":"Run","plan.current":"Current plan","plan.usage":"Usage this period","plan.allowed":"Included","plan.locked":"Not included","plan.manage":"Manage plan","plan.retry":"Reload","history.title":"Credit history","history.empty":"No history yet.","history.unresolved":"Checking","history.grant":"Granted","history.charge":"Used","history.hold":"Reserved · not settled","history.release":"Reservation released","credits.choosePlan":"Credits to add","credits.rechargeDescription":"Choose an amount and review the price and terms.","credits.paymentAmount":"Payment amount","credits.paymentPending":"Checking payment status","credits.remaining":"Remaining credits","credits.available":"Available credits","credits.unit":"credits","credits.held":"Reserved","credits.spent":"Total used","credits.charged":"Charged","credits.low":"Your credit balance is running low.","credits.recharge":"Add credits","credits.checkout":"Continue to payment","credits.noPlans":"No credit packs are available right now.","aiTask.title":"AI task","aiTask.idle":"Not started","aiTask.queued":"Queued","aiTask.running":"Running","aiTask.succeeded":"Complete","aiTask.failed":"Failed","aiTask.cancelled":"Cancelled","aiTask.needs_attention":"Review needed","aiTask.attentionDescription":"We’re checking the result and usage. Reserved credits remain on hold until the review is complete.",close:"Close","modal.close":"Close modal","drawer.close":"Close drawer","dialog.close":"Close dialog","toast.close":"Dismiss notification",clear:"Clear",clearAll:"Clear all",loading:"Loading",required:"Required",optional:"Optional",error:"Error",success:"Success",warning:"Warning",info:"Information",previous:"Previous",next:"Next","select.placeholder":"Select…","select.noResults":"No results","navigationRail.label":"Main navigation","navigationRail.newNotification":"New notification","navigationRail.notifications":"Notifications","tabs.label":"Tabs","pagination.label":"Pagination","search.label":"Search","media.play":"Play","media.pause":"Pause","media.mute":"Mute","media.unmute":"Unmute","media.settings":"Settings","media.shuffle":"Shuffle","media.repeat":"Repeat","media.previous":"Previous track","media.next":"Next track","media.fullscreen":"Full screen","confirm.ok":"Confirm","confirm.cancel":"Cancel","carousel.slide":"slide","carousel.role":"carousel","carousel.previous":"Previous slide","carousel.next":"Next slide","breadcrumbs.expand":"Show hidden path","input.showPassword":"Show password","input.hidePassword":"Hide password","input.clearSearch":"Clear search","verify.done":"Verified","verify.phoneDone":"Your phone number has been verified.","bentoCard.cta":"View details","tweetCard.loading":"Loading post…","tweetCard.error":"Unable to load this post.","tweetCard.viewOnTwitter":"View on X","tweetCard.like":"Like","tweetCard.retweet":"Repost","tweetCard.reply":"Reply","tweetCard.verified":"Verified account","tweetCard.retry":"Try again","chatComposer.placeholder":"Type a message…","chatComposer.send":"Send","chatComposer.stop":"Stop generating","chatComposer.attach":"Attach","voice.start":"Voice input","voice.stop":"Finish dictation","voice.cancel":"Cancel voice input","voice.starting":"Connecting microphone","voice.listening":"Listening","voice.stopping":"Finishing transcription","voice.error":"Voice recognition failed","voice.unsupported":"Voice input not supported"};export{e as ENGLISH_STRINGS};
1
+ const e={"media.sourceSize":"Size","media.sourceRotation":"Rotation","media.sourceTransform":"Source transform","media.resizeLabel":"Resize track label width","layerList.label":"Layers","layerList.delete":"Delete {name}","layerList.lock":"Lock {name}","layerList.unlock":"Unlock {name}","layerList.show":"Show {name}","layerList.hide":"Hide {name}","layerList.empty":"No layers.","appUpdate.notice":"Update available","appUpdate.changes":"What’s new","appUpdate.automatic":"Restarts automatically when complete.","appUpdate.install":"Update","appUpdate.inProgress":"Updating","appUpdate.dialogTitle":"App update","appUpdate.toastComplete":"Updated to {version}.","appUpdate.checking.title":"Checking for updates","appUpdate.checking.description":"Looking for a new update.","appUpdate.up-to-date.title":"You’re up to date","appUpdate.up-to-date.description":"You’re using the latest available version.","appUpdate.available.title":"An update is available","appUpdate.available.description":"Download the latest improvements and stability updates.","appUpdate.downloading.title":"Downloading update","appUpdate.downloading.description":"We’ll let you know how to apply it when the download finishes.","appUpdate.ready.title":"Ready to update","appUpdate.ready.description":"Save your work, then restart the app to apply the update.","appUpdate.applying.title":"Applying update","appUpdate.applying.description":"Keep the app open and wait a moment.","appUpdate.complete.title":"Update complete","appUpdate.complete.description":"The new version has been applied successfully.","appUpdate.error.title":"Update could not be completed","appUpdate.error.description":"Check your connection and try again.","appUpdate.offline.title":"Internet connection required","appUpdate.offline.description":"Connect to the internet, then try again.","appUpdate.current":"Current","appUpdate.target":"New version","appUpdate.required":"Required update","appUpdate.later":"Later","appUpdate.download":"Download update","appUpdate.restart":"Restart to update","appUpdate.retry":"Try again","appUpdate.done":"Done","media.restoreHandles":"Restore handles (H)","media.tangentHelp":"Drag: symmetric · Cmd/Ctrl: independent · H: restore · dashed handles reveal collapsed controls.","media.resizeTimeline":"Resize timeline height","media.resizeTimelineHint":"Drag vertically to resize. Double-click or Home for automatic height.","media.resizeTrack":"Resize track height","easing.outVelocity":"Out velocity ×","easing.inVelocity":"In velocity ×","easing.label":"Easing & velocity","easing.graphMode":"Graph mode","easing.value":"Value","easing.speed":"Speed","easing.valueGraph":"Normalized progress curve","easing.speedGraph":"Normalized velocity graph","easing.custom":"Custom curve","easing.presets":"Easing presets","easing.linear":"Linear","easing.ease":"Ease","easing.ease-in":"Ease in","easing.ease-out":"Ease out","easing.ease-in-out":"Ease in/out","easing.smooth":"Smooth","easing.snappy":"Snappy","easing.swift":"Swift out","easing.gentle":"Gentle","easing.emphasized":"Emphasized","easing.outgoing":"Outgoing tangent","easing.incoming":"Incoming tangent","easing.preview":"Play easing preview","easing.velocityHint":"1× = constant speed","easing.speedHelp":"Drag tangent controls to change influence and endpoint velocity. Vertical scale is normalized; multiply by value change / duration for units per second.","easing.valueHelp":"Drag handles or enter exact coordinates. Arrow keys nudge .01; Shift nudges .1. Escape cancels dragging.","media.trimStart":"Trim start","media.trimEnd":"Trim end","media.fadeIn":"Fade in","media.fadeOut":"Fade out","media.duplicateKeys":"Duplicate keyframes","media.undo":"Undo","media.selectedKeys":"selected","media.keyEasing":"Selected key easing","media.effects":"Applied effects","media.timeline":"Video timeline","media.tracks":"Tracks","media.more":"Scroll to more tracks and clips","media.lock":"Lock track","media.trackLocked":"Locked","media.trackUnlocked":"Unlocked","media.soundOn":"Sound on","media.soundOff":"Muted","media.marker":"Timeline marker","media.trackMute":"Mute track","media.duration":"Duration","media.playhead":"Playhead","media.emptyTracks":"No tracks. Connect a tracks array to begin.","media.keyframes":"Keyframes","media.curve":"Value graph","media.previousKey":"Previous keyframe","media.nextKey":"Next keyframe","media.addKey":"Add keyframe (K)","media.deleteKey":"Delete keyframe","media.outHandle":"Outgoing Bezier handle","media.inHandle":"Incoming Bezier handle","media.keyframe":"Keyframe","media.emptyKeys":"Add a keyframe or double-click the graph.","media.selectKey":"Select a keyframe to edit its time, value and interpolation.","media.time":"Time (seconds)","media.value":"Value","media.interpolation":"Interpolation","media.linear":"Linear","media.hold":"Hold","media.ease-in":"Ease in","media.ease-out":"Ease out","media.ease-in-out":"Ease in / out","media.bezier":"Custom Bezier","selectionBar.count":"{count} selected","selectionBar.clear":"Clear","selectionBar.label":"Selection actions","zoom.label":"Zoom controls","zoom.out":"Zoom out","zoom.in":"Zoom in","zoom.value":"Zoom level","zoom.fit":"Fit to screen","zoom.fitShort":"Fit","zoom.fullscreen":"Enter fullscreen","zoom.exitFullscreen":"Exit fullscreen","zoom.fullscreenError":"Fullscreen is unavailable.","toolPanel.close":"Close panel","toolPanel.tabs":"Panel tabs","toolLayout.resize.sidebar":"Resize sidebar width","toolLayout.resize.inspector":"Resize inspector width","toolLayout.resize.bottom":"Resize bottom panel height","password.weak":"Weak","password.fair":"Fair","password.good":"Strong","password.veryStrong":"Very strong","rating.high":"Very good","rating.medium":"Average","rating.low":"Low","rating.label":"Rating {value}","rating.review":"{count} review","rating.reviews":"{count} reviews","countdown.days":"Days","countdown.hours":"Hours","countdown.minutes":"Minutes","countdown.seconds":"Seconds","upload.title":"Drag files here or click to choose","upload.choose":"Choose files","upload.allFormats":"All file types supported","upload.maximum":" (up to {size})","upload.files":"Uploaded files ({count})","upload.retry":"Retry","upload.remove":"Remove","upload.illustration":"Upload illustration","creditCard.security":"Security code {code}","creditCard.label":"{issuer} card{lastFour}, flip","creditCard.ending":", ending in {digits}","creditCard.front":"Front","creditCard.back":"Back","creditCard.showing":"Showing {side}","creditCard.expiry":"Expires {expiry}","creditCard.show":"Press to show {side}","flight.scheduled":"Scheduled","flight.boarding":"Boarding","flight.in-flight":"In flight","flight.landed":"Landed","flight.delayed":"Delayed","flight.cancelled":"Cancelled","flight.title":"Flight status","flight.progress":"Journey progress","boardingPass.origin":"From","boardingPass.destination":"To","boardingPass.passenger":"Passenger","boardingPass.service":"Flight","boardingPass.date":"Date","boardingPass.boarding":"Boarding","boardingPass.departure":"Departure","boardingPass.arrival":"Arrival","boardingPass.terminal":"Terminal","boardingPass.gate":"Gate","boardingPass.seat":"Seat","boardingPass.travelClass":"Class","boardingPass.title":"Boarding pass","wallet.title":"My boarding passes","wallet.empty":"No boarding passes yet.","wallet.journeys":"Choose a journey","receipt.quantity":"Qty","receipt.subtotal":"Subtotal","receipt.discount":"Discount","receipt.tax":"Tax","receipt.total":"Total","receipt.payment":"Payment","receipt.empty":"No items to display.","receipt.invalid":"Check the items and amounts.","receipt.title":"Receipt","ticket.title":"Ticket","paper.codeImage":"Code image","aiElement.emptyFolder":"Empty folder","aiElement.noFiles":"No files to display.","attachment.title":"Attachments","attachment.empty":"No attachments yet.","attachment.queued":"Queued","attachment.uploading":"Uploading","attachment.processing":"Processing","attachment.ready":"Ready","attachment.error":"Failed","attachment.cancelled":"Cancelled","attachment.unknown":"Checking progress…","attachment.cancel":"Cancel","attachment.retry":"Retry","attachment.remove":"Remove from list","attachment.open":"View details","attachment.reload":"Reload","workspace.members":"Workspace members","workspace.invite":"Invite members","workspace.email":"Email","workspace.role":"Role","workspace.chooseRole":"Choose a role","workspace.description":"Review the email address and role of the person you want to invite.","workspace.submit":"Send invitation","workspace.emailError":"Enter a valid email address of up to 254 characters.","workspace.roleError":"Choose a role for the invitation.","workspace.retry":"Reload","workspace.reinvite":"Invite again","workspace.active":"Active","workspace.invited":"Invitation pending","workspace.expired":"Invitation expired","workspace.empty":"No members to display yet.","aiElement.pending":"Pending","aiElement.running":"In progress","aiElement.complete":"Complete","aiElement.error":"Failed","aiElement.cancelled":"Cancelled","aiElement.approval":"Awaiting approval","aiElement.researching":"Reviewing sources","aiElement.researched":"View research","aiElement.thinking":"Thinking","aiElement.reasoning":"Response summary","aiElement.plan":"Plan","aiElement.queue":"Queued requests","aiElement.remove":"Remove","aiElement.queueEmpty":"No queued requests.","aiElement.sources":"Sources","aiElement.citation":"Source","aiElement.checkpoint":"Checkpoint saved","aiElement.restore":"Restore from here","aiElement.confirmation":"Proceed with this action?","aiElement.allow":"Allow","aiElement.reject":"Reject","aiElement.accepted":"Action approved.","aiElement.rejected":"Action rejected.","aiElement.context":"Context","aiElement.tokens":"Tokens","aiElement.unknown":"Usage is not available yet.","aiElement.files":"Files","aiElement.task":"Task progress","aiElement.subagent":"Subagent","aiElement.tool":"Tool call","aiElement.terminal":"Terminal","aiElement.stackTrace":"Error details","aiElement.tests":"Test results","aiElement.passed":"Passed","aiElement.failed":"Failed","aiElement.input":"Input","aiElement.output":"Output","aiElement.user":"User","aiElement.assistant":"AI response","aiElement.copy":"Copy","aiElement.regenerate":"Regenerate","aiElement.like":"Like","aiElement.dislike":"Dislike","aiElement.latest":"Latest response","aiElement.conversation":"AI conversation","notification.title":"Notifications","notification.filters":"Notification filters","notification.all":"All","notification.unread":"Unread","notification.readAll":"Mark displayed notifications as read","notification.markRead":"Mark as read","notification.retry":"Reload","notification.empty":"No notifications yet.","notification.caughtUp":"You’re all caught up.","notification.countUnknown":"Needs review","aiModel.label":"Model","aiModel.choose":"Choose a model","aiModel.empty":"No models are available right now.","aiModel.capabilities":"Capabilities","aiModel.usage":"Usage unit","aiQuote.expires":"Quote expires","aiQuote.title":"Execution estimate","aiQuote.reserved":"Maximum credit reservation","aiQuote.providerCost":"Estimated provider cost","aiQuote.pricingVersion":"Pricing version","aiQuote.unknown":"Unconfirmed","aiQuote.idle":"Choose a model and enter a request to get an estimate.","aiQuote.loading":"Checking the estimate…","aiQuote.executing":"Processing your request. Do not submit it again.","aiQuote.stale":"The model or input has changed. Refresh the estimate.","aiQuote.expired":"The estimate has expired. Request a new estimate.","aiQuote.balanceUnknown":"Your balance is unavailable. Refresh to try again.","aiQuote.insufficient":"Additional credits needed:","aiQuote.ready":"Credits are reserved when execution starts and settled against actual usage when it finishes.","aiQuote.refresh":"Refresh estimate","aiQuote.request":"Get estimate","aiQuote.execute":"Run","plan.current":"Current plan","plan.usage":"Usage this period","plan.allowed":"Included","plan.locked":"Not included","plan.manage":"Manage plan","plan.retry":"Reload","history.title":"Credit history","history.empty":"No history yet.","history.unresolved":"Checking","history.grant":"Granted","history.charge":"Used","history.hold":"Reserved · not settled","history.release":"Reservation released","credits.choosePlan":"Credits to add","credits.rechargeDescription":"Choose an amount and review the price and terms.","credits.paymentAmount":"Payment amount","credits.paymentPending":"Checking payment status","credits.remaining":"Remaining credits","credits.available":"Available credits","credits.unit":"credits","credits.held":"Reserved","credits.spent":"Total used","credits.charged":"Charged","credits.low":"Your credit balance is running low.","credits.recharge":"Add credits","credits.checkout":"Continue to payment","credits.noPlans":"No credit packs are available right now.","aiTask.title":"AI task","aiTask.idle":"Not started","aiTask.queued":"Queued","aiTask.running":"Running","aiTask.succeeded":"Complete","aiTask.failed":"Failed","aiTask.cancelled":"Cancelled","aiTask.needs_attention":"Review needed","aiTask.attentionDescription":"We’re checking the result and usage. Reserved credits remain on hold until the review is complete.",close:"Close","modal.close":"Close modal","drawer.close":"Close drawer","dialog.close":"Close dialog","toast.close":"Dismiss notification",clear:"Clear",clearAll:"Clear all",loading:"Loading",required:"Required",optional:"Optional",error:"Error",success:"Success",warning:"Warning",info:"Information",previous:"Previous",next:"Next","select.placeholder":"Select…","select.noResults":"No results","navigationRail.label":"Main navigation","navigationRail.newNotification":"New notification","navigationRail.notifications":"Notifications","tabs.label":"Tabs","pagination.label":"Pagination","search.label":"Search","media.play":"Play","media.pause":"Pause","media.mute":"Mute","media.unmute":"Unmute","media.settings":"Settings","media.shuffle":"Shuffle","media.repeat":"Repeat","media.previous":"Previous track","media.next":"Next track","media.fullscreen":"Full screen","confirm.ok":"Confirm","confirm.cancel":"Cancel","carousel.slide":"slide","carousel.role":"carousel","carousel.previous":"Previous slide","carousel.next":"Next slide","breadcrumbs.expand":"Show hidden path","input.showPassword":"Show password","input.hidePassword":"Hide password","input.clearSearch":"Clear search","verify.done":"Verified","verify.phoneDone":"Your phone number has been verified.","bentoCard.cta":"View details","tweetCard.loading":"Loading post…","tweetCard.error":"Unable to load this post.","tweetCard.viewOnTwitter":"View on X","tweetCard.like":"Like","tweetCard.retweet":"Repost","tweetCard.reply":"Reply","tweetCard.verified":"Verified account","tweetCard.retry":"Try again","chatComposer.placeholder":"Type a message…","chatComposer.send":"Send","chatComposer.stop":"Stop generating","chatComposer.attach":"Attach","voice.start":"Voice input","voice.stop":"Finish dictation","voice.cancel":"Cancel voice input","voice.starting":"Connecting microphone","voice.listening":"Listening","voice.stopping":"Finishing transcription","voice.error":"Voice recognition failed","voice.unsupported":"Voice input not supported"};export{e as ENGLISH_STRINGS};
@@ -11,6 +11,9 @@
11
11
  */
12
12
  /** 컴포넌트가 쓰는 문자열 키 → 기본(한국어) 값 */
13
13
  declare const DEFAULT_STRINGS: {
14
+ readonly "media.sourceSize": "크기";
15
+ readonly "media.sourceRotation": "회전";
16
+ readonly "media.sourceTransform": "원본 변형";
14
17
  readonly "media.resizeLabel": "트랙 이름 영역 너비 조정";
15
18
  readonly "layerList.label": "레이어 목록";
16
19
  readonly "layerList.delete": "{name} 삭제";
@@ -1 +1 @@
1
- import{literalEnglish as e}from"./i18n-copy.js";import{ENGLISH_STRINGS as a}from"./i18n-en.js";const i={"media.resizeLabel":"트랙 이름 영역 너비 조정","layerList.label":"레이어 목록","layerList.delete":"{name} 삭제","layerList.lock":"{name} 잠금","layerList.unlock":"{name} 잠금 해제","layerList.show":"{name} 표시","layerList.hide":"{name} 숨기기","layerList.empty":"레이어가 없습니다.","appUpdate.notice":"업데이트 안내","appUpdate.changes":"주요 변경 내용","appUpdate.automatic":"완료 후 자동으로 다시 시작됩니다.","appUpdate.install":"업데이트","appUpdate.inProgress":"업데이트 중","appUpdate.dialogTitle":"앱 업데이트","appUpdate.toastComplete":"{version} 업데이트가 완료됐어요.","appUpdate.checking.title":"업데이트 확인 중","appUpdate.checking.description":"새로운 업데이트가 있는지 확인하고 있습니다.","appUpdate.up-to-date.title":"최신 버전입니다","appUpdate.up-to-date.description":"현재 사용할 수 있는 최신 버전을 이용 중입니다.","appUpdate.available.title":"새로운 업데이트가 있어요","appUpdate.available.description":"개선된 기능과 안정성 업데이트를 다운로드할 수 있습니다.","appUpdate.downloading.title":"업데이트 다운로드 중","appUpdate.downloading.description":"다운로드가 끝나면 적용 방법을 안내해 드립니다.","appUpdate.ready.title":"업데이트를 적용할 준비가 됐어요","appUpdate.ready.description":"작업을 저장한 뒤 앱을 다시 시작해 업데이트를 적용하세요.","appUpdate.applying.title":"업데이트 적용 중","appUpdate.applying.description":"앱을 닫지 말고 잠시 기다려 주세요.","appUpdate.complete.title":"업데이트가 완료됐어요","appUpdate.complete.description":"새 버전이 정상적으로 적용되었습니다.","appUpdate.error.title":"업데이트를 완료하지 못했어요","appUpdate.error.description":"연결 상태를 확인하고 다시 시도해 주세요.","appUpdate.offline.title":"인터넷 연결이 필요해요","appUpdate.offline.description":"인터넷에 연결한 뒤 업데이트를 다시 시도해 주세요.","appUpdate.current":"현재","appUpdate.target":"새 버전","appUpdate.required":"필수 업데이트","appUpdate.later":"나중에","appUpdate.download":"업데이트 다운로드","appUpdate.restart":"다시 시작하여 적용","appUpdate.retry":"다시 시도","appUpdate.done":"확인","media.restoreHandles":"핸들 복구 (H)","media.tangentHelp":"드래그: 대칭 · Cmd/Ctrl: 개별 · H: 복구 · 겹친 핸들은 점선으로 꺼내 표시합니다.","media.resizeTimeline":"타임라인 높이 조절","media.resizeTimelineHint":"위아래 드래그로 높이 조절. 두 번 클릭 또는 Home으로 자동 높이 복원.","media.resizeTrack":"트랙 높이 조절","easing.outVelocity":"나가는 속도 ×","easing.inVelocity":"들어오는 속도 ×","easing.label":"이징 · 속도","easing.graphMode":"그래프 종류","easing.value":"값 곡선","easing.speed":"속도","easing.valueGraph":"정규화된 진행 곡선","easing.speedGraph":"정규화된 속도 그래프","easing.custom":"사용자 곡선","easing.presets":"이징 프리셋","easing.linear":"선형","easing.ease":"기본 이징","easing.ease-in":"시작 가속","easing.ease-out":"종료 감속","easing.ease-in-out":"가속 · 감속","easing.smooth":"부드럽게","easing.snappy":"빠른 전환","easing.swift":"빠른 진입","easing.gentle":"은은하게","easing.emphasized":"강조","easing.outgoing":"나가는 접선","easing.incoming":"들어오는 접선","easing.preview":"이징 미리보기 재생","easing.velocityHint":"1× = 일정한 속도","easing.speedHelp":"접선 핸들로 영향 범위와 끝점 속도를 조정합니다. 실제 초당 속도는 정규화된 값에 값 변화량 / 구간 시간을 곱합니다.","easing.valueHelp":"핸들을 드래그하거나 좌표를 입력하세요. 방향키 .01, Shift .1 단위 조정, Esc 이동 취소.","media.trimStart":"시작 트림","media.trimEnd":"끝 트림","media.fadeIn":"페이드 인","media.fadeOut":"페이드 아웃","media.duplicateKeys":"키프레임 복제","media.undo":"실행 취소","media.selectedKeys":"개 선택됨","media.keyEasing":"선택 키 이징","media.effects":"적용 효과","media.timeline":"영상 타임라인","media.tracks":"트랙","media.more":"오른쪽 클립 더 보기","media.lock":"트랙 잠금","media.trackLocked":"잠김","media.trackUnlocked":"잠금 해제됨","media.soundOn":"소리 켜짐","media.soundOff":"음소거됨","media.marker":"타임라인 마커","media.trackMute":"트랙 음소거","media.duration":"길이","media.playhead":"재생 위치","media.emptyTracks":"트랙 배열을 연결해 시작하세요.","media.keyframes":"키프레임","media.curve":"값 그래프","media.previousKey":"이전 키프레임","media.nextKey":"다음 키프레임","media.addKey":"키프레임 추가 (K)","media.deleteKey":"키프레임 삭제","media.outHandle":"나가는 베지어 핸들","media.inHandle":"들어오는 베지어 핸들","media.keyframe":"키프레임","media.emptyKeys":"키프레임을 추가하거나 그래프를 두 번 클릭하세요.","media.selectKey":"키프레임을 선택해 시간·값·보간을 편집하세요.","media.time":"시간 (초)","media.value":"값","media.interpolation":"보간","media.linear":"선형","media.hold":"유지","media.ease-in":"서서히 시작","media.ease-out":"서서히 종료","media.ease-in-out":"서서히 시작 / 종료","media.bezier":"사용자 베지어","selectionBar.count":"{count}개 선택","selectionBar.clear":"해제","selectionBar.label":"선택 항목 작업","zoom.label":"확대/축소 도구","zoom.out":"축소","zoom.in":"확대","zoom.value":"확대 비율 선택","zoom.fit":"화면에 맞추기","zoom.fitShort":"맞춤","zoom.fullscreen":"전체화면","zoom.exitFullscreen":"전체화면 종료","zoom.fullscreenError":"전체화면을 열 수 없습니다.","toolPanel.close":"패널 닫기","toolPanel.tabs":"패널 탭","toolLayout.resize.sidebar":"사이드 패널 너비 조절","toolLayout.resize.inspector":"인스펙터 너비 조절","toolLayout.resize.bottom":"하단 패널 높이 조절","password.weak":"약함","password.fair":"보통","password.good":"강함","password.veryStrong":"아주 강함","rating.high":"매우 좋음","rating.medium":"보통","rating.low":"낮음","rating.label":"평점 {value}","rating.review":"리뷰 {count}개","rating.reviews":"리뷰 {count}개","countdown.days":"일","countdown.hours":"시간","countdown.minutes":"분","countdown.seconds":"초","upload.title":"파일을 드래그하여 업로드하거나 클릭하여 선택하세요","upload.choose":"파일 선택","upload.allFormats":"모든 파일 형식 지원","upload.maximum":" (최대 {size})","upload.files":"업로드된 파일들 ({count}개)","upload.retry":"재시도","upload.remove":"삭제","upload.illustration":"업로드 일러스트","creditCard.security":"보안 코드 {code}","creditCard.label":"{issuer} 카드{lastFour}, 뒤집기","creditCard.ending":", 끝자리 {digits}","creditCard.front":"앞면","creditCard.back":"뒷면","creditCard.showing":"{side} 표시 중","creditCard.expiry":"유효기간 {expiry}","creditCard.show":"누르면 {side}을 표시합니다","flight.scheduled":"출발 예정","flight.boarding":"탑승 중","flight.in-flight":"운항 중","flight.landed":"도착","flight.delayed":"지연","flight.cancelled":"취소","flight.title":"운항 현황","flight.progress":"여정 진행률","boardingPass.origin":"출발","boardingPass.destination":"도착","boardingPass.passenger":"탑승객","boardingPass.service":"편명","boardingPass.date":"날짜","boardingPass.boarding":"탑승 시각","boardingPass.departure":"출발 시각","boardingPass.arrival":"도착 시각","boardingPass.terminal":"터미널","boardingPass.gate":"게이트","boardingPass.seat":"좌석","boardingPass.travelClass":"등급","boardingPass.title":"탑승권","wallet.title":"나의 탑승권","wallet.empty":"등록된 탑승권이 없습니다.","wallet.journeys":"여정 선택","receipt.quantity":"수량","receipt.subtotal":"소계","receipt.discount":"할인","receipt.tax":"세금","receipt.total":"합계","receipt.payment":"결제수단","receipt.empty":"표시할 품목이 없습니다.","receipt.invalid":"품목과 금액 정보를 확인해주세요.","receipt.title":"영수증","ticket.title":"티켓","paper.codeImage":"코드 이미지","aiElement.emptyFolder":"빈 폴더","aiElement.noFiles":"표시할 파일이 없습니다.","attachment.title":"첨부 파일","attachment.empty":"아직 첨부한 파일이 없습니다.","attachment.queued":"대기 중","attachment.uploading":"업로드 중","attachment.processing":"서버 처리 중","attachment.ready":"완료","attachment.error":"실패","attachment.cancelled":"취소됨","attachment.unknown":"진행률을 확인하고 있어요.","attachment.cancel":"취소","attachment.retry":"재시도","attachment.remove":"목록에서 제거","attachment.open":"상세 보기","attachment.reload":"다시 불러오기","workspace.members":"워크스페이스 멤버","workspace.invite":"멤버 초대","workspace.email":"이메일","workspace.role":"초대 역할","workspace.chooseRole":"역할을 선택하세요","workspace.description":"함께 작업할 사람의 이메일과 역할을 확인하세요.","workspace.submit":"초대 요청","workspace.emailError":"254자 이내의 올바른 이메일 주소를 입력해 주세요.","workspace.roleError":"초대할 역할을 선택해 주세요.","workspace.retry":"다시 불러오기","workspace.reinvite":"다시 초대","workspace.active":"이용 중","workspace.invited":"초대 대기","workspace.expired":"초대 만료","workspace.empty":"아직 표시할 멤버가 없습니다.","aiElement.pending":"대기 중","aiElement.running":"진행 중","aiElement.complete":"완료","aiElement.error":"실패","aiElement.cancelled":"취소됨","aiElement.approval":"승인 대기","aiElement.researching":"자료를 확인하고 있어요","aiElement.researched":"작업 과정 살펴보기","aiElement.thinking":"생각 정리 중","aiElement.reasoning":"응답 요약","aiElement.plan":"작업 계획","aiElement.queue":"대기 중인 요청","aiElement.remove":"제거","aiElement.queueEmpty":"대기 중인 요청이 없습니다.","aiElement.sources":"참고한 출처","aiElement.citation":"출처","aiElement.checkpoint":"체크포인트 저장됨","aiElement.restore":"여기부터 복원","aiElement.confirmation":"이 작업을 진행할까요?","aiElement.allow":"허용","aiElement.reject":"거절","aiElement.accepted":"작업을 허용했습니다.","aiElement.rejected":"작업을 거절했습니다.","aiElement.context":"컨텍스트","aiElement.tokens":"토큰","aiElement.unknown":"사용량을 아직 확인할 수 없습니다.","aiElement.files":"작업 파일","aiElement.task":"작업 진행","aiElement.subagent":"보조 에이전트","aiElement.tool":"도구 실행","aiElement.terminal":"터미널","aiElement.stackTrace":"오류 상세","aiElement.tests":"테스트 결과","aiElement.passed":"통과","aiElement.failed":"실패","aiElement.input":"입력","aiElement.output":"출력","aiElement.user":"사용자","aiElement.assistant":"AI 응답","aiElement.copy":"복사","aiElement.regenerate":"다시 생성","aiElement.like":"좋아요","aiElement.dislike":"싫어요","aiElement.latest":"최신 응답으로","aiElement.conversation":"AI와의 대화","notification.title":"알림","notification.filters":"알림 필터","notification.all":"전체","notification.unread":"안 읽음","notification.readAll":"표시된 알림 모두 읽음","notification.markRead":"읽음으로 표시","notification.retry":"다시 불러오기","notification.empty":"아직 도착한 알림이 없습니다.","notification.caughtUp":"모든 알림을 확인했어요.","notification.countUnknown":"확인 필요","aiModel.label":"사용 모델","aiModel.choose":"모델을 선택하세요","aiModel.empty":"현재 사용할 수 있는 모델이 없습니다.","aiModel.capabilities":"지원 작업","aiModel.usage":"사용량 단위","aiQuote.expires":"견적 유효 시간","aiQuote.title":"실행 전 견적","aiQuote.reserved":"예약할 최대 크레딧","aiQuote.providerCost":"예상 제공사 비용","aiQuote.pricingVersion":"가격표 버전","aiQuote.unknown":"미확정","aiQuote.idle":"모델과 요청 내용을 정한 뒤 견적을 확인하세요.","aiQuote.loading":"견적을 확인하고 있습니다.","aiQuote.executing":"요청을 처리하고 있습니다. 중복 실행하지 마세요.","aiQuote.stale":"모델 또는 입력이 변경됐습니다. 견적을 다시 확인하세요.","aiQuote.expired":"견적이 만료됐습니다. 견적을 다시 확인하세요.","aiQuote.balanceUnknown":"잔액을 확인할 수 없습니다. 새로 확인해 주세요.","aiQuote.insufficient":"추가로 필요한 크레딧:","aiQuote.ready":"실행 시 최대 크레딧을 예약하고, 결과 확인 후 실제 사용량으로 정산합니다.","aiQuote.refresh":"견적 다시 확인","aiQuote.request":"견적 확인","aiQuote.execute":"실행하기","plan.current":"현재 플랜","plan.usage":"이번 기간 사용","plan.allowed":"이용 가능","plan.locked":"미포함","plan.manage":"플랜 관리","plan.retry":"다시 불러오기","history.title":"크레딧 사용 내역","history.empty":"아직 표시할 내역이 없습니다.","history.unresolved":"확인 중","history.grant":"지급","history.charge":"사용","history.hold":"예약 · 정산 전","history.release":"예약 해제","credits.choosePlan":"충전할 크레딧","credits.rechargeDescription":"필요한 만큼 선택하고, 금액과 제공 조건을 확인하세요.","credits.paymentAmount":"결제 금액","credits.paymentPending":"결제 상태 확인 중","credits.remaining":"현재 남은 크레딧","credits.available":"사용 가능 크레딧","credits.unit":"크레딧","credits.held":"예약 중","credits.spent":"누적 사용","credits.charged":"차감","credits.low":"남은 크레딧이 적습니다.","credits.recharge":"크레딧 충전","credits.checkout":"결제 진행","credits.noPlans":"현재 충전 가능한 상품이 없습니다.","aiTask.title":"AI 작업","aiTask.idle":"작업 전","aiTask.queued":"실행 대기","aiTask.running":"실행 중","aiTask.succeeded":"완료","aiTask.failed":"실패","aiTask.cancelled":"취소됨","aiTask.needs_attention":"결과 확인 필요","aiTask.attentionDescription":"결과와 사용량을 확인하고 있습니다. 확인이 끝날 때까지 예약 크레딧이 유지됩니다.",close:"닫기","modal.close":"모달 닫기","drawer.close":"드로어 닫기","dialog.close":"대화상자 닫기","toast.close":"알림 닫기",clear:"지우기",clearAll:"모든 값 지우기",loading:"로딩 중",required:"필수",optional:"선택",error:"오류",success:"성공",warning:"경고",info:"안내",previous:"이전",next:"다음","select.placeholder":"선택하세요","select.noResults":"결과 없음","navigationRail.label":"주요 내비게이션","navigationRail.newNotification":"새 알림","navigationRail.notifications":"알림","tabs.label":"탭 목록","pagination.label":"페이지 번호","search.label":"검색","media.play":"재생","media.pause":"일시정지","media.mute":"음소거","media.unmute":"음소거 해제","media.settings":"설정","media.shuffle":"셔플","media.repeat":"반복","media.previous":"이전 트랙","media.next":"다음 트랙","media.fullscreen":"전체화면","confirm.ok":"확인","confirm.cancel":"취소","carousel.slide":"슬라이드","carousel.role":"캐러셀","carousel.previous":"이전 슬라이드","carousel.next":"다음 슬라이드","breadcrumbs.expand":"생략된 경로 펼치기","input.showPassword":"비밀번호 보기","input.hidePassword":"비밀번호 숨기기","input.clearSearch":"검색어 지우기","verify.done":"인증 완료","verify.phoneDone":"휴대폰 번호 인증이 완료되었습니다.","bentoCard.cta":"자세히 보기","tweetCard.loading":"트윗을 불러오는 중입니다...","tweetCard.error":"트윗을 불러올 수 없습니다.","tweetCard.viewOnTwitter":"X에서 보기","tweetCard.like":"좋아요","tweetCard.retweet":"리포스트","tweetCard.reply":"답글","tweetCard.verified":"인증된 계정","tweetCard.retry":"다시 시도","chatComposer.placeholder":"메시지를 입력하세요…","chatComposer.send":"전송","chatComposer.stop":"생성 중지","chatComposer.attach":"첨부","voice.start":"음성 입력","voice.stop":"받아쓰기 완료","voice.cancel":"음성 입력 취소","voice.starting":"마이크 연결 중","voice.listening":"음성 인식 중","voice.stopping":"마지막 문장 확인 중","voice.error":"음성 인식 실패","voice.unsupported":"음성 입력 미지원"},n=Symbol.for("@designbasekorea/ui-wc/locale/v1"),o=globalThis,r=o[n]??(o[n]={overrides:{},activeLocale:"ko",listeners:new Set}),l=r.listeners;function subscribeDbLocale(e){return l.add(e),()=>{l.delete(e)}}function notifyLocale(){for(const e of[...l])e()}function dbUseLocale(e){r.activeLocale!==e&&(r.activeLocale=e,notifyLocale())}function dbSetLocale(e){r.overrides={...r.overrides,...e},notifyLocale()}function dbResetLocale(){r.overrides={},r.activeLocale="ko",notifyLocale()}function t(n,o){return n in r.overrides?r.overrides[n]:n in i?"en"===r.activeLocale?a[n]:i[n]:n.startsWith("copy.")&&"en"===r.activeLocale?e[n.slice(5)]??o??n:o??n}function labelFrom(e,a,i){return e.getAttribute(a)||t(i)}const s=new Map(Object.entries(e).map(([e,a])=>[a,e]));function dbText(e){const a=e.trim();if(!a)return e;const i=s.get(a.replace(/\s+/g," "))??a;return e.slice(0,e.indexOf(a))+t("copy."+i.replace(/\s+/g," "),i)+e.slice(e.indexOf(a)+a.length)}function getDbLocale(){return r.activeLocale}function dbFormat(e,a){return dbText(e).replace(/\{(\d+)\}/g,(e,i)=>String(a[Number(i)]??""))}export{dbFormat,dbResetLocale,dbSetLocale,dbText,dbUseLocale,getDbLocale,labelFrom,subscribeDbLocale,t};
1
+ import{literalEnglish as e}from"./i18n-copy.js";import{ENGLISH_STRINGS as a}from"./i18n-en.js";const i={"media.sourceSize":"크기","media.sourceRotation":"회전","media.sourceTransform":"원본 변형","media.resizeLabel":"트랙 이름 영역 너비 조정","layerList.label":"레이어 목록","layerList.delete":"{name} 삭제","layerList.lock":"{name} 잠금","layerList.unlock":"{name} 잠금 해제","layerList.show":"{name} 표시","layerList.hide":"{name} 숨기기","layerList.empty":"레이어가 없습니다.","appUpdate.notice":"업데이트 안내","appUpdate.changes":"주요 변경 내용","appUpdate.automatic":"완료 후 자동으로 다시 시작됩니다.","appUpdate.install":"업데이트","appUpdate.inProgress":"업데이트 중","appUpdate.dialogTitle":"앱 업데이트","appUpdate.toastComplete":"{version} 업데이트가 완료됐어요.","appUpdate.checking.title":"업데이트 확인 중","appUpdate.checking.description":"새로운 업데이트가 있는지 확인하고 있습니다.","appUpdate.up-to-date.title":"최신 버전입니다","appUpdate.up-to-date.description":"현재 사용할 수 있는 최신 버전을 이용 중입니다.","appUpdate.available.title":"새로운 업데이트가 있어요","appUpdate.available.description":"개선된 기능과 안정성 업데이트를 다운로드할 수 있습니다.","appUpdate.downloading.title":"업데이트 다운로드 중","appUpdate.downloading.description":"다운로드가 끝나면 적용 방법을 안내해 드립니다.","appUpdate.ready.title":"업데이트를 적용할 준비가 됐어요","appUpdate.ready.description":"작업을 저장한 뒤 앱을 다시 시작해 업데이트를 적용하세요.","appUpdate.applying.title":"업데이트 적용 중","appUpdate.applying.description":"앱을 닫지 말고 잠시 기다려 주세요.","appUpdate.complete.title":"업데이트가 완료됐어요","appUpdate.complete.description":"새 버전이 정상적으로 적용되었습니다.","appUpdate.error.title":"업데이트를 완료하지 못했어요","appUpdate.error.description":"연결 상태를 확인하고 다시 시도해 주세요.","appUpdate.offline.title":"인터넷 연결이 필요해요","appUpdate.offline.description":"인터넷에 연결한 뒤 업데이트를 다시 시도해 주세요.","appUpdate.current":"현재","appUpdate.target":"새 버전","appUpdate.required":"필수 업데이트","appUpdate.later":"나중에","appUpdate.download":"업데이트 다운로드","appUpdate.restart":"다시 시작하여 적용","appUpdate.retry":"다시 시도","appUpdate.done":"확인","media.restoreHandles":"핸들 복구 (H)","media.tangentHelp":"드래그: 대칭 · Cmd/Ctrl: 개별 · H: 복구 · 겹친 핸들은 점선으로 꺼내 표시합니다.","media.resizeTimeline":"타임라인 높이 조절","media.resizeTimelineHint":"위아래 드래그로 높이 조절. 두 번 클릭 또는 Home으로 자동 높이 복원.","media.resizeTrack":"트랙 높이 조절","easing.outVelocity":"나가는 속도 ×","easing.inVelocity":"들어오는 속도 ×","easing.label":"이징 · 속도","easing.graphMode":"그래프 종류","easing.value":"값 곡선","easing.speed":"속도","easing.valueGraph":"정규화된 진행 곡선","easing.speedGraph":"정규화된 속도 그래프","easing.custom":"사용자 곡선","easing.presets":"이징 프리셋","easing.linear":"선형","easing.ease":"기본 이징","easing.ease-in":"시작 가속","easing.ease-out":"종료 감속","easing.ease-in-out":"가속 · 감속","easing.smooth":"부드럽게","easing.snappy":"빠른 전환","easing.swift":"빠른 진입","easing.gentle":"은은하게","easing.emphasized":"강조","easing.outgoing":"나가는 접선","easing.incoming":"들어오는 접선","easing.preview":"이징 미리보기 재생","easing.velocityHint":"1× = 일정한 속도","easing.speedHelp":"접선 핸들로 영향 범위와 끝점 속도를 조정합니다. 실제 초당 속도는 정규화된 값에 값 변화량 / 구간 시간을 곱합니다.","easing.valueHelp":"핸들을 드래그하거나 좌표를 입력하세요. 방향키 .01, Shift .1 단위 조정, Esc 이동 취소.","media.trimStart":"시작 트림","media.trimEnd":"끝 트림","media.fadeIn":"페이드 인","media.fadeOut":"페이드 아웃","media.duplicateKeys":"키프레임 복제","media.undo":"실행 취소","media.selectedKeys":"개 선택됨","media.keyEasing":"선택 키 이징","media.effects":"적용 효과","media.timeline":"영상 타임라인","media.tracks":"트랙","media.more":"오른쪽 클립 더 보기","media.lock":"트랙 잠금","media.trackLocked":"잠김","media.trackUnlocked":"잠금 해제됨","media.soundOn":"소리 켜짐","media.soundOff":"음소거됨","media.marker":"타임라인 마커","media.trackMute":"트랙 음소거","media.duration":"길이","media.playhead":"재생 위치","media.emptyTracks":"트랙 배열을 연결해 시작하세요.","media.keyframes":"키프레임","media.curve":"값 그래프","media.previousKey":"이전 키프레임","media.nextKey":"다음 키프레임","media.addKey":"키프레임 추가 (K)","media.deleteKey":"키프레임 삭제","media.outHandle":"나가는 베지어 핸들","media.inHandle":"들어오는 베지어 핸들","media.keyframe":"키프레임","media.emptyKeys":"키프레임을 추가하거나 그래프를 두 번 클릭하세요.","media.selectKey":"키프레임을 선택해 시간·값·보간을 편집하세요.","media.time":"시간 (초)","media.value":"값","media.interpolation":"보간","media.linear":"선형","media.hold":"유지","media.ease-in":"서서히 시작","media.ease-out":"서서히 종료","media.ease-in-out":"서서히 시작 / 종료","media.bezier":"사용자 베지어","selectionBar.count":"{count}개 선택","selectionBar.clear":"해제","selectionBar.label":"선택 항목 작업","zoom.label":"확대/축소 도구","zoom.out":"축소","zoom.in":"확대","zoom.value":"확대 비율 선택","zoom.fit":"화면에 맞추기","zoom.fitShort":"맞춤","zoom.fullscreen":"전체화면","zoom.exitFullscreen":"전체화면 종료","zoom.fullscreenError":"전체화면을 열 수 없습니다.","toolPanel.close":"패널 닫기","toolPanel.tabs":"패널 탭","toolLayout.resize.sidebar":"사이드 패널 너비 조절","toolLayout.resize.inspector":"인스펙터 너비 조절","toolLayout.resize.bottom":"하단 패널 높이 조절","password.weak":"약함","password.fair":"보통","password.good":"강함","password.veryStrong":"아주 강함","rating.high":"매우 좋음","rating.medium":"보통","rating.low":"낮음","rating.label":"평점 {value}","rating.review":"리뷰 {count}개","rating.reviews":"리뷰 {count}개","countdown.days":"일","countdown.hours":"시간","countdown.minutes":"분","countdown.seconds":"초","upload.title":"파일을 드래그하여 업로드하거나 클릭하여 선택하세요","upload.choose":"파일 선택","upload.allFormats":"모든 파일 형식 지원","upload.maximum":" (최대 {size})","upload.files":"업로드된 파일들 ({count}개)","upload.retry":"재시도","upload.remove":"삭제","upload.illustration":"업로드 일러스트","creditCard.security":"보안 코드 {code}","creditCard.label":"{issuer} 카드{lastFour}, 뒤집기","creditCard.ending":", 끝자리 {digits}","creditCard.front":"앞면","creditCard.back":"뒷면","creditCard.showing":"{side} 표시 중","creditCard.expiry":"유효기간 {expiry}","creditCard.show":"누르면 {side}을 표시합니다","flight.scheduled":"출발 예정","flight.boarding":"탑승 중","flight.in-flight":"운항 중","flight.landed":"도착","flight.delayed":"지연","flight.cancelled":"취소","flight.title":"운항 현황","flight.progress":"여정 진행률","boardingPass.origin":"출발","boardingPass.destination":"도착","boardingPass.passenger":"탑승객","boardingPass.service":"편명","boardingPass.date":"날짜","boardingPass.boarding":"탑승 시각","boardingPass.departure":"출발 시각","boardingPass.arrival":"도착 시각","boardingPass.terminal":"터미널","boardingPass.gate":"게이트","boardingPass.seat":"좌석","boardingPass.travelClass":"등급","boardingPass.title":"탑승권","wallet.title":"나의 탑승권","wallet.empty":"등록된 탑승권이 없습니다.","wallet.journeys":"여정 선택","receipt.quantity":"수량","receipt.subtotal":"소계","receipt.discount":"할인","receipt.tax":"세금","receipt.total":"합계","receipt.payment":"결제수단","receipt.empty":"표시할 품목이 없습니다.","receipt.invalid":"품목과 금액 정보를 확인해주세요.","receipt.title":"영수증","ticket.title":"티켓","paper.codeImage":"코드 이미지","aiElement.emptyFolder":"빈 폴더","aiElement.noFiles":"표시할 파일이 없습니다.","attachment.title":"첨부 파일","attachment.empty":"아직 첨부한 파일이 없습니다.","attachment.queued":"대기 중","attachment.uploading":"업로드 중","attachment.processing":"서버 처리 중","attachment.ready":"완료","attachment.error":"실패","attachment.cancelled":"취소됨","attachment.unknown":"진행률을 확인하고 있어요.","attachment.cancel":"취소","attachment.retry":"재시도","attachment.remove":"목록에서 제거","attachment.open":"상세 보기","attachment.reload":"다시 불러오기","workspace.members":"워크스페이스 멤버","workspace.invite":"멤버 초대","workspace.email":"이메일","workspace.role":"초대 역할","workspace.chooseRole":"역할을 선택하세요","workspace.description":"함께 작업할 사람의 이메일과 역할을 확인하세요.","workspace.submit":"초대 요청","workspace.emailError":"254자 이내의 올바른 이메일 주소를 입력해 주세요.","workspace.roleError":"초대할 역할을 선택해 주세요.","workspace.retry":"다시 불러오기","workspace.reinvite":"다시 초대","workspace.active":"이용 중","workspace.invited":"초대 대기","workspace.expired":"초대 만료","workspace.empty":"아직 표시할 멤버가 없습니다.","aiElement.pending":"대기 중","aiElement.running":"진행 중","aiElement.complete":"완료","aiElement.error":"실패","aiElement.cancelled":"취소됨","aiElement.approval":"승인 대기","aiElement.researching":"자료를 확인하고 있어요","aiElement.researched":"작업 과정 살펴보기","aiElement.thinking":"생각 정리 중","aiElement.reasoning":"응답 요약","aiElement.plan":"작업 계획","aiElement.queue":"대기 중인 요청","aiElement.remove":"제거","aiElement.queueEmpty":"대기 중인 요청이 없습니다.","aiElement.sources":"참고한 출처","aiElement.citation":"출처","aiElement.checkpoint":"체크포인트 저장됨","aiElement.restore":"여기부터 복원","aiElement.confirmation":"이 작업을 진행할까요?","aiElement.allow":"허용","aiElement.reject":"거절","aiElement.accepted":"작업을 허용했습니다.","aiElement.rejected":"작업을 거절했습니다.","aiElement.context":"컨텍스트","aiElement.tokens":"토큰","aiElement.unknown":"사용량을 아직 확인할 수 없습니다.","aiElement.files":"작업 파일","aiElement.task":"작업 진행","aiElement.subagent":"보조 에이전트","aiElement.tool":"도구 실행","aiElement.terminal":"터미널","aiElement.stackTrace":"오류 상세","aiElement.tests":"테스트 결과","aiElement.passed":"통과","aiElement.failed":"실패","aiElement.input":"입력","aiElement.output":"출력","aiElement.user":"사용자","aiElement.assistant":"AI 응답","aiElement.copy":"복사","aiElement.regenerate":"다시 생성","aiElement.like":"좋아요","aiElement.dislike":"싫어요","aiElement.latest":"최신 응답으로","aiElement.conversation":"AI와의 대화","notification.title":"알림","notification.filters":"알림 필터","notification.all":"전체","notification.unread":"안 읽음","notification.readAll":"표시된 알림 모두 읽음","notification.markRead":"읽음으로 표시","notification.retry":"다시 불러오기","notification.empty":"아직 도착한 알림이 없습니다.","notification.caughtUp":"모든 알림을 확인했어요.","notification.countUnknown":"확인 필요","aiModel.label":"사용 모델","aiModel.choose":"모델을 선택하세요","aiModel.empty":"현재 사용할 수 있는 모델이 없습니다.","aiModel.capabilities":"지원 작업","aiModel.usage":"사용량 단위","aiQuote.expires":"견적 유효 시간","aiQuote.title":"실행 전 견적","aiQuote.reserved":"예약할 최대 크레딧","aiQuote.providerCost":"예상 제공사 비용","aiQuote.pricingVersion":"가격표 버전","aiQuote.unknown":"미확정","aiQuote.idle":"모델과 요청 내용을 정한 뒤 견적을 확인하세요.","aiQuote.loading":"견적을 확인하고 있습니다.","aiQuote.executing":"요청을 처리하고 있습니다. 중복 실행하지 마세요.","aiQuote.stale":"모델 또는 입력이 변경됐습니다. 견적을 다시 확인하세요.","aiQuote.expired":"견적이 만료됐습니다. 견적을 다시 확인하세요.","aiQuote.balanceUnknown":"잔액을 확인할 수 없습니다. 새로 확인해 주세요.","aiQuote.insufficient":"추가로 필요한 크레딧:","aiQuote.ready":"실행 시 최대 크레딧을 예약하고, 결과 확인 후 실제 사용량으로 정산합니다.","aiQuote.refresh":"견적 다시 확인","aiQuote.request":"견적 확인","aiQuote.execute":"실행하기","plan.current":"현재 플랜","plan.usage":"이번 기간 사용","plan.allowed":"이용 가능","plan.locked":"미포함","plan.manage":"플랜 관리","plan.retry":"다시 불러오기","history.title":"크레딧 사용 내역","history.empty":"아직 표시할 내역이 없습니다.","history.unresolved":"확인 중","history.grant":"지급","history.charge":"사용","history.hold":"예약 · 정산 전","history.release":"예약 해제","credits.choosePlan":"충전할 크레딧","credits.rechargeDescription":"필요한 만큼 선택하고, 금액과 제공 조건을 확인하세요.","credits.paymentAmount":"결제 금액","credits.paymentPending":"결제 상태 확인 중","credits.remaining":"현재 남은 크레딧","credits.available":"사용 가능 크레딧","credits.unit":"크레딧","credits.held":"예약 중","credits.spent":"누적 사용","credits.charged":"차감","credits.low":"남은 크레딧이 적습니다.","credits.recharge":"크레딧 충전","credits.checkout":"결제 진행","credits.noPlans":"현재 충전 가능한 상품이 없습니다.","aiTask.title":"AI 작업","aiTask.idle":"작업 전","aiTask.queued":"실행 대기","aiTask.running":"실행 중","aiTask.succeeded":"완료","aiTask.failed":"실패","aiTask.cancelled":"취소됨","aiTask.needs_attention":"결과 확인 필요","aiTask.attentionDescription":"결과와 사용량을 확인하고 있습니다. 확인이 끝날 때까지 예약 크레딧이 유지됩니다.",close:"닫기","modal.close":"모달 닫기","drawer.close":"드로어 닫기","dialog.close":"대화상자 닫기","toast.close":"알림 닫기",clear:"지우기",clearAll:"모든 값 지우기",loading:"로딩 중",required:"필수",optional:"선택",error:"오류",success:"성공",warning:"경고",info:"안내",previous:"이전",next:"다음","select.placeholder":"선택하세요","select.noResults":"결과 없음","navigationRail.label":"주요 내비게이션","navigationRail.newNotification":"새 알림","navigationRail.notifications":"알림","tabs.label":"탭 목록","pagination.label":"페이지 번호","search.label":"검색","media.play":"재생","media.pause":"일시정지","media.mute":"음소거","media.unmute":"음소거 해제","media.settings":"설정","media.shuffle":"셔플","media.repeat":"반복","media.previous":"이전 트랙","media.next":"다음 트랙","media.fullscreen":"전체화면","confirm.ok":"확인","confirm.cancel":"취소","carousel.slide":"슬라이드","carousel.role":"캐러셀","carousel.previous":"이전 슬라이드","carousel.next":"다음 슬라이드","breadcrumbs.expand":"생략된 경로 펼치기","input.showPassword":"비밀번호 보기","input.hidePassword":"비밀번호 숨기기","input.clearSearch":"검색어 지우기","verify.done":"인증 완료","verify.phoneDone":"휴대폰 번호 인증이 완료되었습니다.","bentoCard.cta":"자세히 보기","tweetCard.loading":"트윗을 불러오는 중입니다...","tweetCard.error":"트윗을 불러올 수 없습니다.","tweetCard.viewOnTwitter":"X에서 보기","tweetCard.like":"좋아요","tweetCard.retweet":"리포스트","tweetCard.reply":"답글","tweetCard.verified":"인증된 계정","tweetCard.retry":"다시 시도","chatComposer.placeholder":"메시지를 입력하세요…","chatComposer.send":"전송","chatComposer.stop":"생성 중지","chatComposer.attach":"첨부","voice.start":"음성 입력","voice.stop":"받아쓰기 완료","voice.cancel":"음성 입력 취소","voice.starting":"마이크 연결 중","voice.listening":"음성 인식 중","voice.stopping":"마지막 문장 확인 중","voice.error":"음성 인식 실패","voice.unsupported":"음성 입력 미지원"},o=Symbol.for("@designbasekorea/ui-wc/locale/v1"),n=globalThis,r=n[o]??(n[o]={overrides:{},activeLocale:"ko",listeners:new Set}),l=r.listeners;function subscribeDbLocale(e){return l.add(e),()=>{l.delete(e)}}function notifyLocale(){for(const e of[...l])e()}function dbUseLocale(e){r.activeLocale!==e&&(r.activeLocale=e,notifyLocale())}function dbSetLocale(e){r.overrides={...r.overrides,...e},notifyLocale()}function dbResetLocale(){r.overrides={},r.activeLocale="ko",notifyLocale()}function t(o,n){return o in r.overrides?r.overrides[o]:o in i?"en"===r.activeLocale?a[o]:i[o]:o.startsWith("copy.")&&"en"===r.activeLocale?e[o.slice(5)]??n??o:n??o}function labelFrom(e,a,i){return e.getAttribute(a)||t(i)}const s=new Map(Object.entries(e).map(([e,a])=>[a,e]));function dbText(e){const a=e.trim();if(!a)return e;const i=s.get(a.replace(/\s+/g," "))??a;return e.slice(0,e.indexOf(a))+t("copy."+i.replace(/\s+/g," "),i)+e.slice(e.indexOf(a)+a.length)}function getDbLocale(){return r.activeLocale}function dbFormat(e,a){return dbText(e).replace(/\{(\d+)\}/g,(e,i)=>String(a[Number(i)]??""))}export{dbFormat,dbResetLocale,dbSetLocale,dbText,dbUseLocale,getDbLocale,labelFrom,subscribeDbLocale,t};