/* ===========================================================
   Shared components & motion primitives
   =========================================================== */
const { motion, AnimatePresence } = window.Motion;

/* ---- Lucide icon -> React SVG ---- */
function Icon({ name, size = 24, stroke = 1.9, className = "", style }) {
  const node = window.lucide && window.lucide[name];
  // lucide node formats:
  //   newer: ["svg", {attrs}, [ [tag, attrs], ... ]]
  //   older: [ [tag, attrs], ... ]
  let kids = null;
  if (node) kids = Array.isArray(node[0]) ? node : node[2];
  if (!kids) {
    return React.createElement("svg", {
      width: size, height: size, viewBox: "0 0 24 24",
      fill: "none", stroke: "currentColor", strokeWidth: stroke, className, style,
    }, React.createElement("circle", { cx: 12, cy: 12, r: 9 }));
  }
  return React.createElement(
    "svg",
    {
      width: size, height: size, viewBox: "0 0 24 24", fill: "none",
      stroke: "currentColor", strokeWidth: stroke,
      strokeLinecap: "round", strokeLinejoin: "round",
      className, style,
    },
    kids.map((child, i) => React.createElement(child[0], { ...child[1], key: i }))
  );
}

/* ---- Animation capability gate ----
   In hidden/backgrounded documents browsers pause rAF *and* CSS animations,
   so a framer-motion enter (opacity:0 -> 1) would never reveal content.
   When animation can't run we render at the FINAL state instead of the hidden
   one, so content is always visible (print, reduced-motion, background tab).
   App flips this true + replays once the document becomes visible. */
const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
window.__animOK = !document.hidden && !prefersReduced;
const initHidden = (v) => (window.__animOK ? (v === undefined ? "hidden" : v) : false);

/* ---- Motion variants ---- */
const EASE = [0.22, 1, 0.36, 1];

const containerV = {
  hidden: {},
  show: { transition: { staggerChildren: 0.075, delayChildren: 0.18 } },
};
const fadeUpV = {
  hidden: { opacity: 0, y: 26, filter: "blur(6px)" },
  show: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: 0.6, ease: EASE } },
};
const fadeV = {
  hidden: { opacity: 0 },
  show: { opacity: 1, transition: { duration: 0.7, ease: EASE } },
};
const scaleV = {
  hidden: { opacity: 0, scale: 0.92 },
  show: { opacity: 1, scale: 1, transition: { duration: 0.55, ease: EASE } },
};

/* Reusable building blocks */
const keyChildren = (children, prefix) =>
  React.Children.map(children, (c, i) =>
    React.isValidElement(c) ? React.cloneElement(c, { key: c.key != null ? c.key : prefix + i }) : c
  );

const Stagger = ({ children, className = "", style, delay = 0 }) =>
  React.createElement(
    motion.div,
    {
      className, style,
      variants: { hidden: {}, show: { transition: { staggerChildren: 0.075, delayChildren: 0.18 + delay } } },
      initial: initHidden(), animate: "show",
    },
    keyChildren(children, "sg")
  );

const Up = ({ children, className = "", style, as = "div" }) =>
  React.createElement(motion[as] || motion.div, { className, style, variants: fadeUpV }, keyChildren(children, "up"));

/* ---- SlideFitter: scale .slide-inner down so content never collides with
   the persistent chrome on shorter/odd-ratio screens. Native size at 1080p+. */
function SlideFitter({ children, fitKey }) {
  const ref = React.useRef(null);
  React.useLayoutEffect(() => {
    const root = ref.current;
    if (!root) return;
    const slide = root.querySelector(".slide");
    const inner = root.querySelector(".slide-inner");
    if (!slide || !inner) return;
    let raf = 0;
    const fit = () => {
      inner.style.transform = "none";
      const cs = getComputedStyle(slide);
      const availH = slide.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom);
      const availW = slide.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
      const h = inner.offsetHeight;
      const w = inner.offsetWidth;
      if (!h || !w) return;
      const s = Math.min(1, availH / h, availW / w);
      inner.style.transformOrigin = "center center";
      inner.style.transform = s < 0.999 ? "scale(" + s + ")" : "none";
    };
    const schedule = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(fit); };
    fit();
    // re-fit after fonts settle (rAF may be paused while hidden -> use timeout too)
    const t = setTimeout(fit, 120);
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(fit);
    const ro = new ResizeObserver(schedule);
    ro.observe(slide);
    window.addEventListener("resize", schedule);
    return () => { ro.disconnect(); window.removeEventListener("resize", schedule); clearTimeout(t); cancelAnimationFrame(raf); };
  });
  return React.createElement("div", { ref, style: { position: "absolute", inset: 0 } }, children);
}

