{"id":4627,"date":"2026-02-18T11:49:45","date_gmt":"2026-02-18T16:49:45","guid":{"rendered":"https:\/\/smartdsp.pro\/?page_id=4627"},"modified":"2026-02-21T20:59:42","modified_gmt":"2026-02-22T01:59:42","slug":"track-notes","status":"publish","type":"page","link":"https:\/\/smartdsp.pro\/fr\/track-notes\/","title":{"rendered":"Annotations sur Audio"},"content":{"rendered":"<p>[et_pb_section fb_built=&#8221;1&#8243; _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;default&#8221; background_color=&#8221;#000000&#8243; background_image=&#8221;https:\/\/smartdsp.pro\/wp-content\/uploads\/2022\/10\/streamer-79.png&#8221; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_row _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;default&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_column type=&#8221;4_4&#8243; _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;default&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_code _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;default&#8221; hover_enabled=&#8221;0&#8243; global_colors_info=&#8221;{}&#8221; sticky_enabled=&#8221;0&#8243;]<!DOCTYPE html><!-- [et_pb_line_break_holder] --><html lang=\"en\"><!-- [et_pb_line_break_holder] --><head><!-- [et_pb_line_break_holder] --><meta charset=\"UTF-8\"><!-- [et_pb_line_break_holder] --><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><!-- [et_pb_line_break_holder] --><title>TrackNotes \u2014 Collaborative Audio Review<\/title><!-- [et_pb_line_break_holder] --><link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\"><!-- [et_pb_line_break_holder] --><link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin><!-- [et_pb_line_break_holder] --><link href=\"https:\/\/fonts.googleapis.com\/css2?family=Be+Vietnam+Pro:wght@300;400;500;600;700;800&#038;display=swap\" rel=\"stylesheet\"><!-- [et_pb_line_break_holder] --><\/p>\n<style><!-- [et_pb_line_break_holder] -->:root {<!-- [et_pb_line_break_holder] -->  --accent: #f3be29;<!-- [et_pb_line_break_holder] -->  --accent-h: #f99c06;<!-- [et_pb_line_break_holder] -->  --accent2: #cd3ba7;<!-- [et_pb_line_break_holder] -->  --grad: linear-gradient(135deg, #f3be29 0%, #cd3ba7 100%);<!-- [et_pb_line_break_holder] -->  --surface: rgba(255,255,255,0.04);<!-- [et_pb_line_break_holder] -->  --surface2: rgba(255,255,255,0.07);<!-- [et_pb_line_break_holder] -->  --surface3: rgba(255,255,255,0.12);<!-- [et_pb_line_break_holder] -->  --border: rgba(255,255,255,0.09);<!-- [et_pb_line_break_holder] -->  --border2: rgba(255,255,255,0.16);<!-- [et_pb_line_break_holder] -->  --text: #ffffff;<!-- [et_pb_line_break_holder] -->  --text-sub: rgba(255,255,255,0.6);<!-- [et_pb_line_break_holder] -->  --text-dim: rgba(255,255,255,0.38);<!-- [et_pb_line_break_holder] -->  --danger: #f06060;<!-- [et_pb_line_break_holder] -->  --ok: #50d898;<!-- [et_pb_line_break_holder] -->  --r: 10px;<!-- [et_pb_line_break_holder] -->  --r-sm: 6px;<!-- [et_pb_line_break_holder] -->  --font: 'Be Vietnam Pro', sans-serif;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] -->*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}<!-- [et_pb_line_break_holder] -->html,body{font-family:var(--font);background:transparent;color:var(--text);min-height:100vh;font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased}<!-- [et_pb_line_break_holder] -->::-webkit-scrollbar{width:4px;height:4px}<!-- [et_pb_line_break_holder] -->::-webkit-scrollbar-track{background:transparent}<!-- [et_pb_line_break_holder] -->::-webkit-scrollbar-thumb{background:var(--border2);border-radius:99px}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->#app{display:flex;flex-direction:column;min-height:100vh}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* HEADER *\/<!-- [et_pb_line_break_holder] -->.header{<!-- [et_pb_line_break_holder] -->  display:flex;align-items:center;justify-content:space-between;<!-- [et_pb_line_break_holder] -->  padding:14px 24px;border-bottom:1px solid var(--border);<!-- [et_pb_line_break_holder] -->  position:sticky;top:0;z-index:200;<!-- [et_pb_line_break_holder] -->  background:rgba(0,0,0,0.5);backdrop-filter:blur(16px);<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] -->.logo{display:flex;align-items:center;gap:9px;font-size:16px;font-weight:800;letter-spacing:-0.3px}<!-- [et_pb_line_break_holder] -->.logo-mark{width:35px;height:35px;border-radius:7px;background:none;display:flex;align-items:center;justify-content:center;font-size:24px;flex-shrink:0}<!-- [et_pb_line_break_holder] -->.logo-text{background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent}<!-- [et_pb_line_break_holder] -->.h1{background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent}<!-- [et_pb_line_break_holder] -->.hdr-actions{display:flex;align-items:center;gap:8px}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:var(--r-sm);font-family:var(--font);font-size:12.5px;font-weight:600;border:none;cursor:pointer;transition:all .16s;white-space:nowrap}<!-- [et_pb_line_break_holder] -->.btn-ghost{background:var(--surface2);color:var(--text-sub);border:1px solid var(--border)}<!-- [et_pb_line_break_holder] -->.btn-ghost:hover{background:var(--surface3);color:var(--text);border-color:var(--border2)}<!-- [et_pb_line_break_holder] -->.btn-accent{background:var(--grad);color:#ffffff;font-weight:700}<!-- [et_pb_line_break_holder] -->.btn-accent:hover{opacity:.87;box-shadow:0 4px 18px rgba(243,190,41,.3)}<!-- [et_pb_line_break_holder] -->.btn-sm{padding:5px 10px;font-size:11.5px}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* DROP ZONE *\/<!-- [et_pb_line_break_holder] -->#drop-zone{<!-- [et_pb_line_break_holder] -->  flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;<!-- [et_pb_line_break_holder] -->  text-align:center;padding:60px 24px;cursor:default;<!-- [et_pb_line_break_holder] -->  border:2px dashed var(--border2);border-radius:16px;margin:24px;<!-- [et_pb_line_break_holder] -->  background:var(--surface);transition:all .2s;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] -->#drop-zone.drag-over{border-color:var(--accent);background:rgba(243,190,41,.05);box-shadow:0 0 0 4px rgba(243,190,41,.07)}<!-- [et_pb_line_break_holder] -->.dz-icon{font-size:44px;margin-bottom:14px;opacity:.5}<!-- [et_pb_line_break_holder] -->.dz-title{font-size:20px;font-weight:800;margin-bottom:8px;color:white;}<!-- [et_pb_line_break_holder] -->.dz-sub{color:var(--text-sub);font-size:13px;max-width:340px;line-height:1.7;margin-bottom:22px}<!-- [et_pb_line_break_holder] -->.dz-formats{font-size:11px;color:var(--text-dim);margin-top:12px;letter-spacing:.3px}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* WORKSPACE *\/<!-- [et_pb_line_break_holder] -->#workspace{display:none;flex:1;flex-direction:column}<!-- [et_pb_line_break_holder] -->#workspace.active{display:flex}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.ws-topbar{<!-- [et_pb_line_break_holder] -->  display:flex;align-items:center;justify-content:space-between;<!-- [et_pb_line_break_holder] -->  padding:9px 18px;border-bottom:1px solid var(--border);<!-- [et_pb_line_break_holder] -->  background:rgba(0,0,0,.2);flex-shrink:0;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] -->.ws-topbar-left{display:flex;align-items:center;gap:8px}<!-- [et_pb_line_break_holder] -->.shortcuts{font-size:10.5px;color:var(--text-dim)}<!-- [et_pb_line_break_holder] -->kbd{background:var(--surface2);border:1px solid var(--border);padding:1px 5px;border-radius:3px;font-size:9.5px;font-family:var(--font)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.ws-body{display:flex;flex:1;overflow:hidden}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* TRACKS COLUMN *\/<!-- [et_pb_line_break_holder] -->.tracks-col{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:12px}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* TRACK CARD *\/<!-- [et_pb_line_break_holder] -->.track-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;transition:border-color .18s;flex-shrink:0}<!-- [et_pb_line_break_holder] -->.track-card.is-active{border-color:rgba(243,190,41,.4)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.track-head{display:flex;align-items:center;gap:10px;padding:10px 13px;cursor:pointer;user-select:none}<!-- [et_pb_line_break_holder] -->.track-num{font-size:9.5px;font-weight:800;letter-spacing:.6px;color:var(--text-dim);text-transform:uppercase;flex-shrink:0}<!-- [et_pb_line_break_holder] -->.track-name{font-size:13px;font-weight:700;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}<!-- [et_pb_line_break_holder] -->.track-badges{display:flex;gap:4px;align-items:center;flex-shrink:0;flex-wrap:wrap}<!-- [et_pb_line_break_holder] -->.badge{font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;background:var(--surface3);color:var(--text-sub);border:1px solid var(--border);letter-spacing:.3px;text-transform:uppercase}<!-- [et_pb_line_break_holder] -->.badge-a{background:rgba(243,190,41,.12);color:var(--accent);border-color:rgba(243,190,41,.25)}<!-- [et_pb_line_break_holder] -->.track-rm{background:none;border:none;cursor:pointer;color:var(--text-dim);font-size:15px;padding:3px 6px;border-radius:4px;transition:all .14s;flex-shrink:0;line-height:1}<!-- [et_pb_line_break_holder] -->.track-rm:hover{color:var(--danger);background:rgba(240,96,96,.1)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* PLAYER (shown only on active) *\/<!-- [et_pb_line_break_holder] -->.track-player{padding:0 13px 12px;display:none}<!-- [et_pb_line_break_holder] -->.track-card.is-active .track-player{display:block}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* WAVEFORM *\/<!-- [et_pb_line_break_holder] -->.wf-wrap{position:relative;height:72px;border-radius:8px;overflow:hidden;background:rgba(0,0,0,.3);cursor:crosshair;margin-bottom:9px;border:1px solid var(--border)}<!-- [et_pb_line_break_holder] -->.wf-canvas{width:100%;height:100%;display:block}<!-- [et_pb_line_break_holder] -->.wf-prog{position:absolute;top:0;left:0;bottom:0;background:rgba(243,190,41,.07);pointer-events:none;display:none}<!-- [et_pb_line_break_holder] -->.wf-head{position:absolute;top:0;bottom:0;width:2px;background:rgba(255,255,255,.9);pointer-events:none;box-shadow:0 0 5px rgba(255,255,255,.25);display:none}<!-- [et_pb_line_break_holder] -->.track-card.is-active .wf-prog{display:block}<!-- [et_pb_line_break_holder] -->.track-card.is-active .wf-head{display:block}<!-- [et_pb_line_break_holder] -->.wf-hline{position:absolute;top:0;bottom:0;width:1px;background:rgba(255,255,255,.2);pointer-events:none;display:none}<!-- [et_pb_line_break_holder] -->.wf-htime{position:absolute;top:4px;background:rgba(0,0,0,.82);color:var(--accent);font-size:10px;font-weight:700;padding:2px 6px;border-radius:4px;pointer-events:none;white-space:nowrap;display:none;transform:translateX(-50%)}<!-- [et_pb_line_break_holder] -->.wf-sel{position:absolute;top:0;bottom:0;background:rgba(205,59,167,.15);border:1px solid rgba(205,59,167,.38);border-top:none;border-bottom:none;pointer-events:none;display:none}<!-- [et_pb_line_break_holder] -->.wf-markers{position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;z-index:5}<!-- [et_pb_line_break_holder] -->.wf-marker{position:absolute;top:0;bottom:0;width:2px;pointer-events:all;cursor:pointer;transition:opacity .14s}<!-- [et_pb_line_break_holder] -->.wf-marker::before{content:'';position:absolute;top:0;left:-4px;width:10px;height:10px;border-radius:50%;border:2px solid rgba(0,0,0,.5)}<!-- [et_pb_line_break_holder] -->.wf-marker.t-note{background:rgba(90,160,224,.7)}.wf-marker.t-note::before{background:#5aa0e0}<!-- [et_pb_line_break_holder] -->.wf-marker.t-note:hover{background:#5aa0e0}<!-- [et_pb_line_break_holder] -->.wf-marker.t-issue{background:rgba(240,96,96,.7)}.wf-marker.t-issue::before{background:#f06060}<!-- [et_pb_line_break_holder] -->.wf-marker.t-issue:hover{background:#f06060}<!-- [et_pb_line_break_holder] -->.wf-marker.t-cue{background:rgba(255,255,255,.75)}.wf-marker.t-cue::before{background:#ffffff}<!-- [et_pb_line_break_holder] -->.wf-marker.t-cue:hover{background:#ffffff}<!-- [et_pb_line_break_holder] -->.wf-marker.t-ok{background:rgba(80,216,152,.7)}.wf-marker.t-ok::before{background:#50d898}<!-- [et_pb_line_break_holder] -->.wf-marker.t-ok:hover{background:#50d898}<!-- [et_pb_line_break_holder] -->\/* annotation card highlight from waveform hover *\/<!-- [et_pb_line_break_holder] -->.cmt.wf-highlight{border-color:rgba(243,190,41,.55)!important;background:rgba(243,190,41,.06)!important;box-shadow:0 0 0 2px rgba(243,190,41,.12)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* TRANSPORT *\/<!-- [et_pb_line_break_holder] -->.transport{display:flex;align-items:center;gap:10px}<!-- [et_pb_line_break_holder] -->.tr-left{display:flex;align-items:center;gap:5px}<!-- [et_pb_line_break_holder] -->.tr-mid{flex:1;display:flex;align-items:center;gap:10px}<!-- [et_pb_line_break_holder] -->.tr-right{display:flex;align-items:center;gap:7px}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.play-btn{width:36px;height:36px;border-radius:50%;border:none;cursor:pointer;background:var(--grad);color:#160800;display:flex;align-items:center;justify-content:center;transition:all .14s;box-shadow:0 3px 12px rgba(243,190,41,.22);flex-shrink:0}<!-- [et_pb_line_break_holder] -->.play-btn:hover{transform:scale(1.08);box-shadow:0 5px 20px rgba(243,190,41,.38)}<!-- [et_pb_line_break_holder] -->.play-btn:active{transform:scale(.95)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.ctrl-btn{background:none;border:none;cursor:pointer;color:var(--text-sub);font-size:15px;padding:5px;border-radius:5px;transition:all .14s;display:flex;align-items:center;justify-content:center;flex-shrink:0}<!-- [et_pb_line_break_holder] -->.ctrl-btn:hover{color:var(--text);background:var(--surface2)}<!-- [et_pb_line_break_holder] -->.ctrl-btn.on{color:var(--accent)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.t-cur{font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--accent);white-space:nowrap;flex-shrink:0}<!-- [et_pb_line_break_holder] -->.t-sep{color:var(--text-dim);font-weight:400;margin:0 2px}<!-- [et_pb_line_break_holder] -->.t-dur{color:var(--text-sub);font-weight:500}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.prog-bar{flex:1;height:4px;background:var(--surface3);border-radius:99px;cursor:pointer;position:relative;touch-action:none}<!-- [et_pb_line_break_holder] -->.prog-fill{height:100%;border-radius:99px;background:var(--grad);pointer-events:none}<!-- [et_pb_line_break_holder] -->.prog-thumb{position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:11px;height:11px;border-radius:50%;background:var(--accent);border:2px solid rgba(0,0,0,.5);opacity:0;transition:opacity .14s;pointer-events:none}<!-- [et_pb_line_break_holder] -->.prog-bar:hover .prog-thumb,.prog-bar.drag .prog-thumb{opacity:1}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.vol-wrap{display:flex;align-items:center;gap:5px}<!-- [et_pb_line_break_holder] -->.vol-sl{-webkit-appearance:none;appearance:none;width:62px;height:3px;border-radius:99px;background:var(--surface3);outline:none;cursor:pointer}<!-- [et_pb_line_break_holder] -->.vol-sl::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;border-radius:50%;background:var(--accent);cursor:pointer}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.spd-btn{font-size:10.5px;font-weight:800;padding:3px 7px;border-radius:5px;background:var(--surface2);border:1px solid var(--border);color:var(--text-sub);cursor:pointer;transition:all .14s;font-family:var(--font);letter-spacing:.3px}<!-- [et_pb_line_break_holder] -->.spd-btn:hover{color:var(--accent);border-color:rgba(243,190,41,.4)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* ADD MORE AREA *\/<!-- [et_pb_line_break_holder] -->.add-more-zone{display:flex;align-items:center;justify-content:center;padding:13px;border:1px dashed var(--border2);border-radius:var(--r);cursor:pointer;color:var(--text-sub);font-size:12.5px;font-weight:600;gap:7px;transition:all .16s}<!-- [et_pb_line_break_holder] -->.add-more-zone:hover{border-color:var(--accent);color:var(--accent);background:rgba(243,190,41,.04)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* ANNOTATIONS PANEL *\/<!-- [et_pb_line_break_holder] -->.ann-panel{width:350px;flex-shrink:0;border-left:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;background:rgba(0,0,0,.12)}<!-- [et_pb_line_break_holder] -->@media(max-width:820px){.ann-panel{display:none}}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.ann-top{padding:12px 14px;border-bottom:1px solid var(--border);flex-shrink:0;display:flex;align-items:center;justify-content:space-between}<!-- [et_pb_line_break_holder] -->.ann-title{font-size:11px;font-weight:800;letter-spacing:.6px;text-transform:uppercase;color:var(--text-sub);display:flex;align-items:center;gap:7px}<!-- [et_pb_line_break_holder] -->.ann-ct{font-size:10px;font-weight:800;padding:2px 7px;border-radius:99px;background:rgba(243,190,41,.12);color:var(--accent)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* COMPOSE BOX \u2014 sits above list *\/<!-- [et_pb_line_break_holder] -->.compose{padding:12px 13px;border-bottom:1px solid var(--border);flex-shrink:0;background:rgba(0,0,0,.14)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* Timestamp row *\/<!-- [et_pb_line_break_holder] -->.compose-ts-row{display:flex;align-items:center;gap:7px;margin-bottom:8px}<!-- [et_pb_line_break_holder] -->.ts-pill{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border-radius:5px;background:rgba(205,59,167,.12);border:1px solid rgba(205,59,167,.22);font-size:11px;font-weight:700;color:#e080cc;font-variant-numeric:tabular-nums;cursor:default;flex-shrink:0}<!-- [et_pb_line_break_holder] -->.ts-pill svg{width:9px;height:9px;flex-shrink:0}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.range-btn{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:5px;font-size:11px;font-weight:600;background:var(--surface2);border:1px solid var(--border);color:var(--text-sub);cursor:pointer;transition:all .14s;font-family:var(--font)}<!-- [et_pb_line_break_holder] -->.range-btn:hover{color:var(--text);border-color:var(--border2)}<!-- [et_pb_line_break_holder] -->.range-btn.on{color:#e070cc;border-color:rgba(205,59,167,.35);background:rgba(205,59,167,.08)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* Type selector row *\/<!-- [et_pb_line_break_holder] -->.type-row{display:flex;gap:4px;margin-bottom:8px;flex-wrap:wrap}<!-- [et_pb_line_break_holder] -->.type-pill{padding:3px 8px;border-radius:99px;font-size:11px;font-weight:700;cursor:pointer;border:1px solid var(--border);background:none;color:var(--text-dim);transition:all .14s;font-family:var(--font)}<!-- [et_pb_line_break_holder] -->.type-pill:hover{color:var(--text);border-color:var(--border2)}<!-- [et_pb_line_break_holder] -->.type-pill.on[data-t=\"note\"]{background:rgba(90,160,224,.14);color:#90c8f8;border-color:rgba(90,160,224,.3)}<!-- [et_pb_line_break_holder] -->.type-pill.on[data-t=\"issue\"]{background:rgba(240,96,96,.14);color:#f09090;border-color:rgba(240,96,96,.3)}<!-- [et_pb_line_break_holder] -->.type-pill.on[data-t=\"cue\"]{background:rgba(243,190,41,.13);color:var(--accent);border-color:rgba(243,190,41,.28)}<!-- [et_pb_line_break_holder] -->.type-pill.on[data-t=\"ok\"]{background:rgba(80,216,152,.13);color:#70e0b0;border-color:rgba(80,216,152,.28)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* Textarea *\/<!-- [et_pb_line_break_holder] -->.ann-ta{width:100%;background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:8px 11px;color:var(--text);font-family:var(--font);font-size:13px;resize:none;outline:none;transition:border-color .15s;line-height:1.55;min-height:56px;margin-bottom:8px}<!-- [et_pb_line_break_holder] -->.ann-ta:focus{border-color:rgba(243,190,41,.4)}<!-- [et_pb_line_break_holder] -->.ann-ta::placeholder{color:var(--text-dim)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.compose-bottom{display:flex;align-items:center;gap:7px}<!-- [et_pb_line_break_holder] -->.author-inp{flex:1;background:var(--surface2);border:1px solid var(--border);border-radius:6px;padding:6px 10px;color:var(--text);font-family:var(--font);font-size:12px;outline:none;transition:border-color .15s}<!-- [et_pb_line_break_holder] -->.author-inp:focus{border-color:rgba(243,190,41,.35)}<!-- [et_pb_line_break_holder] -->.author-inp::placeholder{color:var(--text-dim)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* FILTERS *\/<!-- [et_pb_line_break_holder] -->.filter-bar{display:flex;align-items:center;gap:3px;padding:7px 11px;border-bottom:1px solid var(--border);flex-shrink:0;overflow-x:auto}<!-- [et_pb_line_break_holder] -->.filter-bar::-webkit-scrollbar{height:0}<!-- [et_pb_line_break_holder] -->.fpill{padding:3px 9px;border-radius:99px;font-size:11px;font-weight:600;cursor:pointer;border:1px solid var(--border);background:none;color:var(--text-sub);transition:all .14s;font-family:var(--font);flex-shrink:0}<!-- [et_pb_line_break_holder] -->.fpill:hover{color:var(--text);border-color:var(--border2)}<!-- [et_pb_line_break_holder] -->.fpill.on{background:var(--surface3);color:var(--accent);border-color:rgba(243,190,41,.3)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* COMMENTS LIST *\/<!-- [et_pb_line_break_holder] -->.comments-scroll{flex:1;overflow-y:auto;padding:9px 11px;display:flex;flex-direction:column;gap:7px}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.empty-ann{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:28px 20px;color:var(--text-sub);gap:7px}<!-- [et_pb_line_break_holder] -->.empty-ann .ei{font-size:30px;opacity:.3}<!-- [et_pb_line_break_holder] -->.empty-ann p{font-size:12px;line-height:1.65;max-width:210px;color:var(--text-dim)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* COMMENT CARD *\/<!-- [et_pb_line_break_holder] -->.cmt{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:10px 12px;transition:all .16s;animation:cIn .2s ease}<!-- [et_pb_line_break_holder] -->@keyframes cIn{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}<!-- [et_pb_line_break_holder] -->.cmt:hover{border-color:var(--border2);background:var(--surface2)}<!-- [et_pb_line_break_holder] -->.cmt.resolved{opacity:.38}<!-- [et_pb_line_break_holder] -->.cmt.resolved .cmt-body{text-decoration:line-through;color:var(--text-sub)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.cmt-top{display:flex;align-items:center;gap:7px;margin-bottom:6px}<!-- [et_pb_line_break_holder] -->.cmt-av{width:23px;height:23px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:800;color:#160800;background:var(--grad)}<!-- [et_pb_line_break_holder] -->.cmt-author{font-size:12px;font-weight:700;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.cmt-ts-btn{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:4px;background:rgba(205,59,167,.1);color:#e080cc;font-size:10.5px;font-weight:700;cursor:pointer;border:1px solid rgba(205,59,167,.18);transition:all .14s;font-variant-numeric:tabular-nums;flex-shrink:0}<!-- [et_pb_line_break_holder] -->.cmt-ts-btn:hover{background:rgba(205,59,167,.2)}<!-- [et_pb_line_break_holder] -->.cmt-ts-btn svg{width:8px;height:8px}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.cmt-type{display:inline-flex;font-size:9.5px;font-weight:700;padding:2px 5px;border-radius:3px;letter-spacing:.3px;text-transform:uppercase;vertical-align:middle;margin-right:4px}<!-- [et_pb_line_break_holder] -->.ct-note{background:rgba(90,160,224,.12);color:#90c8f8;border:1px solid rgba(90,160,224,.2)}<!-- [et_pb_line_break_holder] -->.ct-issue{background:rgba(240,96,96,.12);color:#f09090;border:1px solid rgba(240,96,96,.2)}<!-- [et_pb_line_break_holder] -->.ct-cue{background:rgba(243,190,41,.12);color:var(--accent);border:1px solid rgba(243,190,41,.2)}<!-- [et_pb_line_break_holder] -->.ct-ok{background:rgba(80,216,152,.12);color:#70e0b0;border:1px solid rgba(80,216,152,.2)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.cmt-body{font-size:12.5px;line-height:1.6;word-break:break-word}<!-- [et_pb_line_break_holder] -->.range-tag{display:inline-flex;align-items:center;font-size:9.5px;font-weight:700;color:#e080cc;background:rgba(205,59,167,.09);border:1px solid rgba(205,59,167,.2);padding:1px 5px;border-radius:3px;margin-left:5px;font-variant-numeric:tabular-nums;cursor:pointer}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.cmt-foot{display:flex;align-items:center;gap:5px;margin-top:7px;padding-top:6px;border-top:1px solid var(--border)}<!-- [et_pb_line_break_holder] -->.cmt-date{font-size:10px;color:var(--text-dim);flex:1}<!-- [et_pb_line_break_holder] -->.cmt-acts{display:flex;gap:2px;opacity:0;transition:opacity .14s}<!-- [et_pb_line_break_holder] -->.cmt:hover .cmt-acts{opacity:1}<!-- [et_pb_line_break_holder] -->.ca{background:none;border:none;cursor:pointer;padding:2px 6px;border-radius:4px;font-size:10.5px;font-weight:600;color:var(--text-sub);font-family:var(--font);transition:all .14s}<!-- [et_pb_line_break_holder] -->.ca:hover{background:var(--surface3);color:var(--text)}<!-- [et_pb_line_break_holder] -->.ca.del:hover{color:var(--danger)}<!-- [et_pb_line_break_holder] -->.ca.res:hover{color:var(--ok)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* REPLIES *\/<!-- [et_pb_line_break_holder] -->.replies{margin-top:7px;display:flex;flex-direction:column;gap:4px}<!-- [et_pb_line_break_holder] -->.reply{padding:6px 9px 6px 10px;border-left:2px solid rgba(205,59,167,.28);border-radius:0 5px 5px 0;background:rgba(0,0,0,.18)}<!-- [et_pb_line_break_holder] -->.reply-top{display:flex;align-items:center;gap:5px;margin-bottom:2px}<!-- [et_pb_line_break_holder] -->.reply-author{font-size:11px;font-weight:700}<!-- [et_pb_line_break_holder] -->.reply-date{font-size:9.5px;color:var(--text-dim);margin-left:auto}<!-- [et_pb_line_break_holder] -->.reply-body{font-size:11.5px;color:var(--text-sub);line-height:1.55}<!-- [et_pb_line_break_holder] -->.reply-row{display:flex;gap:5px;align-items:center;margin-top:5px}<!-- [et_pb_line_break_holder] -->.reply-inp{flex:1;background:var(--surface2);border:1px solid var(--border);border-radius:5px;padding:4px 8px;color:var(--text);font-family:var(--font);font-size:11px;outline:none;transition:border-color .14s}<!-- [et_pb_line_break_holder] -->.reply-inp:focus{border-color:var(--border2)}<!-- [et_pb_line_break_holder] -->.reply-inp::placeholder{color:var(--text-dim)}<!-- [et_pb_line_break_holder] -->.reply-post{padding:4px 10px;border-radius:5px;background:var(--surface3);border:1px solid var(--border);color:var(--text);font-family:var(--font);font-size:11px;font-weight:700;cursor:pointer;transition:all .14s}<!-- [et_pb_line_break_holder] -->.reply-post:hover{background:var(--accent);color:#160800;border-color:var(--accent)}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* BOTTOM BAR *\/<!-- [et_pb_line_break_holder] -->.ann-bot{padding:7px 11px;border-top:1px solid var(--border);flex-shrink:0;display:flex;gap:6px;align-items:center}<!-- [et_pb_line_break_holder] -->.ann-sort{background:var(--surface2);border:1px solid var(--border);color:var(--text);font-family:var(--font);font-size:11px;border-radius:5px;padding:4px 8px;outline:none;cursor:pointer;flex:1}<!-- [et_pb_line_break_holder] -->option{background:#111}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* MODALS *\/<!-- [et_pb_line_break_holder] -->.modal-ov{position:fixed;inset:0;background:rgba(0,0,0,.72);backdrop-filter:blur(8px);z-index:999;display:flex;align-items:center;justify-content:center;padding:20px;animation:fIn .2s ease}<!-- [et_pb_line_break_holder] -->.modal-ov.hidden{display:none}<!-- [et_pb_line_break_holder] -->@keyframes fIn{from{opacity:0}to{opacity:1}}<!-- [et_pb_line_break_holder] -->.modal{background:#0f0f14;border:1px solid var(--border2);border-radius:14px;padding:24px;width:100%;max-width:520px;max-height:88vh;overflow-y:auto;animation:mUp .22s ease;box-shadow:0 32px 80px rgba(0,0,0,.6)}<!-- [et_pb_line_break_holder] -->@keyframes mUp{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}<!-- [et_pb_line_break_holder] -->.modal h3{font-size:16px;font-weight:800;margin-bottom:5px}<!-- [et_pb_line_break_holder] -->.modal-sub{color:var(--text-sub);font-size:13px;margin-bottom:18px;line-height:1.6}<!-- [et_pb_line_break_holder] -->.modal-foot{display:flex;gap:7px;justify-content:flex-end;margin-top:20px}<!-- [et_pb_line_break_holder] -->.form-group{margin-bottom:12px}<!-- [et_pb_line_break_holder] -->.form-label{font-size:10.5px;font-weight:700;color:var(--text-sub);text-transform:uppercase;letter-spacing:.5px;display:block;margin-bottom:5px}<!-- [et_pb_line_break_holder] -->.form-input{width:100%;background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:8px 11px;color:var(--text);font-family:var(--font);font-size:13px;outline:none;transition:border-color .15s}<!-- [et_pb_line_break_holder] -->.form-input:focus{border-color:rgba(243,190,41,.4)}<!-- [et_pb_line_break_holder] -->.stat-row{display:flex;gap:10px;margin-bottom:16px}<!-- [et_pb_line_break_holder] -->.stat-box{flex:1;text-align:center;padding:11px;background:var(--surface);border:1px solid var(--border);border-radius:8px}<!-- [et_pb_line_break_holder] -->.stat-v{font-size:22px;font-weight:800;background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent}<!-- [et_pb_line_break_holder] -->.stat-l{font-size:10px;color:var(--text-sub);text-transform:uppercase;letter-spacing:.5px;margin-top:2px}<!-- [et_pb_line_break_holder] -->.exp-pre{background:rgba(0,0,0,.35);border:1px solid var(--border);border-radius:7px;padding:10px;font-size:10.5px;font-family:'Courier New',monospace;color:var(--text-sub);max-height:150px;overflow-y:auto;white-space:pre}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* CUE ROW *\/<!-- [et_pb_line_break_holder] -->.cue-row{display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--border);align-items:flex-start}<!-- [et_pb_line_break_holder] -->.cue-num{font-size:10.5px;font-weight:800;color:var(--text-dim);width:38px;flex-shrink:0}<!-- [et_pb_line_break_holder] -->.cue-time{font-size:12px;font-weight:700;color:var(--accent);font-variant-numeric:tabular-nums;flex-shrink:0;width:52px}<!-- [et_pb_line_break_holder] -->.cue-type{font-size:10px;font-weight:700;color:var(--text-sub);flex-shrink:0;width:42px}<!-- [et_pb_line_break_holder] -->.cue-text{font-size:12.5px;flex:1}<!-- [et_pb_line_break_holder] -->.cue-author{font-size:10px;color:var(--text-dim);flex-shrink:0}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/* EXPORT OPTION CARDS *\/<!-- [et_pb_line_break_holder] -->.export-opts{display:flex;gap:10px;margin-bottom:18px}<!-- [et_pb_line_break_holder] -->.export-opt{flex:1;padding:14px;background:var(--surface);border:2px solid var(--border);border-radius:10px;cursor:pointer;transition:all .16s;text-align:left}<!-- [et_pb_line_break_holder] -->.export-opt:hover{border-color:var(--border2);background:var(--surface2)}<!-- [et_pb_line_break_holder] -->.export-opt.selected{border-color:var(--accent);background:rgba(243,190,41,.06)}<!-- [et_pb_line_break_holder] -->.export-opt-icon{font-size:22px;margin-bottom:7px}<!-- [et_pb_line_break_holder] -->.export-opt-title{font-size:13px;font-weight:800;margin-bottom:4px}<!-- [et_pb_line_break_holder] -->.export-opt-desc{font-size:11px;color:var(--text-sub);line-height:1.55}<!-- [et_pb_line_break_holder] -->\/* import option tabs *\/<!-- [et_pb_line_break_holder] -->.import-tabs{display:flex;gap:0;margin-bottom:16px;border:1px solid var(--border);border-radius:8px;overflow:hidden}<!-- [et_pb_line_break_holder] -->.import-tab{flex:1;padding:9px 14px;font-size:12px;font-weight:700;cursor:pointer;text-align:center;color:var(--text-sub);transition:all .15s;background:none;border:none;font-family:var(--font)}<!-- [et_pb_line_break_holder] -->.import-tab:hover{color:var(--text);background:var(--surface2)}<!-- [et_pb_line_break_holder] -->.import-tab.on{background:var(--surface3);color:var(--accent)}<!-- [et_pb_line_break_holder] -->.import-tab:not(:last-child){border-right:1px solid var(--border)}<!-- [et_pb_line_break_holder] -->.import-pane{display:none}.import-pane.on{display:block}<!-- [et_pb_line_break_holder] -->\/* zip progress *\/<!-- [et_pb_line_break_holder] -->.zip-progress{background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:12px;margin-top:10px;display:none}<!-- [et_pb_line_break_holder] -->.zip-progress-bar{height:4px;background:var(--surface3);border-radius:99px;overflow:hidden;margin-top:8px}<!-- [et_pb_line_break_holder] -->.zip-progress-fill{height:100%;background:var(--grad);border-radius:99px;width:0%;transition:width .2s}<!-- [et_pb_line_break_holder] -->.zip-status{font-size:11.5px;color:var(--text-sub);margin-bottom:4px}<!-- [et_pb_line_break_holder] -->\/* TOASTS *\/<!-- [et_pb_line_break_holder] -->#toasts{position:fixed;bottom:18px;right:18px;z-index:9999;display:flex;flex-direction:column;gap:6px}<!-- [et_pb_line_break_holder] -->.toast{background:#111118;border:1px solid var(--border2);border-radius:8px;padding:9px 13px;font-size:12px;font-weight:600;box-shadow:0 6px 24px rgba(0,0,0,.45);animation:tIn .2s ease;display:flex;align-items:center;gap:8px;min-width:190px}<!-- [et_pb_line_break_holder] -->@keyframes tIn{from{opacity:0;transform:translateX(14px)}to{opacity:1;transform:none}}<!-- [et_pb_line_break_holder] -->.toast.ok{border-color:rgba(80,216,152,.3)}<!-- [et_pb_line_break_holder] -->.toast.err{border-color:rgba(240,96,96,.3)}<!-- [et_pb_line_break_holder] --><\/style>\n<p><!-- [et_pb_line_break_holder] --><\/head><!-- [et_pb_line_break_holder] --><body><!-- [et_pb_line_break_holder] --><\/p>\n<div id=\"app\"><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] --><!-- HEADER --><!-- [et_pb_line_break_holder] --><\/p>\n<header class=\"header\"><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"logo\"><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"logo-mark\">\ud83d\udcdc<\/div>\n<p><!-- [et_pb_line_break_holder] -->    <span class=\"logo-text\">TrackNotes<\/span><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"hdr-actions\"><!-- [et_pb_line_break_holder] -->    <button class=\"btn btn-ghost btn-sm\" id=\"btn-import\"><!-- [et_pb_line_break_holder] -->      <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"\/><polyline points=\"17 8 12 3 7 8\"\/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"\/><\/svg><!-- [et_pb_line_break_holder] -->      Import<!-- [et_pb_line_break_holder] -->    <\/button><!-- [et_pb_line_break_holder] -->    <button class=\"btn btn-ghost btn-sm\" id=\"btn-export\"><!-- [et_pb_line_break_holder] -->      <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"\/><polyline points=\"7 10 12 15 17 10\"\/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"\/><\/svg><!-- [et_pb_line_break_holder] -->      Export<!-- [et_pb_line_break_holder] -->    <\/button><!-- [et_pb_line_break_holder] -->    <button class=\"btn btn-accent btn-sm\" id=\"btn-cuesheet\"><!-- [et_pb_line_break_holder] -->      <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><rect x=\"5\" y=\"2\" width=\"14\" height=\"20\" rx=\"2\"\/><line x1=\"9\" y1=\"7\" x2=\"15\" y2=\"7\"\/><line x1=\"9\" y1=\"11\" x2=\"15\" y2=\"11\"\/><line x1=\"9\" y1=\"15\" x2=\"12\" y2=\"15\"\/><\/svg><!-- [et_pb_line_break_holder] -->      Cue Sheet<!-- [et_pb_line_break_holder] -->    <\/button><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] --><\/header>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] --><!-- DROP ZONE --><!-- [et_pb_line_break_holder] --><\/p>\n<div id=\"drop-zone\"><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"dz-icon\">\ud83c\udfb5<\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/p>\n<h2 class=\"dz-title\">Drop audio files to begin<\/h2>\n<p><!-- [et_pb_line_break_holder] -->  <pee class=\"dz-sub\">Load one or multiple tracks. Everything stays 100% local \u2014 no uploads, no account, zero latency.<\/pee><!-- [et_pb_line_break_holder] -->  <button class=\"btn btn-accent\" id=\"btn-browse\"><!-- [et_pb_line_break_holder] -->    <svg width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" viewBox=\"0 0 24 24\"><path d=\"M12 5v14M5 12l7-7 7 7\"\/><\/svg><!-- [et_pb_line_break_holder] -->    Browse files<!-- [et_pb_line_break_holder] -->  <\/button><!-- [et_pb_line_break_holder] -->  <pee class=\"dz-formats\">WAV \u00b7 FLAC \u00b7 AIFF \u00b7 MP3 \u00b7 AAC \u00b7 OGG \u00b7 M4A \u00b7 OPUS<\/pee><!-- [et_pb_line_break_holder] -->  <input type=\"file\" id=\"file-input\" accept=\"audio\/*\" multiple style=\"display:none\"><!-- [et_pb_line_break_holder] --><\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] --><!-- WORKSPACE --><!-- [et_pb_line_break_holder] --><\/p>\n<div id=\"workspace\"><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"ws-topbar\"><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"ws-topbar-left\"><!-- [et_pb_line_break_holder] -->      <button class=\"btn btn-ghost btn-sm\" id=\"btn-add-more\"><!-- [et_pb_line_break_holder] -->        <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" viewBox=\"0 0 24 24\"><path d=\"M12 5v14M5 12h14\"\/><\/svg><!-- [et_pb_line_break_holder] -->        Add files<!-- [et_pb_line_break_holder] -->      <\/button><!-- [et_pb_line_break_holder] -->      <input type=\"file\" id=\"add-file-input\" accept=\"audio\/*\" multiple style=\"display:none\"><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->    <span class=\"shortcuts\"><!-- [et_pb_line_break_holder] -->      <kbd>Space<\/kbd> Play\/Pause \u00a0\u00a0<!-- [et_pb_line_break_holder] -->      <kbd>\u2190 \u2192<\/kbd> Seek 5s \u00a0\u00a0<!-- [et_pb_line_break_holder] -->      <kbd>Shift+\u2190 \u2192<\/kbd> Seek 10s<!-- [et_pb_line_break_holder] -->    <\/span><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"ws-body\"><!-- [et_pb_line_break_holder] -->    <!-- Tracks list --><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"tracks-col\" id=\"tracks-col\"><\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    <!-- Annotations panel --><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"ann-panel\"><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"ann-top\"><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"ann-title\"><!-- [et_pb_line_break_holder] -->          <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" viewBox=\"0 0 24 24\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"\/><\/svg><!-- [et_pb_line_break_holder] -->          Annotations<!-- [et_pb_line_break_holder] -->          <span class=\"ann-ct\" id=\"ann-ct\">0<\/span><!-- [et_pb_line_break_holder] -->        <\/div>\n<p><!-- [et_pb_line_break_holder] -->        <button class=\"btn btn-ghost btn-sm\" id=\"btn-clear-res\" style=\"font-size:10px\">Clear resolved<\/button><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->      <!-- COMPOSE (top, always visible) --><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"compose\"><!-- [et_pb_line_break_holder] -->        <!-- Timestamp + range --><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"compose-ts-row\"><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"ts-pill\" id=\"ts-pill\"><!-- [et_pb_line_break_holder] -->            <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"\/><polyline points=\"12 6 12 12 16 14\"\/><\/svg><!-- [et_pb_line_break_holder] -->            <span id=\"ts-val\">0:00.0<\/span><!-- [et_pb_line_break_holder] -->            <span id=\"ts-lock-icon\" style=\"display:none;font-size:9px;opacity:.7\">\ud83d\udd12<\/span><!-- [et_pb_line_break_holder] -->          <\/div>\n<p><!-- [et_pb_line_break_holder] -->          <button class=\"range-btn\" id=\"range-btn\" title=\"Drag on waveform to mark a time range, then toggle\"><!-- [et_pb_line_break_holder] -->            <svg width=\"10\" height=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\"\/><polyline points=\"8 7 3 12 8 17\"\/><polyline points=\"16 7 21 12 16 17\"\/><\/svg><!-- [et_pb_line_break_holder] -->            Range<!-- [et_pb_line_break_holder] -->          <\/button><!-- [et_pb_line_break_holder] -->          <span id=\"range-display\" style=\"font-size:10px;color:#e080cc;display:none;font-variant-numeric:tabular-nums\"><\/span><!-- [et_pb_line_break_holder] -->        <\/div>\n<p><!-- [et_pb_line_break_holder] -->        <!-- Type --><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"type-row\"><!-- [et_pb_line_break_holder] -->          <button class=\"type-pill on\" data-t=\"note\">\ud83d\udcdd Note<\/button><!-- [et_pb_line_break_holder] -->          <button class=\"type-pill\" data-t=\"issue\">\ud83d\udd34 Issue<\/button><!-- [et_pb_line_break_holder] -->          <button class=\"type-pill\" data-t=\"cue\">\ud83c\udfaf Cue<\/button><!-- [et_pb_line_break_holder] -->          <button class=\"type-pill\" data-t=\"ok\">\u2705 OK<\/button><!-- [et_pb_line_break_holder] -->        <\/div>\n<p><!-- [et_pb_line_break_holder] -->        <!-- Text --><!-- [et_pb_line_break_holder] -->        <textarea class=\"ann-ta\" id=\"ann-text\" placeholder=\"Write your annotation\u2026 (Enter to post)\" rows=\"2\"><\/textarea><!-- [et_pb_line_break_holder] -->        <!-- Author + post --><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"compose-bottom\"><!-- [et_pb_line_break_holder] -->          <input class=\"author-inp\" id=\"ann-author\" placeholder=\"Your name\" maxlength=\"32\"><!-- [et_pb_line_break_holder] -->          <button class=\"btn btn-accent btn-sm\" id=\"btn-post\">Post<\/button><!-- [et_pb_line_break_holder] -->        <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->      <!-- Filter --><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"filter-bar\"><!-- [et_pb_line_break_holder] -->        <button class=\"fpill on\" data-f=\"all\">All<\/button><!-- [et_pb_line_break_holder] -->        <button class=\"fpill\" data-f=\"note\">Notes<\/button><!-- [et_pb_line_break_holder] -->        <button class=\"fpill\" data-f=\"issue\">Issues<\/button><!-- [et_pb_line_break_holder] -->        <button class=\"fpill\" data-f=\"cue\">Cues<\/button><!-- [et_pb_line_break_holder] -->        <button class=\"fpill\" data-f=\"ok\">OK<\/button><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"comments-scroll\" id=\"comments-scroll\"><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"empty-ann\" id=\"empty-ann\"><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"ei\">\ud83d\udcac<\/div>\n<p><!-- [et_pb_line_break_holder] -->          <pee>Load a track and write your first annotation \u2014 timestamps are captured automatically.<\/pee><!-- [et_pb_line_break_holder] -->        <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"ann-bot\"><!-- [et_pb_line_break_holder] -->        <select class=\"ann-sort\" id=\"ann-sort\"><!-- [et_pb_line_break_holder] --><option value=\"time\">Sort by timestamp<\/option><!-- [et_pb_line_break_holder] --><option value=\"newest\">Newest first<\/option><!-- [et_pb_line_break_holder] --><option value=\"type\">Sort by type<\/option><!-- [et_pb_line_break_holder] -->        <\/select><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] --><\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] --><!-- EXPORT MODAL --><!-- [et_pb_line_break_holder] --><\/p>\n<div class=\"modal-ov hidden\" id=\"modal-export\"><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"modal\"><!-- [et_pb_line_break_holder] -->    <\/p>\n<h3>\ud83d\udce4 Export Session<\/h3>\n<p><!-- [et_pb_line_break_holder] -->    <pee class=\"modal-sub\">Choose what to include. The recipient can import either format.<\/pee><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"stat-row\"><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"stat-box\">\n<div class=\"stat-v\" id=\"exp-t\">0<\/div>\n<div class=\"stat-l\">Tracks<\/div>\n<\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"stat-box\">\n<div class=\"stat-v\" id=\"exp-a\">0<\/div>\n<div class=\"stat-l\">Annotations<\/div>\n<\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"stat-box\">\n<div class=\"stat-v\" id=\"exp-sz\">\u2014<\/div>\n<div class=\"stat-l\">Audio size<\/div>\n<\/div>\n<p><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->    <!-- Option cards --><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"export-opts\"><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"export-opt selected\" id=\"exp-opt-json\" onclick=\"selectExportOpt('json')\"><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"export-opt-icon\">\ud83d\udccb<\/div>\n<p><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"export-opt-title\">Annotations only<\/div>\n<p><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"export-opt-desc\">Tiny JSON file with all timestamps &#038; notes. Recipient needs the same audio files to listen along.<\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"export-opt\" id=\"exp-opt-zip\" onclick=\"selectExportOpt('zip')\"><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"export-opt-icon\">\ud83d\udce6<\/div>\n<p><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"export-opt-title\">Full project (ZIP)<\/div>\n<p><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"export-opt-desc\">Bundles annotations + all audio files into one ZIP. Recipient can import everything in one click \u2014 no files needed separately.<\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"zip-progress\" id=\"zip-progress\"><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"zip-status\" id=\"zip-status\">Preparing ZIP\u2026<\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"zip-progress-bar\">\n<div class=\"zip-progress-fill\" id=\"zip-fill\"><\/div>\n<\/div>\n<p><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"modal-foot\"><!-- [et_pb_line_break_holder] -->      <button class=\"btn btn-ghost\" onclick=\"closeModal('modal-export')\">Cancel<\/button><!-- [et_pb_line_break_holder] -->      <button class=\"btn btn-accent\" id=\"btn-do-export\">Download<\/button><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] --><\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] --><!-- IMPORT MODAL --><!-- [et_pb_line_break_holder] --><\/p>\n<div class=\"modal-ov hidden\" id=\"modal-import\"><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"modal\"><!-- [et_pb_line_break_holder] -->    <\/p>\n<h3>\ud83d\udce5 Import Session<\/h3>\n<p><!-- [et_pb_line_break_holder] -->    <pee class=\"modal-sub\">Choose how you&#8217;re importing. Both methods restore all annotations.<\/pee><!-- [et_pb_line_break_holder] -->    <!-- Tabs --><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"import-tabs\"><!-- [et_pb_line_break_holder] -->      <button class=\"import-tab on\" id=\"itab-json\" onclick=\"switchImportTab('json')\">\ud83d\udccb JSON only<\/button><!-- [et_pb_line_break_holder] -->      <button class=\"import-tab\" id=\"itab-zip\" onclick=\"switchImportTab('zip')\">\ud83d\udce6 Full project (ZIP)<\/button><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    <!-- Pane A: JSON only --><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"import-pane on\" id=\"ipane-json\"><!-- [et_pb_line_break_holder] -->      <\/p>\n<div style=\"background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:14px;font-size:12px;color:var(--text-sub);line-height:1.65\"><!-- [et_pb_line_break_holder] -->        <strong style=\"color:var(--text)\">You already have the audio files.<\/strong><!\u2013- [et_pb_br_holder] -\u2013><!-- [et_pb_line_break_holder] -->        Load your audio files first (via the main interface), then import the JSON here \u2014 annotations will be matched by filename automatically.<!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"form-group\"><!-- [et_pb_line_break_holder] -->        <label class=\"form-label\">TrackNotes session (.json)<\/label><!-- [et_pb_line_break_holder] -->        <input type=\"file\" id=\"import-json-input\" accept=\".json\" class=\"form-input\"><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    <!-- Pane B: ZIP --><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"import-pane\" id=\"ipane-zip\"><!-- [et_pb_line_break_holder] -->      <\/p>\n<div style=\"background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:14px;font-size:12px;color:var(--text-sub);line-height:1.65\"><!-- [et_pb_line_break_holder] -->        <strong style=\"color:var(--text)\">You received a full project ZIP.<\/strong><!\u2013- [et_pb_br_holder] -\u2013><!-- [et_pb_line_break_holder] -->        Select the <code style=\"background:var(--surface2);padding:1px 5px;border-radius:3px;font-size:11px\">.zip<\/code> file \u2014 audio tracks and annotations will all be loaded in one click.<!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"form-group\"><!-- [et_pb_line_break_holder] -->        <label class=\"form-label\">TrackNotes project (.zip)<\/label><!-- [et_pb_line_break_holder] -->        <input type=\"file\" id=\"import-zip-input\" accept=\".zip\" class=\"form-input\"><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"zip-progress\" id=\"import-zip-progress\" style=\"display:none\"><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"zip-status\" id=\"import-zip-status\">Extracting\u2026<\/div>\n<p><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"zip-progress-bar\">\n<div class=\"zip-progress-fill\" id=\"import-zip-fill\"><\/div>\n<\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"modal-foot\"><!-- [et_pb_line_break_holder] -->      <button class=\"btn btn-ghost\" onclick=\"closeModal('modal-import')\">Cancel<\/button><!-- [et_pb_line_break_holder] -->      <button class=\"btn btn-accent\" id=\"btn-do-import\">Import<\/button><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] --><\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] --><!-- CUE SHEET MODAL --><!-- [et_pb_line_break_holder] --><\/p>\n<div class=\"modal-ov hidden\" id=\"modal-cue\"><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"modal\"><!-- [et_pb_line_break_holder] -->    <\/p>\n<h3>\ud83c\udfaf Cue Sheet<\/h3>\n<p><!-- [et_pb_line_break_holder] -->    <pee class=\"modal-sub\">All annotations sorted by timestamp across all tracks.<\/pee><!-- [et_pb_line_break_holder] -->    <\/p>\n<div id=\"cue-rows\"><\/div>\n<p><!-- [et_pb_line_break_holder] -->    <\/p>\n<div class=\"modal-foot\"><!-- [et_pb_line_break_holder] -->      <button class=\"btn btn-ghost\" onclick=\"closeModal('modal-cue')\">Close<\/button><!-- [et_pb_line_break_holder] -->      <button class=\"btn btn-ghost\" id=\"btn-cue-txt\">Export TXT<\/button><!-- [et_pb_line_break_holder] -->      <button class=\"btn btn-accent\" id=\"btn-cue-json\">Export JSON<\/button><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] --><\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] --><\/p>\n<div id=\"toasts\"><\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] --><script><!-- [et_pb_line_break_holder] -->\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<!-- [et_pb_line_break_holder] -->\/\/  TrackNotes v2 \u2014 Rock-solid Web Audio engine + clean UX<!-- [et_pb_line_break_holder] -->\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 App State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->const S = {<!-- [et_pb_line_break_holder] -->  tracks: [],     \/\/ {id, name, file, url, buf, dur, sr, ch, fmt, size}<!-- [et_pb_line_break_holder] -->  anns: {},       \/\/ trackId \u2192 [{id,ts,tsEnd,author,type,text,created,replies[],resolved}]<!-- [et_pb_line_break_holder] -->  activeId: null,<!-- [et_pb_line_break_holder] -->  filter: 'all',<!-- [et_pb_line_break_holder] -->  sort: 'time',<!-- [et_pb_line_break_holder] -->  selType: 'note',<!-- [et_pb_line_break_holder] -->  rangeMode: false,<!-- [et_pb_line_break_holder] -->  rangeStart: null,<!-- [et_pb_line_break_holder] -->  rangeEnd: null,<!-- [et_pb_line_break_holder] -->};<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Audio Engine \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->\/\/ ONE AudioContext, ONE GainNode \u2014 both persistent throughout the session.<!-- [et_pb_line_break_holder] -->\/\/ Volume\/mute are changed by modifying gainNode.gain \u2014 NEVER by restarting playback.<!-- [et_pb_line_break_holder] -->\/\/ Seeking: stop old source, create new one, start from new offset.<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->let ACX = null;        \/\/ AudioContext (created once on first interaction)<!-- [et_pb_line_break_holder] -->let gainNode = null;   \/\/ GainNode wired to destination (persistent)<!-- [et_pb_line_break_holder] -->let srcNode = null;    \/\/ Current BufferSourceNode (recreated on each play\/seek)<!-- [et_pb_line_break_holder] -->let isPlaying = false;<!-- [et_pb_line_break_holder] -->let playStartACT = 0;  \/\/ ACX.currentTime when current srcNode was started<!-- [et_pb_line_break_holder] -->let playOffset = 0;    \/\/ track time (s) at which playback was initiated<!-- [et_pb_line_break_holder] -->let playRate = 1;<!-- [et_pb_line_break_holder] -->let loopOn = false;<!-- [et_pb_line_break_holder] -->let mutedVol = 1;      \/\/ remember pre-mute volume<!-- [et_pb_line_break_holder] -->let isMuted = false;<!-- [et_pb_line_break_holder] -->let rafId = null;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function initACX() {<!-- [et_pb_line_break_holder] -->  if (ACX) return;<!-- [et_pb_line_break_holder] -->  ACX = new (window.AudioContext || window.webkitAudioContext)();<!-- [et_pb_line_break_holder] -->  gainNode = ACX.createGain();<!-- [et_pb_line_break_holder] -->  gainNode.gain.value = 1;<!-- [et_pb_line_break_holder] -->  gainNode.connect(ACX.destination);<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function resumeACX() {<!-- [et_pb_line_break_holder] -->  if (ACX && ACX.state === 'suspended') ACX.resume();<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function nowPos() {<!-- [et_pb_line_break_holder] -->  \/\/ Current playback position in track time<!-- [et_pb_line_break_holder] -->  if (!isPlaying || !ACX) return playOffset;<!-- [et_pb_line_break_holder] -->  return playOffset + (ACX.currentTime - playStartACT) * playRate;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function _startSource(offset) {<!-- [et_pb_line_break_holder] -->  \/\/ Internal: create + start a new BufferSourceNode from given offset.<!-- [et_pb_line_break_holder] -->  \/\/ Assumes isPlaying will be set to true by caller.<!-- [et_pb_line_break_holder] -->  const track = getActive();<!-- [et_pb_line_break_holder] -->  if (!track?.buf) return;<!-- [et_pb_line_break_holder] -->  if (srcNode) {<!-- [et_pb_line_break_holder] -->    try { srcNode.onended = null; srcNode.stop(0); } catch(e) {}<!-- [et_pb_line_break_holder] -->    srcNode = null;<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  const src = ACX.createBufferSource();<!-- [et_pb_line_break_holder] -->  src.buffer = track.buf;<!-- [et_pb_line_break_holder] -->  src.playbackRate.value = playRate;<!-- [et_pb_line_break_holder] -->  src.connect(gainNode);<!-- [et_pb_line_break_holder] -->  const clampedOffset = Math.max(0, Math.min(offset, track.dur - 0.001));<!-- [et_pb_line_break_holder] -->  src.start(0, clampedOffset);<!-- [et_pb_line_break_holder] -->  playStartACT = ACX.currentTime;<!-- [et_pb_line_break_holder] -->  playOffset = clampedOffset;<!-- [et_pb_line_break_holder] -->  srcNode = src;<!-- [et_pb_line_break_holder] -->  src.onended = () => {<!-- [et_pb_line_break_holder] -->    \/\/ Only fire if this is STILL the current source (not replaced by seek\/stop)<!-- [et_pb_line_break_holder] -->    if (src !== srcNode) return;<!-- [et_pb_line_break_holder] -->    isPlaying = false;<!-- [et_pb_line_break_holder] -->    srcNode = null;<!-- [et_pb_line_break_holder] -->    cancelAnimationFrame(rafId);<!-- [et_pb_line_break_holder] -->    if (loopOn) {<!-- [et_pb_line_break_holder] -->      playOffset = 0;<!-- [et_pb_line_break_holder] -->      _startSource(0);<!-- [et_pb_line_break_holder] -->      isPlaying = true;<!-- [et_pb_line_break_holder] -->      tickLoop();<!-- [et_pb_line_break_holder] -->    } else {<!-- [et_pb_line_break_holder] -->      playOffset = getActive()?.dur || 0;<!-- [et_pb_line_break_holder] -->      refreshPlayBtn();<!-- [et_pb_line_break_holder] -->      updateTimeUI();<!-- [et_pb_line_break_holder] -->    }<!-- [et_pb_line_break_holder] -->  };<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function play() {<!-- [et_pb_line_break_holder] -->  initACX(); resumeACX();<!-- [et_pb_line_break_holder] -->  const track = getActive();<!-- [et_pb_line_break_holder] -->  if (!track?.buf) return;<!-- [et_pb_line_break_holder] -->  if (isPlaying) return; \/\/ already playing<!-- [et_pb_line_break_holder] -->  _startSource(playOffset);<!-- [et_pb_line_break_holder] -->  isPlaying = true;<!-- [et_pb_line_break_holder] -->  refreshPlayBtn();<!-- [et_pb_line_break_holder] -->  tickLoop();<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function pause() {<!-- [et_pb_line_break_holder] -->  if (!isPlaying) return;<!-- [et_pb_line_break_holder] -->  playOffset = nowPos(); \/\/ capture position before stopping<!-- [et_pb_line_break_holder] -->  if (srcNode) {<!-- [et_pb_line_break_holder] -->    try { srcNode.onended = null; srcNode.stop(0); } catch(e) {}<!-- [et_pb_line_break_holder] -->    srcNode = null;<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  isPlaying = false;<!-- [et_pb_line_break_holder] -->  cancelAnimationFrame(rafId);<!-- [et_pb_line_break_holder] -->  refreshPlayBtn();<!-- [et_pb_line_break_holder] -->  updateTimeUI();<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function togglePlay() { isPlaying ? pause() : play(); }<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function seekTo(sec) {<!-- [et_pb_line_break_holder] -->  const track = getActive();<!-- [et_pb_line_break_holder] -->  if (!track) return;<!-- [et_pb_line_break_holder] -->  sec = Math.max(0, Math.min(sec, track.dur));<!-- [et_pb_line_break_holder] -->  const wasPlaying = isPlaying;<!-- [et_pb_line_break_holder] -->  \/\/ Stop current source cleanly<!-- [et_pb_line_break_holder] -->  if (srcNode) {<!-- [et_pb_line_break_holder] -->    try { srcNode.onended = null; srcNode.stop(0); } catch(e) {}<!-- [et_pb_line_break_holder] -->    srcNode = null;<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  isPlaying = false;<!-- [et_pb_line_break_holder] -->  playOffset = sec;<!-- [et_pb_line_break_holder] -->  if (wasPlaying) {<!-- [et_pb_line_break_holder] -->    _startSource(sec);<!-- [et_pb_line_break_holder] -->    isPlaying = true;<!-- [et_pb_line_break_holder] -->    refreshPlayBtn();<!-- [et_pb_line_break_holder] -->    tickLoop();<!-- [et_pb_line_break_holder] -->  } else {<!-- [et_pb_line_break_holder] -->    updateTimeUI();<!-- [et_pb_line_break_holder] -->    updateHeadUI();<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function setVolume(v) {<!-- [et_pb_line_break_holder] -->  if (!gainNode) { initACX(); }<!-- [et_pb_line_break_holder] -->  gainNode.gain.value = v;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function setMute(on) {<!-- [et_pb_line_break_holder] -->  initACX();<!-- [et_pb_line_break_holder] -->  isMuted = on;<!-- [et_pb_line_break_holder] -->  if (isMuted) {<!-- [et_pb_line_break_holder] -->    mutedVol = parseFloat(document.getElementById(`vol-${S.activeId}`)?.value ?? 1);<!-- [et_pb_line_break_holder] -->    gainNode.gain.value = 0;<!-- [et_pb_line_break_holder] -->    const vsl = document.getElementById(`vol-${S.activeId}`);<!-- [et_pb_line_break_holder] -->    if (vsl) vsl.value = 0;<!-- [et_pb_line_break_holder] -->  } else {<!-- [et_pb_line_break_holder] -->    gainNode.gain.value = mutedVol;<!-- [et_pb_line_break_holder] -->    const vsl = document.getElementById(`vol-${S.activeId}`);<!-- [et_pb_line_break_holder] -->    if (vsl) vsl.value = mutedVol;<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  document.getElementById(`mute-btn-${S.activeId}`)?.classList.toggle('on', isMuted);<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function refreshPlayBtn() {<!-- [et_pb_line_break_holder] -->  \/\/ Update ALL play buttons \u2014 active track gets pause icon, others get play icon<!-- [et_pb_line_break_holder] -->  S.tracks.forEach(t => {<!-- [et_pb_line_break_holder] -->    const btn = document.getElementById(`pbtn-${t.id}`);<!-- [et_pb_line_break_holder] -->    if (!btn) return;<!-- [et_pb_line_break_holder] -->    const isActiveAndPlaying = isPlaying && t.id === S.activeId;<!-- [et_pb_line_break_holder] -->    btn.innerHTML = isActiveAndPlaying<!-- [et_pb_line_break_holder] -->      ? `<svg width=\"13\" height=\"13\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><rect x=\"6\" y=\"4\" width=\"4\" height=\"16\"\/><rect x=\"14\" y=\"4\" width=\"4\" height=\"16\"\/><\/svg>`<!-- [et_pb_line_break_holder] -->      : `<svg width=\"13\" height=\"13\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><polygon points=\"5 3 19 12 5 21 5 3\"\/><\/svg>`;<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function tickLoop() {<!-- [et_pb_line_break_holder] -->  cancelAnimationFrame(rafId);<!-- [et_pb_line_break_holder] -->  function tick() {<!-- [et_pb_line_break_holder] -->    if (!isPlaying) return;<!-- [et_pb_line_break_holder] -->    updateTimeUI();<!-- [et_pb_line_break_holder] -->    updateHeadUI();<!-- [et_pb_line_break_holder] -->    rafId = requestAnimationFrame(tick);<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  tick();<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Track helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->function getActive() { return S.tracks.find(t => t.id === S.activeId) || null; }<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function activateTrack(id) {<!-- [et_pb_line_break_holder] -->  if (S.activeId === id) return;<!-- [et_pb_line_break_holder] -->  pause();<!-- [et_pb_line_break_holder] -->  playOffset = 0;<!-- [et_pb_line_break_holder] -->  playRate = 1; \/\/ reset speed on track switch<!-- [et_pb_line_break_holder] -->  isMuted = false;<!-- [et_pb_line_break_holder] -->  S.activeId = id;<!-- [et_pb_line_break_holder] -->  S.rangeStart = null; S.rangeEnd = null;<!-- [et_pb_line_break_holder] -->  renderTracks();<!-- [et_pb_line_break_holder] -->  renderAnnotations();<!-- [et_pb_line_break_holder] -->  updateRangeDisplay();<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 File loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->function handleFiles(files) {<!-- [et_pb_line_break_holder] -->  const list = Array.from(files).filter(f => \/audio\/i.test(f.type) || \/\\.(wav|flac|aif|aiff|mp3|aac|ogg|m4a|opus)$\/i.test(f.name));<!-- [et_pb_line_break_holder] -->  if (!list.length) { toast('No supported audio files found', 'err'); return; }<!-- [et_pb_line_break_holder] -->  list.forEach(loadFile);<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->async function loadFile(file) {<!-- [et_pb_line_break_holder] -->  const id = 'tr_' + Date.now() + '_' + Math.random().toString(36).slice(2,5);<!-- [et_pb_line_break_holder] -->  const url = URL.createObjectURL(file);<!-- [et_pb_line_break_holder] -->  const ext = (file.name.split('.').pop() || 'AUDIO').toUpperCase();<!-- [et_pb_line_break_holder] -->  const track = { id, name: file.name, file, url, buf: null, dur: 0, sr: 0, ch: 0, fmt: ext, size: file.size };<!-- [et_pb_line_break_holder] -->  S.tracks.push(track);<!-- [et_pb_line_break_holder] -->  S.anns[id] = [];<!-- [et_pb_line_break_holder] -->  showWS();<!-- [et_pb_line_break_holder] -->  if (!S.activeId) S.activeId = id;<!-- [et_pb_line_break_holder] -->  renderTracks();<!-- [et_pb_line_break_holder] -->  toast(`Loading \"${file.name}\"\u2026`, 'info');<!-- [et_pb_line_break_holder] -->  \/\/ NOTE: this function intentionally awaits full audio decoding before resolving,<!-- [et_pb_line_break_holder] -->  \/\/ so that callers (e.g. ZIP import) can rely on track.name being ready in S.tracks.<!-- [et_pb_line_break_holder] -->  try {<!-- [et_pb_line_break_holder] -->    initACX();<!-- [et_pb_line_break_holder] -->    const ab = await file.arrayBuffer();<!-- [et_pb_line_break_holder] -->    const buf = await ACX.decodeAudioData(ab.slice(0)); \/\/ slice(0) to avoid detached buffer issues<!-- [et_pb_line_break_holder] -->    track.buf = buf;<!-- [et_pb_line_break_holder] -->    track.dur = buf.duration;<!-- [et_pb_line_break_holder] -->    track.sr  = buf.sampleRate;<!-- [et_pb_line_break_holder] -->    track.ch  = buf.numberOfChannels;<!-- [et_pb_line_break_holder] -->    renderTracks();<!-- [et_pb_line_break_holder] -->    \/\/ updateTimeUI for the active track (renderTracks already schedules waveform draw)<!-- [et_pb_line_break_holder] -->    if (S.activeId === id) { updateTimeUI(); }<!-- [et_pb_line_break_holder] -->    toast(`\"${file.name}\" ready`, 'ok');<!-- [et_pb_line_break_holder] -->  } catch(e) {<!-- [et_pb_line_break_holder] -->    toast(`Could not decode \"${file.name}\"`, 'err');<!-- [et_pb_line_break_holder] -->    console.error(e);<!-- [et_pb_line_break_holder] -->    \/\/ Do NOT re-throw \u2014 a bad audio file should not abort a whole ZIP import<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function removeTrack(id) {<!-- [et_pb_line_break_holder] -->  if (S.activeId === id) pause();<!-- [et_pb_line_break_holder] -->  const idx = S.tracks.findIndex(t => t.id === id);<!-- [et_pb_line_break_holder] -->  if (idx < 0) return;<!-- [et_pb_line_break_holder] -->  URL.revokeObjectURL(S.tracks[idx].url);<!-- [et_pb_line_break_holder] -->  S.tracks.splice(idx, 1);<!-- [et_pb_line_break_holder] -->  delete S.anns[id];<!-- [et_pb_line_break_holder] -->  delete wfCache[id];<!-- [et_pb_line_break_holder] -->  if (S.activeId === id) {<!-- [et_pb_line_break_holder] -->    S.activeId = S.tracks.length ? S.tracks[Math.max(0, idx-1)].id : null;<!-- [et_pb_line_break_holder] -->    playOffset = 0;<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  if (!S.tracks.length) {<!-- [et_pb_line_break_holder] -->    document.getElementById('workspace').classList.remove('active');<!-- [et_pb_line_break_holder] -->    document.getElementById('drop-zone').style.display = '';<!-- [et_pb_line_break_holder] -->    return;<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  renderTracks();<!-- [et_pb_line_break_holder] -->  renderAnnotations();<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function showWS() {<!-- [et_pb_line_break_holder] -->  document.getElementById('drop-zone').style.display = 'none';<!-- [et_pb_line_break_holder] -->  document.getElementById('workspace').classList.add('active');<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Render tracks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->const SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 2];<!-- [et_pb_line_break_holder] -->const spdIdx = {}; \/\/ trackId \u2192 index<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function renderTracks() {<!-- [et_pb_line_break_holder] -->  const col = document.getElementById('tracks-col');<!-- [et_pb_line_break_holder] -->  col.innerHTML = '';<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  S.tracks.forEach((track, idx) => {<!-- [et_pb_line_break_holder] -->    const isAct = track.id === S.activeId;<!-- [et_pb_line_break_holder] -->    if (!spdIdx[track.id]) spdIdx[track.id] = 2;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    const badgesHTML = track.buf<!-- [et_pb_line_break_holder] -->      ? `<span class=\"badge\">${track.fmt}<\/span><!-- [et_pb_line_break_holder] -->         <span class=\"badge\">${(track.sr\/1000).toFixed(track.sr%1000===0?0:1)} kHz<\/span><!-- [et_pb_line_break_holder] -->         <span class=\"badge\">${track.ch===1?'Mono':'Stereo'}<\/span><!-- [et_pb_line_break_holder] -->         <span class=\"badge\">${fmtShort(track.dur)}<\/span><!-- [et_pb_line_break_holder] -->         <span class=\"badge\">${fmtSize(track.size)}<\/span>`<!-- [et_pb_line_break_holder] -->      : `<span class=\"badge\">${track.fmt}<\/span><span class=\"badge\" style=\"color:var(--text-dim)\">Loading\u2026<\/span>`;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    const shortName = track.name.length > 40 ? track.name.slice(0,38)+'\u2026' : track.name;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    const card = document.createElement('div');<!-- [et_pb_line_break_holder] -->    card.className = 'track-card' + (isAct ? ' is-active' : '');<!-- [et_pb_line_break_holder] -->    card.id = 'card-' + track.id;<!-- [et_pb_line_break_holder] -->    card.innerHTML = `<!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"track-head\" data-id=\"${track.id}\"><!-- [et_pb_line_break_holder] -->        <span class=\"track-num\">Track ${String(idx+1).padStart(2,'0')}<\/span><!-- [et_pb_line_break_holder] -->        <span class=\"track-name\">${esc(shortName)}<\/span><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"track-badges\">${badgesHTML}<\/div>\n<p><!-- [et_pb_line_break_holder] -->        <button class=\"track-rm\" data-del=\"${track.id}\" title=\"Remove track\">\u2715<\/button><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"track-player\" id=\"player-${track.id}\"><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"wf-wrap\" id=\"wf-${track.id}\"><!-- [et_pb_line_break_holder] -->          <canvas class=\"wf-canvas\" id=\"cv-${track.id}\"><\/canvas><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"wf-prog\" id=\"wfpg-${track.id}\" style=\"width:0%\"><\/div>\n<p><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"wf-head\" id=\"wfhd-${track.id}\" style=\"left:0px\"><\/div>\n<p><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"wf-hline\" id=\"wfhl-${track.id}\"><\/div>\n<p><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"wf-htime\" id=\"wfht-${track.id}\"><\/div>\n<p><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"wf-sel\" id=\"wfsl-${track.id}\"><\/div>\n<p><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"wf-markers\" id=\"wfmk-${track.id}\"><\/div>\n<p><!-- [et_pb_line_break_holder] -->        <\/div>\n<p><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"transport\"><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"tr-left\"><!-- [et_pb_line_break_holder] -->            <button class=\"ctrl-btn\" id=\"skb-${track.id}\" title=\"\u22125s\"><!-- [et_pb_line_break_holder] -->              <svg width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><polygon points=\"11 19 2 12 11 5 11 19\"\/><polygon points=\"22 19 13 12 22 5 22 19\"\/><\/svg><!-- [et_pb_line_break_holder] -->            <\/button><!-- [et_pb_line_break_holder] -->            <button class=\"play-btn\" id=\"pbtn-${track.id}\"><!-- [et_pb_line_break_holder] -->              <svg width=\"13\" height=\"13\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><polygon points=\"5 3 19 12 5 21 5 3\"\/><\/svg><!-- [et_pb_line_break_holder] -->            <\/button><!-- [et_pb_line_break_holder] -->            <button class=\"ctrl-btn\" id=\"skf-${track.id}\" title=\"+5s\"><!-- [et_pb_line_break_holder] -->              <svg width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><polygon points=\"13 19 22 12 13 5 13 19\"\/><polygon points=\"2 19 11 12 2 5 2 19\"\/><\/svg><!-- [et_pb_line_break_holder] -->            <\/button><!-- [et_pb_line_break_holder] -->          <\/div>\n<p><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"tr-mid\"><!-- [et_pb_line_break_holder] -->            <\/p>\n<div class=\"t-cur\"><span id=\"tc-${track.id}\">0:00.0<\/span><span class=\"t-sep\">\/<\/span><span class=\"t-dur\" id=\"td-${track.id}\">${fmtShort(track.dur)}<\/span><\/div>\n<p><!-- [et_pb_line_break_holder] -->            <\/p>\n<div class=\"prog-bar\" id=\"pb-${track.id}\"><!-- [et_pb_line_break_holder] -->              <\/p>\n<div class=\"prog-fill\" id=\"pf-${track.id}\" style=\"width:0%\"><\/div>\n<p><!-- [et_pb_line_break_holder] -->              <\/p>\n<div class=\"prog-thumb\" id=\"pt-${track.id}\"><\/div>\n<p><!-- [et_pb_line_break_holder] -->            <\/div>\n<p><!-- [et_pb_line_break_holder] -->          <\/div>\n<p><!-- [et_pb_line_break_holder] -->          <\/p>\n<div class=\"tr-right\"><!-- [et_pb_line_break_holder] -->            <button class=\"ctrl-btn${loopOn?' on':''}\" id=\"lp-${track.id}\" title=\"Loop\"><!-- [et_pb_line_break_holder] -->              <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><polyline points=\"17 1 21 5 17 9\"\/><path d=\"M3 11V9a4 4 0 0 1 4-4h14\"\/><polyline points=\"7 23 3 19 7 15\"\/><path d=\"M21 13v2a4 4 0 0 1-4 4H3\"\/><\/svg><!-- [et_pb_line_break_holder] -->            <\/button><!-- [et_pb_line_break_holder] -->            <\/p>\n<div class=\"vol-wrap\"><!-- [et_pb_line_break_holder] -->              <button class=\"ctrl-btn${isMuted?' on':''}\" id=\"mute-btn-${track.id}\" title=\"Mute\"><!-- [et_pb_line_break_holder] -->                <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"\/><path d=\"M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07\"\/><\/svg><!-- [et_pb_line_break_holder] -->              <\/button><!-- [et_pb_line_break_holder] -->              <input type=\"range\" class=\"vol-sl\" id=\"vol-${track.id}\" min=\"0\" max=\"1\" step=\"0.01\" value=\"${isMuted?0:mutedVol}\"><!-- [et_pb_line_break_holder] -->            <\/div>\n<p><!-- [et_pb_line_break_holder] -->            <button class=\"spd-btn\" id=\"spd-${track.id}\">${SPEEDS[spdIdx[track.id]]}\u00d7<\/button><!-- [et_pb_line_break_holder] -->          <\/div>\n<p><!-- [et_pb_line_break_holder] -->        <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->    `;<!-- [et_pb_line_break_holder] -->    col.appendChild(card);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Draw waveform<!-- [et_pb_line_break_holder] -->    if (track.buf) setTimeout(() => { renderWaveform(track.id); if (isAct) { updateTimeUI(); updateHeadUI(); } }, 0);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    bindControls(track.id);<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ \"Add more\" zone at bottom<!-- [et_pb_line_break_holder] -->  const zone = document.createElement('div');<!-- [et_pb_line_break_holder] -->  zone.className = 'add-more-zone';<!-- [et_pb_line_break_holder] -->  zone.innerHTML = `<svg width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" viewBox=\"0 0 24 24\"><path d=\"M12 5v14M5 12h14\"\/><\/svg> Add more files`;<!-- [et_pb_line_break_holder] -->  zone.addEventListener('click', () => document.getElementById('add-file-input').click());<!-- [et_pb_line_break_holder] -->  col.appendChild(zone);<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function bindControls(tid) {<!-- [et_pb_line_break_holder] -->  \/\/ Track header click \u2192 activate<!-- [et_pb_line_break_holder] -->  document.querySelector(`#card-${tid} .track-head`).addEventListener('click', (e) => {<!-- [et_pb_line_break_holder] -->    if (e.target.classList.contains('track-rm')) return;<!-- [et_pb_line_break_holder] -->    activateTrack(tid);<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ Remove button<!-- [et_pb_line_break_holder] -->  document.querySelector(`[data-del=\"${tid}\"]`).addEventListener('click', (e) => { e.stopPropagation(); removeTrack(tid); });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ Play button \u2014 activate if needed, then toggle<!-- [et_pb_line_break_holder] -->  document.getElementById(`pbtn-${tid}`).addEventListener('click', () => {<!-- [et_pb_line_break_holder] -->    if (S.activeId !== tid) { activateTrack(tid); setTimeout(play, 50); return; }<!-- [et_pb_line_break_holder] -->    togglePlay();<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ Skip back\/forward<!-- [et_pb_line_break_holder] -->  document.getElementById(`skb-${tid}`).addEventListener('click', () => { if (S.activeId !== tid) activateTrack(tid); else seekTo(nowPos() - 5); });<!-- [et_pb_line_break_holder] -->  document.getElementById(`skf-${tid}`).addEventListener('click', () => { if (S.activeId !== tid) activateTrack(tid); else { const t=getActive(); seekTo(Math.min(nowPos()+5, t?.dur||0)); } });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ Progress bar scrubbing<!-- [et_pb_line_break_holder] -->  const pb = document.getElementById(`pb-${tid}`);<!-- [et_pb_line_break_holder] -->  let pbDragging = false;<!-- [et_pb_line_break_holder] -->  const scrub = (e) => {<!-- [et_pb_line_break_holder] -->    if (S.activeId !== tid) activateTrack(tid);<!-- [et_pb_line_break_holder] -->    const track = S.tracks.find(t=>t.id===tid);<!-- [et_pb_line_break_holder] -->    if (!track?.buf) return;<!-- [et_pb_line_break_holder] -->    const r = pb.getBoundingClientRect();<!-- [et_pb_line_break_holder] -->    const pct = Math.max(0, Math.min(1, (e.clientX - r.left) \/ r.width));<!-- [et_pb_line_break_holder] -->    seekTo(pct * track.dur);<!-- [et_pb_line_break_holder] -->  };<!-- [et_pb_line_break_holder] -->  pb.addEventListener('mousedown', (e) => { pbDragging=true; pb.classList.add('drag'); scrub(e); });<!-- [et_pb_line_break_holder] -->  window.addEventListener('mousemove', (e) => { if (pbDragging) scrub(e); });<!-- [et_pb_line_break_holder] -->  window.addEventListener('mouseup', () => { if (pbDragging) { pbDragging=false; pb.classList.remove('drag'); } });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ Waveform interaction<!-- [et_pb_line_break_holder] -->  bindWaveform(tid);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ Loop<!-- [et_pb_line_break_holder] -->  document.getElementById(`lp-${tid}`).addEventListener('click', () => {<!-- [et_pb_line_break_holder] -->    loopOn = !loopOn;<!-- [et_pb_line_break_holder] -->    document.querySelectorAll('[id^=\"lp-\"]').forEach(el => el.classList.toggle('on', loopOn));<!-- [et_pb_line_break_holder] -->    toast(loopOn ? 'Loop on' : 'Loop off', 'info');<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ Volume \u2014 directly set gain, NO playback restart<!-- [et_pb_line_break_holder] -->  document.getElementById(`vol-${tid}`).addEventListener('input', function() {<!-- [et_pb_line_break_holder] -->    initACX();<!-- [et_pb_line_break_holder] -->    const v = parseFloat(this.value);<!-- [et_pb_line_break_holder] -->    gainNode.gain.value = v;<!-- [et_pb_line_break_holder] -->    isMuted = v === 0;<!-- [et_pb_line_break_holder] -->    document.getElementById(`mute-btn-${tid}`)?.classList.toggle('on', isMuted);<!-- [et_pb_line_break_holder] -->    if (!isMuted) mutedVol = v;<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ Mute toggle<!-- [et_pb_line_break_holder] -->  document.getElementById(`mute-btn-${tid}`).addEventListener('click', () => {<!-- [et_pb_line_break_holder] -->    if (S.activeId !== tid) activateTrack(tid);<!-- [et_pb_line_break_holder] -->    setMute(!isMuted);<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  \/\/ Speed<!-- [et_pb_line_break_holder] -->  document.getElementById(`spd-${tid}`).addEventListener('click', () => {<!-- [et_pb_line_break_holder] -->    spdIdx[tid] = (spdIdx[tid] + 1) % SPEEDS.length;<!-- [et_pb_line_break_holder] -->    const r = SPEEDS[spdIdx[tid]];<!-- [et_pb_line_break_holder] -->    document.getElementById(`spd-${tid}`).textContent = r + '\u00d7';<!-- [et_pb_line_break_holder] -->    if (S.activeId === tid) {<!-- [et_pb_line_break_holder] -->      playRate = r;<!-- [et_pb_line_break_holder] -->      if (srcNode) srcNode.playbackRate.value = r;<!-- [et_pb_line_break_holder] -->    }<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Waveform rendering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->const wfCache = {}; \/\/ trackId \u2192 peaks[]<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function renderWaveform(tid) {<!-- [et_pb_line_break_holder] -->  const track = S.tracks.find(t=>t.id===tid);<!-- [et_pb_line_break_holder] -->  if (!track?.buf) return;<!-- [et_pb_line_break_holder] -->  const cv = document.getElementById(`cv-${tid}`);<!-- [et_pb_line_break_holder] -->  const wrap = document.getElementById(`wf-${tid}`);<!-- [et_pb_line_break_holder] -->  if (!cv || !wrap) return;<!-- [et_pb_line_break_holder] -->  const W = wrap.clientWidth || 640;<!-- [et_pb_line_break_holder] -->  const H = wrap.clientHeight || 72;<!-- [et_pb_line_break_holder] -->  const dpr = window.devicePixelRatio || 1;<!-- [et_pb_line_break_holder] -->  cv.width = W * dpr; cv.height = H * dpr;<!-- [et_pb_line_break_holder] -->  cv.style.width = W+'px'; cv.style.height = H+'px';<!-- [et_pb_line_break_holder] -->  const ctx = cv.getContext('2d');<!-- [et_pb_line_break_holder] -->  ctx.scale(dpr, dpr);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  if (!wfCache[tid]) {<!-- [et_pb_line_break_holder] -->    const data = track.buf.getChannelData(0);<!-- [et_pb_line_break_holder] -->    const bins = Math.floor(W * 2);<!-- [et_pb_line_break_holder] -->    const step = Math.max(1, Math.floor(data.length \/ bins));<!-- [et_pb_line_break_holder] -->    const peaks = [];<!-- [et_pb_line_break_holder] -->    for (let i=0; i<bins; i++) {<!-- [et_pb_line_break_holder] -->      let mx=0;<!-- [et_pb_line_break_holder] -->      for (let j=0; j<step; j++) { const v=Math.abs(data[i*step+j]||0); if(v>mx) mx=v; }<!-- [et_pb_line_break_holder] -->      peaks.push(mx);<!-- [et_pb_line_break_holder] -->    }<!-- [et_pb_line_break_holder] -->    wfCache[tid] = peaks;<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  const peaks = wfCache[tid];<!-- [et_pb_line_break_holder] -->  ctx.clearRect(0, 0, W, H);<!-- [et_pb_line_break_holder] -->  const mid = H\/2, bw = W\/peaks.length;<!-- [et_pb_line_break_holder] -->  \/\/ grid<!-- [et_pb_line_break_holder] -->  ctx.strokeStyle = 'rgba(255,255,255,0.03)'; ctx.lineWidth=1;<!-- [et_pb_line_break_holder] -->  for(let i=1;i<4;i++){const y=i*H\/4;ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();}<!-- [et_pb_line_break_holder] -->  \/\/ bars<!-- [et_pb_line_break_holder] -->  const g = ctx.createLinearGradient(0,0,W,0);<!-- [et_pb_line_break_holder] -->  g.addColorStop(0,'rgba(243,190,41,0.78)'); g.addColorStop(1,'rgba(205,59,167,0.78)');<!-- [et_pb_line_break_holder] -->  ctx.fillStyle = g;<!-- [et_pb_line_break_holder] -->  peaks.forEach((v,i) => { const h=v*(H*0.85); ctx.fillRect(i*bw, mid-h, Math.max(bw-0.8,0.5), h*2); });<!-- [et_pb_line_break_holder] -->  \/\/ center line<!-- [et_pb_line_break_holder] -->  ctx.strokeStyle='rgba(255,255,255,0.04)';ctx.beginPath();ctx.moveTo(0,mid);ctx.lineTo(W,mid);ctx.stroke();<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  renderMarkers(tid);<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function bindWaveform(tid) {<!-- [et_pb_line_break_holder] -->  const wrap = document.getElementById(`wf-${tid}`);<!-- [et_pb_line_break_holder] -->  if (!wrap) return;<!-- [et_pb_line_break_holder] -->  const hl = document.getElementById(`wfhl-${tid}`);<!-- [et_pb_line_break_holder] -->  const ht = document.getElementById(`wfht-${tid}`);<!-- [et_pb_line_break_holder] -->  let wfDragging = false;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  const xToT = (x) => {<!-- [et_pb_line_break_holder] -->    const track = S.tracks.find(t=>t.id===tid);<!-- [et_pb_line_break_holder] -->    if (!track?.buf) return 0;<!-- [et_pb_line_break_holder] -->    return Math.max(0, Math.min(1, x\/wrap.clientWidth)) * track.dur;<!-- [et_pb_line_break_holder] -->  };<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  wrap.addEventListener('mouseenter', () => { if(hl) hl.style.display='block'; if(ht) ht.style.display='block'; });<!-- [et_pb_line_break_holder] -->  wrap.addEventListener('mouseleave', () => { if(hl) hl.style.display='none'; if(ht) ht.style.display='none'; wfDragging=false; });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  wrap.addEventListener('mousemove', (e) => {<!-- [et_pb_line_break_holder] -->    const track = S.tracks.find(t=>t.id===tid);<!-- [et_pb_line_break_holder] -->    if (!track?.buf) return;<!-- [et_pb_line_break_holder] -->    const r = wrap.getBoundingClientRect();<!-- [et_pb_line_break_holder] -->    const x = e.clientX - r.left;<!-- [et_pb_line_break_holder] -->    const t = xToT(x);<!-- [et_pb_line_break_holder] -->    if (hl) hl.style.left = x+'px';<!-- [et_pb_line_break_holder] -->    if (ht) { ht.style.left = x+'px'; ht.textContent = fmtTs(t); }<!-- [et_pb_line_break_holder] -->    if (wfDragging) {<!-- [et_pb_line_break_holder] -->      if (S.rangeMode) {<!-- [et_pb_line_break_holder] -->        S.rangeEnd = t;<!-- [et_pb_line_break_holder] -->        updateSelVisual(tid);<!-- [et_pb_line_break_holder] -->        updateRangeDisplay();<!-- [et_pb_line_break_holder] -->      } else {<!-- [et_pb_line_break_holder] -->        if (S.activeId !== tid) activateTrack(tid);<!-- [et_pb_line_break_holder] -->        seekTo(t);<!-- [et_pb_line_break_holder] -->      }<!-- [et_pb_line_break_holder] -->    }<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  wrap.addEventListener('mousedown', (e) => {<!-- [et_pb_line_break_holder] -->    e.preventDefault();<!-- [et_pb_line_break_holder] -->    const track = S.tracks.find(t=>t.id===tid);<!-- [et_pb_line_break_holder] -->    if (!track?.buf) return;<!-- [et_pb_line_break_holder] -->    wfDragging = true;<!-- [et_pb_line_break_holder] -->    const r = wrap.getBoundingClientRect();<!-- [et_pb_line_break_holder] -->    const t = xToT(e.clientX - r.left);<!-- [et_pb_line_break_holder] -->    if (S.rangeMode) {<!-- [et_pb_line_break_holder] -->      S.rangeStart = t; S.rangeEnd = t;<!-- [et_pb_line_break_holder] -->      updateSelVisual(tid);<!-- [et_pb_line_break_holder] -->    } else {<!-- [et_pb_line_break_holder] -->      if (S.activeId !== tid) activateTrack(tid);<!-- [et_pb_line_break_holder] -->      seekTo(t);<!-- [et_pb_line_break_holder] -->    }<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] -->  wrap.addEventListener('mouseup', () => { wfDragging=false; });<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function updateSelVisual(tid) {<!-- [et_pb_line_break_holder] -->  const sel = document.getElementById(`wfsl-${tid}`);<!-- [et_pb_line_break_holder] -->  const wrap = document.getElementById(`wf-${tid}`);<!-- [et_pb_line_break_holder] -->  const track = S.tracks.find(t=>t.id===tid);<!-- [et_pb_line_break_holder] -->  if (!sel||!wrap||!track?.buf||S.rangeStart===null||S.rangeEnd===null) { if(sel) sel.style.display='none'; return; }<!-- [et_pb_line_break_holder] -->  const W = wrap.clientWidth;<!-- [et_pb_line_break_holder] -->  const a = Math.min(S.rangeStart, S.rangeEnd);<!-- [et_pb_line_break_holder] -->  const b = Math.max(S.rangeStart, S.rangeEnd);<!-- [et_pb_line_break_holder] -->  sel.style.display = 'block';<!-- [et_pb_line_break_holder] -->  sel.style.left = (a\/track.dur*W)+'px';<!-- [et_pb_line_break_holder] -->  sel.style.width = ((b-a)\/track.dur*W)+'px';<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function renderMarkers(tid) {<!-- [et_pb_line_break_holder] -->  const wrap = document.getElementById(`wf-${tid}`);<!-- [et_pb_line_break_holder] -->  const mk = document.getElementById(`wfmk-${tid}`);<!-- [et_pb_line_break_holder] -->  if (!wrap||!mk) return;<!-- [et_pb_line_break_holder] -->  mk.innerHTML='';<!-- [et_pb_line_break_holder] -->  const track = S.tracks.find(t=>t.id===tid);<!-- [et_pb_line_break_holder] -->  if (!track?.buf) return;<!-- [et_pb_line_break_holder] -->  const W = wrap.clientWidth;<!-- [et_pb_line_break_holder] -->  (S.anns[tid]||[]).filter(a=>!a.resolved).forEach(a => {<!-- [et_pb_line_break_holder] -->    const el=document.createElement('div');<!-- [et_pb_line_break_holder] -->    el.className=`wf-marker t-${a.type}`;<!-- [et_pb_line_break_holder] -->    el.style.left=(a.ts\/track.dur*W)+'px';<!-- [et_pb_line_break_holder] -->    el.title=`${fmtTs(a.ts)} \u2014 ${a.author}: ${a.text.slice(0,50)}`;<!-- [et_pb_line_break_holder] -->    el.addEventListener('click',()=>{ if(S.activeId!==tid) activateTrack(tid); seekTo(a.ts); });<!-- [et_pb_line_break_holder] -->    \/\/ Hover \u2192 highlight matching sidebar card<!-- [et_pb_line_break_holder] -->    el.addEventListener('mouseenter',()=>{<!-- [et_pb_line_break_holder] -->      const card=document.querySelector(`.cmt[data-id=\"${a.id}\"]`);<!-- [et_pb_line_break_holder] -->      if(card){card.classList.add('wf-highlight');card.scrollIntoView({block:'nearest',behavior:'smooth'});}<!-- [et_pb_line_break_holder] -->    });<!-- [et_pb_line_break_holder] -->    el.addEventListener('mouseleave',()=>{<!-- [et_pb_line_break_holder] -->      document.querySelector(`.cmt[data-id=\"${a.id}\"]`)?.classList.remove('wf-highlight');<!-- [et_pb_line_break_holder] -->    });<!-- [et_pb_line_break_holder] -->    mk.appendChild(el);<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Time\/head UI \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->function updateTimeUI() {<!-- [et_pb_line_break_holder] -->  const tid = S.activeId; if (!tid) return;<!-- [et_pb_line_break_holder] -->  const track = getActive(); if (!track?.buf) return;<!-- [et_pb_line_break_holder] -->  const pos = nowPos();<!-- [et_pb_line_break_holder] -->  const pct = track.dur>0 ? pos\/track.dur : 0;<!-- [et_pb_line_break_holder] -->  const cur = document.getElementById(`tc-${tid}`);<!-- [et_pb_line_break_holder] -->  if (cur) cur.textContent = fmtTs(pos);<!-- [et_pb_line_break_holder] -->  const fill = document.getElementById(`pf-${tid}`);<!-- [et_pb_line_break_holder] -->  if (fill) fill.style.width = (pct*100)+'%';<!-- [et_pb_line_break_holder] -->  const thumb = document.getElementById(`pt-${tid}`);<!-- [et_pb_line_break_holder] -->  if (thumb) thumb.style.left = (pct*100)+'%';<!-- [et_pb_line_break_holder] -->  const prog = document.getElementById(`wfpg-${tid}`);<!-- [et_pb_line_break_holder] -->  if (prog) prog.style.width = (pct*100)+'%';<!-- [et_pb_line_break_holder] -->  \/\/ update timestamp chip in compose<!-- [et_pb_line_break_holder] -->  updateTsChip();<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function updateHeadUI() {<!-- [et_pb_line_break_holder] -->  const tid = S.activeId; if (!tid) return;<!-- [et_pb_line_break_holder] -->  const track = getActive(); if (!track?.buf) return;<!-- [et_pb_line_break_holder] -->  const wrap = document.getElementById(`wf-${tid}`); if (!wrap) return;<!-- [et_pb_line_break_holder] -->  const head = document.getElementById(`wfhd-${tid}`); if (!head) return;<!-- [et_pb_line_break_holder] -->  const pos = nowPos();<!-- [et_pb_line_break_holder] -->  head.style.left = (track.dur>0 ? pos\/track.dur*wrap.clientWidth : 0)+'px';<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ ts chip in compose box<!-- [et_pb_line_break_holder] -->let tsLocked = false; \/\/ true when user has focused textarea (until post)<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function updateTsChip() {<!-- [et_pb_line_break_holder] -->  const val = document.getElementById('ts-val'); if (!val) return;<!-- [et_pb_line_break_holder] -->  val.textContent = fmtTs(nowPos());<!-- [et_pb_line_break_holder] -->  const lockIcon = document.getElementById('ts-lock-icon');<!-- [et_pb_line_break_holder] -->  if (lockIcon) lockIcon.style.display = tsLocked ? 'inline' : 'none';<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function updateRangeDisplay() {<!-- [et_pb_line_break_holder] -->  const disp = document.getElementById('range-display');<!-- [et_pb_line_break_holder] -->  if (!disp) return;<!-- [et_pb_line_break_holder] -->  if (S.rangeMode && S.rangeStart!==null && S.rangeEnd!==null) {<!-- [et_pb_line_break_holder] -->    const a=Math.min(S.rangeStart,S.rangeEnd), b=Math.max(S.rangeStart,S.rangeEnd);<!-- [et_pb_line_break_holder] -->    disp.textContent = fmtTs(a)+' \u2192 '+fmtTs(b);<!-- [et_pb_line_break_holder] -->    disp.style.display='inline';<!-- [et_pb_line_break_holder] -->  } else {<!-- [et_pb_line_break_holder] -->    disp.style.display='none';<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  \/\/ update all selection visuals<!-- [et_pb_line_break_holder] -->  S.tracks.forEach(t => { if (S.activeId===t.id) updateSelVisual(t.id); });<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Annotations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->\/\/ Stable anonymous ID from localStorage<!-- [et_pb_line_break_holder] -->function getAnonId() {<!-- [et_pb_line_break_holder] -->  let id = null;<!-- [et_pb_line_break_holder] -->  try { id = localStorage.getItem('tn_uid'); } catch(e){}<!-- [et_pb_line_break_holder] -->  if (!id) {<!-- [et_pb_line_break_holder] -->    const chars='ABCDEFGHJKLMNPQRSTUVWXYZ23456789';<!-- [et_pb_line_break_holder] -->    id = Array.from({length:6},()=>chars[Math.floor(Math.random()*chars.length)]).join('');<!-- [et_pb_line_break_holder] -->    try { localStorage.setItem('tn_uid', id); } catch(e){}<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  return id;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] -->const ANON = getAnonId();<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function initAuthor() {<!-- [et_pb_line_break_holder] -->  const inp = document.getElementById('ann-author');<!-- [et_pb_line_break_holder] -->  if (inp && !inp.value) inp.value = ANON;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ When textarea is focused, lock the timestamp<!-- [et_pb_line_break_holder] -->document.getElementById('ann-text').addEventListener('focus', () => {<!-- [et_pb_line_break_holder] -->  tsLocked = true;<!-- [et_pb_line_break_holder] -->  \/\/ Store the position at focus time so it doesn't drift while typing<!-- [et_pb_line_break_holder] -->  document.getElementById('ts-val').textContent = fmtTs(nowPos());<!-- [et_pb_line_break_holder] -->  document.getElementById('ts-lock-icon').style.display = 'inline';<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->document.getElementById('ann-text').addEventListener('keydown', (e) => {<!-- [et_pb_line_break_holder] -->  if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); postAnnotation(); }<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] -->document.getElementById('btn-post').addEventListener('click', postAnnotation);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function postAnnotation() {<!-- [et_pb_line_break_holder] -->  const text = document.getElementById('ann-text').value.trim();<!-- [et_pb_line_break_holder] -->  if (!text) { document.getElementById('ann-text').focus(); return; }<!-- [et_pb_line_break_holder] -->  const track = getActive();<!-- [et_pb_line_break_holder] -->  if (!track) { toast('Select a track first', 'err'); return; }<!-- [et_pb_line_break_holder] -->  const author = document.getElementById('ann-author').value.trim() || ANON;<!-- [et_pb_line_break_holder] -->  const type = S.selType;<!-- [et_pb_line_break_holder] -->  \/\/ Timestamp: what was shown in chip at focus time (already frozen)<!-- [et_pb_line_break_holder] -->  const chipText = document.getElementById('ts-val').textContent;<!-- [et_pb_line_break_holder] -->  const ts = parseTs(chipText);<!-- [et_pb_line_break_holder] -->  let tsEnd = null;<!-- [et_pb_line_break_holder] -->  if (S.rangeMode && S.rangeStart!==null && S.rangeEnd!==null) {<!-- [et_pb_line_break_holder] -->    tsEnd = Math.max(S.rangeStart, S.rangeEnd);<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  const ann = {<!-- [et_pb_line_break_holder] -->    id:'a_'+Date.now()+'_'+Math.random().toString(36).slice(2,4),<!-- [et_pb_line_break_holder] -->    ts, tsEnd, author, type, text,<!-- [et_pb_line_break_holder] -->    created: new Date().toISOString(),<!-- [et_pb_line_break_holder] -->    replies: [], resolved: false<!-- [et_pb_line_break_holder] -->  };<!-- [et_pb_line_break_holder] -->  S.anns[track.id].push(ann);<!-- [et_pb_line_break_holder] -->  document.getElementById('ann-text').value = '';<!-- [et_pb_line_break_holder] -->  tsLocked = false;<!-- [et_pb_line_break_holder] -->  document.getElementById('ts-lock-icon').style.display='none';<!-- [et_pb_line_break_holder] -->  S.rangeStart=null; S.rangeEnd=null;<!-- [et_pb_line_break_holder] -->  S.rangeMode=false;<!-- [et_pb_line_break_holder] -->  document.getElementById('range-btn').classList.remove('on');<!-- [et_pb_line_break_holder] -->  updateRangeDisplay();<!-- [et_pb_line_break_holder] -->  renderAnnotations();<!-- [et_pb_line_break_holder] -->  renderMarkers(track.id);<!-- [et_pb_line_break_holder] -->  setTimeout(()=>{ const s=document.getElementById('comments-scroll'); s.scrollTop=s.scrollHeight; }, 60);<!-- [et_pb_line_break_holder] -->  toast('Annotation posted', 'ok');<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function parseTs(str) {<!-- [et_pb_line_break_holder] -->  \/\/ Parse \"M:SS.d\" \u2192 seconds<!-- [et_pb_line_break_holder] -->  try {<!-- [et_pb_line_break_holder] -->    const [m, rest] = str.split(':');<!-- [et_pb_line_break_holder] -->    return parseInt(m)*60 + parseFloat(rest);<!-- [et_pb_line_break_holder] -->  } catch(e) { return nowPos(); }<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->const TYPE_LABEL = { note:'\ud83d\udcdd Note', issue:'\ud83d\udd34 Issue', cue:'\ud83c\udfaf Cue', ok:'\u2705 OK' };<!-- [et_pb_line_break_holder] -->const TYPE_CLASS = { note:'ct-note', issue:'ct-issue', cue:'ct-cue', ok:'ct-ok' };<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function renderAnnotations() {<!-- [et_pb_line_break_holder] -->  const scroll = document.getElementById('comments-scroll');<!-- [et_pb_line_break_holder] -->  const empty = document.getElementById('empty-ann');<!-- [et_pb_line_break_holder] -->  const track = getActive();<!-- [et_pb_line_break_holder] -->  const all = track ? (S.anns[track.id]||[]) : [];<!-- [et_pb_line_break_holder] -->  let shown = [...all];<!-- [et_pb_line_break_holder] -->  if (S.filter!=='all') shown=shown.filter(a=>a.type===S.filter);<!-- [et_pb_line_break_holder] -->  switch(S.sort) {<!-- [et_pb_line_break_holder] -->    case 'time': shown.sort((a,b)=>a.ts-b.ts); break;<!-- [et_pb_line_break_holder] -->    case 'newest': shown.sort((a,b)=>new Date(b.created)-new Date(a.created)); break;<!-- [et_pb_line_break_holder] -->    case 'type': shown.sort((a,b)=>a.type.localeCompare(b.type)); break;<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  document.getElementById('ann-ct').textContent = all.length;<!-- [et_pb_line_break_holder] -->  scroll.querySelectorAll('.cmt').forEach(el=>el.remove());<!-- [et_pb_line_break_holder] -->  empty.style.display = shown.length ? 'none' : '';<!-- [et_pb_line_break_holder] -->  if (!shown.length) return;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  shown.forEach(a => {<!-- [et_pb_line_break_holder] -->    const card = document.createElement('div');<!-- [et_pb_line_break_holder] -->    card.className = 'cmt'+(a.resolved?' resolved':'');<!-- [et_pb_line_break_holder] -->    card.dataset.id=a.id;<!-- [et_pb_line_break_holder] -->    const init=(a.author.split(' ').map(w=>w[0]?.toUpperCase()||'').join('').slice(0,2)||'?');<!-- [et_pb_line_break_holder] -->    const d=new Date(a.created);<!-- [et_pb_line_break_holder] -->    const ds=d.toLocaleDateString([],{month:'short',day:'numeric'})+' '+d.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'});<!-- [et_pb_line_break_holder] -->    const rangeTag=a.tsEnd!==null?`<span class=\"range-tag\" title=\"Click to seek\">\u25c0 ${fmtTs(a.ts)} \u2192 ${fmtTs(a.tsEnd)} \u25b6<\/span>`:'';<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    card.innerHTML=`<!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"cmt-top\"><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"cmt-av\">${esc(init)}<\/div>\n<p><!-- [et_pb_line_break_holder] -->        <span class=\"cmt-author\">${esc(a.author)}<\/span><!-- [et_pb_line_break_holder] -->        <span class=\"cmt-ts-btn\"><!-- [et_pb_line_break_holder] -->          <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"\/><polyline points=\"12 6 12 12 16 14\"\/><\/svg><!-- [et_pb_line_break_holder] -->          ${fmtTs(a.ts)}<!-- [et_pb_line_break_holder] -->        <\/span><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"cmt-body\"><span class=\"cmt-type ${TYPE_CLASS[a.type]||'ct-note'}\">${TYPE_LABEL[a.type]||'Note'}<\/span>${esc(a.text)}${rangeTag}<\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"replies\" style=\"${a.replies.length?'':'display:none'}\">${renderRepliesHTML(a)}<\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"reply-row\" style=\"display:none\"><input class=\"reply-inp\" placeholder=\"Write a reply\u2026\" maxlength=\"400\"><button class=\"reply-post\">Send<\/button><\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/p>\n<div class=\"cmt-foot\"><!-- [et_pb_line_break_holder] -->        <span class=\"cmt-date\">${ds}<\/span><!-- [et_pb_line_break_holder] -->        <\/p>\n<div class=\"cmt-acts\"><!-- [et_pb_line_break_holder] -->          <button class=\"ca res\">${a.resolved?'\u21ba Unresolve':'\u2713 Resolve'}<\/button><!-- [et_pb_line_break_holder] -->          <button class=\"ca reply-toggle\">\ud83d\udcac Reply<\/button><!-- [et_pb_line_break_holder] -->          <button class=\"ca del\">\ud83d\uddd1<\/button><!-- [et_pb_line_break_holder] -->        <\/div>\n<p><!-- [et_pb_line_break_holder] -->      <\/div>\n<p><!-- [et_pb_line_break_holder] -->    `;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    card.querySelector('.cmt-ts-btn').addEventListener('click',()=>{ if(track) seekTo(a.ts); });<!-- [et_pb_line_break_holder] -->    card.querySelector('.range-tag')?.addEventListener('click',()=>{ if(track) seekTo(a.ts); });<!-- [et_pb_line_break_holder] -->    card.querySelector('.ca.res').addEventListener('click',()=>{<!-- [et_pb_line_break_holder] -->      a.resolved=!a.resolved; renderAnnotations(); renderMarkers(track.id);<!-- [et_pb_line_break_holder] -->    });<!-- [et_pb_line_break_holder] -->    card.querySelector('.ca.del').addEventListener('click',()=>{<!-- [et_pb_line_break_holder] -->      S.anns[track.id]=S.anns[track.id].filter(x=>x.id!==a.id);<!-- [et_pb_line_break_holder] -->      renderAnnotations(); renderMarkers(track.id);<!-- [et_pb_line_break_holder] -->    });<!-- [et_pb_line_break_holder] -->    const replyRow=card.querySelector('.reply-row');<!-- [et_pb_line_break_holder] -->    card.querySelector('.ca.reply-toggle').addEventListener('click',()=>{<!-- [et_pb_line_break_holder] -->      replyRow.style.display=replyRow.style.display==='none'?'flex':'none';<!-- [et_pb_line_break_holder] -->      if(replyRow.style.display!=='none') replyRow.querySelector('.reply-inp').focus();<!-- [et_pb_line_break_holder] -->    });<!-- [et_pb_line_break_holder] -->    const sendReply=()=>{<!-- [et_pb_line_break_holder] -->      const inp=replyRow.querySelector('.reply-inp');<!-- [et_pb_line_break_holder] -->      const txt=inp.value.trim(); if(!txt) return;<!-- [et_pb_line_break_holder] -->      const auth=document.getElementById('ann-author').value.trim()||ANON;<!-- [et_pb_line_break_holder] -->      a.replies.push({id:'r_'+Date.now(),text:txt,author:auth,created:new Date().toISOString()});<!-- [et_pb_line_break_holder] -->      inp.value=''; replyRow.style.display='none';<!-- [et_pb_line_break_holder] -->      renderAnnotations(); toast('Reply posted','ok');<!-- [et_pb_line_break_holder] -->    };<!-- [et_pb_line_break_holder] -->    replyRow.querySelector('.reply-post').addEventListener('click',sendReply);<!-- [et_pb_line_break_holder] -->    replyRow.querySelector('.reply-inp').addEventListener('keydown',e=>{ if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendReply();} });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    scroll.appendChild(card);<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function renderRepliesHTML(a) {<!-- [et_pb_line_break_holder] -->  return a.replies.map(r=>{<!-- [et_pb_line_break_holder] -->    const init=(r.author.split(' ').map(w=>w[0]?.toUpperCase()||'').join('').slice(0,2)||'?');<!-- [et_pb_line_break_holder] -->    const ts=new Date(r.created).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'});<!-- [et_pb_line_break_holder] -->    return`<\/p>\n<div class=\"reply\">\n<div class=\"reply-top\">\n<div class=\"cmt-av\" style=\"width:19px;height:19px;font-size:8px\">${esc(init)}<\/div>\n<p><span class=\"reply-author\">${esc(r.author)}<\/span><span class=\"reply-date\">${ts}<\/span><\/div>\n<div class=\"reply-body\">${esc(r.text)}<\/div>\n<\/div>\n<p>`;<!-- [et_pb_line_break_holder] -->  }).join('');<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ Type pills<!-- [et_pb_line_break_holder] -->document.querySelectorAll('.type-pill').forEach(p=>{<!-- [et_pb_line_break_holder] -->  p.addEventListener('click',()=>{<!-- [et_pb_line_break_holder] -->    document.querySelectorAll('.type-pill').forEach(x=>x.classList.remove('on'));<!-- [et_pb_line_break_holder] -->    p.classList.add('on'); S.selType=p.dataset.t;<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ Range toggle<!-- [et_pb_line_break_holder] -->document.getElementById('range-btn').addEventListener('click',function(){<!-- [et_pb_line_break_holder] -->  S.rangeMode=!S.rangeMode;<!-- [et_pb_line_break_holder] -->  this.classList.toggle('on',S.rangeMode);<!-- [et_pb_line_break_holder] -->  if(!S.rangeMode){S.rangeStart=null;S.rangeEnd=null;updateRangeDisplay();}<!-- [et_pb_line_break_holder] -->  toast(S.rangeMode?'Drag on waveform to set a range':'Range mode off','info');<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ Filters<!-- [et_pb_line_break_holder] -->document.querySelectorAll('.fpill').forEach(p=>{<!-- [et_pb_line_break_holder] -->  p.addEventListener('click',()=>{ document.querySelectorAll('.fpill').forEach(x=>x.classList.remove('on')); p.classList.add('on'); S.filter=p.dataset.f; renderAnnotations(); });<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] -->document.getElementById('ann-sort').addEventListener('change',function(){S.sort=this.value;renderAnnotations();});<!-- [et_pb_line_break_holder] -->document.getElementById('btn-clear-res').addEventListener('click',()=>{<!-- [et_pb_line_break_holder] -->  const tid=S.activeId; if(!tid) return;<!-- [et_pb_line_break_holder] -->  S.anns[tid]=S.anns[tid].filter(a=>!a.resolved);<!-- [et_pb_line_break_holder] -->  renderAnnotations(); renderMarkers(tid); toast('Resolved cleared','ok');<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Export \/ Import \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->function buildSession() {<!-- [et_pb_line_break_holder] -->  return {<!-- [et_pb_line_break_holder] -->    version:'2.0',app:'TrackNotes',exported:new Date().toISOString(),<!-- [et_pb_line_break_holder] -->    tracks:S.tracks.map(t=>({<!-- [et_pb_line_break_holder] -->      name:t.name,format:t.fmt,duration:t.dur,sampleRate:t.sr,channels:t.ch,<!-- [et_pb_line_break_holder] -->      annotations:(S.anns[t.id]||[]).map(a=>({...a,replies:[...a.replies]}))<!-- [et_pb_line_break_holder] -->    }))<!-- [et_pb_line_break_holder] -->  };<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ Export modal open<!-- [et_pb_line_break_holder] -->let exportMode = 'json';<!-- [et_pb_line_break_holder] -->function selectExportOpt(mode) {<!-- [et_pb_line_break_holder] -->  exportMode = mode;<!-- [et_pb_line_break_holder] -->  document.getElementById('exp-opt-json').classList.toggle('selected', mode==='json');<!-- [et_pb_line_break_holder] -->  document.getElementById('exp-opt-zip').classList.toggle('selected', mode==='zip');<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->document.getElementById('btn-export').addEventListener('click', () => {<!-- [et_pb_line_break_holder] -->  const tot = Object.values(S.anns).reduce((s,a)=>s+a.length,0);<!-- [et_pb_line_break_holder] -->  const audioSz = S.tracks.reduce((s,t)=>s+(t.size||0),0);<!-- [et_pb_line_break_holder] -->  document.getElementById('exp-t').textContent = S.tracks.length;<!-- [et_pb_line_break_holder] -->  document.getElementById('exp-a').textContent = tot;<!-- [et_pb_line_break_holder] -->  document.getElementById('exp-sz').textContent = fmtSize(audioSz);<!-- [et_pb_line_break_holder] -->  document.getElementById('zip-progress').style.display = 'none';<!-- [et_pb_line_break_holder] -->  selectExportOpt('json');<!-- [et_pb_line_break_holder] -->  openModal('modal-export');<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->document.getElementById('btn-do-export').addEventListener('click', async () => {<!-- [et_pb_line_break_holder] -->  if (exportMode === 'json') {<!-- [et_pb_line_break_holder] -->    const b = new Blob([JSON.stringify(buildSession(),null,2)],{type:'application\/json'});<!-- [et_pb_line_break_holder] -->    const u = URL.createObjectURL(b);<!-- [et_pb_line_break_holder] -->    const a = document.createElement('a'); a.href=u; a.download='tracknotes-'+Date.now()+'.json'; a.click();<!-- [et_pb_line_break_holder] -->    URL.revokeObjectURL(u);<!-- [et_pb_line_break_holder] -->    closeModal('modal-export');<!-- [et_pb_line_break_holder] -->    toast('Session exported as JSON','ok');<!-- [et_pb_line_break_holder] -->  } else {<!-- [et_pb_line_break_holder] -->    await exportAsZip();<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 ZIP Export (pure browser, no libraries) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->\/\/ We build a ZIP manually using the ZIP local file header format.<!-- [et_pb_line_break_holder] -->\/\/ This supports files up to 4GB and works in all modern browsers.<!-- [et_pb_line_break_holder] -->async function exportAsZip() {<!-- [et_pb_line_break_holder] -->  const prog = document.getElementById('zip-progress');<!-- [et_pb_line_break_holder] -->  const status = document.getElementById('zip-status');<!-- [et_pb_line_break_holder] -->  const fill = document.getElementById('zip-fill');<!-- [et_pb_line_break_holder] -->  prog.style.display = 'block';<!-- [et_pb_line_break_holder] -->  document.getElementById('btn-do-export').disabled = true;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  try {<!-- [et_pb_line_break_holder] -->    const parts = []; \/\/ {name, data: Uint8Array}<!-- [et_pb_line_break_holder] -->    const total = S.tracks.length + 1;<!-- [et_pb_line_break_holder] -->    let done = 0;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Add each audio file<!-- [et_pb_line_break_holder] -->    for (const track of S.tracks) {<!-- [et_pb_line_break_holder] -->      status.textContent = `Reading \"${track.name}\"\u2026`;<!-- [et_pb_line_break_holder] -->      fill.style.width = (done\/total*80)+'%';<!-- [et_pb_line_break_holder] -->      const ab = await track.file.arrayBuffer();<!-- [et_pb_line_break_holder] -->      parts.push({ name: track.name, data: new Uint8Array(ab) });<!-- [et_pb_line_break_holder] -->      done++;<!-- [et_pb_line_break_holder] -->    }<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Add session JSON<!-- [et_pb_line_break_holder] -->    status.textContent = 'Writing annotations\u2026';<!-- [et_pb_line_break_holder] -->    fill.style.width = '85%';<!-- [et_pb_line_break_holder] -->    const jsonStr = JSON.stringify(buildSession(), null, 2);<!-- [et_pb_line_break_holder] -->    const jsonData = new TextEncoder().encode(jsonStr);<!-- [et_pb_line_break_holder] -->    parts.push({ name: 'tracknotes-session.json', data: jsonData });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Build ZIP binary<!-- [et_pb_line_break_holder] -->    status.textContent = 'Building ZIP\u2026';<!-- [et_pb_line_break_holder] -->    fill.style.width = '92%';<!-- [et_pb_line_break_holder] -->    const zipBytes = buildZip(parts);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    status.textContent = 'Downloading\u2026';<!-- [et_pb_line_break_holder] -->    fill.style.width = '100%';<!-- [et_pb_line_break_holder] -->    const blob = new Blob([zipBytes], {type:'application\/zip'});<!-- [et_pb_line_break_holder] -->    const url = URL.createObjectURL(blob);<!-- [et_pb_line_break_holder] -->    const a = document.createElement('a');<!-- [et_pb_line_break_holder] -->    a.href = url; a.download = 'tracknotes-project-'+Date.now()+'.zip'; a.click();<!-- [et_pb_line_break_holder] -->    URL.revokeObjectURL(url);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    closeModal('modal-export');<!-- [et_pb_line_break_holder] -->    toast('Full project ZIP exported \u2713','ok');<!-- [et_pb_line_break_holder] -->  } catch(e) {<!-- [et_pb_line_break_holder] -->    toast('ZIP export failed: '+e.message,'err');<!-- [et_pb_line_break_holder] -->    console.error(e);<!-- [et_pb_line_break_holder] -->  } finally {<!-- [et_pb_line_break_holder] -->    document.getElementById('btn-do-export').disabled = false;<!-- [et_pb_line_break_holder] -->    prog.style.display = 'none';<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function buildZip(files) {<!-- [et_pb_line_break_holder] -->  \/\/ Minimal ZIP builder \u2014 STORED (no compression, so we don't need zlib)<!-- [et_pb_line_break_holder] -->  const enc = new TextEncoder();<!-- [et_pb_line_break_holder] -->  const localHeaders = [];<!-- [et_pb_line_break_holder] -->  const centralDir = [];<!-- [et_pb_line_break_holder] -->  let offset = 0;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  for (const f of files) {<!-- [et_pb_line_break_holder] -->    const nameBytes = enc.encode(f.name);<!-- [et_pb_line_break_holder] -->    const crc = crc32(f.data);<!-- [et_pb_line_break_holder] -->    const sz = f.data.length;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Local file header<!-- [et_pb_line_break_holder] -->    const lh = new Uint8Array(30 + nameBytes.length);<!-- [et_pb_line_break_holder] -->    const lhv = new DataView(lh.buffer);<!-- [et_pb_line_break_holder] -->    lhv.setUint32(0, 0x04034b50, true); \/\/ sig<!-- [et_pb_line_break_holder] -->    lhv.setUint16(4, 20, true);          \/\/ version needed<!-- [et_pb_line_break_holder] -->    lhv.setUint16(6, 0, true);           \/\/ flags<!-- [et_pb_line_break_holder] -->    lhv.setUint16(8, 0, true);           \/\/ compression: STORED<!-- [et_pb_line_break_holder] -->    lhv.setUint16(10, 0, true);          \/\/ mod time<!-- [et_pb_line_break_holder] -->    lhv.setUint16(12, 0, true);          \/\/ mod date<!-- [et_pb_line_break_holder] -->    lhv.setUint32(14, crc, true);        \/\/ crc32<!-- [et_pb_line_break_holder] -->    lhv.setUint32(18, sz, true);         \/\/ compressed size<!-- [et_pb_line_break_holder] -->    lhv.setUint32(22, sz, true);         \/\/ uncompressed size<!-- [et_pb_line_break_holder] -->    lhv.setUint16(26, nameBytes.length, true); \/\/ filename length<!-- [et_pb_line_break_holder] -->    lhv.setUint16(28, 0, true);          \/\/ extra field length<!-- [et_pb_line_break_holder] -->    lh.set(nameBytes, 30);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Central directory entry<!-- [et_pb_line_break_holder] -->    const cd = new Uint8Array(46 + nameBytes.length);<!-- [et_pb_line_break_holder] -->    const cdv = new DataView(cd.buffer);<!-- [et_pb_line_break_holder] -->    cdv.setUint32(0, 0x02014b50, true);  \/\/ sig<!-- [et_pb_line_break_holder] -->    cdv.setUint16(4, 20, true);           \/\/ version made by<!-- [et_pb_line_break_holder] -->    cdv.setUint16(6, 20, true);           \/\/ version needed<!-- [et_pb_line_break_holder] -->    cdv.setUint16(8, 0, true);            \/\/ flags<!-- [et_pb_line_break_holder] -->    cdv.setUint16(10, 0, true);           \/\/ compression<!-- [et_pb_line_break_holder] -->    cdv.setUint16(12, 0, true);           \/\/ mod time<!-- [et_pb_line_break_holder] -->    cdv.setUint16(14, 0, true);           \/\/ mod date<!-- [et_pb_line_break_holder] -->    cdv.setUint32(16, crc, true);         \/\/ crc32<!-- [et_pb_line_break_holder] -->    cdv.setUint32(20, sz, true);          \/\/ compressed size<!-- [et_pb_line_break_holder] -->    cdv.setUint32(24, sz, true);          \/\/ uncompressed size<!-- [et_pb_line_break_holder] -->    cdv.setUint16(28, nameBytes.length, true); \/\/ filename length<!-- [et_pb_line_break_holder] -->    cdv.setUint16(30, 0, true);           \/\/ extra length<!-- [et_pb_line_break_holder] -->    cdv.setUint16(32, 0, true);           \/\/ comment length<!-- [et_pb_line_break_holder] -->    cdv.setUint16(34, 0, true);           \/\/ disk number start<!-- [et_pb_line_break_holder] -->    cdv.setUint16(36, 0, true);           \/\/ internal attr<!-- [et_pb_line_break_holder] -->    cdv.setUint32(38, 0, true);           \/\/ external attr<!-- [et_pb_line_break_holder] -->    cdv.setUint32(42, offset, true);      \/\/ local header offset<!-- [et_pb_line_break_holder] -->    cd.set(nameBytes, 46);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    localHeaders.push(lh, f.data);<!-- [et_pb_line_break_holder] -->    centralDir.push(cd);<!-- [et_pb_line_break_holder] -->    offset += lh.length + sz;<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  const cdFlat = concat(centralDir);<!-- [et_pb_line_break_holder] -->  const eocd = new Uint8Array(22);<!-- [et_pb_line_break_holder] -->  const eov = new DataView(eocd.buffer);<!-- [et_pb_line_break_holder] -->  eov.setUint32(0, 0x06054b50, true); \/\/ sig<!-- [et_pb_line_break_holder] -->  eov.setUint16(4, 0, true);           \/\/ disk num<!-- [et_pb_line_break_holder] -->  eov.setUint16(6, 0, true);           \/\/ disk with cd<!-- [et_pb_line_break_holder] -->  eov.setUint16(8, files.length, true); \/\/ entries on disk<!-- [et_pb_line_break_holder] -->  eov.setUint16(10, files.length, true); \/\/ total entries<!-- [et_pb_line_break_holder] -->  eov.setUint32(12, cdFlat.length, true); \/\/ cd size<!-- [et_pb_line_break_holder] -->  eov.setUint32(16, offset, true);      \/\/ cd offset<!-- [et_pb_line_break_holder] -->  eov.setUint16(20, 0, true);           \/\/ comment length<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  return concat([...localHeaders, cdFlat, eocd]);<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function concat(arrays) {<!-- [et_pb_line_break_holder] -->  const total = arrays.reduce((s,a)=>s+a.length,0);<!-- [et_pb_line_break_holder] -->  const out = new Uint8Array(total);<!-- [et_pb_line_break_holder] -->  let off = 0;<!-- [et_pb_line_break_holder] -->  for (const a of arrays) { out.set(a, off); off += a.length; }<!-- [et_pb_line_break_holder] -->  return out;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function crc32(data) {<!-- [et_pb_line_break_holder] -->  let crc = 0xFFFFFFFF;<!-- [et_pb_line_break_holder] -->  if (!crc32.table) {<!-- [et_pb_line_break_holder] -->    crc32.table = new Uint32Array(256);<!-- [et_pb_line_break_holder] -->    for (let i=0;i<256;i++){<!-- [et_pb_line_break_holder] -->      let c=i;<!-- [et_pb_line_break_holder] -->      for(let j=0;j<8;j++) c = (c&#038;1)?(0xEDB88320^(c>>>1)):(c>>>1);<!-- [et_pb_line_break_holder] -->      crc32.table[i]=c;<!-- [et_pb_line_break_holder] -->    }<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  for (let i=0;i<data.length;i++) crc = crc32.table[(crc^data[i])&#038;0xFF]^(crc>>>8);<!-- [et_pb_line_break_holder] -->  return (crc^0xFFFFFFFF)>>>0;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Import tab switching \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->let importTab = 'json';<!-- [et_pb_line_break_holder] -->function switchImportTab(tab) {<!-- [et_pb_line_break_holder] -->  importTab = tab;<!-- [et_pb_line_break_holder] -->  document.getElementById('itab-json').classList.toggle('on', tab==='json');<!-- [et_pb_line_break_holder] -->  document.getElementById('itab-zip').classList.toggle('on', tab==='zip');<!-- [et_pb_line_break_holder] -->  document.getElementById('ipane-json').classList.toggle('on', tab==='json');<!-- [et_pb_line_break_holder] -->  document.getElementById('ipane-zip').classList.toggle('on', tab==='zip');<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->document.getElementById('btn-import').addEventListener('click', () => {<!-- [et_pb_line_break_holder] -->  switchImportTab('json');<!-- [et_pb_line_break_holder] -->  openModal('modal-import');<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->document.getElementById('btn-do-import').addEventListener('click', async () => {<!-- [et_pb_line_break_holder] -->  if (importTab === 'json') {<!-- [et_pb_line_break_holder] -->    const f = document.getElementById('import-json-input').files[0];<!-- [et_pb_line_break_holder] -->    if (!f) { toast('Select a JSON file first','err'); return; }<!-- [et_pb_line_break_holder] -->    importJsonFile(f);<!-- [et_pb_line_break_holder] -->  } else {<!-- [et_pb_line_break_holder] -->    const f = document.getElementById('import-zip-input').files[0];<!-- [et_pb_line_break_holder] -->    if (!f) { toast('Select a ZIP file first','err'); return; }<!-- [et_pb_line_break_holder] -->    await importZipFile(f);<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function importJsonFile(file) {<!-- [et_pb_line_break_holder] -->  const r = new FileReader();<!-- [et_pb_line_break_holder] -->  r.onload = e => {<!-- [et_pb_line_break_holder] -->    try {<!-- [et_pb_line_break_holder] -->      const sess = JSON.parse(e.target.result);<!-- [et_pb_line_break_holder] -->      let matched = 0;<!-- [et_pb_line_break_holder] -->      (sess.tracks||[]).forEach(t => {<!-- [et_pb_line_break_holder] -->        const found = S.tracks.find(x => x.name === t.name);<!-- [et_pb_line_break_holder] -->        if (found && t.annotations) {<!-- [et_pb_line_break_holder] -->          S.anns[found.id] = t.annotations.map(a=>({...a,replies:a.replies||[]}));<!-- [et_pb_line_break_holder] -->          matched++;<!-- [et_pb_line_break_holder] -->        }<!-- [et_pb_line_break_holder] -->      });<!-- [et_pb_line_break_holder] -->      renderAnnotations();<!-- [et_pb_line_break_holder] -->      S.tracks.forEach(t => renderMarkers(t.id));<!-- [et_pb_line_break_holder] -->      closeModal('modal-import');<!-- [et_pb_line_break_holder] -->      if (matched > 0) {<!-- [et_pb_line_break_holder] -->        toast(`Imported annotations for ${matched} track${matched>1?'s':''}`, 'ok');<!-- [et_pb_line_break_holder] -->      } else {<!-- [et_pb_line_break_holder] -->        toast('No matching tracks found \u2014 load the audio files first, then import', 'err');<!-- [et_pb_line_break_holder] -->      }<!-- [et_pb_line_break_holder] -->    } catch(e2) { toast('Invalid JSON file','err'); }<!-- [et_pb_line_break_holder] -->  };<!-- [et_pb_line_break_holder] -->  r.readAsText(file);<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->async function importZipFile(zipFile) {<!-- [et_pb_line_break_holder] -->  const progWrap = document.getElementById('import-zip-progress');<!-- [et_pb_line_break_holder] -->  const status = document.getElementById('import-zip-status');<!-- [et_pb_line_break_holder] -->  const fill = document.getElementById('import-zip-fill');<!-- [et_pb_line_break_holder] -->  progWrap.style.display = 'block';<!-- [et_pb_line_break_holder] -->  document.getElementById('btn-do-import').disabled = true;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  try {<!-- [et_pb_line_break_holder] -->    status.textContent = 'Reading ZIP\u2026';<!-- [et_pb_line_break_holder] -->    fill.style.width = '10%';<!-- [et_pb_line_break_holder] -->    const ab = await zipFile.arrayBuffer();<!-- [et_pb_line_break_holder] -->    const bytes = new Uint8Array(ab);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Parse ZIP entries<!-- [et_pb_line_break_holder] -->    const entries = parseZipEntries(bytes);<!-- [et_pb_line_break_holder] -->    status.textContent = `Found ${entries.length} files\u2026`;<!-- [et_pb_line_break_holder] -->    fill.style.width = '25%';<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Separate audio files from JSON<!-- [et_pb_line_break_holder] -->    const audioEntries = entries.filter(e => \/\\.(wav|flac|aif|aiff|mp3|aac|ogg|m4a|opus)$\/i.test(e.name));<!-- [et_pb_line_break_holder] -->    const jsonEntry = entries.find(e => e.name === 'tracknotes-session.json');<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    if (!jsonEntry) { toast('No tracknotes-session.json found in ZIP','err'); return; }<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Determine MIME type from extension so the browser can decode the audio<!-- [et_pb_line_break_holder] -->    function mimeForName(name) {<!-- [et_pb_line_break_holder] -->      const ext = name.split('.').pop().toLowerCase();<!-- [et_pb_line_break_holder] -->      const map = { wav:'audio\/wav', flac:'audio\/flac', aif:'audio\/aiff', aiff:'audio\/aiff',<!-- [et_pb_line_break_holder] -->                    mp3:'audio\/mpeg', aac:'audio\/aac', ogg:'audio\/ogg',<!-- [et_pb_line_break_holder] -->                    m4a:'audio\/mp4', opus:'audio\/ogg; codecs=opus' };<!-- [et_pb_line_break_holder] -->      return map[ext] || 'audio\/octet-stream';<!-- [et_pb_line_break_holder] -->    }<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Load audio files SEQUENTIALLY so each loadFile() fully awaits decoding<!-- [et_pb_line_break_holder] -->    \/\/ before the next starts \u2014 this guarantees S.tracks is populated with the<!-- [et_pb_line_break_holder] -->    \/\/ correct names when we try to match annotations below.<!-- [et_pb_line_break_holder] -->    let loaded = 0;<!-- [et_pb_line_break_holder] -->    for (const entry of audioEntries) {<!-- [et_pb_line_break_holder] -->      const mime = mimeForName(entry.name);<!-- [et_pb_line_break_holder] -->      const blob = new Blob([entry.data], { type: mime });<!-- [et_pb_line_break_holder] -->      const file = new File([blob], entry.name, { type: mime });<!-- [et_pb_line_break_holder] -->      await loadFile(file); \/\/ loadFile now awaits decoding internally<!-- [et_pb_line_break_holder] -->      loaded++;<!-- [et_pb_line_break_holder] -->      status.textContent = `Loading audio ${loaded}\/${audioEntries.length}\u2026`;<!-- [et_pb_line_break_holder] -->      fill.style.width = (25 + loaded \/ audioEntries.length * 65) + '%';<!-- [et_pb_line_break_holder] -->    }<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Apply annotations \u2014 no setTimeout needed, tracks are fully loaded above<!-- [et_pb_line_break_holder] -->    status.textContent = 'Applying annotations\u2026';<!-- [et_pb_line_break_holder] -->    fill.style.width = '95%';<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    const jsonText = new TextDecoder().decode(jsonEntry.data);<!-- [et_pb_line_break_holder] -->    const sess = JSON.parse(jsonText);<!-- [et_pb_line_break_holder] -->    let matched = 0;<!-- [et_pb_line_break_holder] -->    (sess.tracks||[]).forEach(t => {<!-- [et_pb_line_break_holder] -->      const found = S.tracks.find(x => x.name === t.name);<!-- [et_pb_line_break_holder] -->      if (found && t.annotations) {<!-- [et_pb_line_break_holder] -->        S.anns[found.id] = t.annotations.map(a=>({...a,replies:a.replies||[]}));<!-- [et_pb_line_break_holder] -->        matched++;<!-- [et_pb_line_break_holder] -->      }<!-- [et_pb_line_break_holder] -->    });<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    fill.style.width = '100%';<!-- [et_pb_line_break_holder] -->    renderAnnotations();<!-- [et_pb_line_break_holder] -->    S.tracks.forEach(t => renderMarkers(t.id));<!-- [et_pb_line_break_holder] -->    closeModal('modal-import');<!-- [et_pb_line_break_holder] -->    toast(`Project imported \u2014 ${audioEntries.length} track${audioEntries.length>1?'s':''}, ${matched} annotated`,'ok');<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  } catch(e) {<!-- [et_pb_line_break_holder] -->    toast('ZIP import failed: ' + e.message, 'err');<!-- [et_pb_line_break_holder] -->    console.error(e);<!-- [et_pb_line_break_holder] -->  } finally {<!-- [et_pb_line_break_holder] -->    document.getElementById('btn-do-import').disabled = false;<!-- [et_pb_line_break_holder] -->    progWrap.style.display = 'none';<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->function parseZipEntries(bytes) {<!-- [et_pb_line_break_holder] -->  \/\/ Find End of Central Directory<!-- [et_pb_line_break_holder] -->  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);<!-- [et_pb_line_break_holder] -->  let eocdOff = -1;<!-- [et_pb_line_break_holder] -->  for (let i = bytes.length - 22; i >= 0; i--) {<!-- [et_pb_line_break_holder] -->    if (view.getUint32(i, true) === 0x06054b50) { eocdOff = i; break; }<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  if (eocdOff < 0) throw new Error('Not a valid ZIP file');<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  const cdCount = view.getUint16(eocdOff + 8, true);<!-- [et_pb_line_break_holder] -->  const cdOffset = view.getUint32(eocdOff + 16, true);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->  const entries = [];<!-- [et_pb_line_break_holder] -->  let off = cdOffset;<!-- [et_pb_line_break_holder] -->  for (let i = 0; i < cdCount; i++) {<!-- [et_pb_line_break_holder] -->    if (view.getUint32(off, true) !== 0x02014b50) break;<!-- [et_pb_line_break_holder] -->    const nameLen    = view.getUint16(off + 28, true);<!-- [et_pb_line_break_holder] -->    const extraLen   = view.getUint16(off + 30, true);<!-- [et_pb_line_break_holder] -->    const commentLen = view.getUint16(off + 32, true);<!-- [et_pb_line_break_holder] -->    const localOff   = view.getUint32(off + 42, true);<!-- [et_pb_line_break_holder] -->    \/\/ Read size from central directory \u2014 this is always reliable.<!-- [et_pb_line_break_holder] -->    \/\/ The local header fields at offset+18 can be 0 when a data descriptor is used.<!-- [et_pb_line_break_holder] -->    const compSize   = view.getUint32(off + 20, true);<!-- [et_pb_line_break_holder] -->    const name = new TextDecoder().decode(bytes.slice(off + 46, off + 46 + nameLen));<!-- [et_pb_line_break_holder] -->    off += 46 + nameLen + extraLen + commentLen;<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Read local file header only for the variable-length fields that affect data offset<!-- [et_pb_line_break_holder] -->    const lhNameLen  = view.getUint16(localOff + 26, true);<!-- [et_pb_line_break_holder] -->    const lhExtraLen = view.getUint16(localOff + 28, true);<!-- [et_pb_line_break_holder] -->    const dataStart  = localOff + 30 + lhNameLen + lhExtraLen;<!-- [et_pb_line_break_holder] -->    const data = bytes.slice(dataStart, dataStart + compSize);<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->    \/\/ Skip directories<!-- [et_pb_line_break_holder] -->    if (!name.endsWith('\/')) entries.push({ name, data });<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  return entries;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ Cue sheet<!-- [et_pb_line_break_holder] -->document.getElementById('btn-cuesheet').addEventListener('click',()=>{<!-- [et_pb_line_break_holder] -->  const rows=document.getElementById('cue-rows'); rows.innerHTML='';<!-- [et_pb_line_break_holder] -->  const allCues=[];<!-- [et_pb_line_break_holder] -->  S.tracks.forEach(t=>{<!-- [et_pb_line_break_holder] -->    (S.anns[t.id]||[]).filter(a=>!a.resolved).sort((a,b)=>a.ts-b.ts).forEach(a=>allCues.push({...a,trackName:t.name}));<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] -->  allCues.sort((a,b)=>a.ts-b.ts);<!-- [et_pb_line_break_holder] -->  if(!allCues.length){rows.innerHTML='<pee style=\"color:var(--text-dim);font-size:13px\">No annotations to display.<\/pee>';}<!-- [et_pb_line_break_holder] -->  else {<!-- [et_pb_line_break_holder] -->    allCues.forEach((a,i)=>{<!-- [et_pb_line_break_holder] -->      const r=document.createElement('div');r.className='cue-row';<!-- [et_pb_line_break_holder] -->      r.innerHTML=`<span class=\"cue-num\">CUE ${String(i+1).padStart(2,'0')}<\/span><span class=\"cue-time\">${fmtTs(a.ts)}${a.tsEnd?'\u2192'+fmtTs(a.tsEnd):''}<\/span><span class=\"cue-type\">${a.type}<\/span><span class=\"cue-text\">${esc(a.text)}<\/span><span class=\"cue-author\">${esc(a.author)}<\/span>`;<!-- [et_pb_line_break_holder] -->      rows.appendChild(r);<!-- [et_pb_line_break_holder] -->    });<!-- [et_pb_line_break_holder] -->  }<!-- [et_pb_line_break_holder] -->  openModal('modal-cue');<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] -->document.getElementById('btn-cue-txt').addEventListener('click',()=>{<!-- [et_pb_line_break_holder] -->  let txt=`CUE SHEET \u2014 TrackNotes\\nExported: ${new Date().toLocaleString()}\\n${'\u2500'.repeat(60)}\\n\\n`;<!-- [et_pb_line_break_holder] -->  const allCues=[];<!-- [et_pb_line_break_holder] -->  S.tracks.forEach(t=>{ (S.anns[t.id]||[]).filter(a=>!a.resolved).sort((a,b)=>a.ts-b.ts).forEach(a=>allCues.push({...a,trackName:t.name})); });<!-- [et_pb_line_break_holder] -->  allCues.sort((a,b)=>a.ts-b.ts);<!-- [et_pb_line_break_holder] -->  allCues.forEach((a,i)=>{<!-- [et_pb_line_break_holder] -->    txt+=`CUE ${String(i+1).padStart(2,'0')}  ${fmtTs(a.ts)}${a.tsEnd?' \u2192 '+fmtTs(a.tsEnd):''}   [${a.type.toUpperCase()}]\\n`;<!-- [et_pb_line_break_holder] -->    txt+=`  Track:  ${a.trackName}\\n  Author: ${a.author}\\n  Note:   ${a.text}\\n`;<!-- [et_pb_line_break_holder] -->    a.replies.forEach(r=>{txt+=`    \u21b3 ${r.author}: ${r.text}\\n`;});<!-- [et_pb_line_break_holder] -->    txt+='\\n';<!-- [et_pb_line_break_holder] -->  });<!-- [et_pb_line_break_holder] -->  const b=new Blob([txt],{type:'text\/plain'});const u=URL.createObjectURL(b);<!-- [et_pb_line_break_holder] -->  const a=document.createElement('a');a.href=u;a.download='cuesheet-'+Date.now()+'.txt';a.click();URL.revokeObjectURL(u);<!-- [et_pb_line_break_holder] -->  toast('TXT exported','ok');<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] -->document.getElementById('btn-cue-json').addEventListener('click',()=>{<!-- [et_pb_line_break_holder] -->  const allCues=[];<!-- [et_pb_line_break_holder] -->  S.tracks.forEach(t=>{ (S.anns[t.id]||[]).filter(a=>!a.resolved).sort((a,b)=>a.ts-b.ts).forEach(a=>allCues.push({...a,trackName:t.name,replies:[...a.replies]})); });<!-- [et_pb_line_break_holder] -->  const b=new Blob([JSON.stringify({exported:new Date().toISOString(),cues:allCues},null,2)],{type:'application\/json'});<!-- [et_pb_line_break_holder] -->  const u=URL.createObjectURL(b);const a=document.createElement('a');a.href=u;a.download='cuesheet-'+Date.now()+'.json';a.click();URL.revokeObjectURL(u);<!-- [et_pb_line_break_holder] -->  toast('JSON exported','ok');<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Modals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->function openModal(id){document.getElementById(id).classList.remove('hidden')}<!-- [et_pb_line_break_holder] -->function closeModal(id){document.getElementById(id).classList.add('hidden')}<!-- [et_pb_line_break_holder] -->document.querySelectorAll('.modal-ov').forEach(el=>el.addEventListener('click',e=>{if(e.target===el)closeModal(el.id);}));<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->function toast(msg, type='info') {<!-- [et_pb_line_break_holder] -->  const icons={ok:'\u2713',err:'\u2715',info:'\u25cf'};<!-- [et_pb_line_break_holder] -->  const el=document.createElement('div');<!-- [et_pb_line_break_holder] -->  el.className=`toast${type==='ok'?' ok':type==='err'?' err':''}`;<!-- [et_pb_line_break_holder] -->  el.innerHTML=`<span>${icons[type]||'\u25cf'}<\/span>${msg}`;<!-- [et_pb_line_break_holder] -->  document.getElementById('toasts').appendChild(el);<!-- [et_pb_line_break_holder] -->  setTimeout(()=>{el.style.opacity='0';el.style.transition='opacity .25s';setTimeout(()=>el.remove(),280);},2600);<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Utilities \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->function fmtTs(s){<!-- [et_pb_line_break_holder] -->  if(!isFinite(s)||s<0)return'0:00.0';<!-- [et_pb_line_break_holder] -->  const m=Math.floor(s\/60),sec=(s%60).toFixed(1);<!-- [et_pb_line_break_holder] -->  return`${m}:${sec.padStart(4,'0')}`;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] -->function fmtShort(s){<!-- [et_pb_line_break_holder] -->  if(!s)return'0:00';<!-- [et_pb_line_break_holder] -->  return`${Math.floor(s\/60)}:${String(Math.floor(s%60)).padStart(2,'0')}`;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] -->function fmtSize(b){<!-- [et_pb_line_break_holder] -->  if(b<1024)return b+'B';if(b<1048576)return(b\/1024).toFixed(1)+'KB';return(b\/1048576).toFixed(1)+'MB';<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] -->function esc(s){<!-- [et_pb_line_break_holder] -->  return String(s).replace(\/&\/g,'&').replace(\/<\/g,'<').replace(\/>\/g,'>').replace(\/\"\/g,'\"');<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 File input \/ drag-drop \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->document.getElementById('btn-browse').addEventListener('click',(e)=>{e.stopPropagation();document.getElementById('file-input').click();});<!-- [et_pb_line_break_holder] -->document.getElementById('drop-zone').addEventListener('click',()=>document.getElementById('file-input').click());<!-- [et_pb_line_break_holder] -->document.getElementById('file-input').addEventListener('change',e=>{handleFiles(e.target.files);e.target.value='';});<!-- [et_pb_line_break_holder] -->document.getElementById('add-file-input').addEventListener('change',e=>{handleFiles(e.target.files);e.target.value='';});<!-- [et_pb_line_break_holder] -->document.getElementById('btn-add-more').addEventListener('click',()=>document.getElementById('add-file-input').click());<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->const dz=document.getElementById('drop-zone');<!-- [et_pb_line_break_holder] -->['dragenter','dragover'].forEach(ev=>{<!-- [et_pb_line_break_holder] -->  dz.addEventListener(ev,e=>{e.preventDefault();dz.classList.add('drag-over');});<!-- [et_pb_line_break_holder] -->  document.body.addEventListener(ev,e=>e.preventDefault());<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] -->['dragleave','drop'].forEach(ev=>dz.addEventListener(ev,e=>{e.preventDefault();dz.classList.remove('drag-over');}));<!-- [et_pb_line_break_holder] -->dz.addEventListener('drop',e=>{e.preventDefault();e.stopPropagation();dz.classList.remove('drag-over');handleFiles(e.dataTransfer.files);});<!-- [et_pb_line_break_holder] -->document.body.addEventListener('drop',e=>{e.preventDefault();if(S.tracks.length&&e.target!==dz&&!dz.contains(e.target))handleFiles(e.dataTransfer.files);});<!-- [et_pb_line_break_holder] -->document.body.addEventListener('dragover',e=>e.preventDefault());<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Keyboard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->document.addEventListener('keydown',e=>{<!-- [et_pb_line_break_holder] -->  const tag=document.activeElement.tagName;<!-- [et_pb_line_break_holder] -->  if(tag==='INPUT'||tag==='TEXTAREA'||tag==='SELECT') return;<!-- [et_pb_line_break_holder] -->  if(e.code==='Space'){e.preventDefault();togglePlay();}<!-- [et_pb_line_break_holder] -->  if(e.code==='ArrowLeft'){e.preventDefault();seekTo(nowPos()-(e.shiftKey?10:5));}<!-- [et_pb_line_break_holder] -->  if(e.code==='ArrowRight'){e.preventDefault();const t=getActive();seekTo(Math.min(nowPos()+(e.shiftKey?10:5),t?.dur||0));}<!-- [et_pb_line_break_holder] -->  if(e.code==='KeyM'){document.getElementById(`mute-btn-${S.activeId}`)?.click();}<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Global rAF (lightweight \u2014 only syncs UI when playing) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->(function gTick(){<!-- [et_pb_line_break_holder] -->  if(isPlaying){updateTimeUI();updateHeadUI();}<!-- [et_pb_line_break_holder] -->  requestAnimationFrame(gTick);<!-- [et_pb_line_break_holder] -->})();<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Resize \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->window.addEventListener('resize',()=>{<!-- [et_pb_line_break_holder] -->  S.tracks.forEach(t=>{if(t.buf){delete wfCache[t.id];renderWaveform(t.id);renderMarkers(t.id);}});<!-- [et_pb_line_break_holder] -->});<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->\/\/ \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<!-- [et_pb_line_break_holder] -->initAuthor();<!-- [et_pb_line_break_holder] -->toast('Drop audio files to start \u2014 100% local, zero latency','info');<!-- [et_pb_line_break_holder] --><\/script><!-- [et_pb_line_break_holder] --><\/body><!-- [et_pb_line_break_holder] --><\/html>[\/et_pb_code][\/et_pb_column][\/et_pb_row][\/et_pb_section][et_pb_section fb_built=&#8221;1&#8243; disabled_on=&#8221;off|off|off&#8221; admin_label=&#8221;Hero Section&#8221; _builder_version=&#8221;4.27.5&#8243; background_color=&#8221;#000000&#8243; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_stops=&#8221;#000000 0%|rgba(0,0,0,0) 20%|rgba(0,0,0,0) 80%|#000000 100%&#8221; background_color_gradient_overlays_image=&#8221;on&#8221; background_image=&#8221;https:\/\/smartdsp.pro\/wp-content\/uploads\/2022\/10\/streamer-79.png&#8221; custom_padding=&#8221;9px|||1px||&#8221; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_row _builder_version=&#8221;4.16&#8243; background_size=&#8221;initial&#8221; background_position=&#8221;top_left&#8221; background_repeat=&#8221;repeat&#8221; module_alignment=&#8221;center&#8221; custom_width_px=&#8221;700px&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_column type=&#8221;4_4&#8243; _builder_version=&#8221;4.16&#8243; custom_padding=&#8221;|||&#8221; global_colors_info=&#8221;{}&#8221; custom_padding__hover=&#8221;|||&#8221;][et_pb_text _builder_version=&#8221;4.16&#8243; text_font=&#8221;||||||||&#8221; text_font_size=&#8221;18px&#8221; text_line_height=&#8221;2em&#8221; header_font=&#8221;Titillium Web||||||||&#8221; header_font_size=&#8221;66px&#8221; header_line_height=&#8221;1.5em&#8221; header_2_font=&#8221;Titillium Web||||||||&#8221; header_2_font_size=&#8221;66px&#8221; header_2_line_height=&#8221;1.5em&#8221; header_3_font=&#8221;Titillium Web||||||||&#8221; header_3_font_size=&#8221;66px&#8221; header_3_line_height=&#8221;1.5em&#8221; header_4_font=&#8221;Titillium Web||||||||&#8221; header_4_font_size=&#8221;66px&#8221; header_4_line_height=&#8221;1.5em&#8221; header_5_font=&#8221;Titillium Web||||||||&#8221; header_5_font_size=&#8221;66px&#8221; header_5_line_height=&#8221;1.5em&#8221; header_6_font=&#8221;Titillium Web||||||||&#8221; header_6_font_size=&#8221;66px&#8221; header_6_line_height=&#8221;1.5em&#8221; text_orientation=&#8221;center&#8221; background_layout=&#8221;dark&#8221; max_width=&#8221;700px&#8221; module_alignment=&#8221;center&#8221; header_font_size_tablet=&#8221;40px&#8221; header_font_size_phone=&#8221;&#8221; header_font_size_last_edited=&#8221;on|phone&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<h1>Frequently Asked<\/h1>\n<p>[\/et_pb_text][et_pb_code _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;default&#8221; global_colors_info=&#8221;{}&#8221;][\/et_pb_code][\/et_pb_column][\/et_pb_row][et_pb_row column_structure=&#8221;1_2,1_2&#8243; custom_padding_last_edited=&#8221;on|desktop&#8221; _builder_version=&#8221;4.27.4&#8243; _module_preset=&#8221;default&#8221; background_color=&#8221;rgba(79,79,79,0.16)&#8221; overflow-x=&#8221;visible&#8221; overflow-y=&#8221;visible&#8221; custom_padding=&#8221;80px|80px|80px|80px|true|true&#8221; custom_padding_tablet=&#8221;30px|30px|30px|30px|true|true&#8221; custom_padding_phone=&#8221;20px|20px|20px|20px|true|true&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; box_shadow_horizontal=&#8221;-1px&#8221; box_shadow_vertical=&#8221;-1px&#8221; box_shadow_blur=&#8221;0px&#8221; box_shadow_color=&#8221;rgba(255,255,255,0.21)&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_column type=&#8221;1_2&#8243; _builder_version=&#8221;4.17.4&#8243; _module_preset=&#8221;default&#8221; custom_padding_tablet=&#8221;||||false|false&#8221; custom_padding_phone=&#8221;||30px||false|false&#8221; custom_padding_last_edited=&#8221;off|phone&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_blurb title=&#8221;How do I use TrackNotes?&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Getting started takes about 30 seconds:<\/strong><\/p>\n<ol class=\"[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3\">\n<li class=\"whitespace-normal break-words pl-2\"><strong>Load your audio<\/strong> \u2014 drag and drop one or more files directly onto the interface, or click &#8220;Browse files.&#8221; Supported formats: WAV, FLAC, AIFF, MP3, AAC, OGG, M4A, OPUS.<\/li>\n<li class=\"whitespace-normal break-words pl-2\"><strong>Listen<\/strong> \u2014 hit the play button (or press <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">Space<\/code>). Click anywhere on the waveform to jump to that position. Use <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">\u2190<\/code> \/ <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">\u2192<\/code> to skip 5 seconds, <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">Shift+\u2190\/\u2192<\/code> for 10 seconds.<\/li>\n<li class=\"whitespace-normal break-words pl-2\"><strong>Annotate<\/strong> \u2014 while listening, click into the annotation field on the right. The timestamp locks automatically to the moment you click in. Type your note and press <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">Enter<\/code> to post. Choose a type first (Note, Issue, Cue, OK) to categorize it.<\/li>\n<li class=\"whitespace-normal break-words pl-2\"><strong>Jump between tracks<\/strong> \u2014 if you loaded multiple files, they appear stacked vertically. Click any track card to activate it and continue annotating without losing context.<\/li>\n<li class=\"whitespace-normal break-words pl-2\"><strong>Export and share<\/strong> \u2014 click &#8220;Export&#8221; in the top bar. Choose <em>Annotations only<\/em> (JSON) if your collaborator already has the audio, or <em>Full project (ZIP)<\/em> to send everything in one file. Your collaborator clicks &#8220;Import&#8221; and picks the matching mode.<\/li>\n<\/ol>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Optional: Range annotations<\/strong> \u2014 toggle &#8220;Range&#8221; in the compose area, then click and drag on the waveform to mark a start-to-end span (useful for &#8220;this whole section needs work&#8221;). Post as usual and the annotation will display both the start and end timestamps.<\/p>\n<\/div>\n<p>[\/et_pb_blurb][et_pb_blurb title=&#8221; Key features and advanced options&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;100ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<div class=\"faq-answer\">\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Playback<\/strong><\/p>\n<ul class=\"[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3\">\n<li class=\"whitespace-normal break-words pl-2\">Lossless, latency-free playback via the browser&#8217;s native Web Audio API \u2014 no re-encoding, no compression<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Variable playback speed: 0.5\u00d7, 0.75\u00d7, 1\u00d7, 1.25\u00d7, 1.5\u00d7, 2\u00d7 \u2014 useful for catching detail in dense mixes or rushing through a long review<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Loop mode for repeating a section while you annotate<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Persistent volume control with mute toggle \u2014 adjusting volume never interrupts playback<\/li>\n<\/ul>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Waveform<\/strong><\/p>\n<ul class=\"[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3\">\n<li class=\"whitespace-normal break-words pl-2\">Full-width waveform rendered per track with a gradient that reflects your session&#8217;s color palette<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Click or drag anywhere on the waveform to seek precisely<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Annotation markers appear directly on the waveform, color-coded by type (blue = Note, red = Issue, yellow = Cue, green = OK) \u2014 hover any marker to highlight the corresponding annotation in the sidebar<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Range selection: drag across the waveform to mark a time span for a single annotation<\/li>\n<\/ul>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Annotations<\/strong><\/p>\n<ul class=\"[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3\">\n<li class=\"whitespace-normal break-words pl-2\">Timestamps are captured automatically the moment you click into the text field \u2014 no manual stamping required<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Four annotation types keep feedback organized: <strong>\ud83d\udcdd Note<\/strong> (general comment), <strong>\ud83d\udd34 Issue<\/strong> (something that needs fixing), <strong>\ud83c\udfaf Cue<\/strong> (a specific moment to mark, useful for post-production), <strong>\u2705 OK<\/strong> (approval or sign-off)<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Threaded replies on any annotation \u2014 each collaborator can respond in context<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Resolve annotations to mark them done without deleting them; clear all resolved in one click<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Filter by type, sort by timestamp, creation time, or type<\/li>\n<li class=\"whitespace-normal break-words pl-2\">Auto-generated anonymous ID per browser (e.g. <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">K7RX2M<\/code>) pre-fills the author field \u2014 collaborators can customize their name any time<\/li>\n<\/ul>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Export \/ Import<\/strong><\/p>\n<ul class=\"[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3\">\n<li class=\"whitespace-normal break-words pl-2\"><strong>Annotations only (JSON):<\/strong> lightweight, shareable, version-controllable text file \u2014 ideal when all parties already have the audio<\/li>\n<li class=\"whitespace-normal break-words pl-2\"><strong>Full project (ZIP):<\/strong> self-contained package with all audio files + the JSON \u2014 built entirely in-browser, no server involved, compatible with any standard unzip tool<\/li>\n<li class=\"whitespace-normal break-words pl-2\"><strong>Cue Sheet export:<\/strong> generates a formatted, professional cue list (TXT or JSON) sorted by timestamp across all tracks \u2014 useful for post-production handoffs, music supervisors, or broadcast delivery<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<p>[\/et_pb_blurb][et_pb_blurb title=&#8221;Does TrackNotes work with lossless audio formats like WAV and FLAC?&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;100ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<div class=\"faq-answer\">\n<p>Yes. TrackNotes uses the browser&#8217;s native Web Audio API to decode audio, which supports WAV, FLAC, AIFF, MP3, AAC, OGG, M4A, and OPUS. Lossless files (WAV, FLAC, AIFF) are decoded without any re-encoding or quality loss. Playback is direct from the decoded PCM buffer \u2014 there is no intermediate compression step.<\/p>\n<\/div>\n<\/div>\n<p>[\/et_pb_blurb][et_pb_blurb title=&#8221;What file formats does TrackNotes export?&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;100ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<div class=\"faq-answer\">\n<p>TrackNotes exports in three formats: a <strong>JSON session file<\/strong> (annotations only, matched by filename), a <strong>ZIP project archive<\/strong> (audio files + JSON bundled together for complete portability), and a <strong>Cue Sheet<\/strong> in either plain text or JSON. All exports are generated locally in the browser with no server-side processing.<\/p>\n<\/div>\n<\/div>\n<p>[\/et_pb_blurb][et_pb_blurb title=&#8221;Is TrackNotes free? Are there any limits?&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;100ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<div class=\"faq-answer\">\n<p>rackNotes is completely free with no usage limits, no file size caps imposed by the tool, no watermarking, and no account required. The only practical constraints are your browser&#8217;s available memory (relevant for very long high-resolution audio files) and your device&#8217;s processing speed for waveform rendering.<\/p>\n<\/div>\n<\/div>\n<p>[\/et_pb_blurb][\/et_pb_column][et_pb_column type=&#8221;1_2&#8243; _builder_version=&#8221;4.17.4&#8243; _module_preset=&#8221;default&#8221; custom_padding_tablet=&#8221;|||0%|false|false&#8221; custom_padding_phone=&#8221;|||0%|false|false&#8221; custom_padding_last_edited=&#8221;off|desktop&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_blurb title=&#8221;Is my audio uploaded anywhere? How is data handled?&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;250ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<div class=\"faq-answer\">\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Absolutely nothing leaves your device.<\/strong> TrackNotes runs entirely inside your browser using the Web Audio API and standard browser file APIs. Your audio files are read directly from your disk into browser memory \u2014 they are never sent to any server, never stored in a cloud, and never logged anywhere.<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\">The same applies to your annotations: all notes, timestamps, and replies exist only in the browser tab&#8217;s memory. Nothing is persisted externally. Even the anonymous user ID used to pre-fill the author field is a short random string stored only in your own browser&#8217;s <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">localStorage<\/code> \u2014 it contains no personal information whatsoever and never leaves your machine.<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\">When you export, the JSON or ZIP file is assembled locally and downloaded directly to your computer. <strong>There are no accounts, no cookies for tracking, no analytics, no ads, and no third-party dependencies loaded at runtime<\/strong> beyond the Google Fonts stylesheet for the typeface.<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Practical implication:<\/strong> if you close the tab without exporting, your session is gone. Always export before closing.<\/p>\n<\/div>\n<\/div>\n<p>[\/et_pb_blurb][et_pb_blurb title=&#8221;Suggestions for getting the most out of TrackNotes&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;150ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Establish a team convention for annotation types<\/strong> \u2014 decide upfront what each type means for your workflow. For example: <em>Note<\/em> = general observation, <em>Issue<\/em> = must fix before delivery, <em>Cue<\/em> = sync point for video\/picture lock, <em>OK<\/em> = this section is approved and frozen. Consistent tagging makes the cue sheet export immediately actionable.<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Use the author field meaningfully<\/strong> \u2014 each collaborator should set their name (not just use the auto ID) before starting a review pass. This makes it immediately clear whose feedback is whose when the annotated JSON comes back.<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Iterate with JSON, ship with ZIP<\/strong> \u2014 during active back-and-forth between a mixer and producer, sharing the JSON alone is faster and keeps file sizes tiny. Reserve the ZIP export for final archiving or when bringing in a new collaborator who doesn&#8217;t have the stems.<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Use range annotations for sections, point annotations for moments<\/strong> \u2014 a range annotation saying &#8220;this chorus feels too compressed&#8221; is more useful than a vague point. Save point annotations for specific transient moments: &#8220;this snare hit at 2:14.3 is clipping.&#8221;<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>The Cue Sheet is your delivery document<\/strong> \u2014 at the end of a session, open Cue Sheet and export it as JSON for your DAW\/NLE workflow or TXT for a human-readable PDF-ready document. This works as a preliminary EDL-style reference for editors.<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal leading-[1.7]\"><strong>Name your audio files clearly before loading<\/strong> \u2014 since import matching works by filename, keeping consistent, descriptive filenames (e.g. <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">Song_Title_Mix_v3.wav<\/code> rather than <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">final_FINAL2.wav<\/code>) ensures that a JSON session imports cleanly every time.<\/p>\n<p>[\/et_pb_blurb][et_pb_blurb title=&#8221;What is TrackNotes?&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;100ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<div class=\"faq-answer\">\n<p>TrackNotes is a free, browser-based audio collaboration and annotation tool for music producers, mixing engineers, mastering engineers, sound designers, and post-production teams. It lets you load audio files, leave timestamped comments directly on the waveform, exchange feedback with collaborators, and export professional cue sheets \u2014 all without creating an account or uploading a single file.<\/p>\n<\/div>\n<\/div>\n<p>[\/et_pb_blurb][et_pb_blurb title=&#8221;How is TrackNotes different from SoundCloud comments or Notetracks?&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;100ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<div class=\"faq-answer\">\n<p>Unlike SoundCloud, Notetracks, Samply, or Nugen Jotter, TrackNotes processes everything locally in your browser. There is no streaming, no cloud storage, no installation, and no subscription. Your audio \u2014 whether a rough mix, a master, or a stem \u2014 never touches an external server. This makes it suitable for NDA-protected sessions, unreleased music, and any workflow where confidentiality matters. It also means zero latency: playback starts instantly from the original file at full quality.<\/p>\n<\/div>\n<\/div>\n<p>[\/et_pb_blurb][et_pb_blurb title=&#8221;Can I use TrackNotes for post-production and cue sheets?&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;100ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<div class=\"faq-answer\">\n<p>Yes. The Cue Sheet feature aggregates all annotations across all loaded tracks, sorted by timecode, and exports them as a structured JSON or plain-text document. This is useful for music supervisors generating cue lists, picture editors referencing audio notes, dubbing mixers marking dialogue cues, or broadcast engineers preparing technical delivery notes.<\/p>\n<\/div>\n<\/div>\n<p>[\/et_pb_blurb][et_pb_blurb title=&#8221;What browsers does TrackNotes support?&#8221; use_icon=&#8221;on&#8221; font_icon=&#8221;&#x75;||divi||400&#8243; icon_color=&#8221;#f3be29&#8243; icon_placement=&#8221;left&#8221; _builder_version=&#8221;4.27.5&#8243; header_font=&#8221;&#8211;et_global_heading_font|700||on|||||&#8221; header_font_size=&#8221;14px&#8221; header_letter_spacing=&#8221;2px&#8221; header_line_height=&#8221;1.8em&#8221; body_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; body_text_color=&#8221;#FFFFFF&#8221; body_font_size=&#8221;16px&#8221; body_line_height=&#8221;1.8em&#8221; background_enable_color=&#8221;off&#8221; use_background_color_gradient=&#8221;on&#8221; background_color_gradient_direction=&#8221;164deg&#8221; background_color_gradient_stops=&#8221;rgba(243,190,41,0.17) 0%|rgba(205,59,167,0.17) 100%&#8221; background_layout=&#8221;dark&#8221; custom_margin=&#8221;||12%|&#8221; custom_margin_tablet=&#8221;70px|||&#8221; custom_margin_last_edited=&#8221;off|desktop&#8221; custom_padding=&#8221;30px|16px|30px|16px&#8221; animation_style=&#8221;fold&#8221; animation_delay=&#8221;100ms&#8221; border_radii=&#8221;on|20px|20px|20px|20px&#8221; box_shadow_style=&#8221;preset1&#8243; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;]<\/p>\n<div class=\"faq-item\">\n<div class=\"faq-answer\">\n<p>TrackNotes works in any modern browser that supports the Web Audio API and ES2020+ JavaScript \u2014 this includes Chrome, Firefox, Safari, Edge, and Brave. For the best experience with large lossless files, a desktop browser is recommended over mobile.<\/p>\n<\/div>\n<\/div>\n<p>[\/et_pb_blurb][\/et_pb_column][\/et_pb_row][et_pb_row _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;default&#8221; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_column type=&#8221;4_4&#8243; _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;default&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_heading title=&#8221;We&#8217;ve much more for you to explore:&#8221; _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;default&#8221; title_level=&#8221;h2&#8243; title_font=&#8221;&#8211;et_global_heading_font||||||||&#8221; title_text_color=&#8221;#FFFFFF&#8221; global_colors_info=&#8221;{}&#8221;][\/et_pb_heading][\/et_pb_column][\/et_pb_row][et_pb_row column_structure=&#8221;1_4,1_4,1_4,1_4&#8243; _builder_version=&#8221;4.18.0&#8243; custom_margin=&#8221;1vw|auto||auto|false|false&#8221; animation_style=&#8221;flip&#8221; locked=&#8221;off&#8221; global_colors_info=&#8221;{}&#8221;][et_pb_column type=&#8221;1_4&#8243; _builder_version=&#8221;4.18.0&#8243; custom_padding=&#8221;|||&#8221; global_colors_info=&#8221;{}&#8221; custom_padding__hover=&#8221;|||&#8221;][et_pb_button button_url=&#8221;https:\/\/smartdsp.pro\/mastering-services&#8221; button_text=&#8221;Mastering&#8221; button_alignment=&#8221;center&#8221; _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;3117bc07-6a8a-4679-b3bb-b6ed74b48a2f&#8221; custom_button=&#8221;on&#8221; button_text_color=&#8221;#FFFFFF&#8221; button_bg_use_color_gradient=&#8221;on&#8221; button_bg_color_gradient_direction=&#8221;229deg&#8221; button_bg_color_gradient_stops=&#8221;rgba(189,22,179,0.8) 0%|rgba(253,202,65,0.8) 100%&#8221; button_border_width=&#8221;0px&#8221; button_border_radius=&#8221;15px&#8221; button_font=&#8221;Be Vietnam Pro|600|||||||&#8221; button_use_icon=&#8221;off&#8221; box_shadow_style=&#8221;preset1&#8243; box_shadow_blur=&#8221;35px&#8221; box_shadow_spread=&#8221;-11px&#8221; box_shadow_color=&#8221;rgba(255,255,255,0.28)&#8221; global_colors_info=&#8221;{}&#8221; button_bg_color__hover_enabled=&#8221;on|desktop&#8221; button_bg_color__hover=&#8221;rgba(0,127,31,0.7)&#8221; button_bg_enable_color__hover=&#8221;on&#8221; button_border_width__hover=&#8221;0px&#8221; button_border_width__hover_enabled=&#8221;on|desktop&#8221; button_border_radius__hover=&#8221;15px&#8221; button_border_radius__hover_enabled=&#8221;on|desktop&#8221; box_shadow_color__hover=&#8221;rgba(255,255,255,0.49)&#8221; box_shadow_color__hover_enabled=&#8221;on|hover&#8221; button_text_size__hover_enabled=&#8221;on|desktop&#8221; button_border_color__hover=&#8221;rgba(255,255,255,0.02)&#8221; button_border_color__hover_enabled=&#8221;on|hover&#8221; button_icon_color__hover=&#8221;#FFFFFF&#8221; button_icon_color__hover_enabled=&#8221;on|hover&#8221;][\/et_pb_button][\/et_pb_column][et_pb_column type=&#8221;1_4&#8243; _builder_version=&#8221;4.18.0&#8243; custom_padding=&#8221;|||&#8221; global_colors_info=&#8221;{}&#8221; custom_padding__hover=&#8221;|||&#8221;][et_pb_button button_url=&#8221;https:\/\/smartdsp.pro\/smart-dsp-products\/&#8221; button_text=&#8221;Audio Effects&#8221; button_alignment=&#8221;center&#8221; _builder_version=&#8221;4.27.4&#8243; _module_preset=&#8221;3117bc07-6a8a-4679-b3bb-b6ed74b48a2f&#8221; custom_button=&#8221;on&#8221; button_text_color=&#8221;#FFFFFF&#8221; button_bg_use_color_gradient=&#8221;on&#8221; button_bg_color_gradient_direction=&#8221;229deg&#8221; button_bg_color_gradient_stops=&#8221;rgba(189,22,179,0.8) 0%|rgba(253,202,65,0.8) 100%&#8221; button_border_width=&#8221;0px&#8221; button_border_radius=&#8221;15px&#8221; button_font=&#8221;Be Vietnam Pro|600|||||||&#8221; button_use_icon=&#8221;off&#8221; box_shadow_style=&#8221;preset1&#8243; box_shadow_blur=&#8221;35px&#8221; box_shadow_spread=&#8221;-11px&#8221; box_shadow_color=&#8221;rgba(255,255,255,0.28)&#8221; global_colors_info=&#8221;{}&#8221; button_bg_color__hover_enabled=&#8221;on|desktop&#8221; button_bg_color__hover=&#8221;rgba(0,127,31,0.7)&#8221; button_bg_enable_color__hover=&#8221;on&#8221; button_border_width__hover=&#8221;0px&#8221; button_border_width__hover_enabled=&#8221;on|desktop&#8221; button_border_radius__hover=&#8221;15px&#8221; button_border_radius__hover_enabled=&#8221;on|desktop&#8221; box_shadow_color__hover=&#8221;rgba(255,255,255,0.49)&#8221; box_shadow_color__hover_enabled=&#8221;on|hover&#8221; button_text_size__hover_enabled=&#8221;on|desktop&#8221; button_border_color__hover=&#8221;rgba(255,255,255,0.02)&#8221; button_border_color__hover_enabled=&#8221;on|hover&#8221; button_icon_color__hover=&#8221;#FFFFFF&#8221; button_icon_color__hover_enabled=&#8221;on|hover&#8221;][\/et_pb_button][\/et_pb_column][et_pb_column type=&#8221;1_4&#8243; _builder_version=&#8221;4.18.0&#8243; custom_padding=&#8221;|||&#8221; global_colors_info=&#8221;{}&#8221; custom_padding__hover=&#8221;|||&#8221;][et_pb_button button_url=&#8221;https:\/\/www.patreon.com\/posts\/how-to-get-most-96445637&#8243; url_new_window=&#8221;on&#8221; button_text=&#8221;Learning&#8221; button_alignment=&#8221;center&#8221; _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;3117bc07-6a8a-4679-b3bb-b6ed74b48a2f&#8221; custom_button=&#8221;on&#8221; button_text_color=&#8221;#FFFFFF&#8221; button_bg_use_color_gradient=&#8221;on&#8221; button_bg_color_gradient_direction=&#8221;229deg&#8221; button_bg_color_gradient_stops=&#8221;rgba(189,22,179,0.8) 0%|rgba(253,202,65,0.8) 100%&#8221; button_border_width=&#8221;0px&#8221; button_border_radius=&#8221;15px&#8221; button_font=&#8221;Be Vietnam Pro|600|||||||&#8221; button_use_icon=&#8221;off&#8221; box_shadow_style=&#8221;preset1&#8243; box_shadow_blur=&#8221;35px&#8221; box_shadow_spread=&#8221;-11px&#8221; box_shadow_color=&#8221;rgba(255,255,255,0.28)&#8221; global_colors_info=&#8221;{}&#8221; button_bg_color__hover_enabled=&#8221;on|desktop&#8221; button_bg_color__hover=&#8221;rgba(0,127,31,0.7)&#8221; button_bg_enable_color__hover=&#8221;on&#8221; button_border_width__hover=&#8221;0px&#8221; button_border_width__hover_enabled=&#8221;on|desktop&#8221; button_border_radius__hover=&#8221;15px&#8221; button_border_radius__hover_enabled=&#8221;on|desktop&#8221; box_shadow_color__hover=&#8221;rgba(255,255,255,0.49)&#8221; box_shadow_color__hover_enabled=&#8221;on|hover&#8221; button_text_size__hover_enabled=&#8221;on|desktop&#8221; button_border_color__hover=&#8221;rgba(255,255,255,0.02)&#8221; button_border_color__hover_enabled=&#8221;on|hover&#8221; button_icon_color__hover=&#8221;#FFFFFF&#8221; button_icon_color__hover_enabled=&#8221;on|hover&#8221;][\/et_pb_button][\/et_pb_column][et_pb_column type=&#8221;1_4&#8243; _builder_version=&#8221;4.18.0&#8243; custom_padding=&#8221;|||&#8221; global_colors_info=&#8221;{}&#8221; custom_padding__hover=&#8221;|||&#8221;][et_pb_button button_url=&#8221;https:\/\/smartdsp.pro\/tools&#8221; button_text=&#8221;Online Tools&#8221; button_alignment=&#8221;center&#8221; _builder_version=&#8221;4.27.5&#8243; _module_preset=&#8221;3117bc07-6a8a-4679-b3bb-b6ed74b48a2f&#8221; custom_button=&#8221;on&#8221; button_text_color=&#8221;#FFFFFF&#8221; button_bg_use_color_gradient=&#8221;on&#8221; button_bg_color_gradient_direction=&#8221;229deg&#8221; button_bg_color_gradient_stops=&#8221;rgba(189,22,179,0.8) 0%|rgba(253,202,65,0.8) 100%&#8221; button_border_width=&#8221;0px&#8221; button_border_radius=&#8221;15px&#8221; button_font=&#8221;Be Vietnam Pro|600|||||||&#8221; button_use_icon=&#8221;off&#8221; box_shadow_style=&#8221;preset1&#8243; box_shadow_blur=&#8221;35px&#8221; box_shadow_spread=&#8221;-11px&#8221; box_shadow_color=&#8221;rgba(255,255,255,0.28)&#8221; global_colors_info=&#8221;{}&#8221; button_bg_color__hover_enabled=&#8221;on|desktop&#8221; button_bg_color__hover=&#8221;rgba(0,127,31,0.7)&#8221; button_bg_enable_color__hover=&#8221;on&#8221; button_border_width__hover=&#8221;0px&#8221; button_border_width__hover_enabled=&#8221;on|desktop&#8221; button_border_radius__hover=&#8221;15px&#8221; button_border_radius__hover_enabled=&#8221;on|desktop&#8221; box_shadow_color__hover=&#8221;rgba(255,255,255,0.49)&#8221; box_shadow_color__hover_enabled=&#8221;on|hover&#8221; button_text_size__hover_enabled=&#8221;on|desktop&#8221; button_border_color__hover=&#8221;rgba(255,255,255,0.02)&#8221; button_border_color__hover_enabled=&#8221;on|hover&#8221; button_icon_color__hover=&#8221;#FFFFFF&#8221; button_icon_color__hover_enabled=&#8221;on|hover&#8221;][\/et_pb_button][\/et_pb_column][\/et_pb_row][\/et_pb_section]<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Timestamped comments on audio files. Easy collab with 1-click export. Free and Ethical. For Music producers, audio engineers &#038; postprod teams!<\/p>","protected":false},"author":392,"featured_media":4636,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_et_pb_use_builder":"on","_et_pb_old_content":"","_et_gb_content_width":"","footnotes":""},"class_list":["post-4627","page","type-page","status-publish","has-post-thumbnail","hentry"],"_links":{"self":[{"href":"https:\/\/smartdsp.pro\/fr\/wp-json\/wp\/v2\/pages\/4627","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/smartdsp.pro\/fr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/smartdsp.pro\/fr\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/smartdsp.pro\/fr\/wp-json\/wp\/v2\/users\/392"}],"replies":[{"embeddable":true,"href":"https:\/\/smartdsp.pro\/fr\/wp-json\/wp\/v2\/comments?post=4627"}],"version-history":[{"count":18,"href":"https:\/\/smartdsp.pro\/fr\/wp-json\/wp\/v2\/pages\/4627\/revisions"}],"predecessor-version":[{"id":4708,"href":"https:\/\/smartdsp.pro\/fr\/wp-json\/wp\/v2\/pages\/4627\/revisions\/4708"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/smartdsp.pro\/fr\/wp-json\/wp\/v2\/media\/4636"}],"wp:attachment":[{"href":"https:\/\/smartdsp.pro\/fr\/wp-json\/wp\/v2\/media?parent=4627"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}