
(function impulseScene() {
  'use strict';

  
  
  if (window.__impScene) return;
  window.__impScene = 1;

  

  
  
  try { if (localStorage.getItem('impap')) return; } catch (e) {}

  var qs = new URLSearchParams(location.search);
  var SK = 'impscene';

  if (qs.get('scene') === 'stop') { try { localStorage.removeItem(SK); } catch (e) {} return; }

  if (qs.get('mark') !== null) {
    var MK = 'impmarks';
    if (qs.get('mark') === '1') { try { localStorage.setItem(MK, '[]'); } catch (e) {} }

    var marks = [];
    try { marks = JSON.parse(localStorage.getItem(MK) || '[]'); } catch (e) { marks = []; }

    var mx = 0, my = 0;
    document.addEventListener('mousemove', function (e) { mx = e.clientX; my = e.clientY; }, true);

    var badge = document.createElement('div');
    badge.style.cssText = 'position:fixed;left:8px;top:8px;z-index:2147483647;background:#c0392b;'
      + 'color:#fff;font:700 11px/1.6 Arial,sans-serif;padding:6px 10px;border-radius:4px;'
      + 'pointer-events:none;white-space:pre';
    function scrollFrac() {
      var max = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
      return +(window.scrollY / max).toFixed(4);
    }
    function paint(note) {

      var NL = String.fromCharCode(10);
      badge.textContent = 'MARK  ' + marks.length + ' point' + (marks.length === 1 ? '' : 's')
        + NL + 'P look   C click   S scroll   Z undo   D dump'
        + (note ? NL + note : '');
    }
    function store() { try { localStorage.setItem(MK, JSON.stringify(marks)); } catch (e) {} }

    function toSteps() {
      var out = [], prevF = null, prevT = null;
      marks.forEach(function (m, i) {

        
        var gap = (prevT === null) ? 1.6 : Math.min(12, Math.max(0.35, (m.t - prevT) / 1000));
        prevT = m.t;
        var travel = Math.min(2.2, Math.max(0.3, gap * 0.55));
        var hold   = Math.max(0.15, gap - travel);

        if (m.k === 's' || prevF === null || Math.abs(m.f - prevF) > 0.01) {
          out.push({ do: 'scroll', to: m.f, dur: +(m.k === 's' ? gap : travel).toFixed(2) });
          prevF = m.f;
          if (m.k === 's') return;
        }
        out.push({ do: m.k === 'c' ? 'clickAt' : 'point',
                   x: m.x, y: m.y, dur: +travel.toFixed(2) });
        out.push({ do: 'wait', t: +hold.toFixed(2) });
      });
      return out;
    }

    document.addEventListener('keydown', function (e) {

      
      var a = document.activeElement;
      if (a && (/^(INPUT|TEXTAREA|SELECT)$/.test(a.tagName) || a.isContentEditable)) return;
      if (e.metaKey || e.ctrlKey || e.altKey) return;

      var k = (e.key || '').toLowerCase();
      if (k === 'p' || k === 's' || k === 'c') {

        
        marks.push({ k: k, t: Date.now(), f: scrollFrac(), p: location.pathname,
                     x: +(mx / Math.max(1, window.innerWidth)).toFixed(4),
                     y: +(my / Math.max(1, window.innerHeight)).toFixed(4) });
        store();
        paint(k === 's' ? 'scroll ' + scrollFrac()
                        : (k === 'c' ? 'CLICK at ' : 'point at ')
                          + Math.round(mx) + ',' + Math.round(my));
        e.preventDefault();
      } else if (k === 'z') {
        marks.pop(); store(); paint('undone');
        e.preventDefault();
      } else if (k === 'd') {
        var steps = toSteps();
        try { console.log(JSON.stringify(steps, null, 2)); } catch (er) {}
        try {
          fetch('/api/instinctor/v1/track/save', {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ track: marks })
          });
        } catch (er) {}
        paint('dumped ' + steps.length + ' steps (console + uploaded)');
        e.preventDefault();
      }
    }, true);

    function mountBadge() {
      if (!document.body) return setTimeout(mountBadge, 100);
      document.body.appendChild(badge);
      paint('');
    }
    mountBadge();
    return;                       
  }

  var GAP = 8;                       
  var QUEUE = [

    
    { biz: 'Fresh Start Cleaning', city: 'Columbus, OH', quoteFirst: true,
      trade: 'Cleaning business doing house cleaning, deep cleans and move-out cleaning' },
    { biz: 'Corner Handyman', city: 'Philadelphia, PA', quoteFirst: true,
      trade: 'Handyman doing repairs, assembly, TV mounting and small jobs around the house' },
    { biz: 'Summit Roofing', city: 'Columbus, OH', quoteFirst: true,
      trade: 'Roofing company doing repairs, replacements and storm damage' },
    { biz: 'Bloom Beauty Salon', city: 'Sacramento, CA',

      trade: 'Beauty salon offering a full range of treatments for women and men' },
    { biz: 'Marchetti Hair', city: 'Austin, TX',
      trade: 'Hair salon doing cuts, color and balayage' },
    { biz: 'Polished Nail Bar', city: 'Austin, TX',
      trade: 'Nail salon doing gel manicures, pedicures and nail art' },
    { biz: 'Rouge Makeup Studio', city: 'Nashville, TN',
      trade: 'Makeup studio doing bridal, events and lessons' },
    { biz: 'Stillwater Day Spa', city: 'Asheville, NC',
      trade: 'Day spa doing facials, massage and body treatments' },
    { biz: 'Lash & Line', city: 'Scottsdale, AZ',
      trade: 'Lash studio doing extensions, lifts and tints' },
    { biz: 'Arch Brow Bar', city: 'Denver, CO',
      trade: 'Brow bar doing shaping, tinting and microblading' },
    { biz: 'Fold & Blade', city: 'Portland, OR',
      trade: 'Barber shop doing fades, beard trims and hot towel shaves' },
    { biz: 'Golden Hour Tanning', city: 'Tampa, FL',
      trade: 'Tanning salon doing spray tans and sunbeds' },
    { biz: 'Smooth Skin Studio', city: 'Charlotte, NC',
      trade: 'Waxing and esthetics studio doing waxing and skin treatments' },
  ];

  function varsFor(entry) {
    
    var stem = entry.biz.toLowerCase()
      .replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).slice(0, 2).join('.');
    var slug = entry.biz.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
    return {
      biz: entry.biz, trade: entry.trade, city: entry.city,
      email: stem + '@gmail.com',
      slug:  slug,
      phone: '(215) 555 0142',
      guest: 'Dana Reyes', guestPhone: '(512) 555 0148',

      quoteNote: 'Two rooms upstairs, and a hallway. When could you take a look?',

      quoteFirst: !!entry.quoteFirst,

      
      newPrice: '$60',
      bookDate: (function () {
        var d = new Date(); d.setDate(d.getDate() + 1);
        return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2)
             + '-' + ('0' + d.getDate()).slice(-2);
      })()
    };
  }

  
  var SCRIPT = {
    scenes: [

      { name: 'home', at: '/', steps: [
        { do: 'wait',  t: 10.0 },
        { do: 'orbit', r: 0.16, dur: 6.0 },
        { do: 'move',  to: 'cta.start', dur: 1.4 },
        { do: 'click', on: 'cta.start' }
      ]},
      { name: 'entry', at: '/make-a-site', steps: [
        { do: 'wait',  t: 1.2 },
        { do: 'type',  on: 'field.biz',   val: 'biz',   dur: 1.1 },
        { do: 'type',  on: 'field.trade', val: 'trade', dur: 1.6 },
        { do: 'type',  on: 'field.city',  val: 'city',  dur: 0.7 },
        { do: 'type',  on: 'field.email', val: 'email', dur: 1.0 },
        { do: 'type',  on: 'field.phone', val: 'phone', dur: 0.8 },
        { do: 'move',  to: 'cta.build', dur: 0.9 },
        { do: 'click', on: 'cta.build' }

      ]},

      
      
      { name: 'reveal', at: '/demo/', steps: [
        { do: 'wait',  t: 2.5 },
        
        { do: 'orbit', cx: 0.72, cy: 0.46, r: 0.15, dur: 6.5 }
      ]},

      

      

      

      

      
      { name: 'colours', steps: [
        { do: 'into',  on: 'section.prices', dur: 3.2 },
        { do: 'wait',  t: 1.0 },
        { do: 'wake' },

        
        { do: 'move',  to: 'chip.2', dur: 0.7 },
        { do: 'click', on: 'chip.2' }, { do: 'wait', t: 1.3 },
        { do: 'move',  to: 'chip.0', dur: 0.5 },
        { do: 'click', on: 'chip.0' }, { do: 'wait', t: 1.0 },
        { do: 'sleep' }
      ]},

      

      

      

      { name: 'edit', steps: [
        { do: 'into',  on: 'edit.price', offset: 200, dur: 3.0 },
        { do: 'wait',  t: 1.2 },
        { do: 'edit',  on: 'edit.price', val: 'newPrice', dur: 1.8 },
        { do: 'wait',  t: 5.5 }        
      ]},
      { name: 'tour', steps: [
        { do: 'scroll', to: 0.25, dur: 6.0 },
        { do: 'scroll', to: 0.45, dur: 6.0 },
        { do: 'scroll', to: 0.65, dur: 6.0 },
        { do: 'scroll', to: 0.85, dur: 6.0 },
        { do: 'scroll', to: 1.00, dur: 4.0 },
        { do: 'wait',   t: 2.0 }
      ]},
      { name: 'publish', steps: [
        { do: 'move',  to: 'cta.publish', dur: 1.0 },
        { do: 'click', on: 'cta.publish' },
        { do: 'wait',  t: 1.6 },
        { do: 'type',  on: 'field.slug', val: 'slug', dur: 0.9 },
        { do: 'move',  to: 'cta.publishGo', dur: 0.7 },
        { do: 'click', on: 'cta.publishGo' },
        { do: 'wait',  t: 3.0 }
      ]},

      

      { name: 'dashboard', at: '/dashboard', steps: [
        { do: 'cursor', act: 'hide' },     
        { do: 'wait', t: 3.5 },
        { do: 'goto', url: '/dashboard/bookings',  altUrl: '/dashboard/quotes', dwell: 6.0 },
        { do: 'goto', url: '/dashboard/clients',   dwell: 6.0 },
        { do: 'goto', url: '/dashboard/chat',      dwell: 6.0 },

        
        
        { do: 'goto', url: '/dashboard/assistant', dwell: 13.0 },
        { do: 'goto', url: '/dashboard/reviews', dwell: 4.0 },   

        { do: 'goto', url: '/dashboard/team',      dwell: 5.5 },
        { do: 'goto', url: '/dashboard/settings',  dwell: 6.0 },
        { do: 'goto', url: '/dashboard',           dwell: 3.0 }
      ]},

      { name: 'live', steps: [
        { do: 'cursor', act: 'show' },     
        { do: 'move',   to: 'cta.viewSite', dur: 1.2 },
        { do: 'follow', on: 'cta.viewSite', dwell: 3.0 },
        { do: 'orbit',  cx: 0.72, cy: 0.46, r: 0.14, dur: 5.0 },
        { do: 'scroll', to: 0.22, dur: 3.5 },
        { do: 'scroll', to: 0.45, dur: 3.5 },
        { do: 'scroll', to: 0.68, dur: 3.5 },
        { do: 'scroll', to: 0.88, dur: 3.0 },
        { do: 'wait',   t: 3.5 }
      ]},

      
      { name: 'quoting', when: 'quoteFirst', steps: [
        { do: 'into',  on: 'quote.name', offset: 90, dur: 3.0 },
        { do: 'wait',  t: 0.8 },
        { do: 'set',   on: 'quote.name',  val: 'guest', hold: 1.0 },
        { do: 'set',   on: 'quote.phone', val: 'guestPhone', hold: 1.0 },
        { do: 'set',   on: 'quote.note',  val: 'quoteNote', hold: 1.4 },
        { do: 'move',  to: 'quote.submit', dur: 1.0 },
        { do: 'click', on: 'quote.submit' },
        { do: 'wait',  t: 5.0 }
      ]},
      { name: 'booking', when: 'books', steps: [

        

        { do: 'into',  on: 'book.service', offset: 90, dur: 3.0 },
        { do: 'wait',  t: 0.8 },
        { do: 'set',   on: 'book.service', opt: 1, hold: 1.0 },
        { do: 'set',   on: 'book.date',    val: 'bookDate', hold: 1.0 },
        { do: 'set',   on: 'book.time',    opt: 12, hold: 1.0 },
        { do: 'set',   on: 'book.name',    val: 'guest', hold: 0.9 },
        { do: 'set',   on: 'book.phone',   val: 'guestPhone', hold: 0.9 },
        { do: 'move',  to: 'book.submit', dur: 1.0 },
        { do: 'click', on: 'book.submit' },
        { do: 'wait',  t: 5.0 }
      ]},

      

      
      { name: 'landed', steps: [
        { do: 'cursor', act: 'hide' },
        { do: 'goto', url: '/dashboard/bookings', altUrl: '/dashboard/quotes', dwell: 11.0 },
        { do: 'wait', t: 2.5 }
      ]}
    ]
  };

  var TARGETS = {

    
    'cta.start':      ['a[href="/make-a-site"]'],
    'cta.build':      ['[data-demo-go]', 'button.demo-btn'],
    'field.biz':      ['input[placeholder^="Your business name"]'],
    'field.trade':    ['textarea[placeholder^="What you do"]', 'input[placeholder^="What you do"]'],
    'field.city':     ['input[placeholder^="City"]'],
    'field.email':    ['input[placeholder^="Email"]'],
    'field.phone':    ['input[placeholder^="Phone"]'],
    'field.slug':     ['#imp-pub-slug'],
    'cta.publish':    ['#imp-cp-claimfab'],
    'cta.publishGo':  ['#imp-pub-go'],
    'hero':           ['section[data-depth-image]'],
    
    'section.prices': ['.impulse-price-item', '.impulse-pricing', '.impulse-plans'],
    'cta.viewSite':   ['a.btn.ghost[href]'],

    'quote.name':     ['input.ime-name'],
    'quote.phone':    ['input.ime-phone'],
    'quote.note':     ['textarea.ime-note'],
    'quote.submit':   ['button.ime-btn'],
    'book.service':   ['select.ibk-req-svc', 'select.ibk-svc'],
    'book.date':      ['input.ibk-req-date', 'input.ibk-date'],
    'book.time':      ['select.ibk-req-time'],
    'book.name':      ['input.ibk-name'],
    'book.phone':     ['input.ibk-phone'],
    'book.submit':    ['button.ibk-req-btn', 'button.ibk-submit-btn'],

    

    'edit.price':     [{ sel: '.impulse-pl-price', idx: 0 }],
    'edit.service':   [{ sel: '.impulse-pl-title', idx: 0 }],
    'chip.0':         [{ sel: 'button.imp-color-chip', idx: 0 }],
    'chip.1':         [{ sel: 'button.imp-color-chip', idx: 1 }],
    'chip.2':         [{ sel: 'button.imp-color-chip', idx: 2 }],
    'chip.3':         [{ sel: 'button.imp-color-chip', idx: 3 }]
  };

  function resolve(name) {
    var list = TARGETS[name] || [name];
    for (var i = 0; i < list.length; i++) {
      var t = list[i], el = null;
      try {
        el = (typeof t === 'string') ? document.querySelector(t)
                                     : document.querySelectorAll(t.sel)[t.idx];
      } catch (e) { el = null; }
      if (el) return el;
    }
    return null;
  }

  var st = null;
  try { st = JSON.parse(localStorage.getItem(SK) || 'null'); } catch (e) { st = null; }

  if (qs.get('scene') === '1' || qs.get('scene') === 'all') {
    var stamp = Date.now().toString(36);

    
    
    var qStart = Math.min(QUEUE.length - 1, Math.max(0, (parseInt(qs.get('start'), 10) || 1) - 1));
    st = {
      t0: Date.now(),
      speed: parseFloat(qs.get('speed') || '1') || 1,
      scene: qs.get('from') || 'home',
      step: 0,
      edit: qs.get('edit') === '1',
      queue: qs.get('scene') === 'all' ? qStart : null,   

      
      
      vars: qs.get('scene') === 'all' ? varsFor(QUEUE[qStart]) : {
        biz:   qs.get('biz')   || 'Polished Nail Bar',
        trade: qs.get('trade') || 'Nail salon doing gel manicures, pedicures and nail art',
        city:  qs.get('city')  || 'Austin, TX',
        email: qs.get('email') || 'your.salon@gmail.com',
        phone: qs.get('phone') || '(215) 555 0142',
        slug:  qs.get('slug')  || 'your-salon',
        guest: qs.get('guest') || 'Dana Reyes',
        guestPhone: qs.get('guestPhone') || '(512) 555 0148',
        
        bookDate: (function () {
          var d = new Date(); d.setDate(d.getDate() + 1);
          return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2)
               + '-' + ('0' + d.getDate()).slice(-2);
        })()
      }
    };
    try { localStorage.setItem(SK, JSON.stringify(st)); } catch (e) {}
    location.replace(location.pathname);
    return;
  }
  if (!st) return;

  var VARS   = st.vars || {};
  
  var SCENES = (st.override && st.script ? st.script : SCRIPT).scenes;
  var speed  = st.speed || 1;

  var cur = document.createElement('div');
  cur.style.cssText = 'position:fixed;left:50%;top:50%;z-index:2147483646;width:22px;height:22px;'
    + 'margin:-11px 0 0 -11px;border-radius:50%;background:rgba(255,255,255,0.94);'
    + 'box-shadow:0 0 0 2px rgba(0,0,0,0.35),0 3px 12px rgba(0,0,0,0.4);pointer-events:none;'
    + 'transition:transform 90ms ease-in-out';
  var hud = document.createElement('div');
  hud.style.cssText = 'position:fixed;left:8px;top:8px;z-index:2147483647;background:#1d2327;color:#fff;'
    + 'font:600 11px/1.5 Arial,sans-serif;padding:5px 9px;border-radius:3px;pointer-events:none;opacity:.85';
  function mount() {
    if (!document.body) return;
    cur.style.display = curHidden ? 'none' : '';
    if (!cur.isConnected) document.body.appendChild(cur);

    
    if (st.edit && !hud.isConnected) document.body.appendChild(hud);
  }
  if (document.body) mount(); else document.addEventListener('DOMContentLoaded', mount);

  var px = window.innerWidth / 2, py = window.innerHeight / 2;

  function put(x, y) {
    px = x; py = y;
    cur.style.left = x + 'px';
    cur.style.top  = y + 'px';
    var under = document.elementFromPoint(x, y) || document.documentElement;
    var init = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y };
    try { under.dispatchEvent(new MouseEvent('mousemove', init)); } catch (e) {}
    if (window.PointerEvent) {
      try {
        var pi = {}; for (var k in init) pi[k] = init[k];
        pi.pointerId = 1; pi.pointerType = 'mouse'; pi.isPrimary = true;
        under.dispatchEvent(new PointerEvent('pointermove', pi));
      } catch (e) {}
    }
  }
  function pulse() {
    cur.style.transform = 'scale(0.6)';
    setTimeout(function () { cur.style.transform = 'scale(1)'; }, 130);
  }

  function easeInOut(p) { return p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2; }

  function pathPoint(a, b, p) {
    var e = easeInOut(p);
    var x = a.x + (b.x - a.x) * e, y = a.y + (b.y - a.y) * e;
    var dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy);
    if (d > 40) {
      var bow = Math.min(d * 0.08, 46) * Math.sin(Math.PI * p);
      x += (-dy / d) * bow;
      y += ( dx / d) * bow;
    }
    return { x: x, y: y };
  }

  var sceneIdx = Math.max(0, SCENES.findIndex(function (s) { return s.name === st.scene; }));
  var stepIdx  = st.step || 0;
  var busy = null;            
  var lastSave = 0;
  var holdUntil = st.holdUntil || 0;   
  var waitStart = 0;                   
  var curHidden = !!st.curHidden;       

  function save() {
    var n = Date.now();
    if (n - lastSave < 400) return;
    lastSave = n;
    try {
      localStorage.setItem(SK, JSON.stringify({
        t0: st.t0, speed: speed, scene: SCENES[sceneIdx] ? SCENES[sceneIdx].name : '',
        step: stepIdx, edit: st.edit, queue: st.queue, override: st.override,
        curHidden: curHidden,
        script: st.script, vars: VARS,
        holdUntil: holdUntil
      }));
    } catch (e) {}
  }

  function centreOf(el) {
    var r = el.getBoundingClientRect();
    return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
  }

  function begin(step) {
    var now = performance.now();
    var el, c;
    switch (step.do) {
      case 'wait':
        return { kind: 'wait', until: now + (step.t || 1) * 1000 / speed };

      case 'move':
        el = resolve(step.to);
        if (!el) return null;                       
        c = centreOf(el);
        return { kind: 'move', from: { x: px, y: py }, to: c,
                 until: now + (step.dur || 1) * 1000 / speed,
                 dur: (step.dur || 1) * 1000 / speed, start: now };

      case 'cursor': {

        
        curHidden = (step.act === 'hide');
        cur.style.display = curHidden ? 'none' : '';
        lastSave = 0; save();
        return { kind: 'wait', until: now + 60 };
      }

      case 'wake': {

        

        

        
        var bar = document.getElementById('impulse-demo-bar');
        if (bar) {
          bar.classList.add('in');
          bar.style.opacity = '1';
          bar.style.translate = '0 0';
        }
        return { kind: 'wait', until: now + 600 / speed };
      }

      case 'sleep': {
        
        var sb = document.getElementById('impulse-demo-bar');
        if (sb) { sb.style.opacity = ''; sb.style.translate = ''; }
        return { kind: 'wait', until: now + 200 / speed };
      }

      case 'pick': {

        var thumbs = document.querySelectorAll('.imp-swap-grid .imp-swap-thumb');
        var pick = thumbs[Math.min(step.i || 1, Math.max(0, thumbs.length - 1))];
        if (pick) {
          var pr = pick.getBoundingClientRect();
          var pcx = pr.left + pr.width / 2, pcy = pr.top + pr.height / 2;
          put(pcx, pcy); pulse();
          ['mousedown', 'mouseup', 'click'].forEach(function (type) {
            try {
              pick.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true,
                view: window, clientX: pcx, clientY: pcy }));
            } catch (e) {}
          });
        }
        return { kind: 'wait', until: now + 700 / speed };
      }

      case 'clickAt': {

        var cx = step.x * window.innerWidth, cy = step.y * window.innerHeight;
        var hit = document.elementFromPoint(cx, cy);
        put(cx, cy); pulse();
        if (hit && !(hit.tagName === 'A' && /^https?:\/\//i.test(hit.getAttribute('href') || '')
                     && (hit.getAttribute('href') || '').indexOf(location.host) === -1)) {
          ['mousedown', 'mouseup', 'click'].forEach(function (type) {
            try {
              hit.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true,
                view: window, clientX: cx, clientY: cy }));
            } catch (e) {}
          });
        }
        return { kind: 'wait', until: now + 260 / speed };
      }

      case 'point':
        
        return { kind: 'move', from: { x: px, y: py },
                 to: { x: step.x * window.innerWidth, y: step.y * window.innerHeight },
                 start: now, dur: (step.dur || 1.2) * 1000 / speed,
                 until: now + (step.dur || 1.2) * 1000 / speed };

      case 'orbit':

        
        
        return { kind: 'orbit', r: step.r || 0.15,
                 cx: step.cx === undefined ? 0.5 : step.cx,
                 cy: step.cy === undefined ? 0.48 : step.cy, start: now,
                 dur: (step.dur || 5) * 1000 / speed,
                 until: now + (step.dur || 5) * 1000 / speed };

      case 'follow': {

        
        var link = resolve(step.on);
        if (!link) return { kind: 'wait', until: now + 200 };
        var lc = centreOf(link);
        put(lc.x, lc.y); pulse();
        return { kind: 'goto', url: link.getAttribute('href') || '/',
                 until: now + (step.dwell || 2) * 1000 / speed, went: false };
      }

      case 'set': {

        var f = resolve(step.on);
        if (!f) return { kind: 'wait', until: now + 150 };
        var fc = centreOf(f);
        put(fc.x, fc.y);
        try { f.focus(); } catch (e) {}
        if (f.tagName === 'SELECT') {
          var opts = f.options || [];
          var idx = Math.min(step.opt || 1, Math.max(0, opts.length - 1));
          f.selectedIndex = idx;
        } else {
          f.value = String(VARS[step.val] || step.val || '');
        }
        try {
          f.dispatchEvent(new Event('input',  { bubbles: true }));
          f.dispatchEvent(new Event('change', { bubbles: true }));
        } catch (e) {}
        pulse();
        return { kind: 'wait', until: now + (step.hold || 0.8) * 1000 / speed };
      }

      case 'goto':

        

        
        return { kind: 'goto', url: (VARS.quoteFirst && step.altUrl) ? step.altUrl : step.url,
                 until: now + (step.dwell || 3) * 1000 / speed, went: false };

      case 'drift':
        return { kind: 'drift', box: step.box || [0.35, 0.3, 0.65, 0.6],
                 start: now, dur: (step.dur || 3) * 1000 / speed,
                 until: now + (step.dur || 3) * 1000 / speed, from: { x: px, y: py } };

      case 'click':
        el = resolve(step.on);

        if (el && el.tagName === 'A') {
          var href = el.getAttribute('href') || '';
          if (/^https?:\/\//i.test(href) && href.indexOf(location.host) === -1) {
            if (st.edit) hud.textContent = 'blocked off-site click: ' + href.slice(0, 40);
            return { kind: 'wait', until: now + 200 };
          }
        }
        if (el) {
          c = centreOf(el);
          put(c.x, c.y); pulse();
          ['mousedown', 'mouseup', 'click'].forEach(function (type) {
            try {
              el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true,
                view: window, clientX: c.x, clientY: c.y }));
            } catch (e) {}
          });
        }
        return { kind: 'wait', until: now + 260 / speed };

      case 'type':
        el = resolve(step.on);
        if (!el) return null;
        c = centreOf(el);
        put(c.x, c.y);
        try { el.focus(); } catch (e) {}
        return { kind: 'type', el: el, txt: String(VARS[step.val] || step.val || ''),
                 start: now, dur: (step.dur || 1.2) * 1000 / speed,
                 until: now + (step.dur || 1.2) * 1000 / speed, n: 0 };

      case 'edit': {

        

        
        
        el = resolve(step.on);
        if (!el) return { kind: 'wait', until: now + 150 };
        c = centreOf(el);
        put(c.x, c.y); pulse();
        try { el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true })); } catch (e) {}
        try { el.focus(); } catch (e) {}
        var eDur = (step.dur || 2.2) * 1000 / speed;
        var eArm = now + 400 / speed;          
        return { kind: 'edit', el: el, txt: String(VARS[step.val] || step.val || ''),
                 start: eArm, dur: eDur, until: eArm + eDur + 600 / speed, n: -1, done: false };
      }

      case 'into': {

        
        
        var tgt = resolve(step.on);
        if (!tgt) return { kind: 'wait', until: now + 150 };
        var sec = tgt.closest ? (tgt.closest('section') || tgt) : tgt;
        var top = sec.getBoundingClientRect().top + window.scrollY - (step.offset || 0);
        return { kind: 'scroll', from: window.scrollY,
                 to: Math.max(0, Math.min(top, document.documentElement.scrollHeight - window.innerHeight)),
                 start: now, dur: (step.dur || 3) * 1000 / speed,
                 until: now + (step.dur || 3) * 1000 / speed };
      }

      case 'scroll':

        var openModal = document.querySelector('.imp-swap-modal.in');
        if (openModal) {
          var xb = document.querySelector('.imp-swap-close');
          if (xb) xb.click(); else openModal.classList.remove('in');
        }
        var max = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
        return { kind: 'scroll', from: window.scrollY, to: (step.to || 0) * max,
                 start: now, dur: (step.dur || 2) * 1000 / speed,
                 until: now + (step.dur || 2) * 1000 / speed };

      case 'gallery':
        if (step.act === 'close') {
          var cl = document.querySelector('.imp-swap-close');
          if (cl) { pulse(); cl.click(); }
          return { kind: 'wait', until: now + 500 / speed };
        }
        var hero = resolve('hero');
        if (hero) {

          
          var hrect = hero.getBoundingClientRect();
          if (hrect.top < 0 || hrect.bottom > window.innerHeight) {
            window.scrollTo({ top: Math.max(0, hero.offsetTop - 40), behavior: 'instant' });
          }
          var hr = centreOf(hero);
          put(hr.x, hr.y);
          put(hr.x + 6, hr.y + 4);

          
          
          try {
            hero.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true,
              view: window, clientX: hr.x, clientY: hr.y }));
          } catch (e) {}

          

          

          setTimeout(function () {
            var cta = document.getElementById('imp-swap-cta');
            if (cta && getComputedStyle(cta).display !== 'none') { pulse(); cta.click(); }
          }, 450);
        }
        return { kind: 'wait', until: now + 900 / speed };

      case 'tiles':
        var tiles = Array.prototype.slice.call(document.querySelectorAll('.grid a.tile'));
        return { kind: 'tiles', tiles: tiles, i: 0, dwell: (step.dwell || 3) * 1000 / speed,
                 next: now, until: now + 1e9 };

      default:
        return { kind: 'wait', until: now + 200 };
    }
  }

  function advance(b, now) {
    switch (b.kind) {
      case 'move': {
        var p = Math.min(1, (now - b.start) / b.dur);
        var pt = pathPoint(b.from, b.to, p);
        put(pt.x, pt.y);
        return p >= 1;
      }
      case 'orbit': {
        var op = (now - b.start) / b.dur;
        var ang = op * Math.PI * 2;
        put((b.cx + Math.cos(ang) * b.r * 1.35) * window.innerWidth,
            (b.cy + Math.sin(ang) * b.r) * window.innerHeight);
        return now >= b.until;
      }
      case 'goto': {
        if (b.went) return now >= b.until;
        b.went = true;

        
        stepIdx++;
        holdUntil = Date.now() + (b.until - now);
        lastSave = 0; save();
        location.href = b.url;
        return false;                    
      }
      case 'drift': {
        
        var t = (now - b.start) / 1000;
        var bx = b.box;
        var cx = (bx[0] + bx[2]) / 2, cy = (bx[1] + bx[3]) / 2;
        var rx = (bx[2] - bx[0]) / 2, ry = (bx[3] - bx[1]) / 2;
        put((cx + Math.sin(t * 0.9) * rx) * window.innerWidth,
            (cy + Math.cos(t * 0.62) * ry) * window.innerHeight);
        return now >= b.until;
      }
      case 'type': {
        var q = Math.min(1, (now - b.start) / b.dur);
        var want = Math.round(q * b.txt.length);
        if (want !== b.n) {
          b.n = want;
          var part = b.txt.slice(0, want);
          
          if (/^(INPUT|TEXTAREA)$/.test(b.el.tagName)) b.el.value = part;
          else b.el.textContent = part;
          try { b.el.dispatchEvent(new Event('input', { bubbles: true })); } catch (e) {}
        }
        return q >= 1;
      }
      case 'edit': {
        if (now < b.start) return false;
        var qe = Math.min(1, (now - b.start) / b.dur);
        var wantE = Math.round(qe * b.txt.length);
        if (wantE !== b.n) {
          b.n = wantE;                       
          b.el.textContent = b.txt.slice(0, wantE);
          try { b.el.dispatchEvent(new Event('input', { bubbles: true })); } catch (e) {}
        }
        if (qe >= 1 && !b.done) {
          b.done = true;
          try { b.el.blur(); } catch (e) {}  
        }
        return now >= b.until;               
      }
      case 'scroll': {
        var s = Math.min(1, (now - b.start) / b.dur);
        window.scrollTo({ top: b.from + (b.to - b.from) * easeInOut(s), behavior: 'instant' });
        return s >= 1;
      }
      case 'tiles': {
        if (!b.tiles.length) return true;
        if (now < b.next) return false;
        if (b.i >= b.tiles.length) return true;
        var tile = b.tiles[b.i++];
        var tc = centreOf(tile);
        put(tc.x, tc.y); pulse();
        try { tile.click(); } catch (e) {}
        b.next = now + b.dwell;

        setTimeout(function () { if (location.pathname !== '/dashboard') history.back(); },
                   Math.max(600, b.dwell - 700));
        return false;
      }
      default:
        return now >= b.until;
    }
  }

  function tick() {
    var now = performance.now();
    
    if (holdUntil && Date.now() < holdUntil) return;
    if (holdUntil) { holdUntil = 0; save(); }
    var scene = SCENES[sceneIdx];

    
    
    while (scene && scene.when
           && ((scene.when === 'quoteFirst' && !VARS.quoteFirst)
            || (scene.when === 'books' && VARS.quoteFirst))) {
      sceneIdx++; stepIdx = 0; busy = null; save();
      scene = SCENES[sceneIdx];
    }

    if (!scene) {

      var qi = st.queue;
      if (qi !== null && qi !== undefined && qi + 1 < QUEUE.length) {
        clearInterval(tickHandle);
        var next = qi + 1;
        if (st.edit) hud.textContent = 'next: ' + QUEUE[next].biz + ' in ' + GAP + 's';
        cur.remove();
        try {
          localStorage.setItem(SK, JSON.stringify({
            t0: Date.now(), speed: speed, scene: 'home', step: 0, curHidden: false,
            edit: st.edit, queue: next, vars: varsFor(QUEUE[next])
          }));
        } catch (e) {}
        setTimeout(function () { location.href = '/'; }, GAP * 1000);
        return;
      }
      if (st.edit) hud.textContent = 'scene: done';
      try { localStorage.removeItem(SK); } catch (e) {}
      setTimeout(function () { cur.remove(); hud.remove(); }, 1500);
      return;
    }

    

    
    if (stepIdx >= scene.steps.length) { sceneIdx++; stepIdx = 0; busy = null; save(); return; }

    if (scene.at && location.pathname.indexOf(scene.at) !== 0
        && !(scene.at === '/' && location.pathname === '/')) {

      

      

      
      if (stepIdx > 0) { sceneIdx++; stepIdx = 0; busy = null; waitStart = 0; save(); return; }
      if (!waitStart) waitStart = now;
      if (now - waitStart > 45000) {
        waitStart = 0; sceneIdx++; stepIdx = 0; busy = null; save();
        return;
      }
      if (st.edit) hud.textContent = 'scene: ' + scene.name + ' (waiting for ' + scene.at
        + '  ' + Math.round((45000 - (now - waitStart)) / 1000) + 's)';
      return;
    }
    waitStart = 0;

    if (!busy) {
      var step = scene.steps[stepIdx];
      if (!step) { sceneIdx++; stepIdx = 0; save(); return; }
      busy = begin(step);
      if (!busy) { stepIdx++; save(); return; }     
    }

    
    
    if (!busy.hardStop) busy.hardStop = now + 25000;
    if (now > busy.hardStop) {
      if (st.edit) hud.textContent = 'skipped a stalled step';
      busy = null; stepIdx++; save();
    } else if (advance(busy, now)) { busy = null; stepIdx++; save(); }

    if (st.edit) hud.textContent = 'scene ' + scene.name + '  step ' + (stepIdx + 1) + '/' + scene.steps.length;
  }

  var tickHandle = setInterval(tick, 16);

  if (st.edit) {
    var panel = document.createElement('div');
    panel.style.cssText = 'position:fixed;right:10px;top:10px;width:250px;max-height:76vh;overflow:auto;'
      + 'z-index:2147483647;background:rgba(20,23,26,.94);color:#fff;font:12px/1.5 Arial,sans-serif;'
      + 'border-radius:10px;padding:10px 12px;box-shadow:0 10px 40px rgba(0,0,0,.5)';
    function draw() {
      var s = SCENES[sceneIdx] || { name: '-', steps: [] };
      var h = '<b>' + s.name + '</b> &middot; step ' + (stepIdx + 1) + '/' + s.steps.length
            + '<div style="margin:6px 0 8px;color:#9aa">cursor ' + Math.round(px) + ', ' + Math.round(py) + '</div>';
      h += '<button data-sc="prev" style="width:48%">&laquo; scene</button>'
         + '<button data-sc="next" style="width:48%;float:right">scene &raquo;</button>';
      h += '<div style="margin-top:8px">speed <input data-speed type="number" step="0.1" min="0.2" '
         + 'value="' + speed + '" style="width:60px"></div>';
      h += '<div style="margin-top:8px;color:#9aa">steps</div>';
      s.steps.forEach(function (st2, i) {
        h += '<div style="padding:2px 0;' + (i === stepIdx ? 'color:#ffd479' : '') + '">'
           + (i + 1) + '. ' + st2.do + (st2.to || st2.on || '') + ' '
           + '<input data-dur="' + i + '" type="number" step="0.1" value="' + (st2.dur || st2.t || '') + '" '
           + 'style="width:52px;float:right"></div>';
      });
      h += '<button data-sc="copy" style="width:100%;margin-top:10px">copy script JSON</button>';
      panel.innerHTML = h;
    }
    panel.addEventListener('click', function (e) {
      var a = e.target.getAttribute && e.target.getAttribute('data-sc');
      if (!a) return;
      if (a === 'prev') { sceneIdx = Math.max(0, sceneIdx - 1); stepIdx = 0; busy = null; }
      if (a === 'next') { sceneIdx++; stepIdx = 0; busy = null; }
      if (a === 'copy') {
        try { navigator.clipboard.writeText(JSON.stringify(st.script, null, 2)); } catch (err) {}
      }
      save(); draw();
    });
    panel.addEventListener('change', function (e) {
      var d = e.target.getAttribute && e.target.getAttribute('data-dur');
      if (d !== null && d !== undefined) {
        var s = SCENES[sceneIdx], k = s.steps[+d];
        if (k) { if (k.dur !== undefined) k.dur = +e.target.value; else k.t = +e.target.value; }
        st.script = { scenes: SCENES }; st.override = true;   
      }
      if (e.target.hasAttribute && e.target.hasAttribute('data-speed')) speed = +e.target.value || 1;
      save();
    });
    (document.body || document.documentElement).appendChild(panel);
    setInterval(draw, 500);
  }
})();