/* ---- Kicker (eyebrow) ---- */
function Kicker({ children, index }) {
  return React.createElement(
    motion.div,
    { className: "kicker", variants: fadeUpV },
    React.createElement("span", { className: "kicker-dot" }),
    typeof index === "number"
      ? React.createElement("span", { className: "num" }, String(index).padStart(2, "0"))
      : null,
    React.createElement("span", null, children)
  );
}

/* ---- Title ---- */
function Title({ children, style }) {
  return React.createElement(motion.h1, { className: "title", variants: fadeUpV, style }, children);
}

/* ---- AnimatedNumber: count-up on mount ---- */
function AnimatedNumber({ value, duration = 1.6, format, style, className }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const fmt = format || ((n) => Math.round(n).toLocaleString("ru-RU"));
    if (!window.__animOK) { el.textContent = fmt(value); return; }
    let raf = 0, start = 0;
    const ease = (t) => 1 - Math.pow(1 - t, 3);
    const step = (ts) => {
      if (!start) start = ts;
      const t = Math.min(1, (ts - start) / (duration * 1000));
      el.textContent = fmt(value * ease(t));
      if (t < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [value, duration]);
  return React.createElement("span", { ref, className, style }, "0");
}

/* ---- Background: liquid glowing orbs + grid + cursor parallax ---- */
function Background() {
  const orbs = [
    { cls: "orb--yellow", size: "46vw", top: "-14vh", left: "58vw", dur: 19, dx: 40, dy: 30, op: 0.55, depth: 22 },
    { cls: "orb--red", size: "34vw", top: "52vh", left: "-8vw", dur: 23, dx: 50, dy: -40, op: 0.5, depth: 34 },
    { cls: "orb--yellow", size: "26vw", top: "62vh", left: "62vw", dur: 27, dx: -45, dy: -30, op: 0.32, depth: 16 },
  ];
  const layerRefs = React.useRef([]);
  React.useEffect(() => {
    if (window.__animOK === false) return;
    let raf = 0;
    const onMove = (e) => {
      const nx = (e.clientX / window.innerWidth - 0.5) * 2;
      const ny = (e.clientY / window.innerHeight - 0.5) * 2;
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        layerRefs.current.forEach((el, i) => {
          if (!el) return;
          const d = orbs[i].depth;
          el.style.transform = "translate(" + (-nx * d) + "px," + (-ny * d) + "px)";
        });
      });
    };
    window.addEventListener("pointermove", onMove);
    return () => { window.removeEventListener("pointermove", onMove); cancelAnimationFrame(raf); };
  }, []);
  return React.createElement(
    "div",
    { className: "stage" },
    React.createElement("div", { className: "ambient", key: "amb" }),
    React.createElement("div", { className: "grid-overlay", key: "grid" }),
    orbs.map((o, i) =>
      React.createElement(
        "div",
        { key: "lay" + i, className: "parallax", ref: (el) => (layerRefs.current[i] = el) },
        React.createElement("div", {
          className: "orb " + o.cls,
          style: {
            width: o.size, height: o.size, top: o.top, left: o.left, opacity: o.op,
            "--dx": o.dx + "px", "--dy": o.dy + "px",
            animationDuration: o.dur + "s",
          },
        })
      )
    ),
    React.createElement("div", { className: "vignette", key: "vig" })
  );
}

