/**
 * sx.js — S-expression parser, evaluator, and DOM renderer. [v2-debug]
 *
 * Client-side counterpart to shared/sx/ Python modules.
 * Parses s-expression text, evaluates it, and renders to DOM nodes.
 *
 * Usage:
 *   Sx.loadComponents('(defcomp ~card (&key title) (div :class "c" title))');
 *   const node = Sx.render('(~card :title "Hello")');
 *   document.body.appendChild(node);
 */
;(function (global) {
  "use strict";

  // --- Types ---

  /** Singleton nil — falsy placeholder. */
  var NIL = Object.freeze({ _nil: true, toString: function () { return "nil"; } });

  function isNil(x) { return x === NIL || x === null || x === undefined; }
  function isSxTruthy(x) { return x !== false && !isNil(x); }

  function Symbol(name) { this.name = name; }
  Symbol.prototype.toString = function () { return this.name; };
  Symbol.prototype._sym = true;

  function Keyword(name) { this.name = name; }
  Keyword.prototype.toString = function () { return ":" + this.name; };
  Keyword.prototype._kw = true;

  function Lambda(params, body, closure, name) {
    this.params = params;
    this.body = body;
    this.closure = closure || {};
    this.name = name || null;
  }
  Lambda.prototype._lambda = true;

  function Component(name, params, hasChildren, body, closure) {
    this.name = name;
    this.params = params;
    this.hasChildren = hasChildren;
    this.body = body;
    this.closure = closure || {};
  }
  Component.prototype._component = true;

  function Macro(params, restParam, body, closure, name) {
    this.params = params;
    this.restParam = restParam;
    this.body = body;
    this.closure = closure || {};
    this.name = name || null;
  }
  Macro.prototype._macro = true;

  /** Thunk — deferred evaluation for tail-call optimization. */
  function _Thunk(expr, env) { this.expr = expr; this.env = env; }
  _Thunk.prototype._thunk = true;
  function isThunk(x) { return x && x._thunk; }

  /** Marker for pre-rendered HTML that bypasses escaping. */
  function RawHTML(html) { this.html = html; }
  RawHTML.prototype._raw = true;

  /** CSSX StyleValue — generated CSS class with rules. */
  function StyleValue(className, declarations, mediaRules, pseudoRules, keyframes) {
    this.className = className;
    this.declarations = declarations || "";
    this.mediaRules = mediaRules || [];
    this.pseudoRules = pseudoRules || [];
    this.keyframes = keyframes || [];
  }
  StyleValue.prototype._styleValue = true;

  function isSym(x) { return x && x._sym === true; }
  function isKw(x) { return x && x._kw === true; }
  function isLambda(x) { return x && x._lambda === true; }
  function isComponent(x) { return x && x._component === true; }
  function isMacro(x) { return x && x._macro === true; }
  function isRaw(x) { return x && x._raw === true; }
  function isStyleValue(x) { return x && x._styleValue === true; }

  // --- Parser ---

  var RE_WS = /\s+/y;
  var RE_COMMENT = /;[^\n]*/y;
  var RE_STRING = /"(?:[^"\\]|\\[\s\S])*"/y;
  var RE_NUMBER = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/y;
  var RE_KEYWORD = /:[a-zA-Z_][a-zA-Z0-9_>:\-]*/y;
  var RE_SYMBOL = /[a-zA-Z_~*+\-><=/!?&][a-zA-Z0-9_~*+\-><=/!?.:&]*/y;

  function Tokenizer(text) {
    this.text = text;
    this.pos = 0;
    this.line = 1;
    this.col = 1;
  }

  Tokenizer.prototype._advance = function (count) {
    for (var i = 0; i < count; i++) {
      if (this.pos < this.text.length) {
        if (this.text[this.pos] === "\n") { this.line++; this.col = 1; }
        else { this.col++; }
        this.pos++;
      }
    }
  };

  Tokenizer.prototype._skip = function () {
    while (this.pos < this.text.length) {
      RE_WS.lastIndex = this.pos;
      var m = RE_WS.exec(this.text);
      if (m && m.index === this.pos) { this._advance(m[0].length); continue; }
      RE_COMMENT.lastIndex = this.pos;
      m = RE_COMMENT.exec(this.text);
      if (m && m.index === this.pos) { this._advance(m[0].length); continue; }
      break;
    }
  };

  Tokenizer.prototype.peek = function () {
    this._skip();
    return this.pos < this.text.length ? this.text[this.pos] : null;
  };

  Tokenizer.prototype.next = function () {
    this._skip();
    if (this.pos >= this.text.length) return null;

    var ch = this.text[this.pos];

    // Delimiters
    if ("()[]{}".indexOf(ch) !== -1) { this._advance(1); return ch; }

    // String
    if (ch === '"') {
      RE_STRING.lastIndex = this.pos;
      var m = RE_STRING.exec(this.text);
      if (!m || m.index !== this.pos) throw parseErr("Unterminated string", this);
      this._advance(m[0].length);
      var raw = m[0].slice(1, -1);
      return raw.replace(/\\n/g, "\n").replace(/\\t/g, "\t")
                .replace(/\\"/g, '"').replace(/\\[/]/g, "/").replace(/\\\\/g, "\\");
    }

    // Keyword
    if (ch === ":") {
      RE_KEYWORD.lastIndex = this.pos;
      m = RE_KEYWORD.exec(this.text);
      if (!m || m.index !== this.pos) throw parseErr("Invalid keyword", this);
      this._advance(m[0].length);
      return new Keyword(m[0].slice(1));
    }

    // Number (before symbol due to leading -)
    if (isDigit(ch) || (ch === "-" && this.pos + 1 < this.text.length &&
        (isDigit(this.text[this.pos + 1]) || this.text[this.pos + 1] === "."))) {
      RE_NUMBER.lastIndex = this.pos;
      m = RE_NUMBER.exec(this.text);
      if (m && m.index === this.pos) {
        this._advance(m[0].length);
        var s = m[0];
        return (s.indexOf(".") !== -1 || s.indexOf("e") !== -1 || s.indexOf("E") !== -1)
          ? parseFloat(s) : parseInt(s, 10);
      }
    }

    // Symbol
    RE_SYMBOL.lastIndex = this.pos;
    m = RE_SYMBOL.exec(this.text);
    if (m && m.index === this.pos) {
      this._advance(m[0].length);
      var name = m[0];
      if (name === "true") return true;
      if (name === "false") return false;
      if (name === "nil") return NIL;
      return new Symbol(name);
    }

    var ctx = this.text.substring(Math.max(0, this.pos - 40), this.pos + 40);
    throw parseErr("Unexpected character: " + ch + " | context: «" + ctx.replace(/\n/g, "\\n") + "»", this);
  };

  function isDigit(c) { return c >= "0" && c <= "9"; }

  function parseErr(msg, tok) {
    return new Error(msg + " at line " + tok.line + ", col " + tok.col);
  }

  function parseExpr(tok) {
    // Use peek() (raw character) for structural decisions so that string
    // values like ")" or "(" don't get confused with actual delimiters.
    var raw = tok.peek();
    if (raw === null) throw parseErr("Unexpected end of input", tok);
    if (raw === ")" || raw === "]" || raw === "}") {
      tok.next(); // consume the delimiter
      throw parseErr("Unexpected " + raw, tok);
    }
    if (raw === "(") { tok.next(); return parseList(tok, ")"); }
    if (raw === "[") { tok.next(); return parseList(tok, "]"); }
    if (raw === "{") { tok.next(); return parseMap(tok); }
    // Quasiquote syntax
    if (raw === "`") { tok._advance(1); return [new Symbol("quasiquote"), parseExpr(tok)]; }
    if (raw === ",") {
      tok._advance(1);
      if (tok.pos < tok.text.length && tok.text[tok.pos] === "@") {
        tok._advance(1);
        return [new Symbol("splice-unquote"), parseExpr(tok)];
      }
      return [new Symbol("unquote"), parseExpr(tok)];
    }
    return tok.next();
  }

  function parseList(tok, closer) {
    var items = [];
    while (true) {
      var c = tok.peek();
      if (c === null) throw parseErr("Unterminated list, expected " + closer, tok);
      if (c === closer) { tok.next(); return items; }
      items.push(parseExpr(tok));
    }
  }

  function parseMap(tok) {
    var result = {};
    while (true) {
      var c = tok.peek();
      if (c === null) throw parseErr("Unterminated map", tok);
      if (c === "}") { tok.next(); return result; }
      var key = parseExpr(tok);
      var keyStr = isKw(key) ? key.name : String(key);
      result[keyStr] = parseExpr(tok);
    }
  }

  /** Parse a single s-expression. */
  function parse(text) {
    var tok = new Tokenizer(text);
    var result = parseExpr(tok);
    if (tok.peek() !== null) throw parseErr("Unexpected content after expression", tok);
    return result;
  }

  /** Parse zero or more s-expressions. */
  function parseAll(text) {
    var tok = new Tokenizer(text);
    var results = [];
    while (tok.peek() !== null) results.push(parseExpr(tok));
    return results;
  }

  /** Serialize a JS object as SX dict {:key "val" ...} for attribute values. */
  function _serializeDict(obj) {
    var parts = [];
    for (var k in obj) {
      if (!obj.hasOwnProperty(k)) continue;
      var v = obj[k];
      var vs = typeof v === "string" ? '"' + v.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"' : String(v);
      parts.push(":" + k + " " + vs);
    }
    return "{" + parts.join(" ") + "}";
  }

  // --- Primitives ---

  var PRIMITIVES = {};

  // Arithmetic
  PRIMITIVES["+"] = function () { var s = 0; for (var i = 0; i < arguments.length; i++) s += arguments[i]; return s; };
  PRIMITIVES["-"] = function (a, b) { return arguments.length === 1 ? -a : a - b; };
  PRIMITIVES["*"] = function () { var s = 1; for (var i = 0; i < arguments.length; i++) s *= arguments[i]; return s; };
  PRIMITIVES["/"] = function (a, b) { return a / b; };
  PRIMITIVES["mod"] = function (a, b) { return a % b; };
  PRIMITIVES["inc"] = function (n) { return n + 1; };
  PRIMITIVES["dec"] = function (n) { return n - 1; };
  PRIMITIVES["abs"] = Math.abs;
  PRIMITIVES["floor"] = Math.floor;
  PRIMITIVES["ceil"] = Math.ceil;
  PRIMITIVES["round"] = Math.round;
  PRIMITIVES["min"] = Math.min;
  PRIMITIVES["max"] = Math.max;
  PRIMITIVES["sqrt"] = Math.sqrt;
  PRIMITIVES["pow"] = Math.pow;

  // Comparison
  PRIMITIVES["="] = function (a, b) { return a == b; };  // loose, matches Python sx
  PRIMITIVES["!="] = function (a, b) { return a != b; };
  PRIMITIVES["<"] = function (a, b) { return a < b; };
  PRIMITIVES[">"] = function (a, b) { return a > b; };
  PRIMITIVES["<="] = function (a, b) { return a <= b; };
  PRIMITIVES[">="] = function (a, b) { return a >= b; };

  // Logic
  PRIMITIVES["not"] = function (x) { return !isSxTruthy(x); };

  // String
  PRIMITIVES["str"] = function () {
    var parts = [];
    for (var i = 0; i < arguments.length; i++) {
      var v = arguments[i];
      if (isNil(v)) continue;
      parts.push(String(v));
    }
    return parts.join("");
  };
  PRIMITIVES["upper"] = function (s) { return String(s).toUpperCase(); };
  PRIMITIVES["lower"] = function (s) { return String(s).toLowerCase(); };
  PRIMITIVES["trim"] = function (s) { return String(s).trim(); };
  PRIMITIVES["split"] = function (s, sep) { return String(s).split(sep); };
  PRIMITIVES["join"] = function (sep, coll) { return coll.join(sep); };
  PRIMITIVES["starts-with?"] = function (s, p) { return String(s).indexOf(p) === 0; };
  PRIMITIVES["ends-with?"] = function (s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; };
  PRIMITIVES["concat"] = function () {
    var out = [];
    for (var i = 0; i < arguments.length; i++) out = out.concat(arguments[i]);
    return out;
  };

  // Predicates
  PRIMITIVES["nil?"] = function (x) { return isNil(x); };
  PRIMITIVES["number?"] = function (x) { return typeof x === "number"; };
  PRIMITIVES["string?"] = function (x) { return typeof x === "string"; };
  PRIMITIVES["list?"] = function (x) { return Array.isArray(x); };
  PRIMITIVES["dict?"] = function (x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; };
  PRIMITIVES["empty?"] = function (c) { return !c || (Array.isArray(c) ? c.length === 0 : Object.keys(c).length === 0); };
  PRIMITIVES["contains?"] = function (c, k) { return typeof c === "string" ? c.indexOf(k) !== -1 : Array.isArray(c) ? c.indexOf(k) !== -1 : k in c; };
  PRIMITIVES["odd?"] = function (n) { return n % 2 !== 0; };
  PRIMITIVES["even?"] = function (n) { return n % 2 === 0; };
  PRIMITIVES["zero?"] = function (n) { return n === 0; };

  // Collections
  PRIMITIVES["list"] = function () { return Array.prototype.slice.call(arguments); };
  PRIMITIVES["dict"] = function () {
    var d = {};
    for (var i = 0; i < arguments.length - 1; i += 2) d[arguments[i]] = arguments[i + 1];
    return d;
  };
  PRIMITIVES["get"] = function (c, k, def) { var v = (c && c[k]); return v !== undefined ? v : (def !== undefined ? def : NIL); };
  PRIMITIVES["len"] = function (c) { return Array.isArray(c) ? c.length : Object.keys(c).length; };
  PRIMITIVES["first"] = function (c) { return c && c.length > 0 ? c[0] : NIL; };
  PRIMITIVES["last"] = function (c) { return c && c.length > 0 ? c[c.length - 1] : NIL; };
  PRIMITIVES["rest"] = function (c) { return c ? c.slice(1) : []; };
  PRIMITIVES["nth"] = function (c, n) { return c && n < c.length ? c[n] : NIL; };
  PRIMITIVES["slice"] = function (c, start, end) { return c ? (end !== undefined && end !== NIL ? c.slice(start, end) : c.slice(start)) : c; };
  PRIMITIVES["cons"] = function (x, c) { return [x].concat(c || []); };
  PRIMITIVES["append"] = function (c, x) { return (c || []).concat([x]); };
  PRIMITIVES["keys"] = function (d) { return Object.keys(d || {}); };
  PRIMITIVES["vals"] = function (d) { var r = []; for (var k in d) r.push(d[k]); return r; };
  PRIMITIVES["merge"] = function () {
    var out = {};
    for (var i = 0; i < arguments.length; i++) { var d = arguments[i]; for (var k in d) out[k] = d[k]; }
    return out;
  };
  PRIMITIVES["assoc"] = function (d) {
    var out = {}; for (var k in d) out[k] = d[k];
    for (var i = 1; i < arguments.length - 1; i += 2) out[arguments[i]] = arguments[i + 1];
    return out;
  };
  PRIMITIVES["range"] = function (a, b, step) {
    var r = []; step = step || 1;
    if (b === undefined) { b = a; a = 0; }
    for (var i = a; step > 0 ? i < b : i > b; i += step) r.push(i);
    return r;
  };
  PRIMITIVES["dissoc"] = function (d) {
    var out = {}; for (var k in d) out[k] = d[k];
    for (var i = 1; i < arguments.length; i++) delete out[arguments[i]];
    return out;
  };
  PRIMITIVES["into"] = function (target, src) {
    if (Array.isArray(target)) return target.concat(src || []);
    var out = {}; for (var k in target) out[k] = target[k]; for (var k2 in src) out[k2] = src[k2]; return out;
  };

  // String operations
  PRIMITIVES["replace"] = function (s, from, to) { return s ? String(s).split(from).join(to) : ""; };
  PRIMITIVES["upper"] = function (s) { return s ? String(s).toUpperCase() : ""; };
  PRIMITIVES["lower"] = function (s) { return s ? String(s).toLowerCase() : ""; };
  PRIMITIVES["trim"] = function (s) { return s ? String(s).trim() : ""; };
  PRIMITIVES["starts-with?"] = function (s, pfx) { return s ? String(s).indexOf(pfx) === 0 : false; };
  PRIMITIVES["ends-with?"] = function (s, sfx) { var str = String(s || ""); return str.indexOf(sfx, str.length - sfx.length) !== -1; };
  PRIMITIVES["escape"] = function (s) {
    if (!s) return "";
    return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
  };
  PRIMITIVES["strip-tags"] = function (s) { return s ? String(s).replace(/<[^>]*>/g, "") : ""; };
  PRIMITIVES["split"] = function (s, sep) { return s ? String(s).split(sep) : []; };
  PRIMITIVES["join"] = function (lst, sep) { return (lst || []).join(sep !== undefined ? sep : ""); };
  PRIMITIVES["pluralize"] = function (n, singular, plural) { return n === 1 ? singular : (plural || singular + "s"); };

  // Numeric
  PRIMITIVES["clamp"] = function (val, lo, hi) { return Math.max(lo, Math.min(hi, val)); };
  PRIMITIVES["parse-int"] = function (s, def) { var n = parseInt(s, 10); return isNaN(n) ? (def !== undefined ? def : 0) : n; };
  PRIMITIVES["format-decimal"] = function (n, places) { return Number(n || 0).toFixed(places !== undefined ? places : 2); };

  // Date formatting (basic)
  PRIMITIVES["format-date"] = function (s, fmt) {
    if (!s) return "";
    try {
      var d = new Date(s);
      if (isNaN(d.getTime())) return String(s);
      // Basic strftime-like: %Y %m %d %H %M %B %b %-d
      var months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
      var short_months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
      return fmt.replace(/%-d/g, d.getDate()).replace(/%d/g, ("0"+d.getDate()).slice(-2))
        .replace(/%B/g, months[d.getMonth()]).replace(/%b/g, short_months[d.getMonth()])
        .replace(/%Y/g, d.getFullYear()).replace(/%m/g, ("0"+(d.getMonth()+1)).slice(-2))
        .replace(/%H/g, ("0"+d.getHours()).slice(-2)).replace(/%M/g, ("0"+d.getMinutes()).slice(-2));
    } catch (e) { return String(s); }
  };
  PRIMITIVES["parse-datetime"] = function (s) { return s ? String(s) : NIL; };
  PRIMITIVES["split-ids"] = function (s) {
    if (!s) return [];
    return String(s).split(",").map(function(x) { return x.trim(); }).filter(function(x) { return x; });
  };

  // --- CSSX Style Dictionary + Resolver ---

  var _styleAtoms = {};        // atom → CSS declarations
  var _pseudoVariants = {};    // variant → CSS pseudo-selector
  var _responsiveBreakpoints = {};  // variant → media query
  var _styleKeyframes = {};    // name → @keyframes rule
  var _arbitraryPatterns = []; // [{re: RegExp, tmpl: string}, ...]
  var _childSelectorPrefixes = []; // ["space-x-", "space-y-", ...]
  var _styleCache = {};        // atoms-key → StyleValue
  var _injectedStyles = {};    // className → true (already in <style>)

  function _loadStyleDict(data) {
    _styleAtoms = data.a || {};
    _pseudoVariants = data.v || {};
    _responsiveBreakpoints = data.b || {};
    _styleKeyframes = data.k || {};
    _childSelectorPrefixes = data.c || [];
    _arbitraryPatterns = [];
    var pats = data.p || [];
    for (var i = 0; i < pats.length; i++) {
      _arbitraryPatterns.push({ re: new RegExp("^" + pats[i][0] + "$"), tmpl: pats[i][1] });
    }
    _styleCache = {};
  }

  function _splitVariant(atom) {
    // Check responsive prefix first
    for (var bp in _responsiveBreakpoints) {
      var prefix = bp + ":";
      if (atom.indexOf(prefix) === 0) {
        var rest = atom.substring(prefix.length);
        for (var pv in _pseudoVariants) {
          var inner = pv + ":";
          if (rest.indexOf(inner) === 0) return [bp + ":" + pv, rest.substring(inner.length)];
        }
        return [bp, rest];
      }
    }
    for (var pv2 in _pseudoVariants) {
      var prefix2 = pv2 + ":";
      if (atom.indexOf(prefix2) === 0) return [pv2, atom.substring(prefix2.length)];
    }
    return [null, atom];
  }

  function _resolveAtom(atom) {
    var decls = _styleAtoms[atom];
    if (decls !== undefined) return decls;
    // Dynamic keyframes: animate-{name} → animation-name:{name}
    if (atom.indexOf("animate-") === 0) {
      var kfName = atom.substring(8);
      if (_styleKeyframes[kfName]) return "animation-name:" + kfName;
    }
    for (var i = 0; i < _arbitraryPatterns.length; i++) {
      var m = atom.match(_arbitraryPatterns[i].re);
      if (m) {
        var result = _arbitraryPatterns[i].tmpl;
        for (var j = 1; j < m.length; j++) result = result.replace("{" + (j - 1) + "}", m[j]);
        return result;
      }
    }
    return null;
  }

  function _isChildSelectorAtom(atom) {
    for (var i = 0; i < _childSelectorPrefixes.length; i++) {
      if (atom.indexOf(_childSelectorPrefixes[i]) === 0) return true;
    }
    return false;
  }

  /** SHA-256 hash (first 6 hex chars) for content-addressed class names. */
  function _hashStyle(input) {
    // Simple FNV-1a 32-bit hash — fast, deterministic, good distribution
    var h = 0x811c9dc5;
    for (var i = 0; i < input.length; i++) {
      h ^= input.charCodeAt(i);
      h = (h * 0x01000193) >>> 0;
    }
    return h.toString(16).padStart(8, "0").substring(0, 6);
  }

  function _resolveStyle(atoms) {
    var key = atoms.join("\0");
    if (_styleCache[key]) return _styleCache[key];

    var baseDecls = [], mediaRules = [], pseudoRules = [], kfNeeded = [];
    for (var i = 0; i < atoms.length; i++) {
      var a = atoms[i];
      if (!a) continue;
      if (a.charAt(0) === ":") a = a.substring(1);

      var parts = _splitVariant(a);
      var variant = parts[0], base = parts[1];
      var decls = _resolveAtom(base);
      if (!decls) continue;

      // Check keyframes
      if (base.indexOf("animate-") === 0) {
        var kfName = base.substring(8);
        if (_styleKeyframes[kfName]) kfNeeded.push([kfName, _styleKeyframes[kfName]]);
      }

      if (variant === null) {
        baseDecls.push(decls);
      } else if (_responsiveBreakpoints[variant]) {
        mediaRules.push([_responsiveBreakpoints[variant], decls]);
      } else if (_pseudoVariants[variant]) {
        pseudoRules.push([_pseudoVariants[variant], decls]);
      } else {
        // Compound variant: "sm:hover" → split
        var vparts = variant.split(":");
        var mediaPart = null, pseudoPart = null;
        for (var vi = 0; vi < vparts.length; vi++) {
          if (_responsiveBreakpoints[vparts[vi]]) mediaPart = _responsiveBreakpoints[vparts[vi]];
          else if (_pseudoVariants[vparts[vi]]) pseudoPart = _pseudoVariants[vparts[vi]];
        }
        if (mediaPart) mediaRules.push([mediaPart, decls]);
        if (pseudoPart) pseudoRules.push([pseudoPart, decls]);
        if (!mediaPart && !pseudoPart) baseDecls.push(decls);
      }
    }

    // Build hash input
    var hashInput = baseDecls.join(";");
    for (var mi = 0; mi < mediaRules.length; mi++) hashInput += "@" + mediaRules[mi][0] + "{" + mediaRules[mi][1] + "}";
    for (var pi = 0; pi < pseudoRules.length; pi++) hashInput += pseudoRules[pi][0] + "{" + pseudoRules[pi][1] + "}";
    for (var ki = 0; ki < kfNeeded.length; ki++) hashInput += kfNeeded[ki][1];

    var cn = "sx-" + _hashStyle(hashInput);
    var sv = new StyleValue(cn, baseDecls.join(";"), mediaRules, pseudoRules, kfNeeded);
    _styleCache[key] = sv;

    // Inject CSS rules into <style id="sx-css">
    _injectStyleValue(sv, atoms);
    return sv;
  }

  function _injectStyleValue(sv, atoms) {
    if (_injectedStyles[sv.className]) return;
    _injectedStyles[sv.className] = true;

    var cssTarget = document.getElementById("sx-css");
    if (!cssTarget) return;

    var rules = [];
    if (sv.declarations) {
      // Check if any atoms need child selectors
      var hasChild = false;
      if (atoms) {
        for (var ai = 0; ai < atoms.length; ai++) {
          if (_isChildSelectorAtom(atoms[ai])) { hasChild = true; break; }
        }
      }
      if (hasChild) {
        rules.push("." + sv.className + ">:not(:first-child){" + sv.declarations + "}");
      } else {
        rules.push("." + sv.className + "{" + sv.declarations + "}");
      }
    }
    for (var pi = 0; pi < sv.pseudoRules.length; pi++) {
      var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1];
      if (sel.indexOf("&") >= 0) {
        rules.push(sel.replace(/&/g, "." + sv.className) + "{" + decls + "}");
      } else {
        rules.push("." + sv.className + sel + "{" + decls + "}");
      }
    }
    for (var mi = 0; mi < sv.mediaRules.length; mi++) {
      rules.push("@media " + sv.mediaRules[mi][0] + "{." + sv.className + "{" + sv.mediaRules[mi][1] + "}}");
    }
    for (var ki = 0; ki < sv.keyframes.length; ki++) {
      rules.push(sv.keyframes[ki][1]);
    }
    cssTarget.textContent += rules.join("");
  }

  function _mergeStyleValues(styles) {
    if (styles.length === 1) return styles[0];
    var allDecls = [], allMedia = [], allPseudo = [], allKf = [];
    var allAtoms = [];
    for (var i = 0; i < styles.length; i++) {
      var sv = styles[i];
      if (sv.declarations) allDecls.push(sv.declarations);
      allMedia = allMedia.concat(sv.mediaRules);
      allPseudo = allPseudo.concat(sv.pseudoRules);
      allKf = allKf.concat(sv.keyframes);
    }
    var hashInput = allDecls.join(";");
    for (var mi = 0; mi < allMedia.length; mi++) hashInput += "@" + allMedia[mi][0] + "{" + allMedia[mi][1] + "}";
    for (var pi = 0; pi < allPseudo.length; pi++) hashInput += allPseudo[pi][0] + "{" + allPseudo[pi][1] + "}";
    for (var ki = 0; ki < allKf.length; ki++) hashInput += allKf[ki][1];
    var cn = "sx-" + _hashStyle(hashInput);
    var merged = new StyleValue(cn, allDecls.join(";"), allMedia, allPseudo, allKf);
    _injectStyleValue(merged, allAtoms);
    return merged;
  }

  // css primitive: (css :flex :gap-4 :hover:bg-sky-200) → StyleValue
  PRIMITIVES["css"] = function () {
    var atoms = [];
    for (var i = 0; i < arguments.length; i++) {
      var a = arguments[i];
      if (isNil(a) || a === false) continue;
      atoms.push(isKw(a) ? a.name : String(a));
    }
    if (!atoms.length) return NIL;
    return _resolveStyle(atoms);
  };

  // merge-styles: combine multiple StyleValues
  PRIMITIVES["merge-styles"] = function () {
    var valid = [];
    for (var i = 0; i < arguments.length; i++) {
      if (isStyleValue(arguments[i])) valid.push(arguments[i]);
    }
    if (!valid.length) return NIL;
    if (valid.length === 1) return valid[0];
    return _mergeStyleValues(valid);
  };

  // --- Evaluator ---

  /** Unwrap thunks by re-entering the evaluator until we get an actual value. */
  function trampoline(val) {
    while (isThunk(val)) val = _sxEval(val.expr, val.env);
    return val;
  }

  /** Public evaluator — trampolines thunks from tail positions. */
  function sxEval(expr, env) { return trampoline(_sxEval(expr, env)); }

  /** Internal evaluator — may return _Thunk for tail positions. */
  function _sxEval(expr, env) {
    // Literals
    if (typeof expr === "number" || typeof expr === "string" || typeof expr === "boolean") return expr;
    if (isNil(expr)) return NIL;

    // Symbol lookup
    if (isSym(expr)) {
      var name = expr.name;
      if (name in env) return env[name];
      if (name in PRIMITIVES) return PRIMITIVES[name];
      if (name === "true") return true;
      if (name === "false") return false;
      if (name === "nil") return NIL;
      throw new Error("Undefined symbol: " + name);
    }

    // Keyword → its name
    if (isKw(expr)) return expr.name;

    // Dict literal
    if (expr && typeof expr === "object" && !Array.isArray(expr) && !expr._sym && !expr._kw && !expr._raw) {
      var d = {};
      for (var dk in expr) d[dk] = sxEval(expr[dk], env);
      return d;
    }

    // List
    if (!Array.isArray(expr)) return expr;
    if (expr.length === 0) return [];

    var head = expr[0];

    // Non-callable head → data list
    if (!isSym(head) && !isLambda(head) && !Array.isArray(head)) {
      return expr.map(function (x) { return sxEval(x, env); });
    }

    // Special forms
    if (isSym(head)) {
      var sf = SPECIAL_FORMS[head.name];
      if (sf) return sf(expr, env);
      var ho = HO_FORMS[head.name];
      if (ho) {
        // If name is also an HTML tag and first arg is Keyword → not a HO call
        if (!(HTML_TAGS[head.name] && expr.length > 1 && isKw(expr[1]))) return ho(expr, env);
      }

      // Macro expansion
      if (head.name in env) {
        var macroVal = env[head.name];
        if (isMacro(macroVal)) {
          var expanded = expandMacro(macroVal, expr.slice(1), env);
          return new _Thunk(expanded, env);
        }
      }

      // HTML tag or component in data position — delegate to renderDOM
      if (_isRenderExpr(expr)) return renderDOM(expr, env);
    }

    // Function call
    var fn = sxEval(head, env);
    var args = [];
    for (var ai = 1; ai < expr.length; ai++) args.push(sxEval(expr[ai], env));

    if (typeof fn === "function") return fn.apply(null, args);
    if (isLambda(fn)) return callLambda(fn, args, env);
    if (isComponent(fn)) return callComponent(fn, expr.slice(1), env);
    throw new Error("Not callable: " + fn);
  }

  function callLambda(fn, args, callerEnv) {
    if (args.length !== fn.params.length) {
      throw new Error((fn.name || "lambda") + " expects " + fn.params.length + " args, got " + args.length);
    }
    var local = merge({}, fn.closure, callerEnv);
    for (var i = 0; i < fn.params.length; i++) local[fn.params[i]] = args[i];
    return new _Thunk(fn.body, local);
  }

  function callComponent(comp, rawArgs, env) {
    var kwargs = {}, children = [];
    var i = 0;
    while (i < rawArgs.length) {
      if (isKw(rawArgs[i]) && i + 1 < rawArgs.length) {
        kwargs[rawArgs[i].name] = sxEval(rawArgs[i + 1], env);
        i += 2;
      } else {
        children.push(sxEval(rawArgs[i], env));
        i++;
      }
    }
    var local = merge({}, comp.closure, env);
    for (var pi = 0; pi < comp.params.length; pi++) {
      var p = comp.params[pi];
      local[p] = (p in kwargs) ? kwargs[p] : NIL;
    }
    if (comp.hasChildren) local["children"] = children;
    return new _Thunk(comp.body, local);
  }

  // --- Shared helpers for special/render forms ---

  function _processBindings(bindings, env) {
    var local = merge({}, env);
    if (Array.isArray(bindings)) {
      if (bindings.length && Array.isArray(bindings[0])) {
        for (var i = 0; i < bindings.length; i++) {
          var vname = isSym(bindings[i][0]) ? bindings[i][0].name : bindings[i][0];
          local[vname] = sxEval(bindings[i][1], local);
        }
      } else {
        for (var j = 0; j < bindings.length; j += 2) {
          var vn = isSym(bindings[j]) ? bindings[j].name : bindings[j];
          local[vn] = sxEval(bindings[j + 1], local);
        }
      }
    }
    return local;
  }

  function _evalCond(clauses, env) {
    if (!clauses.length) return null;
    if (Array.isArray(clauses[0]) && clauses[0].length === 2) {
      for (var i = 0; i < clauses.length; i++) {
        var test = clauses[i][0];
        if ((isSym(test) && (test.name === "else" || test.name === ":else")) ||
            (isKw(test) && test.name === "else")) return clauses[i][1];
        if (isSxTruthy(sxEval(test, env))) return clauses[i][1];
      }
    } else {
      for (var j = 0; j < clauses.length - 1; j += 2) {
        var t = clauses[j];
        if ((isKw(t) && t.name === "else") || (isSym(t) && (t.name === ":else" || t.name === "else")))
          return clauses[j + 1];
        if (isSxTruthy(sxEval(t, env))) return clauses[j + 1];
      }
    }
    return null;
  }

  function _logParseError(label, text, err, windowSize) {
    var colMatch = err.message && err.message.match(/col (\d+)/);
    var lineMatch = err.message && err.message.match(/line (\d+)/);
    if (colMatch && text) {
      var errLine = lineMatch ? parseInt(lineMatch[1]) : 1;
      var errCol = parseInt(colMatch[1]);
      var lines = text.split("\n");
      var pos = 0;
      for (var i = 0; i < errLine - 1 && i < lines.length; i++) pos += lines[i].length + 1;
      pos += errCol;
      var start = Math.max(0, pos - windowSize);
      var end = Math.min(text.length, pos + windowSize);
      console.error("sx.js " + label + ":", err.message,
        "\n  total length:", text.length, "lines:", lines.length,
        "\n  error line " + errLine + ":", lines[errLine - 1] ? lines[errLine - 1].substring(0, 200) : "(no such line)",
        "\n  around error (pos ~" + pos + "):",
        "\n  «" + text.substring(start, pos) + "⛔" + text.substring(pos, end) + "»");
    } else {
      console.error("sx.js " + label + ":", err.message || err);
    }
  }

  // --- Special forms -------------------------------------------------------

  var SPECIAL_FORMS = {};

  SPECIAL_FORMS["if"] = function (expr, env) {
    var cond = sxEval(expr[1], env);
    if (isSxTruthy(cond)) return new _Thunk(expr[2], env);
    return expr.length > 3 ? new _Thunk(expr[3], env) : NIL;
  };

  SPECIAL_FORMS["when"] = function (expr, env) {
    if (!isSxTruthy(sxEval(expr[1], env))) return NIL;
    for (var i = 2; i < expr.length - 1; i++) sxEval(expr[i], env);
    return new _Thunk(expr[expr.length - 1], env);
  };

  SPECIAL_FORMS["cond"] = function (expr, env) {
    var branch = _evalCond(expr.slice(1), env);
    return branch ? new _Thunk(branch, env) : NIL;
  };

  SPECIAL_FORMS["case"] = function (expr, env) {
    var val = sxEval(expr[1], env);
    for (var i = 2; i < expr.length - 1; i += 2) {
      var t = expr[i];
      if ((isKw(t) && t.name === "else") || (isSym(t) && (t.name === ":else" || t.name === "else")))
        return new _Thunk(expr[i + 1], env);
      if (val == sxEval(t, env)) return new _Thunk(expr[i + 1], env);
    }
    return NIL;
  };

  SPECIAL_FORMS["and"] = function (expr, env) {
    var result = true;
    for (var i = 1; i < expr.length; i++) {
      result = sxEval(expr[i], env);
      if (!isSxTruthy(result)) return result;
    }
    return result;
  };

  SPECIAL_FORMS["or"] = function (expr, env) {
    var result = false;
    for (var i = 1; i < expr.length; i++) {
      result = sxEval(expr[i], env);
      if (isSxTruthy(result)) return result;
    }
    return result;
  };

  SPECIAL_FORMS["let"] = SPECIAL_FORMS["let*"] = function (expr, env) {
    var local = _processBindings(expr[1], env);
    for (var k = 2; k < expr.length - 1; k++) sxEval(expr[k], local);
    return expr.length > 2 ? new _Thunk(expr[expr.length - 1], local) : NIL;
  };

  SPECIAL_FORMS["lambda"] = SPECIAL_FORMS["fn"] = function (expr, env) {
    var paramsExpr = expr[1], paramNames = [];
    for (var i = 0; i < paramsExpr.length; i++) {
      var p = paramsExpr[i];
      paramNames.push(isSym(p) ? p.name : String(p));
    }
    return new Lambda(paramNames, expr[2], merge({}, env));
  };

  SPECIAL_FORMS["define"] = function (expr, env) {
    var name = expr[1].name;
    var value = sxEval(expr[2], env);
    if (isLambda(value) && !value.name) value.name = name;
    env[name] = value;
    return value;
  };

  SPECIAL_FORMS["defstyle"] = function (expr, env) {
    var name = expr[1].name;
    var value = sxEval(expr[2], env);
    env[name] = value;
    return value;
  };

  SPECIAL_FORMS["defkeyframes"] = function (expr, env) {
    var kfName = expr[1].name;
    var steps = [];
    for (var i = 2; i < expr.length; i++) {
      var step = expr[i];
      var selector = isSym(step[0]) ? step[0].name : String(step[0]);
      var body = sxEval(step[1], env);
      var decls = isStyleValue(body) ? body.declarations : String(body);
      steps.push(selector + "{" + decls + "}");
    }
    var kfRule = "@keyframes " + kfName + "{" + steps.join("") + "}";

    // Register in keyframes dict for animate-{name} lookup
    _styleKeyframes[kfName] = kfRule;
    _styleCache = {};  // Clear cache so new keyframes are picked up

    // Build a StyleValue for the animation
    var cn = "sx-" + _hashStyle(kfRule);
    var sv = new StyleValue(cn, "animation-name:" + kfName, [], [], [[kfName, kfRule]]);
    _injectStyleValue(sv, []);
    env[kfName] = sv;
    return sv;
  };

  SPECIAL_FORMS["defcomp"] = function (expr, env) {
    var nameSym = expr[1];
    var compName = nameSym.name.replace(/^~/, "");
    var paramsExpr = expr[2];
    var params = [], hasChildren = false, inKey = false;
    for (var i = 0; i < paramsExpr.length; i++) {
      var p = paramsExpr[i];
      if (isSym(p)) {
        if (p.name === "&key") { inKey = true; continue; }
        if (p.name === "&rest") { hasChildren = true; continue; }
        if (inKey || hasChildren) { if (!hasChildren) params.push(p.name); }
        else params.push(p.name);
      }
    }
    var comp = new Component(compName, params, hasChildren, expr[3], merge({}, env));
    env[nameSym.name] = comp;
    return comp;
  };

  SPECIAL_FORMS["begin"] = SPECIAL_FORMS["do"] = function (expr, env) {
    for (var i = 1; i < expr.length - 1; i++) sxEval(expr[i], env);
    return expr.length > 1 ? new _Thunk(expr[expr.length - 1], env) : NIL;
  };

  SPECIAL_FORMS["quote"] = function (expr) { return expr[1]; };

  SPECIAL_FORMS["set!"] = function (expr, env) {
    var v = sxEval(expr[2], env);
    env[expr[1].name] = v;
    return v;
  };

  SPECIAL_FORMS["->"] = function (expr, env) {
    var result = sxEval(expr[1], env);
    for (var i = 2; i < expr.length; i++) {
      var form = expr[i];
      var fn, args;
      if (Array.isArray(form)) {
        fn = sxEval(form[0], env);
        args = [result];
        for (var j = 1; j < form.length; j++) args.push(sxEval(form[j], env));
      } else {
        fn = sxEval(form, env);
        args = [result];
      }
      if (typeof fn === "function") result = fn.apply(null, args);
      else if (isLambda(fn)) result = trampoline(callLambda(fn, args, env));
      else throw new Error("-> form not callable: " + fn);
    }
    return result;
  };

  SPECIAL_FORMS["defmacro"] = function (expr, env) {
    var nameSym = expr[1];
    var paramsExpr = expr[2];
    var params = [], restParam = null;
    for (var i = 0; i < paramsExpr.length; i++) {
      var p = paramsExpr[i];
      if (isSym(p) && p.name === "&rest") {
        if (i + 1 < paramsExpr.length) {
          var rp = paramsExpr[i + 1];
          restParam = isSym(rp) ? rp.name : String(rp);
        }
        break;
      }
      if (isSym(p)) params.push(p.name);
      else if (typeof p === "string") params.push(p);
    }
    var macro = new Macro(params, restParam, expr[3], merge({}, env), nameSym.name);
    env[nameSym.name] = macro;
    return macro;
  };

  SPECIAL_FORMS["quasiquote"] = function (expr, env) {
    return qqExpand(expr[1], env);
  };

  function qqExpand(template, env) {
    if (!Array.isArray(template)) return template;
    if (!template.length) return [];
    var head = template[0];
    if (isSym(head)) {
      if (head.name === "unquote") return sxEval(template[1], env);
      if (head.name === "splice-unquote") throw new Error("splice-unquote not inside a list");
    }
    var result = [];
    for (var i = 0; i < template.length; i++) {
      var item = template[i];
      if (Array.isArray(item) && item.length === 2 && isSym(item[0]) && item[0].name === "splice-unquote") {
        var spliced = sxEval(item[1], env);
        if (Array.isArray(spliced)) { for (var j = 0; j < spliced.length; j++) result.push(spliced[j]); }
        else if (!isNil(spliced)) result.push(spliced);
      } else {
        result.push(qqExpand(item, env));
      }
    }
    return result;
  }

  function expandMacro(macro, rawArgs, env) {
    var local = merge({}, macro.closure, env);
    for (var i = 0; i < macro.params.length; i++) {
      local[macro.params[i]] = i < rawArgs.length ? rawArgs[i] : NIL;
    }
    if (macro.restParam !== null) {
      local[macro.restParam] = rawArgs.slice(macro.params.length);
    }
    return sxEval(macro.body, local);
  }

  // --- Higher-order forms --------------------------------------------------

  var HO_FORMS = {};

  HO_FORMS["map"] = function (expr, env) {
    var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
    return coll.map(function (item) { return isLambda(fn) ? trampoline(callLambda(fn, [item], env)) : fn(item); });
  };

  HO_FORMS["map-indexed"] = function (expr, env) {
    var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
    return coll.map(function (item, i) { return isLambda(fn) ? trampoline(callLambda(fn, [i, item], env)) : fn(i, item); });
  };

  HO_FORMS["filter"] = function (expr, env) {
    var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
    return coll.filter(function (item) {
      var r = isLambda(fn) ? trampoline(callLambda(fn, [item], env)) : fn(item);
      return isSxTruthy(r);
    });
  };

  HO_FORMS["reduce"] = function (expr, env) {
    var fn = sxEval(expr[1], env), acc = sxEval(expr[2], env), coll = sxEval(expr[3], env);
    for (var i = 0; i < coll.length; i++) acc = isLambda(fn) ? trampoline(callLambda(fn, [acc, coll[i]], env)) : fn(acc, coll[i]);
    return acc;
  };

  HO_FORMS["some"] = function (expr, env) {
    var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
    for (var i = 0; i < coll.length; i++) {
      var r = isLambda(fn) ? trampoline(callLambda(fn, [coll[i]], env)) : fn(coll[i]);
      if (isSxTruthy(r)) return r;
    }
    return NIL;
  };

  HO_FORMS["every?"] = function (expr, env) {
    var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
    for (var i = 0; i < coll.length; i++) {
      if (!isSxTruthy(isLambda(fn) ? trampoline(callLambda(fn, [coll[i]], env)) : fn(coll[i]))) return false;
    }
    return true;
  };

  HO_FORMS["for-each"] = function (expr, env) {
    var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
    for (var i = 0; i < coll.length; i++) isLambda(fn) ? trampoline(callLambda(fn, [coll[i]], env)) : fn(coll[i]);
    return NIL;
  };

  // --- DOM Renderer ---

  var HTML_TAGS = makeSet(
    "html head body title meta link style script base noscript " +
    "header footer main nav aside section article address hgroup " +
    "h1 h2 h3 h4 h5 h6 " +
    "div p blockquote pre figure figcaption ul ol li dl dt dd hr " +
    "a span em strong small s cite q abbr code var samp kbd sub sup " +
    "i b u mark ruby rt rp bdi bdo br wbr time data " +
    "ins del " +
    "img picture source iframe embed object param video audio track canvas map area " +
    "svg math path circle ellipse line polygon polyline rect g defs use text tspan " +
    "clipPath mask linearGradient radialGradient stop filter " +
    "feGaussianBlur feOffset feMerge feMergeNode " +
    "feTurbulence feColorMatrix feBlend " +
    "feComponentTransfer feFuncR feFuncG feFuncB feFuncA " +
    "feDisplacementMap feComposite feFlood feImage " +
    "feMorphology feSpecularLighting feDiffuseLighting " +
    "fePointLight feSpotLight feDistantLight " +
    "animate animateTransform " +
    "table thead tbody tfoot tr th td caption colgroup col " +
    "form fieldset legend label input button select option optgroup textarea output " +
    "datalist progress meter details summary dialog template slot"
  );

  var VOID_ELEMENTS = makeSet(
    "area base br col embed hr img input link meta param source track wbr"
  );

  var BOOLEAN_ATTRS = makeSet(
    "async autofocus autoplay checked controls default defer disabled " +
    "formnovalidate hidden inert ismap loop multiple muted nomodule " +
    "novalidate open playsinline readonly required reversed selected"
  );

  // SVG elements that need createElementNS
  var SVG_TAGS = makeSet(
    "svg path circle ellipse line polygon polyline rect g defs use text tspan " +
    "clipPath mask linearGradient radialGradient stop filter " +
    "feGaussianBlur feOffset feMerge feMergeNode " +
    "feTurbulence feColorMatrix feBlend " +
    "feComponentTransfer feFuncR feFuncG feFuncB feFuncA " +
    "feDisplacementMap feComposite feFlood feImage " +
    "feMorphology feSpecularLighting feDiffuseLighting " +
    "fePointLight feSpotLight feDistantLight " +
    "animate animateTransform"
  );

  var SVG_NS = "http://www.w3.org/2000/svg";
  var MATH_NS = "http://www.w3.org/1998/Math/MathML";

  /**
   * Render an s-expression to DOM node(s).
   * Returns a DocumentFragment, Element, or Text node.
   * @param {*} expr - s-expression
   * @param {Object} env - variable bindings
   * @param {string|null} ns - namespace URI (SVG_NS or MATH_NS) when inside svg/math
   */
  function renderDOM(expr, env, ns) {
    // nil / false → empty
    if (isNil(expr) || expr === false || expr === true) return document.createDocumentFragment();

    // Pre-rendered HTML
    if (isRaw(expr)) {
      var tpl = document.createElement("template");
      tpl.innerHTML = expr.html;
      return tpl.content;
    }

    // String → text node
    if (typeof expr === "string") return document.createTextNode(expr);

    // Number → text node
    if (typeof expr === "number") return document.createTextNode(String(expr));

    // Symbol → evaluate then render
    if (isSym(expr)) return renderDOM(sxEval(expr, env), env, ns);

    // Keyword → text
    if (isKw(expr)) return document.createTextNode(expr.name);

    // Pre-rendered DOM node → return as-is
    if (expr && expr.nodeType) return expr;

    // Dict → empty
    if (expr && typeof expr === "object" && !Array.isArray(expr)) return document.createDocumentFragment();

    // List → dispatch
    if (Array.isArray(expr)) {
      if (!expr.length) return document.createDocumentFragment();
      return renderList(expr, env, ns);
    }

    return document.createTextNode(String(expr));
  }

  /** Render-aware special forms for DOM output. */
  var RENDER_FORMS = {};

  RENDER_FORMS["if"] = function (expr, env, ns) {
    var cond = sxEval(expr[1], env);
    if (isSxTruthy(cond)) return renderDOM(expr[2], env, ns);
    return expr.length > 3 ? renderDOM(expr[3], env, ns) : document.createDocumentFragment();
  };

  RENDER_FORMS["when"] = function (expr, env, ns) {
    if (!isSxTruthy(sxEval(expr[1], env))) return document.createDocumentFragment();
    var frag = document.createDocumentFragment();
    for (var i = 2; i < expr.length; i++) frag.appendChild(renderDOM(expr[i], env, ns));
    return frag;
  };

  RENDER_FORMS["cond"] = function (expr, env, ns) {
    var branch = _evalCond(expr.slice(1), env);
    return branch ? renderDOM(branch, env, ns) : document.createDocumentFragment();
  };

  RENDER_FORMS["let"] = RENDER_FORMS["let*"] = function (expr, env, ns) {
    var local = _processBindings(expr[1], env);
    var frag = document.createDocumentFragment();
    for (var k = 2; k < expr.length; k++) frag.appendChild(renderDOM(expr[k], local, ns));
    return frag;
  };

  RENDER_FORMS["begin"] = RENDER_FORMS["do"] = function (expr, env, ns) {
    var frag = document.createDocumentFragment();
    for (var i = 1; i < expr.length; i++) frag.appendChild(renderDOM(expr[i], env, ns));
    return frag;
  };

  RENDER_FORMS["define"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
  RENDER_FORMS["defstyle"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
  RENDER_FORMS["defkeyframes"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
  RENDER_FORMS["defcomp"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
  RENDER_FORMS["defmacro"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
  RENDER_FORMS["defhandler"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };

  RENDER_FORMS["map"] = function (expr, env, ns) {
    var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
    var frag = document.createDocumentFragment();
    for (var i = 0; i < coll.length; i++) {
      var val = isLambda(fn) ? renderLambdaDOM(fn, [coll[i]], env, ns) : renderDOM(fn(coll[i]), env, ns);
      frag.appendChild(val);
    }
    return frag;
  };

  RENDER_FORMS["map-indexed"] = function (expr, env, ns) {
    var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
    var frag = document.createDocumentFragment();
    for (var i = 0; i < coll.length; i++) {
      var val = isLambda(fn) ? renderLambdaDOM(fn, [i, coll[i]], env, ns) : renderDOM(fn(i, coll[i]), env, ns);
      frag.appendChild(val);
    }
    return frag;
  };

  RENDER_FORMS["filter"] = function (expr, env, ns) {
    var result = sxEval(expr, env);
    return renderDOM(result, env, ns);
  };

  RENDER_FORMS["for-each"] = function (expr, env, ns) {
    var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
    var frag = document.createDocumentFragment();
    for (var i = 0; i < coll.length; i++) {
      var val = isLambda(fn) ? renderLambdaDOM(fn, [coll[i]], env, ns) : renderDOM(fn(coll[i]), env, ns);
      frag.appendChild(val);
    }
    return frag;
  };

  function renderLambdaDOM(fn, args, env, ns) {
    var local = merge({}, fn.closure, env);
    for (var i = 0; i < fn.params.length; i++) local[fn.params[i]] = args[i];
    return renderDOM(fn.body, local, ns);
  }

  /** True when the array expr is a render-only form (HTML tag, <>, raw!, ~comp). */
  function _isRenderExpr(v) {
    if (!Array.isArray(v) || !v.length) return false;
    var h = v[0];
    if (!isSym(h)) return false;
    var n = h.name;
    return !!(HTML_TAGS[n] || SVG_TAGS[n] || n === "<>" || n === "raw!" || n.charAt(0) === "~" || n.indexOf("html:") === 0 || (n.indexOf("-") > 0 && v.length > 1 && isKw(v[1])));
  }

  function renderComponentDOM(comp, args, env) {
    var kwargs = {}, children = [];
    var i = 0;
    while (i < args.length) {
      if (isKw(args[i]) && i + 1 < args.length) {
        // Evaluate kwarg values eagerly in the caller's env so expressions
        // like (get t "src") resolve while lambda params are still bound.
        var v = args[i + 1];
        if (typeof v === "string" || typeof v === "number" ||
          typeof v === "boolean" || isNil(v) || isKw(v)) {
          kwargs[args[i].name] = v;
        } else if (isSym(v)) {
          kwargs[args[i].name] = sxEval(v, env);
        } else if (Array.isArray(v) && v.length && isSym(v[0])) {
          // Expression with Symbol head — evaluate in caller's env.
          // Render-only forms go through renderDOM; data exprs through sxEval.
          if (_isRenderExpr(v)) {
            kwargs[args[i].name] = renderDOM(v, env);
          } else {
            kwargs[args[i].name] = sxEval(v, env);
          }
        } else {
          // Data arrays, dicts, etc — evaluate in caller's env
          kwargs[args[i].name] = sxEval(v, env);
        }
        i += 2;
      } else {
        children.push(args[i]);
        i++;
      }
    }
    var local = merge({}, comp.closure, env);
    for (var pi = 0; pi < comp.params.length; pi++) {
      var p = comp.params[pi];
      local[p] = (p in kwargs) ? kwargs[p] : NIL;
    }
    if (comp.hasChildren) {
      // Pre-render children to a fragment, wrap as RawHTML for raw! compatibility
      var childFrag = document.createDocumentFragment();
      for (var ci = 0; ci < children.length; ci++) childFrag.appendChild(renderDOM(children[ci], env));
      local["children"] = childFrag;
    }
    return renderDOM(comp.body, local);
  }

  function renderList(expr, env, ns) {
    var head = expr[0];

    if (isSym(head)) {
      var name = head.name;

      // raw! → insert unescaped
      if (name === "raw!") {
        var frag = document.createDocumentFragment();
        for (var ri = 1; ri < expr.length; ri++) {
          var val = sxEval(expr[ri], env);
          if (typeof val === "string") {
            var tpl = document.createElement("template");
            tpl.innerHTML = val;
            // Scripts in innerHTML don't execute — recreate them as live elements
            var deadScripts = tpl.content.querySelectorAll("script");
            for (var si = 0; si < deadScripts.length; si++) {
              var dead = deadScripts[si];
              var live = document.createElement("script");
              for (var ai = 0; ai < dead.attributes.length; ai++)
                live.setAttribute(dead.attributes[ai].name, dead.attributes[ai].value);
              live.textContent = dead.textContent;
              dead.parentNode.replaceChild(live, dead);
            }
            frag.appendChild(tpl.content);
          } else if (val && val.nodeType) {
            // Already a DOM node (e.g. from children fragment)
            frag.appendChild(val.cloneNode ? val.cloneNode(true) : val);
          } else if (!isNil(val)) {
            frag.appendChild(document.createTextNode(String(val)));
          }
        }
        return frag;
      }

      // <> → fragment
      if (name === "<>") {
        var f = document.createDocumentFragment();
        for (var fi = 1; fi < expr.length; fi++) f.appendChild(renderDOM(expr[fi], env, ns));
        return f;
      }

      // html: prefix → force element rendering
      if (name.indexOf("html:") === 0) return renderElement(name.substring(5), expr.slice(1), env, ns);

      // Render-aware special forms
      // If name is also an HTML tag and (keyword arg or SVG/MathML ns) → tag call
      if (RENDER_FORMS[name]) {
        if (HTML_TAGS[name] && ((expr.length > 1 && isKw(expr[1])) || ns)) return renderElement(name, expr.slice(1), env, ns);
        return RENDER_FORMS[name](expr, env, ns);
      }

      // Macro expansion
      if (name in env && isMacro(env[name])) {
        var mExpanded = expandMacro(env[name], expr.slice(1), env);
        return renderDOM(mExpanded, env, ns);
      }

      // HTML tag
      if (HTML_TAGS[name]) return renderElement(name, expr.slice(1), env, ns);

      // Component
      if (name.charAt(0) === "~") {
        var comp = env[name];
        if (isComponent(comp)) return renderComponentDOM(comp, expr.slice(1), env);
        // Unknown component — render a visible warning, don't crash
        console.warn("sx.js: unknown component " + name);
        var warn = document.createElement("div");
        warn.setAttribute("style",
          "background:#fef2f2;border:1px solid #fca5a5;color:#991b1b;" +
          "padding:4px 8px;margin:2px;border-radius:4px;font-size:12px;font-family:monospace");
        warn.textContent = "Unknown component: " + name;
        return warn;
      }

      // Custom element (hyphenated name with keyword attrs) → render as element
      if (name.indexOf("-") > 0 && expr.length > 1 && isKw(expr[1])) return renderElement(name, expr.slice(1), env, ns);

      // SVG/MathML namespace auto-detection: inside (svg ...) or (math ...),
      // unknown tags are created with the inherited namespace
      if (ns) return renderElement(name, expr.slice(1), env, ns);

      // Fallback: evaluate then render
      return renderDOM(sxEval(expr, env), env, ns);
    }

    // Lambda/list head → evaluate
    if (isLambda(head) || Array.isArray(head)) return renderDOM(sxEval(expr, env), env, ns);

    // Data list
    var dl = document.createDocumentFragment();
    for (var di = 0; di < expr.length; di++) dl.appendChild(renderDOM(expr[di], env, ns));
    return dl;
  }

  function renderElement(tag, args, env, ns) {
    // Detect namespace from tag: svg → SVG_NS, math → MATH_NS
    if (tag === "svg") ns = SVG_NS;
    else if (tag === "math") ns = MATH_NS;

    var el = ns
      ? document.createElementNS(ns, tag)
      : (SVG_TAGS[tag] ? document.createElementNS(SVG_NS, tag) : document.createElement(tag));

    var extraClass = null;
    var i = 0;
    while (i < args.length) {
      var arg = args[i];
      if (isKw(arg) && i + 1 < args.length) {
        var attrName = arg.name;
        var attrVal = sxEval(args[i + 1], env);
        i += 2;
        if (isNil(attrVal) || attrVal === false) continue;
        // :style StyleValue → convert to class
        if (attrName === "style" && isStyleValue(attrVal)) {
          extraClass = attrVal.className;
          continue;
        }
        if (BOOLEAN_ATTRS[attrName]) {
          if (attrVal) el.setAttribute(attrName, "");
        } else if (attrVal === true) {
          el.setAttribute(attrName, "");
        } else {
          el.setAttribute(attrName, typeof attrVal === "object" && attrVal !== null && !Array.isArray(attrVal) ? _serializeDict(attrVal) : String(attrVal));
        }
      } else {
        // Child
        if (!(tag in VOID_ELEMENTS)) {
          el.appendChild(renderDOM(arg, env, ns));
        }
        i++;
      }
    }

    // Merge StyleValue class into element's class attribute
    if (extraClass) {
      var existing = el.getAttribute("class");
      el.setAttribute("class", existing ? existing + " " + extraClass : extraClass);
    }

    return el;
  }

  // --- Helpers ---

  function merge(target) {
    for (var i = 1; i < arguments.length; i++) {
      var src = arguments[i];
      if (src) for (var k in src) target[k] = src[k];
    }
    return target;
  }

  function makeSet(str) {
    var s = {}, parts = str.split(/\s+/);
    for (var i = 0; i < parts.length; i++) if (parts[i]) s[parts[i]] = true;
    return s;
  }

  /** Convert snake_case kwargs to kebab-case for sx conventions. */
  function toKebab(s) { return s.replace(/_/g, "-"); }

  // --- Public API ---

  var _componentEnv = {};

  // --- Head auto-hoist ---

  var HEAD_HOIST_SELECTOR =
    "meta, title, link[rel='canonical'], script[type='application/ld+json']";

  function _hoistHeadElements(root) {
    var els = root.querySelectorAll(HEAD_HOIST_SELECTOR);
    if (!els.length) return;
    var head = document.head;
    for (var i = 0; i < els.length; i++) {
      var el = els[i];
      var tag = el.tagName.toLowerCase();
      // For <title>, replace existing
      if (tag === "title") {
        document.title = el.textContent || "";
        el.parentNode.removeChild(el);
        continue;
      }
      // For <meta>, remove existing with same name/property to avoid duplicates
      if (tag === "meta") {
        var name = el.getAttribute("name");
        var prop = el.getAttribute("property");
        if (name) {
          var old = head.querySelector('meta[name="' + name + '"]');
          if (old) old.parentNode.removeChild(old);
        }
        if (prop) {
          var old2 = head.querySelector('meta[property="' + prop + '"]');
          if (old2) old2.parentNode.removeChild(old2);
        }
      }
      // For <link rel=canonical>, remove existing
      if (tag === "link" && el.getAttribute("rel") === "canonical") {
        var oldLink = head.querySelector('link[rel="canonical"]');
        if (oldLink) oldLink.parentNode.removeChild(oldLink);
      }
      // Move from body to head
      el.parentNode.removeChild(el);
      head.appendChild(el);
    }
  }

  var Sx = {
    // Types
    NIL: NIL,
    Symbol: Symbol,
    Keyword: Keyword,

    // Parser
    parse: parse,
    parseAll: parseAll,

    // Evaluator
    eval: function (expr, env) { return sxEval(expr, env || _componentEnv); },

    // DOM Renderer
    render: function (exprOrText, extraEnv) {
      var env = extraEnv ? merge({}, _componentEnv, extraEnv) : _componentEnv;
      if (typeof exprOrText === "string") {
        // Try single expression first; fall back to multi-expression fragment
        try {
          return renderDOM(parse(exprOrText), env);
        } catch (e) {
          var exprs = parseAll(exprOrText);
          if (exprs.length === 0) throw e;
          var frag = document.createDocumentFragment();
          for (var i = 0; i < exprs.length; i++) {
            var node = renderDOM(exprs[i], env);
            if (node) frag.appendChild(node);
          }
          return frag;
        }
      }
      return renderDOM(exprOrText, env);
    },

    /**
     * Render a named component with keyword args (Python-style API).
     *   Sx.renderComponent("card", {title: "Hi"})
     */
    renderComponent: function (name, kwargs, extraEnv) {
      var fullName = name.charAt(0) === "~" ? name : "~" + name;
      var env = extraEnv ? merge({}, _componentEnv, extraEnv) : _componentEnv;
      var comp = env[fullName];
      if (!isComponent(comp)) throw new Error("Unknown component: " + fullName);
      // Build a synthetic call expression
      var callExpr = [new Symbol(fullName)];
      if (kwargs) {
        for (var k in kwargs) {
          callExpr.push(new Keyword(toKebab(k)));
          callExpr.push(kwargs[k]);
        }
      }
      return renderDOM(callExpr, env);
    },

    // Component management
    loadComponents: function (text) {
      try {
        var exprs = parseAll(text);
        for (var i = 0; i < exprs.length; i++) sxEval(exprs[i], _componentEnv);
      } catch (err) {
        _logParseError("loadComponents PARSE ERROR", text, err, 120);
        throw err;
      }
    },

    getEnv: function () { return _componentEnv; },

    // Utility
    isTruthy: isSxTruthy,
    isNil: isNil,

    /**
     * Mount a sx expression into a DOM element, replacing its contents.
     *   Sx.mount(el, '(~card :title "Hi")')
     *   Sx.mount("#target", '(~card :title "Hi")')
     *   Sx.mount(el, '(~card :title name)', {name: "Jo"})
     */
    mount: function (target, exprOrText, extraEnv) {
      var el = typeof target === "string" ? document.querySelector(target) : target;
      if (!el) return;
      var node;
      try {
        node = Sx.render(exprOrText, extraEnv);
      } catch (e) {
        if (typeof exprOrText === "string") _logParseError("MOUNT PARSE ERROR", exprOrText, e, 80);
        throw e;
      }
      el.textContent = "";
      el.appendChild(node);
      // Auto-hoist head elements (meta, title, link, script[ld+json]) to <head>
      _hoistHeadElements(el);
      // Process sx- attributes and hydrate the newly mounted content
      if (typeof SxEngine !== "undefined") SxEngine.process(el);
      Sx.hydrate(el);
    },

    /**
     * Process all <script type="text/sx"> tags in the document.
     * Tags with data-components load component definitions.
     * Tags with data-mount="<selector>" render into that element.
     */
    processScripts: function (root) {
      var scripts = (root || document).querySelectorAll('script[type="text/sx"]');
      for (var i = 0; i < scripts.length; i++) {
        var s = scripts[i];
        if (s._sxProcessed) continue;
        s._sxProcessed = true;

        var text = s.textContent;

        // data-components: check before empty-text guard (may load from localStorage)
        if (s.hasAttribute("data-components")) {
          var hash = s.getAttribute("data-hash");
          if (hash) {
            var hasInline = text && text.trim();
            try {
              var cachedHash = localStorage.getItem("sx-components-hash");
              if (cachedHash === hash) {
                // Cache hit
                if (hasInline) {
                  // Server sent full source (cookie was missing/stale) — update cache
                  localStorage.setItem("sx-components-src", text);
                  Sx.loadComponents(text);
                  console.log("[sx.js] components: downloaded (cookie stale)");
                } else {
                  // Server omitted source — load from cache
                  var cached = localStorage.getItem("sx-components-src");
                  if (cached) {
                    Sx.loadComponents(cached);
                    console.log("[sx.js] components: cached (" + hash + ")");
                  } else {
                    // Cache entry missing — clear cookie and reload to get full source
                    _clearSxCompCookie();
                    location.reload();
                    return;
                  }
                }
              } else {
                // Cache miss — hash mismatch
                if (hasInline) {
                  // Server sent full source — parse and cache
                  localStorage.setItem("sx-components-hash", hash);
                  localStorage.setItem("sx-components-src", text);
                  Sx.loadComponents(text);
                  console.log("[sx.js] components: downloaded (" + hash + ")");
                } else {
                  // Server omitted source but our cache is stale — clear and reload
                  localStorage.removeItem("sx-components-hash");
                  localStorage.removeItem("sx-components-src");
                  _clearSxCompCookie();
                  location.reload();
                  return;
                }
              }
            } catch (e) {
              // localStorage unavailable — fall back to inline
              if (hasInline) Sx.loadComponents(text);
            }
            _setSxCompCookie(hash);
          } else {
            // Legacy: no hash attribute — just load inline
            if (text && text.trim()) Sx.loadComponents(text);
          }
          continue;
        }

        if (!text || !text.trim()) continue;

        // data-mount="<selector>": render into target
        var mountSel = s.getAttribute("data-mount");
        if (mountSel) {
          var target = document.querySelector(mountSel);
          if (target) Sx.mount(target, text);
          continue;
        }

        // Default: load as components
        Sx.loadComponents(text);
      }
    },

    /**
     * Bind client-side sx rendering to elements with data-sx-* attrs.
     *
     * Pattern:
     *   <div data-sx="(~card :title title)" data-sx-env='{"title":"Hi"}'>
     *     <!-- server-rendered HTML (hydration target) -->
     *   </div>
     *
     * Call Sx.update(el, {title: "New"}) to re-render with new data.
     */
    update: function (target, newEnv) {
      var el = typeof target === "string" ? document.querySelector(target) : target;
      if (!el) return;
      var source = el.getAttribute("data-sx");
      if (!source) return;
      var baseEnv = {};
      var envAttr = el.getAttribute("data-sx-env");
      if (envAttr) {
        try { baseEnv = JSON.parse(envAttr); } catch (e) { /* ignore */ }
      }
      var env = merge({}, _componentEnv, baseEnv, newEnv || {});
      var node = renderDOM(parse(source), env);
      el.textContent = "";
      el.appendChild(node);
      if (newEnv) {
        merge(baseEnv, newEnv);
        el.setAttribute("data-sx-env", JSON.stringify(baseEnv));
      }
    },

    /**
     * Find all [data-sx] elements within root and render them.
     * Useful after HTMX swaps bring in new sx-enabled elements.
     */
    hydrate: function (root) {
      var els = (root || document).querySelectorAll("[data-sx]");
      for (var i = 0; i < els.length; i++) {
        if (els[i]._sxHydrated) continue;
        els[i]._sxHydrated = true;
        Sx.update(els[i]);
      }
    },

    // For testing / sx-test.js
    _types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML, StyleValue: StyleValue },
    _eval: sxEval,
    _expandMacro: expandMacro,
    _callLambda: function (fn, args, env) { return trampoline(callLambda(fn, args, env)); },
    _renderDOM: renderDOM,
  };

  global.Sx = Sx;

  // --- SxEngine — native fetch/swap/history engine ---

  var SxEngine = (function () {
    if (typeof document === "undefined") return {};

    // ---- helpers ----------------------------------------------------------
    var PROCESSED = "_sxBound";
    var VERBS = ["get", "post", "put", "delete", "patch"];
    var DEFAULT_SWAP = "outerHTML";
    var _config = { globalViewTransitions: false };

    /** Wrap a function in View Transition API if supported and enabled. */
    function _withTransition(enabled, fn) {
      if (enabled && document.startViewTransition) {
        document.startViewTransition(fn);
      } else {
        fn();
      }
    }


    function dispatch(el, name, detail) {
      var evt = new CustomEvent(name, { bubbles: true, cancelable: true, detail: detail || {} });
      return el.dispatchEvent(evt);
    }

    /** Parse and dispatch SX-Trigger header events.
     *  Value can be: "myEvent" (plain string), '{"myEvent": {"key": "val"}}' (JSON). */
    function _dispatchTriggerEvents(el, headerVal) {
      if (!headerVal) return;
      try {
        var parsed = JSON.parse(headerVal);
        if (typeof parsed === "object" && parsed !== null) {
          for (var evtName in parsed) dispatch(el, evtName, parsed[evtName]);
        } else {
          dispatch(el, String(parsed), {});
        }
      } catch (e) {
        // Plain string — may be comma-separated event names
        headerVal.split(",").forEach(function (name) {
          var n = name.trim();
          if (n) dispatch(el, n, {});
        });
      }
    }

    function csrfToken() {
      var m = document.querySelector('meta[name="csrf-token"]');
      return m ? m.getAttribute("content") : null;
    }

    function sameOrigin(url) {
      try { return new URL(url, location.href).origin === location.origin; } catch (e) { return true; }
    }

    function resolveTarget(el, attr) {
      var sel = el.getAttribute("sx-target") || attr;
      if (!sel || sel === "this") return el;
      if (sel === "closest") return el.parentElement;
      return document.querySelector(sel);
    }

    function getVerb(el) {
      for (var i = 0; i < VERBS.length; i++) {
        var v = VERBS[i];
        if (el.hasAttribute("sx-" + v)) return { method: v.toUpperCase(), url: el.getAttribute("sx-" + v) };
      }
      return null;
    }

    // ---- Sync manager -----------------------------------------------------
    var _controllers = new WeakMap();

    function abortPrevious(el) {
      var prev = _controllers.get(el);
      if (prev) prev.abort();
    }

    function trackController(el, ctrl) {
      _controllers.set(el, ctrl);
    }

    // ---- Request executor -------------------------------------------------

    function executeRequest(el, verbInfo, extraParams) {
      // Re-read verb from element in case attributes were morphed since binding
      var currentVerb = getVerb(el);
      if (currentVerb) verbInfo = currentVerb;
      var method = verbInfo.method;
      var url = verbInfo.url;

      // Reset retry backoff on fresh (non-retry) requests
      if (!el.classList.contains("sx-error")) {
        el.removeAttribute("data-sx-retry-ms");
      }

      // sx-media: skip if media query doesn't match
      var media = el.getAttribute("sx-media");
      if (media && !window.matchMedia(media).matches) return Promise.resolve();

      // sx-confirm: show dialog first
      var confirmMsg = el.getAttribute("sx-confirm");
      if (confirmMsg) {
        if (typeof Swal !== "undefined") {
          return Swal.fire({
            title: confirmMsg,
            icon: "warning",
            showCancelButton: true,
            confirmButtonText: "Yes",
            cancelButtonText: "Cancel"
          }).then(function (result) {
            if (!result.isConfirmed) return;
            return _doFetch(el, verbInfo, method, url, extraParams);
          });
        }
        if (!window.confirm(confirmMsg)) return Promise.resolve();
      }

      // sx-prompt: show prompt dialog, send result as SX-Prompt header
      var promptMsg = el.getAttribute("sx-prompt");
      if (promptMsg) {
        var promptVal = window.prompt(promptMsg);
        if (promptVal === null) return Promise.resolve(); // cancelled
        extraParams = extraParams || {};
        extraParams.promptValue = promptVal;
      }

      return _doFetch(el, verbInfo, method, url, extraParams);
    }

    function _doFetch(el, verbInfo, method, url, extraParams) {
      // sx-sync: abort previous
      var sync = el.getAttribute("sx-sync");
      if (sync && sync.indexOf("replace") >= 0) abortPrevious(el);

      var ctrl = new AbortController();
      trackController(el, ctrl);

      // Build headers
      var headers = {
        "SX-Request": "true",
        "SX-Current-URL": location.href
      };
      var targetSel = el.getAttribute("sx-target");
      if (targetSel) headers["SX-Target"] = targetSel;

      // Send loaded component names so cross-domain responses can prepend missing defs
      var loadedNames = Object.keys(_componentEnv).filter(function (k) {
        return k.charAt(0) === "~";
      });
      if (loadedNames.length) headers["SX-Components"] = loadedNames.join(",");

      // Send known CSS classes so server only sends new rules
      var cssHeader = _getSxCssHeader();
      if (cssHeader) headers["SX-Css"] = cssHeader;

      // Extra headers from sx-headers (SX dict {:key "val"} or JSON)
      var extraH = el.getAttribute("sx-headers");
      if (extraH) {
        try {
          var parsed = extraH.charAt(0) === "{" && extraH.charAt(1) === ":" ? parse(extraH) : JSON.parse(extraH);
          for (var k in parsed) headers[k] = String(parsed[k]);
        } catch (e) { /* ignore */ }
      }

      // SX-Prompt header from sx-prompt dialog result
      if (extraParams && extraParams.promptValue !== undefined) {
        headers["SX-Prompt"] = extraParams.promptValue;
      }

      // CSRF for same-origin mutating requests
      if (method !== "GET" && sameOrigin(url)) {
        var csrf = csrfToken();
        if (csrf) headers["X-CSRFToken"] = csrf;
      }

      // Build body
      var body = null;
      var isJson = el.getAttribute("sx-encoding") === "json";

      if (method !== "GET") {
        var form = el.closest("form") || (el.tagName === "FORM" ? el : null);
        if (form) {
          if (isJson) {
            var fd = new FormData(form);
            var obj = {};
            fd.forEach(function (v, k) {
              if (obj[k] !== undefined) {
                if (!Array.isArray(obj[k])) obj[k] = [obj[k]];
                obj[k].push(v);
              } else {
                obj[k] = v;
              }
            });
            body = JSON.stringify(obj);
            headers["Content-Type"] = "application/json";
          } else {
            body = new URLSearchParams(new FormData(form));
            headers["Content-Type"] = "application/x-www-form-urlencoded";
          }
        }
      }

      // sx-params: filter form parameters
      var paramsSpec = el.getAttribute("sx-params");
      if (paramsSpec && body instanceof URLSearchParams) {
        if (paramsSpec === "none") {
          body = new URLSearchParams();
        } else if (paramsSpec.indexOf("not ") === 0) {
          var excluded = paramsSpec.substring(4).split(",").map(function (s) { return s.trim(); });
          excluded.forEach(function (k) { body.delete(k); });
        } else if (paramsSpec !== "*") {
          var allowed = paramsSpec.split(",").map(function (s) { return s.trim(); });
          var filtered = new URLSearchParams();
          allowed.forEach(function (k) {
            body.getAll(k).forEach(function (v) { filtered.append(k, v); });
          });
          body = filtered;
        }
      }

      // Include extra inputs
      var includeSel = el.getAttribute("sx-include");
      if (includeSel && method !== "GET") {
        var extras = document.querySelectorAll(includeSel);
        if (!body) body = new URLSearchParams();
        extras.forEach(function (inp) {
          if (inp.name) body.append(inp.name, inp.value);
        });
      }

      // sx-vals: merge extra key-value pairs
      var valsAttr = el.getAttribute("sx-vals");
      if (valsAttr) {
        try {
          var vals = valsAttr.charAt(0) === "{" && valsAttr.charAt(1) === ":" ? parse(valsAttr) : JSON.parse(valsAttr);
          if (method === "GET") {
            for (var vk in vals) {
              url += (url.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(vk) + "=" + encodeURIComponent(vals[vk]);
            }
          } else if (body instanceof URLSearchParams) {
            for (var vk2 in vals) body.append(vk2, vals[vk2]);
          } else if (!body) {
            body = new URLSearchParams();
            for (var vk3 in vals) body.append(vk3, vals[vk3]);
            headers["Content-Type"] = "application/x-www-form-urlencoded";
          }
        } catch (e) { /* ignore */ }
      }

      // For GET with form data, append to URL
      if (method === "GET") {
        var form2 = el.closest("form") || (el.tagName === "FORM" ? el : null);
        if (form2) {
          var qs = new URLSearchParams(new FormData(form2)).toString();
          if (qs) url += (url.indexOf("?") >= 0 ? "&" : "?") + qs;
        }
        // Also handle inputs/selects/textareas with name attr
        if ((el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA") && el.name) {
          var param = encodeURIComponent(el.name) + "=" + encodeURIComponent(el.value);
          url += (url.indexOf("?") >= 0 ? "&" : "?") + param;
        }
      }

      // Lifecycle: beforeRequest
      if (!dispatch(el, "sx:beforeRequest", { method: method, url: url })) return Promise.resolve();

      // Loading state
      el.classList.add("sx-request");
      el.setAttribute("aria-busy", "true");

      // sx-indicator: show indicator element
      var indicatorSel = el.getAttribute("sx-indicator");
      var indicatorEl = indicatorSel ? (document.querySelector(indicatorSel) || el.closest(indicatorSel)) : null;
      if (indicatorEl) {
        indicatorEl.classList.add("sx-request");
        indicatorEl.style.display = "";
      }

      // sx-disabled-elt: disable elements during request
      var disabledEltSel = el.getAttribute("sx-disabled-elt");
      var disabledElts = disabledEltSel ? Array.prototype.slice.call(document.querySelectorAll(disabledEltSel)) : [];
      disabledElts.forEach(function (e) { e.disabled = true; });

      var fetchOpts = { method: method, headers: headers, signal: ctrl.signal };
      // Cross-origin credentials for known subdomains
      try {
        var urlHost = new URL(url, location.href).hostname;
        if (urlHost !== location.hostname &&
            (urlHost.endsWith(".rose-ash.com") || urlHost.endsWith(".localhost"))) {
          fetchOpts.credentials = "include";
        }
      } catch (e) {}
      if (body && method !== "GET") fetchOpts.body = body;

      // sx-preload: use cached response if available
      var preloaded = method === "GET" ? _getPreloaded(url) : null;
      var fetchPromise = preloaded
        ? Promise.resolve({ ok: true, status: 200, headers: new Headers({ "Content-Type": preloaded.contentType }), text: function () { return Promise.resolve(preloaded.text); }, _preloaded: true })
        : fetch(url, fetchOpts);

      return fetchPromise.then(function (resp) {
        el.classList.remove("sx-request");
        el.removeAttribute("aria-busy");
        if (indicatorEl) { indicatorEl.classList.remove("sx-request"); indicatorEl.style.display = "none"; }
        disabledElts.forEach(function (e) { e.disabled = false; });

        if (!resp.ok) {
          dispatch(el, "sx:responseError", { response: resp, status: resp.status });
          return _handleRetry(el, verbInfo, extraParams);
        }

        return resp.text().then(function (text) {
          dispatch(el, "sx:afterRequest", { response: resp });

          // --- Response header processing ---

          // SX-Redirect: navigate away (skip swap entirely)
          var hdrRedirect = resp.headers.get("SX-Redirect");
          if (hdrRedirect) { location.assign(hdrRedirect); return; }

          // SX-Refresh: reload page (skip swap entirely)
          var hdrRefresh = resp.headers.get("SX-Refresh");
          if (hdrRefresh === "true") { location.reload(); return; }

          // SX-Trigger: dispatch custom events on target
          var hdrTrigger = resp.headers.get("SX-Trigger");
          if (hdrTrigger) _dispatchTriggerEvents(el, hdrTrigger);

          // Process the response
          var rawSwap = el.getAttribute("sx-swap") || DEFAULT_SWAP;
          var target = resolveTarget(el, null);
          var selectSel = el.getAttribute("sx-select");

          // SX-Retarget: server overrides target
          var hdrRetarget = resp.headers.get("SX-Retarget");
          if (hdrRetarget) target = document.querySelector(hdrRetarget) || target;

          // SX-Reswap: server overrides swap strategy
          var hdrReswap = resp.headers.get("SX-Reswap");
          if (hdrReswap) rawSwap = hdrReswap;

          // Parse swap style and modifiers (e.g. "innerHTML transition:true")
          var swapParts = rawSwap.split(/\s+/);
          var swapStyle = swapParts[0];
          var useTransition = _config.globalViewTransitions;
          for (var sp = 1; sp < swapParts.length; sp++) {
            if (swapParts[sp] === "transition:true") useTransition = true;
            else if (swapParts[sp] === "transition:false") useTransition = false;
          }

          // Check for text/sx content type — use direct DOM rendering path
          var ct = resp.headers.get("Content-Type") || "";
          if (ct.indexOf("text/sx") >= 0) {
            try {
              // Strip and load any <script type="text/sx" data-components> blocks
              text = text.replace(/<script[^>]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi,
                function (_, defs) { Sx.loadComponents(defs); return ""; });
              // Process on-demand CSS: extract <style data-sx-css> and inject into head
              text = _processCssResponse(text, resp);
              var sxSource = text.trim();

              // Parse and render to live DOM nodes (skip renderToString + DOMParser)
              if (sxSource && sxSource.charAt(0) !== "(") {
                console.error("sx.js: sxSource does not start with '(' — first 200 chars:", sxSource.substring(0, 200));
              }
              var sxDom = Sx.render(sxSource);

              // Wrap in container for querySelectorAll (DocumentFragment doesn't support it)
              var container = document.createElement("div");
              container.appendChild(sxDom);

              // OOB processing on live DOM nodes
              _processOOBSwaps(container, _swapDOM);

              // sx-select filtering
              var selectedDOM;
              if (selectSel) {
                selectedDOM = document.createDocumentFragment();
                selectSel.split(",").forEach(function (sel) {
                  container.querySelectorAll(sel.trim()).forEach(function (m) {
                    selectedDOM.appendChild(m);
                  });
                });
              } else {
                // Use all remaining children
                selectedDOM = document.createDocumentFragment();
                while (container.firstChild) selectedDOM.appendChild(container.firstChild);
              }

              // Main swap using DOM morph
              if (swapStyle !== "none" && target) {
                _withTransition(useTransition, function () {
                  _swapDOM(target, selectedDOM, swapStyle);
                  _hoistHeadElements(target);
                });
              }
            } catch (err) {
              console.error("sx.js render error [v2]:", err, "\nsxSource first 500:", sxSource ? sxSource.substring(0, 500) : "(empty)");
              return;
            }
          } else {
            // HTML string path — existing DOMParser pipeline
            var parser = new DOMParser();
            var doc = parser.parseFromString(text, "text/html");

            // Process any sx script blocks in the response
            Sx.processScripts(doc);

            // OOB processing
            _processOOBSwaps(doc, function (t, o, s) { _swapContent(t, o.outerHTML, s); });

            // Build final content
            var content;
            if (selectSel) {
              var parts = selectSel.split(",").map(function (s) { return s.trim(); });
              var frags = [];
              parts.forEach(function (sel) {
                var matches = doc.querySelectorAll(sel);
                matches.forEach(function (m) { frags.push(m.outerHTML); });
              });
              content = frags.join("");
            } else {
              content = doc.body ? doc.body.innerHTML : text;
            }

            // Main swap
            if (swapStyle !== "none" && target) {
              _withTransition(useTransition, function () {
                _swapContent(target, content, swapStyle);
                _hoistHeadElements(target);
              });
            }
          }

          // SX-Location: server-driven client-side navigation
          var hdrLocation = resp.headers.get("SX-Location");
          if (hdrLocation) {
            var locUrl = hdrLocation;
            try { var locObj = JSON.parse(hdrLocation); locUrl = locObj.path || locObj; } catch (e) {}
            fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function (r) {
              return r.text().then(function (t) {
                var main = document.getElementById("main-panel");
                if (main) { _swapContent(main, t, "innerHTML"); _postSwap(main); }
                try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {}
              });
            });
            return;
          }

          // History: sx-push-url (pushState) and sx-replace-url (replaceState)
          var pushUrl = el.getAttribute("sx-push-url");
          var replaceUrl = el.getAttribute("sx-replace-url");
          // SX-Replace-Url response header overrides client-side attribute
          var hdrReplaceUrl = resp.headers.get("SX-Replace-Url");
          if (hdrReplaceUrl) {
            try { history.replaceState({ sxUrl: hdrReplaceUrl, scrollY: window.scrollY }, "", hdrReplaceUrl); } catch (e) {}
          } else if (pushUrl === "true" || (pushUrl && pushUrl !== "false")) {
            var pushTarget = pushUrl === "true" ? url : pushUrl;
            try {
              history.pushState({ sxUrl: pushTarget, scrollY: window.scrollY }, "", pushTarget);
            } catch (e) {
              location.assign(pushTarget);
              return;
            }
          } else if (replaceUrl === "true" || (replaceUrl && replaceUrl !== "false")) {
            var replTarget = replaceUrl === "true" ? url : replaceUrl;
            try {
              history.replaceState({ sxUrl: replTarget, scrollY: window.scrollY }, "", replTarget);
            } catch (e) { /* ignore */ }
          }

          dispatch(el, "sx:afterSwap", { target: target });
          // SX-Trigger-After-Swap
          var hdrTriggerSwap = resp.headers.get("SX-Trigger-After-Swap");
          if (hdrTriggerSwap) _dispatchTriggerEvents(el, hdrTriggerSwap);
          // Settle tick
          requestAnimationFrame(function () {
            dispatch(el, "sx:afterSettle", { target: target });
            // SX-Trigger-After-Settle
            var hdrTriggerSettle = resp.headers.get("SX-Trigger-After-Settle");
            if (hdrTriggerSettle) _dispatchTriggerEvents(el, hdrTriggerSettle);
          });
        });
      }).catch(function (err) {
        el.classList.remove("sx-request");
        el.removeAttribute("aria-busy");
        if (indicatorEl) { indicatorEl.classList.remove("sx-request"); indicatorEl.style.display = "none"; }
        disabledElts.forEach(function (e) { e.disabled = false; });
        if (err.name === "AbortError") return;
        dispatch(el, "sx:sendError", { error: err });
        return _handleRetry(el, verbInfo, extraParams);
      });
    }

    // ---- DOM morphing ------------------------------------------------------

    /**
     * Lightweight DOM reconciler — patches oldNode to match newNode in-place,
     * preserving event listeners, focus, scroll position, and form state on
     * keyed (id) elements.
     */
    function _morphDOM(oldNode, newNode) {
      // sx-preserve / sx-ignore: skip morphing entirely
      if (oldNode.hasAttribute && (oldNode.hasAttribute("sx-preserve") || oldNode.hasAttribute("sx-ignore"))) return;

      // Different node types or tag names → replace wholesale
      if (oldNode.nodeType !== newNode.nodeType ||
          oldNode.nodeName !== newNode.nodeName) {
        oldNode.parentNode.replaceChild(newNode.cloneNode(true), oldNode);
        return;
      }

      // Text/comment nodes → update content
      if (oldNode.nodeType === 3 || oldNode.nodeType === 8) {
        if (oldNode.nodeValue !== newNode.nodeValue)
          oldNode.nodeValue = newNode.nodeValue;
        return;
      }

      // Element nodes → sync attributes, then recurse children
      if (oldNode.nodeType === 1) {
        // Skip morphing focused input to preserve user's in-progress edits
        if (oldNode === document.activeElement &&
            (oldNode.tagName === "INPUT" || oldNode.tagName === "TEXTAREA" || oldNode.tagName === "SELECT")) {
          _syncAttrs(oldNode, newNode);  // sync non-value attrs (class, style, etc.)
          return;  // don't touch value or children
        }
        _syncAttrs(oldNode, newNode);
        _morphChildren(oldNode, newNode);
      }
    }

    function _syncAttrs(old, neu) {
      // Add/update attributes from new
      var newAttrs = neu.attributes;
      for (var i = 0; i < newAttrs.length; i++) {
        var a = newAttrs[i];
        if (old.getAttribute(a.name) !== a.value)
          old.setAttribute(a.name, a.value);
      }
      // Remove attributes not in new
      var oldAttrs = old.attributes;
      for (var j = oldAttrs.length - 1; j >= 0; j--) {
        if (!neu.hasAttribute(oldAttrs[j].name))
          old.removeAttribute(oldAttrs[j].name);
      }
    }

    function _morphChildren(oldParent, newParent) {
      var oldChildren = Array.prototype.slice.call(oldParent.childNodes);
      var newChildren = Array.prototype.slice.call(newParent.childNodes);

      // Build ID map of old children for keyed matching
      var oldById = {};
      for (var k = 0; k < oldChildren.length; k++) {
        var kid = oldChildren[k];
        if (kid.id) oldById[kid.id] = kid;
      }

      var oi = 0;
      for (var ni = 0; ni < newChildren.length; ni++) {
        var newChild = newChildren[ni];
        var matchById = newChild.id ? oldById[newChild.id] : null;

        if (matchById) {
          // Keyed match — move into position if needed, then morph
          if (matchById !== oldChildren[oi]) {
            oldParent.insertBefore(matchById, oldChildren[oi] || null);
          }
          _morphDOM(matchById, newChild);
          oi++;
        } else if (oi < oldChildren.length) {
          // Positional match — morph in place
          var oldChild = oldChildren[oi];
          if (oldChild.id && !newChild.id) {
            // Old has ID, new doesn't — insert new before old (don't clobber keyed)
            oldParent.insertBefore(newChild.cloneNode(true), oldChild);
          } else {
            _morphDOM(oldChild, newChild);
            oi++;
          }
        } else {
          // Extra new children — append
          oldParent.appendChild(newChild.cloneNode(true));
        }
      }

      // Remove leftover old children (skip sx-preserve / sx-ignore)
      while (oi < oldChildren.length) {
        var leftover = oldChildren[oi];
        if (leftover.parentNode === oldParent &&
            !(leftover.hasAttribute && (leftover.hasAttribute("sx-preserve") || leftover.hasAttribute("sx-ignore")))) {
          oldParent.removeChild(leftover);
        }
        oi++;
      }
    }

    // ---- DOM-native swap engine --------------------------------------------

    /**
     * Swap using live DOM nodes (from Sx.render) instead of HTML strings.
     * Uses _morphDOM for innerHTML/outerHTML to preserve state.
     */
    function _swapDOM(target, newNodes, strategy) {
      // newNodes is a DocumentFragment, Element, or Text node
      var wrapper;
      switch (strategy) {
        case "innerHTML":
          // Morph children of target to match newNodes
          if (newNodes.nodeType === 11) {
            // DocumentFragment — morph its children into target
            _morphChildren(target, newNodes);
          } else {
            wrapper = document.createElement("div");
            wrapper.appendChild(newNodes);
            _morphChildren(target, wrapper);
          }
          break;
        case "outerHTML":
          var parent = target.parentNode;
          if (newNodes.nodeType === 11) {
            // Fragment — morph first child, insert rest
            var first = newNodes.firstChild;
            if (first) {
              _morphDOM(target, first);
              var sib = first.nextSibling;  // skip first (used as morph template, not consumed)
              while (sib) {
                var next = sib.nextSibling;
                parent.insertBefore(sib, target.nextSibling);
                sib = next;
              }
            } else {
              parent.removeChild(target);
            }
          } else {
            _morphDOM(target, newNodes);
          }
          _postSwap(parent);
          return;  // early return like existing outerHTML
        case "afterend":
          target.parentNode.insertBefore(newNodes, target.nextSibling);
          break;
        case "beforeend":
          target.appendChild(newNodes);
          break;
        case "afterbegin":
          target.insertBefore(newNodes, target.firstChild);
          break;
        case "beforebegin":
          target.parentNode.insertBefore(newNodes, target);
          break;
        case "delete":
          target.parentNode.removeChild(target);
          return;
        default:  // fallback = innerHTML
          if (newNodes.nodeType === 11) {
            _morphChildren(target, newNodes);
          } else {
            wrapper = document.createElement("div");
            wrapper.appendChild(newNodes);
            _morphChildren(target, wrapper);
          }
      }
      _postSwap(target);
    }

    // ---- Swap engine (string-based, kept as fallback) ----------------------

    function _processOOBSwaps(container, swapFn, postSwapFn) {
      ["sx-swap-oob", "hx-swap-oob"].forEach(function (attr) {
        container.querySelectorAll("[" + attr + "]").forEach(function (oob) {
          var swapType = oob.getAttribute(attr) || "outerHTML";
          var target = document.getElementById(oob.id);
          oob.removeAttribute(attr);
          if (oob.parentNode) oob.parentNode.removeChild(oob);
          if (target) {
            swapFn(target, oob, swapType);
            if (postSwapFn) postSwapFn(target);
          }
        });
      });
    }

    /** Scripts inserted via innerHTML/insertAdjacentHTML don't execute.
     *  Recreate them as live elements so the browser fetches & runs them. */
    function _activateScripts(root) {
      var dead = root.querySelectorAll("script:not([type]), script[type='text/javascript']");
      for (var i = 0; i < dead.length; i++) {
        var d = dead[i];
        var live = document.createElement("script");
        for (var a = 0; a < d.attributes.length; a++)
          live.setAttribute(d.attributes[a].name, d.attributes[a].value);
        live.textContent = d.textContent;
        d.parentNode.replaceChild(live, d);
      }
    }

    function _postSwap(root) {
      _activateScripts(root);
      Sx.processScripts(root);
      Sx.hydrate(root);
      SxEngine.process(root);
    }

    function _swapContent(target, html, strategy) {
      switch (strategy) {
        case "innerHTML":
          // Detach sx-preserve elements, swap, then re-attach
          var preserved = [];
          target.querySelectorAll("[sx-preserve][id]").forEach(function (el) {
            preserved.push({ id: el.id, node: el });
            el.parentNode.removeChild(el);
          });
          target.innerHTML = html;
          preserved.forEach(function (p) {
            var placeholder = target.querySelector("#" + CSS.escape(p.id));
            if (placeholder) placeholder.parentNode.replaceChild(p.node, placeholder);
            else target.appendChild(p.node);
          });
          break;
        case "outerHTML":
          var tgt = target;
          var parent = tgt.parentNode;
          tgt.insertAdjacentHTML("afterend", html);
          parent.removeChild(tgt);
          _postSwap(parent);
          return; // early return — afterSwap handling done inline
        case "afterend":
          target.insertAdjacentHTML("afterend", html);
          break;
        case "beforeend":
          target.insertAdjacentHTML("beforeend", html);
          break;
        case "afterbegin":
          target.insertAdjacentHTML("afterbegin", html);
          break;
        case "beforebegin":
          target.insertAdjacentHTML("beforebegin", html);
          break;
        case "delete":
          target.parentNode.removeChild(target);
          return;
        default:
          target.innerHTML = html;
      }
      _postSwap(target);
    }

    // ---- Retry system -----------------------------------------------------

    function _handleRetry(el, verbInfo, extraParams) {
      var retry = el.getAttribute("sx-retry");
      if (!retry) return;

      var parts = retry.split(":");
      var strategy = parts[0]; // "exponential"
      var startMs = parseInt(parts[1], 10) || 1000;
      var capMs = parseInt(parts[2], 10) || 30000;

      var currentMs = parseInt(el.getAttribute("data-sx-retry-ms"), 10) || startMs;

      el.classList.add("sx-error");
      el.classList.remove("sx-loading");

      setTimeout(function () {
        el.classList.remove("sx-error");
        el.classList.add("sx-loading");
        el.setAttribute("data-sx-retry-ms", Math.min(currentMs * 2, capMs));
        executeRequest(el, verbInfo, extraParams);
      }, currentMs);
    }

    // ---- Trigger system ---------------------------------------------------

    function _parseTime(s) {
      // Parse time string: "2s" → 2000, "500ms" → 500, "1.5s" → 1500
      if (!s) return 0;
      if (s.indexOf("ms") >= 0) return parseInt(s, 10);
      if (s.indexOf("s") >= 0) return parseFloat(s) * 1000;
      return parseInt(s, 10);
    }

    function parseTrigger(spec) {
      if (!spec) return null;
      var triggers = [];
      var parts = spec.split(",");
      for (var i = 0; i < parts.length; i++) {
        var p = parts[i].trim();
        if (!p) continue;
        var tokens = p.split(/\s+/);
        // Handle "every <time>" as a special trigger
        if (tokens[0] === "every" && tokens.length >= 2) {
          triggers.push({ event: "every", modifiers: { interval: _parseTime(tokens[1]) } });
          continue;
        }
        var trigger = { event: tokens[0], modifiers: {} };
        for (var j = 1; j < tokens.length; j++) {
          var tok = tokens[j];
          if (tok === "once") trigger.modifiers.once = true;
          else if (tok === "changed") trigger.modifiers.changed = true;
          else if (tok.indexOf("delay:") === 0) trigger.modifiers.delay = _parseTime(tok.substring(6));
          else if (tok.indexOf("from:") === 0) trigger.modifiers.from = tok.substring(5);
        }
        triggers.push(trigger);
      }
      return triggers;
    }

    function bindTriggers(el, verbInfo) {
      var triggerSpec = el.getAttribute("sx-trigger");
      var triggers;

      if (triggerSpec) {
        triggers = parseTrigger(triggerSpec);
      } else {
        // Defaults
        if (el.tagName === "FORM") {
          triggers = [{ event: "submit", modifiers: {} }];
        } else if (el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA") {
          triggers = [{ event: "change", modifiers: {} }];
        } else {
          triggers = [{ event: "click", modifiers: {} }];
        }
      }

      triggers.forEach(function (trig) {
        if (trig.event === "every") {
          var ms = trig.modifiers.interval || 1000;
          setInterval(function () { executeRequest(el, verbInfo); }, ms);
        } else if (trig.event === "intersect") {
          _bindIntersect(el, verbInfo, trig.modifiers);
        } else if (trig.event === "load") {
          setTimeout(function () { executeRequest(el, verbInfo); }, 0);
        } else if (trig.event === "revealed") {
          _bindIntersect(el, verbInfo, { once: true });
        } else {
          _bindEvent(el, verbInfo, trig);
        }
      });
    }

    function _bindEvent(el, verbInfo, trig) {
      var eventName = trig.event;
      var mods = trig.modifiers;
      var listenTarget = mods.from ? document.querySelector(mods.from) || el : el;
      var timer = null;
      var lastVal = undefined;

      var handler = function (e) {
        // For form submissions, prevent default
        if (eventName === "submit") e.preventDefault();
        // For links, prevent navigation
        if (eventName === "click" && el.tagName === "A") e.preventDefault();

        // sx-validate: run validation before request
        var validateAttr = el.getAttribute("sx-validate");
        if (validateAttr === null) {
          var vForm = el.closest("[sx-validate]");
          if (vForm) validateAttr = vForm.getAttribute("sx-validate");
        }
        if (validateAttr !== null) {
          var formToValidate = el.tagName === "FORM" ? el : el.closest("form");
          if (formToValidate && !formToValidate.reportValidity()) {
            dispatch(el, "sx:validationFailed", {});
            return;
          }
          // Custom validator function
          if (validateAttr && validateAttr !== "true" && validateAttr !== "") {
            var validatorFn = window[validateAttr];
            if (typeof validatorFn === "function" && !validatorFn(el)) {
              dispatch(el, "sx:validationFailed", {});
              return;
            }
          }
        }

        // changed modifier: only fire if value changed
        if (mods.changed && el.value !== undefined) {
          if (el.value === lastVal) return;
          lastVal = el.value;
        }

        // sx-optimistic: apply preview before request
        var optimisticState = _applyOptimistic(el);

        var _execAndReconcile = function () {
          var p = executeRequest(el, verbInfo);
          if (optimisticState && p && p.catch) {
            p.catch(function () { _revertOptimistic(optimisticState); });
          }
        };

        if (mods.delay) {
          clearTimeout(timer);
          timer = setTimeout(_execAndReconcile, mods.delay);
        } else {
          _execAndReconcile();
        }
      };

      listenTarget.addEventListener(eventName, handler, { once: !!mods.once });
    }

    function _bindIntersect(el, verbInfo, mods) {
      if (!("IntersectionObserver" in window)) {
        executeRequest(el, verbInfo);
        return;
      }
      var fired = false;
      var delay = mods.delay || 0;
      var obs = new IntersectionObserver(function (entries) {
        entries.forEach(function (entry) {
          if (!entry.isIntersecting) return;
          if (mods.once && fired) return;
          fired = true;
          if (mods.once) obs.unobserve(el);
          if (delay) {
            setTimeout(function () { executeRequest(el, verbInfo); }, delay);
          } else {
            executeRequest(el, verbInfo);
          }
        });
      });
      obs.observe(el);
    }

    // ---- History manager --------------------------------------------------

    if (typeof window !== "undefined") {
      window.addEventListener("popstate", function (e) {
        var url = location.href;
        var main = document.getElementById("main-panel");
        if (!main) { location.reload(); return; }

        var histHeaders = { "SX-Request": "true", "SX-History-Restore": "true" };
        var cssH = _getSxCssHeader();
        if (cssH) histHeaders["SX-Css"] = cssH;
        var loadedN = Object.keys(_componentEnv).filter(function (k) { return k.charAt(0) === "~"; });
        if (loadedN.length) histHeaders["SX-Components"] = loadedN.join(",");
        var histOpts = { headers: histHeaders };
        try {
          var hHost = new URL(url, location.href).hostname;
          if (hHost !== location.hostname &&
              (hHost.endsWith(".rose-ash.com") || hHost.endsWith(".localhost"))) {
            histOpts.credentials = "include";
          }
        } catch (e) {}

        fetch(url, histOpts).then(function (resp) {
          return resp.text().then(function (t) { return { text: t, resp: resp }; });
        }).then(function (r) {
          var text = r.text;
          var resp = r.resp;
          // Strip and load any <script type="text/sx" data-components> blocks
          text = text.replace(/<script[^>]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi,
            function (_, defs) { Sx.loadComponents(defs); return ""; });
          // Process on-demand CSS
          text = _processCssResponse(text, resp);
          text = text.trim();

          if (text.charAt(0) === "(") {
            // sx response — render to live DOM, morph into main
            try {
              var popDom = Sx.render(text);
              var popContainer = document.createElement("div");
              popContainer.appendChild(popDom);

              // Process OOB swaps (sidebar, filter, menu, headers)
              _processOOBSwaps(popContainer, _swapDOM, function (t) { Sx.hydrate(t); SxEngine.process(t); });

              var newMain = popContainer.querySelector("#main-panel");
              _morphChildren(main, newMain || popContainer);
              _postSwap(main);
              dispatch(document.body, "sx:afterSettle", { target: main });
              window.scrollTo(0, e.state && e.state.scrollY || 0);
            } catch (err) {
              console.error("sx.js popstate render error [v2]:", err, "\ntext first 500:", text ? text.substring(0, 500) : "(empty)");
              location.reload();
            }
          } else {
            // HTML response — parse and morph
            var parser = new DOMParser();
            var doc = parser.parseFromString(text, "text/html");

            // Process OOB swaps from HTML response
            _processOOBSwaps(doc, function (t, o, s) { _swapContent(t, o.outerHTML, s); });

            var newMain = doc.getElementById("main-panel");
            if (newMain) {
              _morphChildren(main, newMain);
              _postSwap(main);
              dispatch(document.body, "sx:afterSettle", { target: main });
              window.scrollTo(0, e.state && e.state.scrollY || 0);
            } else {
              location.reload();
            }
          }
        }).catch(function () {
          location.reload();
        });
      });
    }

    // ---- sx-on:* inline event handlers ------------------------------------

    function _bindInlineHandlers(el) {
      var attrs = el.attributes;
      for (var i = 0; i < attrs.length; i++) {
        var name = attrs[i].name;
        if (name.indexOf("sx-on:") === 0) {
          var evtName = name.substring(6);
          el.addEventListener(evtName, new Function("event", attrs[i].value));
        }
      }
    }

    // ---- sx-optimistic ----------------------------------------------------

    function _applyOptimistic(el) {
      var directive = el.getAttribute("sx-optimistic");
      if (!directive) return null;
      var target = resolveTarget(el, null) || el;
      var state = { target: target, directive: directive };

      if (directive === "remove") {
        state.display = target.style.display;
        state.opacity = target.style.opacity;
        target.style.opacity = "0";
        target.style.pointerEvents = "none";
      } else if (directive === "disable") {
        state.disabled = target.disabled;
        target.disabled = true;
      } else if (directive.indexOf("add-class:") === 0) {
        var cls = directive.substring(10);
        state.addClass = cls;
        target.classList.add(cls);
      }
      return state;
    }

    function _revertOptimistic(state) {
      if (!state) return;
      var target = state.target;
      if (state.directive === "remove") {
        target.style.opacity = state.opacity || "";
        target.style.pointerEvents = "";
      } else if (state.directive === "disable") {
        target.disabled = state.disabled || false;
      } else if (state.addClass) {
        target.classList.remove(state.addClass);
      }
    }

    // ---- sx-preload -------------------------------------------------------

    var _preloadCache = {};
    var _PRELOAD_TTL = 30000; // 30 seconds

    function _bindPreload(el) {
      if (!el.hasAttribute("sx-preload")) return;
      var mode = el.getAttribute("sx-preload") || "mousedown";
      var events = mode === "mouseover" ? ["mouseenter", "focusin"] : ["mousedown", "focusin"];
      var debounceTimer = null;
      var debounceMs = mode === "mouseover" ? 100 : 0;

      events.forEach(function (evt) {
        el.addEventListener(evt, function () {
          var verb = getVerb(el);
          if (!verb) return;
          var url = verb.url;
          var cached = _preloadCache[url];
          if (cached && (Date.now() - cached.timestamp < _PRELOAD_TTL)) return; // already cached

          if (debounceMs) {
            clearTimeout(debounceTimer);
            debounceTimer = setTimeout(function () { _doPreload(url); }, debounceMs);
          } else {
            _doPreload(url);
          }
        });
      });
    }

    function _doPreload(url) {
      var headers = { "SX-Request": "true", "SX-Current-URL": location.href };
      var cssH = _getSxCssHeader();
      if (cssH) headers["SX-Css"] = cssH;
      var loadedN = Object.keys(_componentEnv).filter(function (k) { return k.charAt(0) === "~"; });
      if (loadedN.length) headers["SX-Components"] = loadedN.join(",");

      fetch(url, { headers: headers }).then(function (resp) {
        if (!resp.ok) return;
        var ct = resp.headers.get("Content-Type") || "";
        return resp.text().then(function (text) {
          _preloadCache[url] = { text: text, contentType: ct, timestamp: Date.now() };
        });
      }).catch(function () { /* ignore preload errors */ });
    }

    function _getPreloaded(url) {
      var cached = _preloadCache[url];
      if (!cached) return null;
      if (Date.now() - cached.timestamp > _PRELOAD_TTL) {
        delete _preloadCache[url];
        return null;
      }
      delete _preloadCache[url]; // consume once
      return cached;
    }

    // ---- sx-boost ---------------------------------------------------------

    function _processBoosted(root) {
      var boostContainers = root.querySelectorAll("[sx-boost]");
      if (root.matches && root.matches("[sx-boost]")) {
        _boostDescendants(root);
      }
      for (var i = 0; i < boostContainers.length; i++) {
        _boostDescendants(boostContainers[i]);
      }
    }

    function _boostDescendants(container) {
      // Boost links
      var links = container.querySelectorAll("a[href]");
      for (var i = 0; i < links.length; i++) {
        var link = links[i];
        if (link[PROCESSED] || link[PROCESSED + "boost"]) continue;
        var href = link.getAttribute("href");
        // Skip anchors, external, javascript:, mailto:, already sx-processed
        if (!href || href.charAt(0) === "#" || href.indexOf("javascript:") === 0 ||
            href.indexOf("mailto:") === 0 || !sameOrigin(href) ||
            link.hasAttribute("sx-get") || link.hasAttribute("sx-post") ||
            link.hasAttribute("sx-disable")) continue;
        link[PROCESSED + "boost"] = true;
        (function (el, url) {
          el.addEventListener("click", function (e) {
            e.preventDefault();
            executeRequest(el, { method: "GET", url: url }).then(function () {
              try { history.pushState({ sxUrl: url, scrollY: window.scrollY }, "", url); } catch (err) {}
            });
          });
        })(link, href);
        // Default target for boosted links
        if (!link.hasAttribute("sx-target")) link.setAttribute("sx-target", "#main-panel");
        if (!link.hasAttribute("sx-swap")) link.setAttribute("sx-swap", "innerHTML");
        if (!link.hasAttribute("sx-select")) link.setAttribute("sx-select", "#main-panel");
      }

      // Boost forms
      var forms = container.querySelectorAll("form");
      for (var j = 0; j < forms.length; j++) {
        var form = forms[j];
        if (form[PROCESSED] || form[PROCESSED + "boost"]) continue;
        if (form.hasAttribute("sx-get") || form.hasAttribute("sx-post") ||
            form.hasAttribute("sx-disable")) continue;
        form[PROCESSED + "boost"] = true;
        (function (el) {
          var method = (el.getAttribute("method") || "GET").toUpperCase();
          var action = el.getAttribute("action") || location.href;
          el.addEventListener("submit", function (e) {
            e.preventDefault();
            executeRequest(el, { method: method, url: action }).then(function () {
              try { history.pushState({ sxUrl: action, scrollY: window.scrollY }, "", action); } catch (err) {}
            });
          });
        })(form);
        if (!form.hasAttribute("sx-target")) form.setAttribute("sx-target", "#main-panel");
        if (!form.hasAttribute("sx-swap")) form.setAttribute("sx-swap", "innerHTML");
      }
    }

    // ---- SSE (Server-Sent Events) ----------------------------------------

    function _processSSE(root) {
      var sseEls = root.querySelectorAll("[sx-sse]");
      if (root.matches && root.matches("[sx-sse]")) _bindSSE(root);
      for (var i = 0; i < sseEls.length; i++) _bindSSE(sseEls[i]);
    }

    function _bindSSE(el) {
      if (el._sxSSE) return; // already connected
      var url = el.getAttribute("sx-sse");
      if (!url) return;

      var source = new EventSource(url);
      el._sxSSE = source;

      // Bind swap handlers for sx-sse-swap="eventName" attributes on el and descendants
      var swapEls = el.querySelectorAll("[sx-sse-swap]");
      if (el.hasAttribute("sx-sse-swap")) _bindSSESwap(el, source);
      for (var i = 0; i < swapEls.length; i++) _bindSSESwap(swapEls[i], source);

      source.addEventListener("error", function () { dispatch(el, "sx:sseError", {}); });
      source.addEventListener("open", function () { dispatch(el, "sx:sseOpen", {}); });

      // Cleanup: close EventSource when element is removed from DOM
      if (typeof MutationObserver !== "undefined") {
        var obs = new MutationObserver(function () {
          if (!document.body.contains(el)) {
            source.close();
            el._sxSSE = null;
            obs.disconnect();
          }
        });
        obs.observe(document.body, { childList: true, subtree: true });
      }
    }

    function _bindSSESwap(el, source) {
      var eventName = el.getAttribute("sx-sse-swap") || "message";
      source.addEventListener(eventName, function (e) {
        var target = resolveTarget(el, null) || el;
        var swapStyle = el.getAttribute("sx-swap") || "innerHTML";
        var data = e.data;
        if (data.trim().charAt(0) === "(") {
          try {
            var dom = Sx.render(data);
            _swapDOM(target, dom, swapStyle);
          } catch (err) {
            _swapContent(target, data, swapStyle);
          }
        } else {
          _swapContent(target, data, swapStyle);
        }
        _postSwap(target);
        dispatch(el, "sx:sseMessage", { data: data, event: eventName });
      });
    }

    // ---- Process function -------------------------------------------------

    function process(root) {
      root = root || document.body;
      if (!root || !root.querySelectorAll) return;

      var selector = "[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]";
      var elements = root.querySelectorAll(selector);

      // Also check root itself
      if (root.matches && root.matches(selector)) {
        _processOne(root);
      }

      for (var i = 0; i < elements.length; i++) {
        _processOne(elements[i]);
      }

      // Process sx-boost containers
      _processBoosted(root);

      // Process SSE connections
      _processSSE(root);

      // Bind sx-on:* handlers on all elements
      var allOnEls = root.querySelectorAll("[sx-on\\:beforeRequest],[sx-on\\:afterRequest],[sx-on\\:afterSwap],[sx-on\\:afterSettle],[sx-on\\:responseError]");
      allOnEls.forEach(function (el) {
        if (el[PROCESSED + "on"]) return;
        el[PROCESSED + "on"] = true;
        _bindInlineHandlers(el);
      });
    }

    function _processOne(el) {
      if (el[PROCESSED]) return;
      // sx-disable: skip processing
      if (el.hasAttribute("sx-disable") || el.closest("[sx-disable]")) return;
      el[PROCESSED] = true;

      var verbInfo = getVerb(el);
      if (!verbInfo) return;

      bindTriggers(el, verbInfo);
      _bindPreload(el);
    }

    // ---- Public API -------------------------------------------------------

    var engine = {
      process: process,
      executeRequest: executeRequest,
      config: _config,
      version: "1.0.0"
    };

    return engine;
  })();

  global.SxEngine = SxEngine;

  // --- Auto-init in browser ---

  Sx.VERSION = "2026-03-04-cssx2";

  // CSS class tracking for on-demand CSS delivery
  var _sxCssHash = "";  // 8-char hex hash from server

  function _initCssTracking() {
    var meta = document.querySelector('meta[name="sx-css-classes"]');
    if (meta) {
      var content = meta.getAttribute("content");
      if (content) _sxCssHash = content;
    }
  }

  function _getSxCssHeader() {
    return _sxCssHash;
  }

  function _processCssResponse(text, resp) {
    var hashHeader = resp.headers.get("SX-Css-Hash");
    if (hashHeader) _sxCssHash = hashHeader;

    // Extract <style data-sx-css>...</style> blocks and inject into <style id="sx-css">
    var cssTarget = document.getElementById("sx-css");
    if (cssTarget) {
      text = text.replace(/<style[^>]*data-sx-css[^>]*>([\s\S]*?)<\/style>/gi,
        function (_, css) {
          cssTarget.textContent += css;
          return "";
        });
    }
    return text;
  }

  // --- sx-comp-hash cookie helpers ---

  function _setSxCompCookie(hash) {
    document.cookie = "sx-comp-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax";
  }

  function _clearSxCompCookie() {
    document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
  }

  // --- sx-styles-hash cookie helpers ---

  function _setSxStylesCookie(hash) {
    document.cookie = "sx-styles-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax";
  }

  function _clearSxStylesCookie() {
    document.cookie = "sx-styles-hash=;path=/;max-age=0;SameSite=Lax";
  }

  function _initStyleDict() {
    var scripts = document.querySelectorAll('script[type="text/sx-styles"]');
    for (var i = 0; i < scripts.length; i++) {
      var s = scripts[i];
      if (s._sxProcessed) continue;
      s._sxProcessed = true;

      var text = s.textContent;
      var hash = s.getAttribute("data-hash");
      if (!hash) {
        if (text && text.trim()) {
          try { _loadStyleDict(JSON.parse(text)); } catch (e) { console.warn("[sx.js] style dict parse error", e); }
        }
        continue;
      }

      var hasInline = text && text.trim();
      try {
        var cachedHash = localStorage.getItem("sx-styles-hash");
        if (cachedHash === hash) {
          if (hasInline) {
            localStorage.setItem("sx-styles-src", text);
            _loadStyleDict(JSON.parse(text));
            console.log("[sx.js] styles: downloaded (cookie stale)");
          } else {
            var cached = localStorage.getItem("sx-styles-src");
            if (cached) {
              _loadStyleDict(JSON.parse(cached));
              console.log("[sx.js] styles: cached (" + hash + ")");
            } else {
              _clearSxStylesCookie();
              location.reload();
              return;
            }
          }
        } else {
          if (hasInline) {
            localStorage.setItem("sx-styles-hash", hash);
            localStorage.setItem("sx-styles-src", text);
            _loadStyleDict(JSON.parse(text));
            console.log("[sx.js] styles: downloaded (" + hash + ")");
          } else {
            localStorage.removeItem("sx-styles-hash");
            localStorage.removeItem("sx-styles-src");
            _clearSxStylesCookie();
            location.reload();
            return;
          }
        }
      } catch (e) {
        if (hasInline) {
          try { _loadStyleDict(JSON.parse(text)); } catch (e2) { console.warn("[sx.js] style dict parse error", e2); }
        }
      }
      _setSxStylesCookie(hash);
    }
  }

  if (typeof document !== "undefined") {
    var init = function () {
      console.log("[sx.js] v" + Sx.VERSION + " init");
      _initCssTracking();
      _initStyleDict();
      Sx.processScripts();
      Sx.hydrate();
      SxEngine.process();
    };
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", init);
    } else {
      init();
    }



  }

})(typeof window !== "undefined" ? window : this);
