gsap-timeline-viewer 0.1.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/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # GSAP Timeline Viewer
2
+
3
+ A lightweight, framework-agnostic development tool for visualizing GSAP timelines. Debug and scrub through your animations with a visual timeline panel.
4
+
5
+ **4.8 KB gzipped** | Works with React, Vue, Angular, Svelte, or vanilla JS
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install gsap-timeline-viewer
11
+ ```
12
+
13
+ Note: GSAP is a peer dependency. Make sure you have it installed:
14
+
15
+ ```bash
16
+ npm install gsap
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```javascript
22
+ import { TimelineViewer } from 'gsap-timeline-viewer';
23
+ import gsap from 'gsap';
24
+
25
+ // Create your GSAP timeline
26
+ const tl = gsap.timeline();
27
+ tl.to('.box', { x: 100, duration: 1 })
28
+ .to('.box', { y: 50, duration: 0.5 });
29
+
30
+ // Attach the viewer
31
+ const viewer = new TimelineViewer({ timeline: tl });
32
+ viewer.attach();
33
+ ```
34
+
35
+ ## Features
36
+
37
+ - Visual timeline with colored tracks for each tween
38
+ - Scrubber/playhead synced with your timeline
39
+ - Playback controls (play/pause, skip to start/end)
40
+ - Speed control (0.25x, 0.5x, 1x, 2x, 4x)
41
+ - Loop toggle
42
+ - Auto-scaling time ruler
43
+ - Keyboard shortcut: `Space` to play/pause
44
+ - Collapsible panel
45
+ - Dark theme
46
+
47
+ ## API
48
+
49
+ ### TimelineViewer
50
+
51
+ ```typescript
52
+ import { TimelineViewer } from 'gsap-timeline-viewer';
53
+
54
+ const viewer = new TimelineViewer({
55
+ timeline: myTimeline, // Required: GSAP timeline instance
56
+ height: 200, // Optional: Panel height in pixels (default: 200)
57
+ });
58
+
59
+ viewer.attach(); // Add viewer to document.body
60
+ viewer.attach(containerElement); // Add to specific container
61
+ viewer.detach(); // Remove from DOM
62
+ viewer.setTimeline(newTimeline); // Switch to a different timeline
63
+ ```
64
+
65
+ ### Named Tweens
66
+
67
+ For better labels in the viewer, add an `id` to your tweens:
68
+
69
+ ```javascript
70
+ tl.to('.hero', {
71
+ opacity: 1,
72
+ duration: 1,
73
+ id: 'Hero Fade In' // This label appears in the viewer
74
+ });
75
+ ```
76
+
77
+ ## Web Component
78
+
79
+ The viewer is also available as a custom element:
80
+
81
+ ```html
82
+ <gsap-timeline-viewer></gsap-timeline-viewer>
83
+
84
+ <script type="module">
85
+ import 'gsap-timeline-viewer';
86
+
87
+ const viewer = document.querySelector('gsap-timeline-viewer');
88
+ viewer.setTimeline(myTimeline);
89
+ </script>
90
+ ```
91
+
92
+ ## UMD / Script Tag
93
+
94
+ ```html
95
+ <script src="https://unpkg.com/gsap"></script>
96
+ <script src="https://unpkg.com/gsap-timeline-viewer"></script>
97
+
98
+ <script>
99
+ const viewer = new GSAPTimelineViewer.TimelineViewer({ timeline: tl });
100
+ viewer.attach();
101
+ </script>
102
+ ```
103
+
104
+ ## Browser Support
105
+
106
+ Works in all modern browsers that support Web Components (Chrome, Firefox, Safari, Edge).
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,72 @@
1
+ var GSAPTimelineViewer=function(o){"use strict";var C=Object.defineProperty;var D=(o,c,h)=>c in o?C(o,c,{enumerable:!0,configurable:!0,writable:!0,value:h}):o[c]=h;var s=(o,c,h)=>D(o,typeof c!="symbol"?c+"":c,h);let c=0;function h(n){if(!n||n.length===0)return"Unknown";const i=n[0];return i.id?`#${i.id}`:i.classList&&i.classList.length>0?`.${i.classList[0]}`:i.tagName?i.tagName.toLowerCase():"element"}function k(n){const i=["ease","duration","delay","onComplete","onStart","onUpdate","onCompleteParams","onStartParams","onUpdateParams","repeat","repeatDelay","yoyo","stagger","overwrite","immediateRender","lazy","autoAlpha","id","paused","reversed","startAt"];return Object.keys(n).filter(t=>!i.includes(t))}function S(n){const i=[];return n.getChildren(!0,!0,!1).forEach((e,a)=>{if(!("targets"in e))return;const r=e,l=r.targets(),d=r.vars||{},p=k(d);let v="";if(d.id&&typeof d.id=="string")v=d.id;else{const f=h(l),x=p.slice(0,2).join(", ");v=x?`${f} (${x})`:f}const b=e.startTime(),y=e.duration();i.push({id:`tween-${++c}`,label:v,startTime:b,endTime:b+y,duration:y,targets:h(l),properties:p,colorIndex:a%6})}),{duration:n.duration(),tweens:i}}function w(){c=0}function g(n,i=!0){const t=Math.abs(n);return i?t.toFixed(2):t.toFixed(0)}const T=":host{--gtv-bg: #1a1a1a;--gtv-bg-secondary: #252525;--gtv-border: #333;--gtv-text: #e0e0e0;--gtv-text-muted: #888;--gtv-accent: #4a9eff;--gtv-playhead: #4a9eff;--gtv-ruler-bg: #1f1f1f;--gtv-track-height: 28px;--gtv-controls-height: 40px;--gtv-ruler-height: 24px;--gtv-track-1: #3b82f6;--gtv-track-2: #f59e0b;--gtv-track-3: #ec4899;--gtv-track-4: #10b981;--gtv-track-5: #8b5cf6;--gtv-track-6: #ef4444}*{box-sizing:border-box;margin:0;padding:0}.gtv-container{position:fixed;bottom:0;left:0;right:0;background:var(--gtv-bg);border-top:1px solid var(--gtv-border);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:12px;color:var(--gtv-text);z-index:999999;display:flex;flex-direction:column}.gtv-container.collapsed{height:auto!important}.gtv-container.collapsed .gtv-timeline-area{display:none}.gtv-controls{display:flex;align-items:center;justify-content:space-between;height:var(--gtv-controls-height);padding:0 12px;background:var(--gtv-bg-secondary);border-bottom:1px solid var(--gtv-border);gap:16px}.gtv-controls-left,.gtv-controls-center,.gtv-controls-right{display:flex;align-items:center;gap:8px}.gtv-controls-center{flex:0 0 auto}.gtv-time-display{font-variant-numeric:tabular-nums;min-width:100px;text-align:center}.gtv-time-current{color:var(--gtv-text)}.gtv-time-total{color:var(--gtv-text-muted)}.gtv-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;background:transparent;border:none;border-radius:4px;color:var(--gtv-text);cursor:pointer;transition:background .15s}.gtv-btn:hover{background:#ffffff1a}.gtv-btn:active{background:#ffffff26}.gtv-btn.active{color:var(--gtv-accent)}.gtv-btn svg{width:16px;height:16px;fill:currentColor}.gtv-btn-play svg{width:20px;height:20px}.gtv-speed-btn{width:auto;padding:0 8px;font-size:11px;font-weight:500}.gtv-collapse-btn{margin-left:auto}.gtv-timeline-area{display:flex;flex-direction:column;overflow:hidden;flex:1}.gtv-ruler{position:relative;height:var(--gtv-ruler-height);background:var(--gtv-ruler-bg);border-bottom:1px solid var(--gtv-border);overflow:hidden;flex-shrink:0}.gtv-ruler-inner{position:relative;height:100%}.gtv-ruler-marker{position:absolute;top:0;height:100%;display:flex;flex-direction:column;align-items:center}.gtv-ruler-marker-line{width:1px;height:6px;background:var(--gtv-text-muted)}.gtv-ruler-marker-label{font-size:10px;color:var(--gtv-text-muted);margin-top:2px}.gtv-tracks-container{position:relative;overflow-y:auto;overflow-x:hidden;flex:1}.gtv-tracks-scroll{position:relative;min-height:100%}.gtv-track{position:relative;height:var(--gtv-track-height);border-bottom:1px solid var(--gtv-border)}.gtv-track-bar{position:absolute;top:4px;height:calc(var(--gtv-track-height) - 8px);border-radius:4px;display:flex;align-items:center;padding:0 8px;font-size:11px;font-weight:500;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:default;transition:filter .15s}.gtv-track-bar:hover{filter:brightness(1.1)}.gtv-track-bar.is-active{box-shadow:0 0 0 2px var(--gtv-accent)}.gtv-playhead-container{position:absolute;top:0;bottom:0;width:1px;pointer-events:none;z-index:10}.gtv-playhead-head{position:absolute;top:0;left:-5px;width:11px;height:11px;background:var(--gtv-playhead);clip-path:polygon(50% 100%,0 0,100% 0)}.gtv-playhead-line{position:absolute;top:11px;bottom:0;left:0;width:1px;background:var(--gtv-playhead)}.gtv-scrub-area{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.gtv-empty{display:flex;align-items:center;justify-content:center;padding:24px;color:var(--gtv-text-muted)}",L=["var(--gtv-track-1)","var(--gtv-track-2)","var(--gtv-track-3)","var(--gtv-track-4)","var(--gtv-track-5)","var(--gtv-track-6)"],u=[.25,.5,1,2,4];class m extends HTMLElement{constructor(){super();s(this,"shadow");s(this,"timeline",null);s(this,"timelineData",null);s(this,"isPlaying",!1);s(this,"isLooping",!1);s(this,"speedIndex",2);s(this,"collapsed",!1);s(this,"height",200);s(this,"isDragging",!1);s(this,"container");s(this,"playBtn");s(this,"loopBtn");s(this,"speedBtn");s(this,"timeDisplay");s(this,"rulerInner");s(this,"tracksScroll");s(this,"playhead");s(this,"scrubArea");this.shadow=this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this.setupEventListeners()}disconnectedCallback(){this.detachTimeline()}setTimeline(t){this.detachTimeline(),this.timeline=t,w(),this.timelineData=S(t),t.eventCallback("onUpdate",()=>this.onTimelineUpdate()),this.renderTracks(),this.updatePlayhead(),this.updateTimeDisplay(),this.updatePlayState()}detachTimeline(){this.timeline&&(this.timeline.eventCallback("onUpdate",null),this.timeline=null,this.timelineData=null)}render(){this.shadow.innerHTML=`
2
+ <style>${T}</style>
3
+ <div class="gtv-container ${this.collapsed?"collapsed":""}" style="height: ${this.height}px;">
4
+ <!-- Controls Bar -->
5
+ <div class="gtv-controls">
6
+ <div class="gtv-controls-left">
7
+ <button class="gtv-btn" data-action="skip-start" title="Skip to start">
8
+ <svg viewBox="0 0 24 24"><path d="M6 6h2v12H6V6zm3.5 6l8.5 6V6l-8.5 6z"/></svg>
9
+ </button>
10
+ <button class="gtv-btn gtv-btn-play" data-action="play" title="Play/Pause">
11
+ <svg class="play-icon" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
12
+ <svg class="pause-icon" viewBox="0 0 24 24" style="display: none;"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
13
+ </button>
14
+ <button class="gtv-btn" data-action="skip-end" title="Skip to end">
15
+ <svg viewBox="0 0 24 24"><path d="M6 18l8.5-6L6 6v12zm2 0V6l6.5 6L8 18zm8-12v12h2V6h-2z"/></svg>
16
+ </button>
17
+ </div>
18
+
19
+ <div class="gtv-controls-center">
20
+ <button class="gtv-btn" data-action="loop" title="Loop">
21
+ <svg viewBox="0 0 24 24"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
22
+ </button>
23
+ <span class="gtv-time-display">
24
+ <span class="gtv-time-current">0.00</span>
25
+ <span class="gtv-time-total"> / 0.00</span>
26
+ </span>
27
+ <button class="gtv-btn gtv-speed-btn" data-action="speed" title="Playback speed">1x</button>
28
+ </div>
29
+
30
+ <div class="gtv-controls-right">
31
+ <button class="gtv-btn gtv-collapse-btn" data-action="collapse" title="Collapse/Expand">
32
+ <svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>
33
+ </button>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Timeline Area -->
38
+ <div class="gtv-timeline-area">
39
+ <!-- Ruler -->
40
+ <div class="gtv-ruler">
41
+ <div class="gtv-ruler-inner"></div>
42
+ <div class="gtv-playhead-container">
43
+ <div class="gtv-playhead-head"></div>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Tracks -->
48
+ <div class="gtv-tracks-container">
49
+ <div class="gtv-tracks-scroll">
50
+ <div class="gtv-scrub-area"></div>
51
+ <div class="gtv-playhead-container">
52
+ <div class="gtv-playhead-line"></div>
53
+ </div>
54
+ </div>
55
+ <div class="gtv-empty">No timeline attached. Call setTimeline() to visualize a GSAP timeline.</div>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ `,this.container=this.shadow.querySelector(".gtv-container"),this.playBtn=this.shadow.querySelector('[data-action="play"]'),this.loopBtn=this.shadow.querySelector('[data-action="loop"]'),this.speedBtn=this.shadow.querySelector('[data-action="speed"]'),this.timeDisplay=this.shadow.querySelector(".gtv-time-display"),this.rulerInner=this.shadow.querySelector(".gtv-ruler-inner"),this.tracksScroll=this.shadow.querySelector(".gtv-tracks-scroll"),this.playhead=this.shadow.querySelector(".gtv-ruler .gtv-playhead-container"),this.scrubArea=this.shadow.querySelector(".gtv-scrub-area")}setupEventListeners(){this.shadow.addEventListener("click",t=>{const a=t.target.closest("[data-action]");if(!a)return;switch(a.dataset.action){case"play":this.togglePlay();break;case"skip-start":this.skipToStart();break;case"skip-end":this.skipToEnd();break;case"loop":this.toggleLoop();break;case"speed":this.cycleSpeed();break;case"collapse":this.toggleCollapse();break}}),this.scrubArea.addEventListener("mousedown",t=>this.startScrub(t)),this.shadow.querySelector(".gtv-ruler").addEventListener("mousedown",t=>this.startScrub(t)),document.addEventListener("mousemove",t=>this.onScrub(t)),document.addEventListener("mouseup",()=>this.endScrub()),document.addEventListener("keydown",t=>{t.target===document.body&&t.code==="Space"&&(t.preventDefault(),this.togglePlay())})}startScrub(t){this.timeline&&(this.isDragging=!0,this.scrubToPosition(t))}onScrub(t){!this.isDragging||!this.timeline||this.scrubToPosition(t)}endScrub(){this.isDragging=!1}scrubToPosition(t){if(!this.timeline||!this.timelineData)return;const e=this.rulerInner.getBoundingClientRect(),r=Math.max(0,Math.min(t.clientX-e.left,e.width))/e.width;this.timeline.progress(r),this.timeline.pause(),this.updatePlayState()}togglePlay(){this.timeline&&(this.timeline.paused()||this.timeline.progress()===1?this.timeline.progress()===1?this.timeline.restart():this.timeline.play():this.timeline.pause(),this.updatePlayState())}skipToStart(){this.timeline&&(this.timeline.progress(0),this.timeline.pause(),this.updatePlayState())}skipToEnd(){this.timeline&&(this.timeline.progress(1),this.timeline.pause(),this.updatePlayState())}toggleLoop(){this.timeline&&(this.isLooping=!this.isLooping,this.timeline.repeat(this.isLooping?-1:0),this.loopBtn.classList.toggle("active",this.isLooping))}cycleSpeed(){if(!this.timeline)return;this.speedIndex=(this.speedIndex+1)%u.length;const t=u[this.speedIndex];this.timeline.timeScale(t),this.speedBtn.textContent=`${t}x`}toggleCollapse(){this.collapsed=!this.collapsed,this.container.classList.toggle("collapsed",this.collapsed);const t=this.shadow.querySelector('[data-action="collapse"]');t.innerHTML=this.collapsed?'<svg viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>':'<svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>'}updatePlayState(){if(!this.timeline)return;this.isPlaying=!this.timeline.paused()&&this.timeline.progress()<1;const t=this.playBtn.querySelector(".play-icon"),e=this.playBtn.querySelector(".pause-icon");t.style.display=this.isPlaying?"none":"block",e.style.display=this.isPlaying?"block":"none"}onTimelineUpdate(){this.updatePlayhead(),this.updateTimeDisplay(),this.updateActiveTracks(),this.updatePlayState()}updatePlayhead(){if(!this.timeline||!this.timelineData)return;const e=`${this.timeline.progress()*100}%`;this.playhead.style.left=e;const a=this.tracksScroll.querySelector(".gtv-playhead-container");a&&(a.style.left=e)}updateTimeDisplay(){if(!this.timeline||!this.timelineData)return;const t=this.timeline.time(),e=this.timelineData.duration,a=this.timeDisplay.querySelector(".gtv-time-current"),r=this.timeDisplay.querySelector(".gtv-time-total");a.textContent=g(t),r.textContent=` / ${g(e)}`}updateActiveTracks(){if(!this.timeline||!this.timelineData)return;const t=this.timeline.time();this.tracksScroll.querySelectorAll(".gtv-track-bar").forEach((a,r)=>{const l=this.timelineData.tweens[r],d=t>=l.startTime&&t<=l.endTime;a.classList.toggle("is-active",d)})}renderTracks(){if(!this.timelineData)return;const{duration:t,tweens:e}=this.timelineData,a=this.shadow.querySelector(".gtv-empty");a.style.display=e.length>0?"none":"flex",this.renderRuler(t);const r=e.map(p=>this.renderTrack(p,t)).join(""),l=this.tracksScroll.querySelector(".gtv-scrub-area"),d=this.tracksScroll.querySelector(".gtv-playhead-container");this.tracksScroll.innerHTML=r,this.tracksScroll.prepend(l),this.tracksScroll.appendChild(d),this.scrubArea=l}renderRuler(t){const e=[],a=this.calculateInterval(t);for(let r=0;r<=t;r+=a){const l=r/t*100;e.push(`
60
+ <div class="gtv-ruler-marker" style="left: ${l}%;">
61
+ <div class="gtv-ruler-marker-line"></div>
62
+ <span class="gtv-ruler-marker-label">${g(r,!1)}s</span>
63
+ </div>
64
+ `)}this.rulerInner.innerHTML=e.join("")}calculateInterval(t){return t<=1?.25:t<=3?.5:t<=10?1:t<=30?5:10}renderTrack(t,e){const a=t.startTime/e*100,r=t.duration/e*100,l=L[t.colorIndex];return`
65
+ <div class="gtv-track">
66
+ <div class="gtv-track-bar"
67
+ style="left: ${a}%; width: ${r}%; background: ${l};"
68
+ title="${t.label} (${g(t.startTime)}s - ${g(t.endTime)}s)">
69
+ ${t.label}
70
+ </div>
71
+ </div>
72
+ `}}customElements.define("gsap-timeline-viewer",m);class P{constructor(i){s(this,"element");this.element=document.createElement("gsap-timeline-viewer"),i.height&&this.element.style.setProperty("--viewer-height",`${i.height}px`),i.timeline&&setTimeout(()=>{this.element.setTimeline(i.timeline)},0)}attach(i=document.body){i.appendChild(this.element)}detach(){this.element.remove()}setTimeline(i){this.element.setTimeline(i)}get htmlElement(){return this.element}}return o.TimelineViewer=P,o.TimelineViewerElement=m,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),o}({});
@@ -0,0 +1,331 @@
1
+ var f = Object.defineProperty;
2
+ var x = (a, e, t) => e in a ? f(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
3
+ var s = (a, e, t) => x(a, typeof e != "symbol" ? e + "" : e, t);
4
+ let y = 0;
5
+ function m(a) {
6
+ if (!a || a.length === 0) return "Unknown";
7
+ const e = a[0];
8
+ return e.id ? `#${e.id}` : e.classList && e.classList.length > 0 ? `.${e.classList[0]}` : e.tagName ? e.tagName.toLowerCase() : "element";
9
+ }
10
+ function k(a) {
11
+ const e = [
12
+ "ease",
13
+ "duration",
14
+ "delay",
15
+ "onComplete",
16
+ "onStart",
17
+ "onUpdate",
18
+ "onCompleteParams",
19
+ "onStartParams",
20
+ "onUpdateParams",
21
+ "repeat",
22
+ "repeatDelay",
23
+ "yoyo",
24
+ "stagger",
25
+ "overwrite",
26
+ "immediateRender",
27
+ "lazy",
28
+ "autoAlpha",
29
+ "id",
30
+ "paused",
31
+ "reversed",
32
+ "startAt"
33
+ ];
34
+ return Object.keys(a).filter((t) => !e.includes(t));
35
+ }
36
+ function S(a) {
37
+ const e = [];
38
+ return a.getChildren(!0, !0, !1).forEach((i, r) => {
39
+ if (!("targets" in i)) return;
40
+ const n = i, l = n.targets(), o = n.vars || {}, d = k(o);
41
+ let h = "";
42
+ if (o.id && typeof o.id == "string")
43
+ h = o.id;
44
+ else {
45
+ const v = m(l), u = d.slice(0, 2).join(", ");
46
+ h = u ? `${v} (${u})` : v;
47
+ }
48
+ const g = i.startTime(), p = i.duration();
49
+ e.push({
50
+ id: `tween-${++y}`,
51
+ label: h,
52
+ startTime: g,
53
+ endTime: g + p,
54
+ duration: p,
55
+ targets: m(l),
56
+ properties: d,
57
+ colorIndex: r % 6
58
+ });
59
+ }), {
60
+ duration: a.duration(),
61
+ tweens: e
62
+ };
63
+ }
64
+ function w() {
65
+ y = 0;
66
+ }
67
+ function c(a, e = !0) {
68
+ const t = Math.abs(a);
69
+ return e ? t.toFixed(2) : t.toFixed(0);
70
+ }
71
+ const T = ":host{--gtv-bg: #1a1a1a;--gtv-bg-secondary: #252525;--gtv-border: #333;--gtv-text: #e0e0e0;--gtv-text-muted: #888;--gtv-accent: #4a9eff;--gtv-playhead: #4a9eff;--gtv-ruler-bg: #1f1f1f;--gtv-track-height: 28px;--gtv-controls-height: 40px;--gtv-ruler-height: 24px;--gtv-track-1: #3b82f6;--gtv-track-2: #f59e0b;--gtv-track-3: #ec4899;--gtv-track-4: #10b981;--gtv-track-5: #8b5cf6;--gtv-track-6: #ef4444}*{box-sizing:border-box;margin:0;padding:0}.gtv-container{position:fixed;bottom:0;left:0;right:0;background:var(--gtv-bg);border-top:1px solid var(--gtv-border);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:12px;color:var(--gtv-text);z-index:999999;display:flex;flex-direction:column}.gtv-container.collapsed{height:auto!important}.gtv-container.collapsed .gtv-timeline-area{display:none}.gtv-controls{display:flex;align-items:center;justify-content:space-between;height:var(--gtv-controls-height);padding:0 12px;background:var(--gtv-bg-secondary);border-bottom:1px solid var(--gtv-border);gap:16px}.gtv-controls-left,.gtv-controls-center,.gtv-controls-right{display:flex;align-items:center;gap:8px}.gtv-controls-center{flex:0 0 auto}.gtv-time-display{font-variant-numeric:tabular-nums;min-width:100px;text-align:center}.gtv-time-current{color:var(--gtv-text)}.gtv-time-total{color:var(--gtv-text-muted)}.gtv-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;background:transparent;border:none;border-radius:4px;color:var(--gtv-text);cursor:pointer;transition:background .15s}.gtv-btn:hover{background:#ffffff1a}.gtv-btn:active{background:#ffffff26}.gtv-btn.active{color:var(--gtv-accent)}.gtv-btn svg{width:16px;height:16px;fill:currentColor}.gtv-btn-play svg{width:20px;height:20px}.gtv-speed-btn{width:auto;padding:0 8px;font-size:11px;font-weight:500}.gtv-collapse-btn{margin-left:auto}.gtv-timeline-area{display:flex;flex-direction:column;overflow:hidden;flex:1}.gtv-ruler{position:relative;height:var(--gtv-ruler-height);background:var(--gtv-ruler-bg);border-bottom:1px solid var(--gtv-border);overflow:hidden;flex-shrink:0}.gtv-ruler-inner{position:relative;height:100%}.gtv-ruler-marker{position:absolute;top:0;height:100%;display:flex;flex-direction:column;align-items:center}.gtv-ruler-marker-line{width:1px;height:6px;background:var(--gtv-text-muted)}.gtv-ruler-marker-label{font-size:10px;color:var(--gtv-text-muted);margin-top:2px}.gtv-tracks-container{position:relative;overflow-y:auto;overflow-x:hidden;flex:1}.gtv-tracks-scroll{position:relative;min-height:100%}.gtv-track{position:relative;height:var(--gtv-track-height);border-bottom:1px solid var(--gtv-border)}.gtv-track-bar{position:absolute;top:4px;height:calc(var(--gtv-track-height) - 8px);border-radius:4px;display:flex;align-items:center;padding:0 8px;font-size:11px;font-weight:500;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:default;transition:filter .15s}.gtv-track-bar:hover{filter:brightness(1.1)}.gtv-track-bar.is-active{box-shadow:0 0 0 2px var(--gtv-accent)}.gtv-playhead-container{position:absolute;top:0;bottom:0;width:1px;pointer-events:none;z-index:10}.gtv-playhead-head{position:absolute;top:0;left:-5px;width:11px;height:11px;background:var(--gtv-playhead);clip-path:polygon(50% 100%,0 0,100% 0)}.gtv-playhead-line{position:absolute;top:11px;bottom:0;left:0;width:1px;background:var(--gtv-playhead)}.gtv-scrub-area{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.gtv-empty{display:flex;align-items:center;justify-content:center;padding:24px;color:var(--gtv-text-muted)}", L = [
72
+ "var(--gtv-track-1)",
73
+ "var(--gtv-track-2)",
74
+ "var(--gtv-track-3)",
75
+ "var(--gtv-track-4)",
76
+ "var(--gtv-track-5)",
77
+ "var(--gtv-track-6)"
78
+ ], b = [0.25, 0.5, 1, 2, 4];
79
+ class P extends HTMLElement {
80
+ constructor() {
81
+ super();
82
+ s(this, "shadow");
83
+ s(this, "timeline", null);
84
+ s(this, "timelineData", null);
85
+ s(this, "isPlaying", !1);
86
+ s(this, "isLooping", !1);
87
+ s(this, "speedIndex", 2);
88
+ // 1x
89
+ s(this, "collapsed", !1);
90
+ s(this, "height", 200);
91
+ s(this, "isDragging", !1);
92
+ // DOM references
93
+ s(this, "container");
94
+ s(this, "playBtn");
95
+ s(this, "loopBtn");
96
+ s(this, "speedBtn");
97
+ s(this, "timeDisplay");
98
+ s(this, "rulerInner");
99
+ s(this, "tracksScroll");
100
+ s(this, "playhead");
101
+ s(this, "scrubArea");
102
+ this.shadow = this.attachShadow({ mode: "open" });
103
+ }
104
+ connectedCallback() {
105
+ this.render(), this.setupEventListeners();
106
+ }
107
+ disconnectedCallback() {
108
+ this.detachTimeline();
109
+ }
110
+ setTimeline(t) {
111
+ this.detachTimeline(), this.timeline = t, w(), this.timelineData = S(t), t.eventCallback("onUpdate", () => this.onTimelineUpdate()), this.renderTracks(), this.updatePlayhead(), this.updateTimeDisplay(), this.updatePlayState();
112
+ }
113
+ detachTimeline() {
114
+ this.timeline && (this.timeline.eventCallback("onUpdate", null), this.timeline = null, this.timelineData = null);
115
+ }
116
+ render() {
117
+ this.shadow.innerHTML = `
118
+ <style>${T}</style>
119
+ <div class="gtv-container ${this.collapsed ? "collapsed" : ""}" style="height: ${this.height}px;">
120
+ <!-- Controls Bar -->
121
+ <div class="gtv-controls">
122
+ <div class="gtv-controls-left">
123
+ <button class="gtv-btn" data-action="skip-start" title="Skip to start">
124
+ <svg viewBox="0 0 24 24"><path d="M6 6h2v12H6V6zm3.5 6l8.5 6V6l-8.5 6z"/></svg>
125
+ </button>
126
+ <button class="gtv-btn gtv-btn-play" data-action="play" title="Play/Pause">
127
+ <svg class="play-icon" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
128
+ <svg class="pause-icon" viewBox="0 0 24 24" style="display: none;"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
129
+ </button>
130
+ <button class="gtv-btn" data-action="skip-end" title="Skip to end">
131
+ <svg viewBox="0 0 24 24"><path d="M6 18l8.5-6L6 6v12zm2 0V6l6.5 6L8 18zm8-12v12h2V6h-2z"/></svg>
132
+ </button>
133
+ </div>
134
+
135
+ <div class="gtv-controls-center">
136
+ <button class="gtv-btn" data-action="loop" title="Loop">
137
+ <svg viewBox="0 0 24 24"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
138
+ </button>
139
+ <span class="gtv-time-display">
140
+ <span class="gtv-time-current">0.00</span>
141
+ <span class="gtv-time-total"> / 0.00</span>
142
+ </span>
143
+ <button class="gtv-btn gtv-speed-btn" data-action="speed" title="Playback speed">1x</button>
144
+ </div>
145
+
146
+ <div class="gtv-controls-right">
147
+ <button class="gtv-btn gtv-collapse-btn" data-action="collapse" title="Collapse/Expand">
148
+ <svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>
149
+ </button>
150
+ </div>
151
+ </div>
152
+
153
+ <!-- Timeline Area -->
154
+ <div class="gtv-timeline-area">
155
+ <!-- Ruler -->
156
+ <div class="gtv-ruler">
157
+ <div class="gtv-ruler-inner"></div>
158
+ <div class="gtv-playhead-container">
159
+ <div class="gtv-playhead-head"></div>
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Tracks -->
164
+ <div class="gtv-tracks-container">
165
+ <div class="gtv-tracks-scroll">
166
+ <div class="gtv-scrub-area"></div>
167
+ <div class="gtv-playhead-container">
168
+ <div class="gtv-playhead-line"></div>
169
+ </div>
170
+ </div>
171
+ <div class="gtv-empty">No timeline attached. Call setTimeline() to visualize a GSAP timeline.</div>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ `, this.container = this.shadow.querySelector(".gtv-container"), this.playBtn = this.shadow.querySelector('[data-action="play"]'), this.loopBtn = this.shadow.querySelector('[data-action="loop"]'), this.speedBtn = this.shadow.querySelector('[data-action="speed"]'), this.timeDisplay = this.shadow.querySelector(".gtv-time-display"), this.rulerInner = this.shadow.querySelector(".gtv-ruler-inner"), this.tracksScroll = this.shadow.querySelector(".gtv-tracks-scroll"), this.playhead = this.shadow.querySelector(".gtv-ruler .gtv-playhead-container"), this.scrubArea = this.shadow.querySelector(".gtv-scrub-area");
176
+ }
177
+ setupEventListeners() {
178
+ this.shadow.addEventListener("click", (t) => {
179
+ const r = t.target.closest("[data-action]");
180
+ if (!r) return;
181
+ switch (r.dataset.action) {
182
+ case "play":
183
+ this.togglePlay();
184
+ break;
185
+ case "skip-start":
186
+ this.skipToStart();
187
+ break;
188
+ case "skip-end":
189
+ this.skipToEnd();
190
+ break;
191
+ case "loop":
192
+ this.toggleLoop();
193
+ break;
194
+ case "speed":
195
+ this.cycleSpeed();
196
+ break;
197
+ case "collapse":
198
+ this.toggleCollapse();
199
+ break;
200
+ }
201
+ }), this.scrubArea.addEventListener("mousedown", (t) => this.startScrub(t)), this.shadow.querySelector(".gtv-ruler").addEventListener("mousedown", (t) => this.startScrub(t)), document.addEventListener("mousemove", (t) => this.onScrub(t)), document.addEventListener("mouseup", () => this.endScrub()), document.addEventListener("keydown", (t) => {
202
+ t.target === document.body && t.code === "Space" && (t.preventDefault(), this.togglePlay());
203
+ });
204
+ }
205
+ startScrub(t) {
206
+ this.timeline && (this.isDragging = !0, this.scrubToPosition(t));
207
+ }
208
+ onScrub(t) {
209
+ !this.isDragging || !this.timeline || this.scrubToPosition(t);
210
+ }
211
+ endScrub() {
212
+ this.isDragging = !1;
213
+ }
214
+ scrubToPosition(t) {
215
+ if (!this.timeline || !this.timelineData) return;
216
+ const i = this.rulerInner.getBoundingClientRect(), n = Math.max(0, Math.min(t.clientX - i.left, i.width)) / i.width;
217
+ this.timeline.progress(n), this.timeline.pause(), this.updatePlayState();
218
+ }
219
+ togglePlay() {
220
+ this.timeline && (this.timeline.paused() || this.timeline.progress() === 1 ? this.timeline.progress() === 1 ? this.timeline.restart() : this.timeline.play() : this.timeline.pause(), this.updatePlayState());
221
+ }
222
+ skipToStart() {
223
+ this.timeline && (this.timeline.progress(0), this.timeline.pause(), this.updatePlayState());
224
+ }
225
+ skipToEnd() {
226
+ this.timeline && (this.timeline.progress(1), this.timeline.pause(), this.updatePlayState());
227
+ }
228
+ toggleLoop() {
229
+ this.timeline && (this.isLooping = !this.isLooping, this.timeline.repeat(this.isLooping ? -1 : 0), this.loopBtn.classList.toggle("active", this.isLooping));
230
+ }
231
+ cycleSpeed() {
232
+ if (!this.timeline) return;
233
+ this.speedIndex = (this.speedIndex + 1) % b.length;
234
+ const t = b[this.speedIndex];
235
+ this.timeline.timeScale(t), this.speedBtn.textContent = `${t}x`;
236
+ }
237
+ toggleCollapse() {
238
+ this.collapsed = !this.collapsed, this.container.classList.toggle("collapsed", this.collapsed);
239
+ const t = this.shadow.querySelector('[data-action="collapse"]');
240
+ t.innerHTML = this.collapsed ? '<svg viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>' : '<svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>';
241
+ }
242
+ updatePlayState() {
243
+ if (!this.timeline) return;
244
+ this.isPlaying = !this.timeline.paused() && this.timeline.progress() < 1;
245
+ const t = this.playBtn.querySelector(".play-icon"), i = this.playBtn.querySelector(".pause-icon");
246
+ t.style.display = this.isPlaying ? "none" : "block", i.style.display = this.isPlaying ? "block" : "none";
247
+ }
248
+ onTimelineUpdate() {
249
+ this.updatePlayhead(), this.updateTimeDisplay(), this.updateActiveTracks(), this.updatePlayState();
250
+ }
251
+ updatePlayhead() {
252
+ if (!this.timeline || !this.timelineData) return;
253
+ const i = `${this.timeline.progress() * 100}%`;
254
+ this.playhead.style.left = i;
255
+ const r = this.tracksScroll.querySelector(".gtv-playhead-container");
256
+ r && (r.style.left = i);
257
+ }
258
+ updateTimeDisplay() {
259
+ if (!this.timeline || !this.timelineData) return;
260
+ const t = this.timeline.time(), i = this.timelineData.duration, r = this.timeDisplay.querySelector(".gtv-time-current"), n = this.timeDisplay.querySelector(".gtv-time-total");
261
+ r.textContent = c(t), n.textContent = ` / ${c(i)}`;
262
+ }
263
+ updateActiveTracks() {
264
+ if (!this.timeline || !this.timelineData) return;
265
+ const t = this.timeline.time();
266
+ this.tracksScroll.querySelectorAll(".gtv-track-bar").forEach((r, n) => {
267
+ const l = this.timelineData.tweens[n], o = t >= l.startTime && t <= l.endTime;
268
+ r.classList.toggle("is-active", o);
269
+ });
270
+ }
271
+ renderTracks() {
272
+ if (!this.timelineData) return;
273
+ const { duration: t, tweens: i } = this.timelineData, r = this.shadow.querySelector(".gtv-empty");
274
+ r.style.display = i.length > 0 ? "none" : "flex", this.renderRuler(t);
275
+ const n = i.map((d) => this.renderTrack(d, t)).join(""), l = this.tracksScroll.querySelector(".gtv-scrub-area"), o = this.tracksScroll.querySelector(".gtv-playhead-container");
276
+ this.tracksScroll.innerHTML = n, this.tracksScroll.prepend(l), this.tracksScroll.appendChild(o), this.scrubArea = l;
277
+ }
278
+ renderRuler(t) {
279
+ const i = [], r = this.calculateInterval(t);
280
+ for (let n = 0; n <= t; n += r) {
281
+ const l = n / t * 100;
282
+ i.push(`
283
+ <div class="gtv-ruler-marker" style="left: ${l}%;">
284
+ <div class="gtv-ruler-marker-line"></div>
285
+ <span class="gtv-ruler-marker-label">${c(n, !1)}s</span>
286
+ </div>
287
+ `);
288
+ }
289
+ this.rulerInner.innerHTML = i.join("");
290
+ }
291
+ calculateInterval(t) {
292
+ return t <= 1 ? 0.25 : t <= 3 ? 0.5 : t <= 10 ? 1 : t <= 30 ? 5 : 10;
293
+ }
294
+ renderTrack(t, i) {
295
+ const r = t.startTime / i * 100, n = t.duration / i * 100, l = L[t.colorIndex];
296
+ return `
297
+ <div class="gtv-track">
298
+ <div class="gtv-track-bar"
299
+ style="left: ${r}%; width: ${n}%; background: ${l};"
300
+ title="${t.label} (${c(t.startTime)}s - ${c(t.endTime)}s)">
301
+ ${t.label}
302
+ </div>
303
+ </div>
304
+ `;
305
+ }
306
+ }
307
+ customElements.define("gsap-timeline-viewer", P);
308
+ class D {
309
+ constructor(e) {
310
+ s(this, "element");
311
+ this.element = document.createElement("gsap-timeline-viewer"), e.height && this.element.style.setProperty("--viewer-height", `${e.height}px`), e.timeline && setTimeout(() => {
312
+ this.element.setTimeline(e.timeline);
313
+ }, 0);
314
+ }
315
+ attach(e = document.body) {
316
+ e.appendChild(this.element);
317
+ }
318
+ detach() {
319
+ this.element.remove();
320
+ }
321
+ setTimeline(e) {
322
+ this.element.setTimeline(e);
323
+ }
324
+ get htmlElement() {
325
+ return this.element;
326
+ }
327
+ }
328
+ export {
329
+ D as TimelineViewer,
330
+ P as TimelineViewerElement
331
+ };
@@ -0,0 +1,72 @@
1
+ (function(l,n){typeof exports=="object"&&typeof module<"u"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(l=typeof globalThis<"u"?globalThis:l||self,n(l.GSAPTimelineViewer={}))})(this,function(l){"use strict";var C=Object.defineProperty;var D=(l,n,h)=>n in l?C(l,n,{enumerable:!0,configurable:!0,writable:!0,value:h}):l[n]=h;var s=(l,n,h)=>D(l,typeof n!="symbol"?n+"":n,h);let n=0;function h(o){if(!o||o.length===0)return"Unknown";const i=o[0];return i.id?`#${i.id}`:i.classList&&i.classList.length>0?`.${i.classList[0]}`:i.tagName?i.tagName.toLowerCase():"element"}function k(o){const i=["ease","duration","delay","onComplete","onStart","onUpdate","onCompleteParams","onStartParams","onUpdateParams","repeat","repeatDelay","yoyo","stagger","overwrite","immediateRender","lazy","autoAlpha","id","paused","reversed","startAt"];return Object.keys(o).filter(t=>!i.includes(t))}function S(o){const i=[];return o.getChildren(!0,!0,!1).forEach((e,a)=>{if(!("targets"in e))return;const r=e,c=r.targets(),d=r.vars||{},p=k(d);let v="";if(d.id&&typeof d.id=="string")v=d.id;else{const y=h(c),x=p.slice(0,2).join(", ");v=x?`${y} (${x})`:y}const f=e.startTime(),b=e.duration();i.push({id:`tween-${++n}`,label:v,startTime:f,endTime:f+b,duration:b,targets:h(c),properties:p,colorIndex:a%6})}),{duration:o.duration(),tweens:i}}function w(){n=0}function g(o,i=!0){const t=Math.abs(o);return i?t.toFixed(2):t.toFixed(0)}const T=":host{--gtv-bg: #1a1a1a;--gtv-bg-secondary: #252525;--gtv-border: #333;--gtv-text: #e0e0e0;--gtv-text-muted: #888;--gtv-accent: #4a9eff;--gtv-playhead: #4a9eff;--gtv-ruler-bg: #1f1f1f;--gtv-track-height: 28px;--gtv-controls-height: 40px;--gtv-ruler-height: 24px;--gtv-track-1: #3b82f6;--gtv-track-2: #f59e0b;--gtv-track-3: #ec4899;--gtv-track-4: #10b981;--gtv-track-5: #8b5cf6;--gtv-track-6: #ef4444}*{box-sizing:border-box;margin:0;padding:0}.gtv-container{position:fixed;bottom:0;left:0;right:0;background:var(--gtv-bg);border-top:1px solid var(--gtv-border);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:12px;color:var(--gtv-text);z-index:999999;display:flex;flex-direction:column}.gtv-container.collapsed{height:auto!important}.gtv-container.collapsed .gtv-timeline-area{display:none}.gtv-controls{display:flex;align-items:center;justify-content:space-between;height:var(--gtv-controls-height);padding:0 12px;background:var(--gtv-bg-secondary);border-bottom:1px solid var(--gtv-border);gap:16px}.gtv-controls-left,.gtv-controls-center,.gtv-controls-right{display:flex;align-items:center;gap:8px}.gtv-controls-center{flex:0 0 auto}.gtv-time-display{font-variant-numeric:tabular-nums;min-width:100px;text-align:center}.gtv-time-current{color:var(--gtv-text)}.gtv-time-total{color:var(--gtv-text-muted)}.gtv-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;background:transparent;border:none;border-radius:4px;color:var(--gtv-text);cursor:pointer;transition:background .15s}.gtv-btn:hover{background:#ffffff1a}.gtv-btn:active{background:#ffffff26}.gtv-btn.active{color:var(--gtv-accent)}.gtv-btn svg{width:16px;height:16px;fill:currentColor}.gtv-btn-play svg{width:20px;height:20px}.gtv-speed-btn{width:auto;padding:0 8px;font-size:11px;font-weight:500}.gtv-collapse-btn{margin-left:auto}.gtv-timeline-area{display:flex;flex-direction:column;overflow:hidden;flex:1}.gtv-ruler{position:relative;height:var(--gtv-ruler-height);background:var(--gtv-ruler-bg);border-bottom:1px solid var(--gtv-border);overflow:hidden;flex-shrink:0}.gtv-ruler-inner{position:relative;height:100%}.gtv-ruler-marker{position:absolute;top:0;height:100%;display:flex;flex-direction:column;align-items:center}.gtv-ruler-marker-line{width:1px;height:6px;background:var(--gtv-text-muted)}.gtv-ruler-marker-label{font-size:10px;color:var(--gtv-text-muted);margin-top:2px}.gtv-tracks-container{position:relative;overflow-y:auto;overflow-x:hidden;flex:1}.gtv-tracks-scroll{position:relative;min-height:100%}.gtv-track{position:relative;height:var(--gtv-track-height);border-bottom:1px solid var(--gtv-border)}.gtv-track-bar{position:absolute;top:4px;height:calc(var(--gtv-track-height) - 8px);border-radius:4px;display:flex;align-items:center;padding:0 8px;font-size:11px;font-weight:500;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:default;transition:filter .15s}.gtv-track-bar:hover{filter:brightness(1.1)}.gtv-track-bar.is-active{box-shadow:0 0 0 2px var(--gtv-accent)}.gtv-playhead-container{position:absolute;top:0;bottom:0;width:1px;pointer-events:none;z-index:10}.gtv-playhead-head{position:absolute;top:0;left:-5px;width:11px;height:11px;background:var(--gtv-playhead);clip-path:polygon(50% 100%,0 0,100% 0)}.gtv-playhead-line{position:absolute;top:11px;bottom:0;left:0;width:1px;background:var(--gtv-playhead)}.gtv-scrub-area{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.gtv-empty{display:flex;align-items:center;justify-content:center;padding:24px;color:var(--gtv-text-muted)}",L=["var(--gtv-track-1)","var(--gtv-track-2)","var(--gtv-track-3)","var(--gtv-track-4)","var(--gtv-track-5)","var(--gtv-track-6)"],u=[.25,.5,1,2,4];class m extends HTMLElement{constructor(){super();s(this,"shadow");s(this,"timeline",null);s(this,"timelineData",null);s(this,"isPlaying",!1);s(this,"isLooping",!1);s(this,"speedIndex",2);s(this,"collapsed",!1);s(this,"height",200);s(this,"isDragging",!1);s(this,"container");s(this,"playBtn");s(this,"loopBtn");s(this,"speedBtn");s(this,"timeDisplay");s(this,"rulerInner");s(this,"tracksScroll");s(this,"playhead");s(this,"scrubArea");this.shadow=this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this.setupEventListeners()}disconnectedCallback(){this.detachTimeline()}setTimeline(t){this.detachTimeline(),this.timeline=t,w(),this.timelineData=S(t),t.eventCallback("onUpdate",()=>this.onTimelineUpdate()),this.renderTracks(),this.updatePlayhead(),this.updateTimeDisplay(),this.updatePlayState()}detachTimeline(){this.timeline&&(this.timeline.eventCallback("onUpdate",null),this.timeline=null,this.timelineData=null)}render(){this.shadow.innerHTML=`
2
+ <style>${T}</style>
3
+ <div class="gtv-container ${this.collapsed?"collapsed":""}" style="height: ${this.height}px;">
4
+ <!-- Controls Bar -->
5
+ <div class="gtv-controls">
6
+ <div class="gtv-controls-left">
7
+ <button class="gtv-btn" data-action="skip-start" title="Skip to start">
8
+ <svg viewBox="0 0 24 24"><path d="M6 6h2v12H6V6zm3.5 6l8.5 6V6l-8.5 6z"/></svg>
9
+ </button>
10
+ <button class="gtv-btn gtv-btn-play" data-action="play" title="Play/Pause">
11
+ <svg class="play-icon" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
12
+ <svg class="pause-icon" viewBox="0 0 24 24" style="display: none;"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
13
+ </button>
14
+ <button class="gtv-btn" data-action="skip-end" title="Skip to end">
15
+ <svg viewBox="0 0 24 24"><path d="M6 18l8.5-6L6 6v12zm2 0V6l6.5 6L8 18zm8-12v12h2V6h-2z"/></svg>
16
+ </button>
17
+ </div>
18
+
19
+ <div class="gtv-controls-center">
20
+ <button class="gtv-btn" data-action="loop" title="Loop">
21
+ <svg viewBox="0 0 24 24"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
22
+ </button>
23
+ <span class="gtv-time-display">
24
+ <span class="gtv-time-current">0.00</span>
25
+ <span class="gtv-time-total"> / 0.00</span>
26
+ </span>
27
+ <button class="gtv-btn gtv-speed-btn" data-action="speed" title="Playback speed">1x</button>
28
+ </div>
29
+
30
+ <div class="gtv-controls-right">
31
+ <button class="gtv-btn gtv-collapse-btn" data-action="collapse" title="Collapse/Expand">
32
+ <svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>
33
+ </button>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Timeline Area -->
38
+ <div class="gtv-timeline-area">
39
+ <!-- Ruler -->
40
+ <div class="gtv-ruler">
41
+ <div class="gtv-ruler-inner"></div>
42
+ <div class="gtv-playhead-container">
43
+ <div class="gtv-playhead-head"></div>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Tracks -->
48
+ <div class="gtv-tracks-container">
49
+ <div class="gtv-tracks-scroll">
50
+ <div class="gtv-scrub-area"></div>
51
+ <div class="gtv-playhead-container">
52
+ <div class="gtv-playhead-line"></div>
53
+ </div>
54
+ </div>
55
+ <div class="gtv-empty">No timeline attached. Call setTimeline() to visualize a GSAP timeline.</div>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ `,this.container=this.shadow.querySelector(".gtv-container"),this.playBtn=this.shadow.querySelector('[data-action="play"]'),this.loopBtn=this.shadow.querySelector('[data-action="loop"]'),this.speedBtn=this.shadow.querySelector('[data-action="speed"]'),this.timeDisplay=this.shadow.querySelector(".gtv-time-display"),this.rulerInner=this.shadow.querySelector(".gtv-ruler-inner"),this.tracksScroll=this.shadow.querySelector(".gtv-tracks-scroll"),this.playhead=this.shadow.querySelector(".gtv-ruler .gtv-playhead-container"),this.scrubArea=this.shadow.querySelector(".gtv-scrub-area")}setupEventListeners(){this.shadow.addEventListener("click",t=>{const a=t.target.closest("[data-action]");if(!a)return;switch(a.dataset.action){case"play":this.togglePlay();break;case"skip-start":this.skipToStart();break;case"skip-end":this.skipToEnd();break;case"loop":this.toggleLoop();break;case"speed":this.cycleSpeed();break;case"collapse":this.toggleCollapse();break}}),this.scrubArea.addEventListener("mousedown",t=>this.startScrub(t)),this.shadow.querySelector(".gtv-ruler").addEventListener("mousedown",t=>this.startScrub(t)),document.addEventListener("mousemove",t=>this.onScrub(t)),document.addEventListener("mouseup",()=>this.endScrub()),document.addEventListener("keydown",t=>{t.target===document.body&&t.code==="Space"&&(t.preventDefault(),this.togglePlay())})}startScrub(t){this.timeline&&(this.isDragging=!0,this.scrubToPosition(t))}onScrub(t){!this.isDragging||!this.timeline||this.scrubToPosition(t)}endScrub(){this.isDragging=!1}scrubToPosition(t){if(!this.timeline||!this.timelineData)return;const e=this.rulerInner.getBoundingClientRect(),r=Math.max(0,Math.min(t.clientX-e.left,e.width))/e.width;this.timeline.progress(r),this.timeline.pause(),this.updatePlayState()}togglePlay(){this.timeline&&(this.timeline.paused()||this.timeline.progress()===1?this.timeline.progress()===1?this.timeline.restart():this.timeline.play():this.timeline.pause(),this.updatePlayState())}skipToStart(){this.timeline&&(this.timeline.progress(0),this.timeline.pause(),this.updatePlayState())}skipToEnd(){this.timeline&&(this.timeline.progress(1),this.timeline.pause(),this.updatePlayState())}toggleLoop(){this.timeline&&(this.isLooping=!this.isLooping,this.timeline.repeat(this.isLooping?-1:0),this.loopBtn.classList.toggle("active",this.isLooping))}cycleSpeed(){if(!this.timeline)return;this.speedIndex=(this.speedIndex+1)%u.length;const t=u[this.speedIndex];this.timeline.timeScale(t),this.speedBtn.textContent=`${t}x`}toggleCollapse(){this.collapsed=!this.collapsed,this.container.classList.toggle("collapsed",this.collapsed);const t=this.shadow.querySelector('[data-action="collapse"]');t.innerHTML=this.collapsed?'<svg viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>':'<svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>'}updatePlayState(){if(!this.timeline)return;this.isPlaying=!this.timeline.paused()&&this.timeline.progress()<1;const t=this.playBtn.querySelector(".play-icon"),e=this.playBtn.querySelector(".pause-icon");t.style.display=this.isPlaying?"none":"block",e.style.display=this.isPlaying?"block":"none"}onTimelineUpdate(){this.updatePlayhead(),this.updateTimeDisplay(),this.updateActiveTracks(),this.updatePlayState()}updatePlayhead(){if(!this.timeline||!this.timelineData)return;const e=`${this.timeline.progress()*100}%`;this.playhead.style.left=e;const a=this.tracksScroll.querySelector(".gtv-playhead-container");a&&(a.style.left=e)}updateTimeDisplay(){if(!this.timeline||!this.timelineData)return;const t=this.timeline.time(),e=this.timelineData.duration,a=this.timeDisplay.querySelector(".gtv-time-current"),r=this.timeDisplay.querySelector(".gtv-time-total");a.textContent=g(t),r.textContent=` / ${g(e)}`}updateActiveTracks(){if(!this.timeline||!this.timelineData)return;const t=this.timeline.time();this.tracksScroll.querySelectorAll(".gtv-track-bar").forEach((a,r)=>{const c=this.timelineData.tweens[r],d=t>=c.startTime&&t<=c.endTime;a.classList.toggle("is-active",d)})}renderTracks(){if(!this.timelineData)return;const{duration:t,tweens:e}=this.timelineData,a=this.shadow.querySelector(".gtv-empty");a.style.display=e.length>0?"none":"flex",this.renderRuler(t);const r=e.map(p=>this.renderTrack(p,t)).join(""),c=this.tracksScroll.querySelector(".gtv-scrub-area"),d=this.tracksScroll.querySelector(".gtv-playhead-container");this.tracksScroll.innerHTML=r,this.tracksScroll.prepend(c),this.tracksScroll.appendChild(d),this.scrubArea=c}renderRuler(t){const e=[],a=this.calculateInterval(t);for(let r=0;r<=t;r+=a){const c=r/t*100;e.push(`
60
+ <div class="gtv-ruler-marker" style="left: ${c}%;">
61
+ <div class="gtv-ruler-marker-line"></div>
62
+ <span class="gtv-ruler-marker-label">${g(r,!1)}s</span>
63
+ </div>
64
+ `)}this.rulerInner.innerHTML=e.join("")}calculateInterval(t){return t<=1?.25:t<=3?.5:t<=10?1:t<=30?5:10}renderTrack(t,e){const a=t.startTime/e*100,r=t.duration/e*100,c=L[t.colorIndex];return`
65
+ <div class="gtv-track">
66
+ <div class="gtv-track-bar"
67
+ style="left: ${a}%; width: ${r}%; background: ${c};"
68
+ title="${t.label} (${g(t.startTime)}s - ${g(t.endTime)}s)">
69
+ ${t.label}
70
+ </div>
71
+ </div>
72
+ `}}customElements.define("gsap-timeline-viewer",m);class P{constructor(i){s(this,"element");this.element=document.createElement("gsap-timeline-viewer"),i.height&&this.element.style.setProperty("--viewer-height",`${i.height}px`),i.timeline&&setTimeout(()=>{this.element.setTimeline(i.timeline)},0)}attach(i=document.body){i.appendChild(this.element)}detach(){this.element.remove()}setTimeline(i){this.element.setTimeline(i)}get htmlElement(){return this.element}}l.TimelineViewer=P,l.TimelineViewerElement=m,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
@@ -0,0 +1,63 @@
1
+ export declare class TimelineViewer {
2
+ private element;
3
+ constructor(config: TimelineViewerConfig);
4
+ attach(container?: HTMLElement): void;
5
+ detach(): void;
6
+ setTimeline(timeline: gsap.core.Timeline): void;
7
+ get htmlElement(): TimelineViewerElement;
8
+ }
9
+
10
+ export declare interface TimelineViewerConfig {
11
+ timeline: gsap.core.Timeline;
12
+ height?: number;
13
+ collapsed?: boolean;
14
+ }
15
+
16
+ export declare class TimelineViewerElement extends HTMLElement {
17
+ private shadow;
18
+ private timeline;
19
+ private timelineData;
20
+ private isPlaying;
21
+ private isLooping;
22
+ private speedIndex;
23
+ private collapsed;
24
+ private height;
25
+ private isDragging;
26
+ private container;
27
+ private playBtn;
28
+ private loopBtn;
29
+ private speedBtn;
30
+ private timeDisplay;
31
+ private rulerInner;
32
+ private tracksScroll;
33
+ private playhead;
34
+ private scrubArea;
35
+ constructor();
36
+ connectedCallback(): void;
37
+ disconnectedCallback(): void;
38
+ setTimeline(timeline: gsap.core.Timeline): void;
39
+ private detachTimeline;
40
+ private render;
41
+ private setupEventListeners;
42
+ private startScrub;
43
+ private onScrub;
44
+ private endScrub;
45
+ private scrubToPosition;
46
+ private togglePlay;
47
+ private skipToStart;
48
+ private skipToEnd;
49
+ private toggleLoop;
50
+ private cycleSpeed;
51
+ private toggleCollapse;
52
+ private updatePlayState;
53
+ private onTimelineUpdate;
54
+ private updatePlayhead;
55
+ private updateTimeDisplay;
56
+ private updateActiveTracks;
57
+ private renderTracks;
58
+ private renderRuler;
59
+ private calculateInterval;
60
+ private renderTrack;
61
+ }
62
+
63
+ export { }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "gsap-timeline-viewer",
3
+ "version": "0.1.0",
4
+ "description": "A lightweight, framework-agnostic timeline viewer for GSAP animations",
5
+ "type": "module",
6
+ "main": "./dist/gsap-timeline-viewer.umd.cjs",
7
+ "module": "./dist/gsap-timeline-viewer.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/gsap-timeline-viewer.js",
12
+ "require": "./dist/gsap-timeline-viewer.umd.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "dev": "vite",
21
+ "build": "tsc && vite build",
22
+ "preview": "vite preview"
23
+ },
24
+ "keywords": [
25
+ "gsap",
26
+ "timeline",
27
+ "animation",
28
+ "devtools",
29
+ "viewer",
30
+ "debug"
31
+ ],
32
+ "author": "reboiedo",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/reboiedo/gsap-timeline-viewer.git"
37
+ },
38
+ "homepage": "https://github.com/reboiedo/gsap-timeline-viewer#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/reboiedo/gsap-timeline-viewer/issues"
41
+ },
42
+ "peerDependencies": {
43
+ "gsap": "^3.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "gsap": "^3.12.5",
47
+ "typescript": "^5.3.3",
48
+ "vite": "^5.0.10",
49
+ "vite-plugin-dts": "^3.7.0"
50
+ }
51
+ }