/* ---- Persistent chrome: logo, counter, progress, nav, dots, theme toggle ---- */
function Chrome({ index, total, onPrev, onNext, onGoto, isDark, onToggleTheme }) {
  return React.createElement(
    "div",
    { className: "chrome" },
    // logo
    React.createElement(
      "div",
      { className: "logo" },
      React.createElement(
        "div",
        { className: "logo-mark logo-mark--img" },
        React.createElement("img", {
          src: "logo1cm.png",
          alt: "1С",
          style: { width: "100%", height: "100%", objectFit: "contain", display: "block" },
        })
      ),
      React.createElement(
        "div",
        { className: "logo-text" },
        React.createElement("b", null, "СБП B2B"),
        React.createElement("span", null, "Фирма 1С")
      )
    ),
    // counter
    React.createElement(
      "div",
      { className: "counter" },
      React.createElement("span", { className: "cur" }, String(index + 1).padStart(2, "0")),
      React.createElement("span", { className: "sep" }, "/"),
      React.createElement("span", null, String(total).padStart(2, "0"))
    ),
    // dots
    React.createElement(
      "div",
      { className: "dots" },
      Array.from({ length: total }).map((_, i) =>
        React.createElement("div", {
          key: i,
          className: "dot" + (i === index ? " active" : ""),
          onClick: () => onGoto(i),
        })
      )
    ),
    // nav
    React.createElement(
      "div",
      { className: "nav" },
      React.createElement(
        "button",
        { className: "nav-btn", onClick: onPrev, disabled: index === 0, "aria-label": "Назад" },
        React.createElement(Icon, { name: "ArrowLeft", size: 22 })
      ),
      React.createElement(
        "button",
        { className: "nav-btn", onClick: onNext, disabled: index === total - 1, "aria-label": "Вперёд" },
        React.createElement(Icon, { name: "ArrowRight", size: 22 })
      )
    ),
    // progress
    React.createElement(
      "div",
      { className: "progress" },
      React.createElement("div", {
        className: "progress-fill",
        style: { width: ((index + 1) / total) * 100 + "%" },
      })
    ),
    // theme toggle removed
  );
}

/* ---- Tilt3D: cards tilt toward the cursor in real 3D space ---- */
function Tilt3D({ children, max = 11, className = "", style, lift = true }) {
  const ref = React.useRef(null);
  const raf = React.useRef(0);
  const enabled = window.__animOK && !window.matchMedia("(pointer: coarse)").matches;
  const onMove = (e) => {
    if (!enabled) return;
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const px = (e.clientX - r.left) / r.width - 0.5;
    const py = (e.clientY - r.top) / r.height - 0.5;
    cancelAnimationFrame(raf.current);
    raf.current = requestAnimationFrame(() => {
      el.style.transform =
        "rotateY(" + (px * max) + "deg) rotateX(" + (-py * max) + "deg)" + (lift ? " translateZ(24px)" : "");
      el.style.setProperty("--glare-pos", (px * 120 + 50) + "% " + (py * 120 + 50) + "%");
    });
  };
  const onLeave = () => {
    const el = ref.current;
    if (!el) return;
    cancelAnimationFrame(raf.current);
    el.style.transform = "rotateY(0deg) rotateX(0deg)";
    el.style.setProperty("--glare-pos", "120% 0%");
  };
  return React.createElement(
    "div",
    { className: "tilt-scene " + className, style, onPointerMove: onMove, onPointerLeave: onLeave },
    React.createElement("div", { className: "tilt-inner", ref }, children)
  );
}

/* ---- LetterReveal: cinematic per-glyph reveal from deep blur ---- */
function LetterReveal({ text, className = "", style, stagger = 0.052, delay = 0.1 }) {
  const chars = Array.from(text);
  const childV = {
    hidden: { opacity: 0, y: "0.5em", filter: "blur(18px)", rotateX: -55 },
    show: { opacity: 1, y: 0, filter: "blur(0px)", rotateX: 0, transition: { duration: 0.78, ease: EASE } },
  };
  return React.createElement(
    motion.span,
    {
      className,
      "aria-label": text,
      style: { display: "inline-block", perspective: "700px", transformStyle: "preserve-3d", ...style },
      variants: { hidden: {}, show: { transition: { staggerChildren: stagger, delayChildren: delay } } },
      initial: initHidden(),
      animate: "show",
    },
    chars.map((c, i) =>
      React.createElement(
        motion.span,
        { key: i, "aria-hidden": true, variants: childV, style: { display: "inline-block", whiteSpace: "pre", transformOrigin: "50% 100%" } },
        c === " " ? "\u00A0" : c
      )
    )
  );
}

/* ---- NeuralPipeline: glowing glass orbs joined by luminous data packets ---- */
function NeuralPipeline({ nodes, height = 150 }) {
  const W = 1200;
  const n = nodes.length;
  const padX = W / (n * 2);
  const cy = height / 2;
  const xs = nodes.map((_, i) => padX + (i * (W - padX * 2)) / (n - 1));
  const linePath = "M " + xs.map((x) => x + " " + cy).join(" L ");
  return React.createElement(
    motion.div,
    { variants: fadeV, style: { width: "100%", position: "relative" } },
    React.createElement(
      "svg",
      { className: "neural", viewBox: "0 0 " + W + " " + height, preserveAspectRatio: "xMidYMid meet" },
      React.createElement("path", { className: "neural-line", d: linePath, key: "base" }),
      React.createElement("path", { className: "neural-flow", d: linePath, key: "flow" }),
      React.createElement("path", { className: "neural-flow", d: linePath, key: "flow2", style: { animationDelay: "-1.6s", opacity: 0.7 } }),
      xs.map((x, i) =>
        React.createElement(
          "g",
          { key: "o" + i },
          React.createElement("circle", { cx: x, cy, r: 26, fill: "rgba(20,23,29,0.9)", stroke: nodes[i].key ? "rgba(255,204,0,0.6)" : "rgba(255,255,255,0.14)", strokeWidth: 1.4 }),
          React.createElement("circle", { className: nodes[i].key ? "neural-orb-core" : "", cx: x, cy, r: 6.5, fill: nodes[i].key ? "var(--yellow)" : "rgba(255,255,255,0.5)" }),
          React.createElement(
            "text",
            {
              x,
              y: cy + 26 + 26,
              textAnchor: "middle",
              fill: nodes[i].key ? "var(--yellow)" : "var(--ink-soft)",
              style: {
                fontFamily: '"JetBrains Mono", monospace',
                fontSize: "17px",
                fontWeight: nodes[i].key ? 700 : 500,
                letterSpacing: "0.04em",
              },
            },
            nodes[i].label
          )
        )
      )
    )
  );
}

/* ---- PaymentWidget: gooey "Ожидание оплаты" → elastic "ОПЛАЧЕНО" ---- */
function PaymentWidget() {
  // phase: 0 waiting · 1 collapsing · 2 paid
  const [phase, setPhase] = React.useState(window.__animOK ? 0 : 2);
  React.useEffect(() => {
    if (!window.__animOK) return;
    let t = [];
    const cycle = () => {
      setPhase(0);
      t.push(setTimeout(() => setPhase(1), 3000));
      t.push(setTimeout(() => setPhase(2), 3450));
      t.push(setTimeout(cycle, 7200));
    };
    cycle();
    return () => t.forEach(clearTimeout);
  }, []);

  const burst = Array.from({ length: 10 });
  return React.createElement(
    "div",
    { style: { position: "relative", height: 78, display: "grid", placeItems: "center" } },
    React.createElement(
      AnimatePresence,
      { mode: "wait" },
      phase < 2
        ? React.createElement(
            motion.div,
            {
              key: "wait",
              initial: { opacity: 0, scale: 0.9 },
              animate: phase === 1 ? { opacity: 0, scale: 0.18, transition: { duration: 0.42, ease: [0.6, 0, 0.9, 0.2] } } : { opacity: 1, scale: 1 },
              exit: { opacity: 0, scale: 0.1, transition: { duration: 0.2 } },
              style: {
                display: "inline-flex", alignItems: "center", gap: 14,
                padding: "18px 30px", borderRadius: 999,
                background: "rgba(255,255,255,0.04)", border: "1px solid var(--hairline)",
                fontWeight: 700, fontSize: "clamp(15px,1.4vw,20px)", color: "var(--ink-soft)",
              },
            },
            React.createElement("span", { className: "pay-spinner" }),
            "Ожидание оплаты"
          )
        : React.createElement(
            motion.div,
            {
              key: "paid",
              initial: { scale: 0, opacity: 0 },
              animate: { scale: 1, opacity: 1, transition: { type: "spring", stiffness: 260, damping: 11, mass: 0.9 } },
              style: {
                position: "relative",
                display: "inline-flex", alignItems: "center", gap: 14,
                padding: "18px 34px", borderRadius: 999,
                background: "linear-gradient(150deg, rgba(67,224,138,0.22), rgba(67,224,138,0.06))",
                border: "1px solid rgba(67,224,138,0.5)",
                boxShadow: "0 0 40px rgba(67,224,138,0.35)",
                fontWeight: 800, fontSize: "clamp(16px,1.6vw,23px)", letterSpacing: "0.04em", color: "#7ef0ae",
              },
            },
            burst.map((_, i) => {
              const a = (i / burst.length) * Math.PI * 2;
              return React.createElement(motion.span, {
                key: i, className: "pay-burst",
                initial: { x: 0, y: 0, opacity: 1, scale: 1 },
                animate: { x: Math.cos(a) * 90, y: Math.sin(a) * 60, opacity: 0, scale: 0.2, transition: { duration: 0.7, ease: "easeOut" } },
              });
            }),
            React.createElement(Icon, { name: "Check", size: 24, stroke: 3 }),
            "ОПЛАЧЕНО"
          )
    )
  );
}

/* ---- KineticWords: massive watermark type sliding behind content ---- */
function KineticWords({ words }) {
  return React.createElement(
    "div",
    { className: "kinetic-layer" },
    words.map((w, i) =>
      React.createElement(
        "div",
        {
          key: i,
          className: "kinetic-word" + (i % 2 ? " solid" : ""),
          style: {
            top: w.top,
            left: "50%",
            animation: (i % 2 ? "kineticR " : "kineticL ") + (w.dur || 30) + "s linear infinite",
            animationDelay: (w.delay || 0) + "s",
          },
        },
        w.text
      )
    )
  );
}

/* ---- QRCode: dynamic QR with cinematic particle assembly animation ---- */
function QRCode({ url, size = 400, fgColor = "#ffcc00", bgColor = "#0c0c14", quietZone = 2, style }) {
  const canvasRef = React.useRef(null);

  /* shared: draw one rounded-rect module */
  function drawModule(ctx, x, y, s, r) {
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + s - r, y);
    ctx.quadraticCurveTo(x + s, y, x + s, y + r);
    ctx.lineTo(x + s, y + s - r);
    ctx.quadraticCurveTo(x + s, y + s, x + s - r, y + s);
    ctx.lineTo(x + r, y + s);
    ctx.quadraticCurveTo(x, y + s, x, y + s - r);
    ctx.lineTo(x, y + r);
    ctx.quadraticCurveTo(x, y, x + r, y);
    ctx.fill();
  }

  /* draw the final crisp QR */
  function drawFinal(ctx, qr, count, quietZone, cellSize, size, fgColor, bgColor) {
    ctx.globalAlpha = 1;
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, size, size);
    const r = cellSize * 0.32;
    ctx.fillStyle = fgColor;
    for (let row = 0; row < count; row++) {
      for (let col = 0; col < count; col++) {
        if (!qr.isDark(row, col)) continue;
        drawModule(ctx, (col + quietZone) * cellSize, (row + quietZone) * cellSize, cellSize + 0.5, r);
      }
    }
  }

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas || !url) return;

    const qr = qrcode(0, "M");
    qr.addData(url);
    qr.make();
    const count = qr.getModuleCount();
    const total = count + quietZone * 2;
    const cellSize = size / total;
    canvas.width = size;
    canvas.height = size;
    const ctx = canvas.getContext("2d");

    /* no animation → draw immediately */
    if (!window.__animOK) {
      drawFinal(ctx, qr, count, quietZone, cellSize, size, fgColor, bgColor);
      return;
    }

    /* collect dark modules as particles */
    const center = size / 2;
    const particles = [];
    for (let row = 0; row < count; row++) {
      for (let col = 0; col < count; col++) {
        if (!qr.isDark(row, col)) continue;
        const tx = (col + quietZone) * cellSize;
        const ty = (row + quietZone) * cellSize;
        /* start far away — random angle, distance 1.2–2.5× canvas size */
        const angle = Math.random() * Math.PI * 2;
        const dist = size * (1.2 + Math.random() * 1.3);
        /* stagger: inner modules arrive last (spiral-in feel) */
        const fromCenter = Math.hypot(tx + cellSize / 2 - center, ty + cellSize / 2 - center) / (size * 0.7);
        const delay = (1 - fromCenter) * 0.35 + Math.random() * 0.12;
        particles.push({
          sx: center + Math.cos(angle) * dist,
          sy: center + Math.sin(angle) * dist,
          tx, ty,
          delay,
          /* slight curve via a random control point offset */
          cx: center + (Math.random() - 0.5) * size * 0.6,
          cy: center + (Math.random() - 0.5) * size * 0.6,
        });
      }
    }

    const DURATION = 3500; /* 3.5 s */
    const r = cellSize * 0.32;
    const start = performance.now();
    let raf;

    function frame(now) {
      const elapsed = now - start;
      const globalT = Math.min(1, elapsed / DURATION);

      ctx.clearRect(0, 0, size, size);
      ctx.fillStyle = bgColor;
      ctx.fillRect(0, 0, size, size);

      /* glow layer (drawn first, underneath) */
      ctx.shadowColor = fgColor;
      ctx.shadowBlur = 0;

      for (const p of particles) {
        /* per-particle progress with stagger */
        const raw = (globalT - p.delay * 0.6) / (1 - p.delay * 0.6);
        const t = Math.max(0, Math.min(1, raw));

        if (t === 0) continue; /* hasn't started yet */

        /* easeOutCubic for smooth deceleration + slight overshoot snap */
        const ease = t < 0.85
          ? 1 - Math.pow(1 - (t / 0.85), 3)
          : 1 + Math.sin((t - 0.85) / 0.15 * Math.PI) * 0.02;

        /* quadratic Bézier: start → control → target */
        const inv = 1 - ease;
        const x = inv * inv * p.sx + 2 * inv * ease * p.cx + ease * ease * p.tx;
        const y = inv * inv * p.sy + 2 * inv * ease * p.cy + ease * ease * p.ty;

        /* scale: tiny → full */
        const scale = 0.25 + 0.75 * Math.min(1, t * 1.8);
        const s = (cellSize + 0.5) * scale;

        /* opacity: fade in quickly */
        const alpha = Math.min(1, t * 3);

        /* glow trail (only while flying) */
        if (t < 0.9) {
          ctx.shadowBlur = (1 - t) * 12;
        } else {
          ctx.shadowBlur = 0;
        }

        ctx.globalAlpha = alpha;
        ctx.fillStyle = fgColor;
        drawModule(ctx, x + (cellSize - s) / 2, y + (cellSize - s) / 2, s, r * scale);
      }

      ctx.shadowBlur = 0;
      ctx.globalAlpha = 1;

      if (globalT < 1) {
        raf = requestAnimationFrame(frame);
      } else {
        /* final crisp render — pixel-perfect */
        drawFinal(ctx, qr, count, quietZone, cellSize, size, fgColor, bgColor);
      }
    }

    raf = requestAnimationFrame(frame);
    return () => cancelAnimationFrame(raf);
  }, [url, size, fgColor, bgColor]);

  return React.createElement("canvas", {
    ref: canvasRef,
    style: { width: "100%", height: "100%", display: "block", borderRadius: "inherit", ...style },
  });
}

Object.assign(window, {
  Icon, motion, AnimatePresence,
  containerV, fadeUpV, fadeV, scaleV, EASE, initHidden,
  Stagger, Up, SlideFitter, Kicker, Title, Background, Chrome,
  AnimatedNumber, Tilt3D, LetterReveal, NeuralPipeline, PaymentWidget, KineticWords, QRCode,
});
