/* loop.jsx — "Det levende systemet": animated living-system loop */
const { useState, useEffect, useRef } = React;
const { useTweaks, TweaksPanel, TweakSection, TweakSlider, TweakToggle, TweakColor } = window;
const I = window.Ico;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "nodeSize": 58,
  "hubSize": 84,
  "accent": "#2B43C4",
  "speed": 3.4,
  "flow": true
} /*EDITMODE-END*/;

/* ---- mockup cards (per pillar) ---- */
const MOCKS = {
  reactivation:
  <div className="mock">
      <div className="mock__hd"><span className="t">CRM · Reaktivering</span><span className="pill">342 kunder</span></div>
      <div className="mock__body">
        {[["TL", "Tom Lie", "Varmepumpe · 11 mnd"], ["KB", "Kari Berg", "Maling · 9 mnd"], ["JO", "Jan Olsen", "Tak · 14 mnd"]].map(([a, n, s]) =>
      <div className="mock__row" key={n}><span className="av">{a}</span><span className="nm"><b>{n}</b><span>{s}</span></span><span className="add">Aktiver</span></div>
      )}
        <div className="mock__note">Sovende kunder vekkes med ett klikk — uten annonsekostnad.</div>
      </div>
    </div>,

  "reactivation-out":
  <div className="mock">
      <div className="mock__hd"><span className="t">Systemer</span></div>
      <div className="mock__body">
        {[["TX", "Tripletex", "#2A6FDB"], ["GK", "Google Kalender", "#1F8A5B"], ["NS", "Nettside-skjema", "#64748B"], ["SF", "Salesforce", "#00A1E0"]].map(([a, n, c]) =>
      <div className="mock__row" key={n}><span className="av" style={{ background: c, color: "#fff" }}>{a}</span><span className="nm"><b>{n}</b></span><span className="conn">Tilkoblet</span></div>
      )}
        <div className="mock__note">Toveis-synk holder hver kundeprofil komplett og oppdatert.</div>
      </div>
    </div>,

  "reviews-out":
  <div className="mock">
      <div className="mock__hd"><span className="t">Kvalitetssløyfe</span><span className="pill green">SMS sendt</span></div>
      <div className="mock__body">
        <span className="mock__bubble out">Hei! Hvordan vil du vurdere jobben vi gjorde? Svar 1–5 ⭐</span>
        <div className="mock__rating">
          <span className="mock__stars">{[0,1,2,3,4].map((i) => <span key={i} style={{ color: i < 2 ? "var(--accent)" : "#D9DEE5" }}>{I.star()}</span>)}</span>
          <span className="mock__route warn">2 ★ → Privat</span>
        </div>
        <div className="mock__row" style={{ borderStyle: "dashed" }}><span className="nm"><b>Privat tilbakemelding</b><span>Arkivert · analyseres</span></span><span className="conn" style={{ color: "var(--accent-strong)" }}>Skjult</span></div>
        <div className="mock__note">4–5 ★ → Google-anmeldelse. 1–3 ★ fanges privat — så du kan forbedre uten dårlig omtale.</div>
      </div>
    </div>,

  booking:
  <div className="mock">
      <div className="mock__hd"><span className="t">Booking & Callback</span><span className="pill blue">24/7 live</span></div>
      <div className="mock__body">
        <span className="mock__bubble in">Har dere ledig for befaring fredag?</span>
        <span className="mock__bubble out">Ja! Passer kl. 16:00? 👍</span>
        <div className="mock__cal"><span className="tm">16:00</span><span className="ev"><b>Befaring · ny kunde</b><span>Booket automatisk · fredag</span></span></div>
      </div>
    </div>,

  reviews:
  <div className="mock">
      <div className="mock__hd"><span className="t">Anmeldelsesmotor</span><span className="pill green">5,0 ★</span></div>
      <div className="mock__body">
        <div className="mock__stars">{[0, 1, 2, 3, 4].map((i) => <span key={i}>{I.star()}</span>)}</div>
        <div className="mock__rank"><span className="hash">#1</span>Første valg på «rørlegger oslo»</div>
        <div className="mock__note">1–3 ★ rutes til privat tilbakemelding — aldri offentlig på Google.</div>
      </div>
    </div>,

  analyse:
  <div className="mock">
      <div className="mock__hd"><span className="t">Alviss-dashbord</span><span className="pill green">▲ denne mnd</span></div>
      <div className="mock__body">
        <div className="mock__stats">
          <div className="mock__stat"><div className="v">128</div><div className="l">Nye leads</div></div>
          <div className="mock__stat"><div className="v">41<small>▲</small></div><div className="l">Avtaler</div></div>
          <div className="mock__stat"><div className="v">5,0</div><div className="l">Snitt ★</div></div>
        </div>
        <div className="mock__bars">{[40, 55, 48, 70, 62, 84, 96].map((h, i) => <i key={i} className={i === 6 ? "hot" : ""} style={{ height: h + "%" }}></i>)}</div>
        <div className="mock__note">Sanntid i dashbordet — og en grundig rapport fra oss hver måned.</div>
      </div>
    </div>,

  marketing:
  <div className="mock">
      <div className="mock__hd"><span className="t">Målrettet annonsering</span><span className="pill">Meta · Google</span></div>
      <div className="mock__body">
        <div className="mock__chips"><span className="mock__chip"><span className="dot"></span>Meta Ads · Aktiv</span><span className="mock__chip"><span className="dot"></span>Google Ads · Aktiv</span></div>
        <div className="mock__cal"><span className="tm">+38</span><span className="ev"><b>Nye leads denne uken</b><span>Kostnad per lead ned 24 %</span></span></div>
        <div className="mock__feed">{I.megaphone()} Nye leads mates rett inn i CRM-et {I.arrow()}</div>
      </div>
    </div>

};

/* ---- pillar nodes (ring) + their external source ---- */
const NODES = [
{
  id: "analyse", pos: "top", icon: "chart",
  n: { x: 550, y: 212, l: "50%", t: "29.44%" },
  e: { x: 550, y: 138, l: "50%", t: "19.2%" },
  label: "Analyse og rapportering", short: "Dashbord + rapport",
  tip: "Alt måles. Dashbord i sanntid + månedlig rapport fra oss.",
  extIcon: "", extLabel: "Alviss Analyse", extSub: "Segmentering · Automasjon · Rapportering", extBadges: ["target", "bolt", "robot"],
  extTip: "All data mates tilbake — systemet lærer og blir smartere for hver runde.",
  title: "Analyse og rapportering", tag: "Alviss-dashbord · månedlig rapport",
  desc: "Alt systemet gjør måles. I Alviss-dashbordet ser du effekten i sanntid, og hver måned får du en grundig rapport fra oss. Innsikten gjør neste runde smartere."
},
{
  id: "booking", pos: "right", icon: "chat",
  n: { x: 698, y: 360, l: "63.45%", t: "50%" },
  e: { x: 985, y: 360, l: "89.5%", t: "50%" },
  label: "AI-resepsjonisten", short: "Kalenderbooking · Callback",
  tip: "Henvendelser besvares og bookes automatisk — 24/7.",
  extIcon: "users", extLabel: "Kundehenvendelser", extSub: "Rask oppfølging · Hele døgnet",
  extTip: "Henvendelser fra nettside og telefon fanges opp — ingen går tapt.",
  title: "Kundebooking & callback", tag: "Ingen tapte anrop · booking 24/7",
  desc: "Hver henvendelse — tapt anrop, skjema eller SMS — besvares og bookes automatisk, døgnet rundt. Du mister aldri en kunde til konkurrenten igjen."
},
{
  id: "reactivation", pos: "bottom", icon: "refresh",
  n: { x: 550, y: 508, l: "50%", t: "70.56%" },
  e: { x: 550, y: 690, l: "50%", t: "95.8%" },
  label: "Reaktiveringskampanjer", short: "Vekk sovende kunder · SMS · Nye bookinger",
  tip: "Sovende kunder vekkes med relevante kampanjer.",
  extIcon: "database", extLabel: "Eksterne systemer", extSub: "CRM · Kalender · Regnskap",
  extTip: "Hele kundebasen din — klar til å reaktiveres automatisk.",
  title: "Reaktiveringskampanjer", tag: "CRM → nye oppdrag",
  desc: "Alviss henter sovende kunder rett fra CRM-et og vekker dem med relevante tilbud. Gamle relasjoner blir til ny inntekt — uten annonsekostnad.",
  oTitle: "Eksterne systemer", oTag: "Toveis-synk med verktøyene du allerede bruker",
  oDesc: "Synk kundedata på tvers av systemene dine, hent inn nye leads fra egne skjema og landingssider, og utløs meldinger fra eksterne systemer. Hent CRM-felter og send berikede profiler tilbake — så hver kundeprofil er komplett og oppdatert."
},
{
  id: "reviews", pos: "left", icon: "star",
  n: { x: 402, y: 360, l: "36.55%", t: "50%" },
  e: { x: 160, y: 360, l: "14.5%", t: "50%" },
  label: "Anmeldelsesmotor", short: "Flere 5-stjerner, helt automatisk",
  tip: "Gode opplevelser blir til 5★ på Google.",
  extIcon: "swear", extLabel: "Kvalitetssløyfe", extSub: "Forbedringsarkiv",
  extTip: "Rating-SMS etter jobb: 4–5 ★ → Google, 1–3 ★ → privat tilbakemelding.",
  title: "Anmeldelsesmotor", tag: "Første valg på Google-søk",
  desc: "Gode opplevelser blir 5-stjerners Google-anmeldelser som løfter deg til toppen av lokale søk. Dårlige fanges som private tilbakemeldinger — aldri offentlig.",
  oTitle: "Kvalitetssløyfe", oTag: "Tilbakemelding → arkiv → forbedring",
  oDesc: "Etter hvert oppdrag sender Alviss en SMS som ber kunden vurdere jobben 1–5. Får du 4–5 ★ ledes kunden til en Google-anmeldelse; 1–3 ★ rutes til en privat tilbakemelding — så misnøye aldri havner offentlig. Alviss samler inn, arkiverer og analyserer alt, og rapporterer tilbake til deg slik at du kan gjøre konkrete forbedringer."
}];

const HUB = {
  id: "marketing", icon: "rise",
  tip: "Markedsføring aktiverer hele systemet — og mater nye kunder inn i CRM-et.",
  title: "Markedsføring", tag: "Motoren som aktiverer hele systemet",
  desc: "Når fundamentet konverterer, skrur vi på målrettet annonsering mot lokale kunder. Nye leads mates inn i CRM-et — og hele det levende systemet starter på nytt, smartere enn før."
};
const ALL = [...NODES, HUB];
const CYCLE = ["reactivation", "booking", "reviews", "analyse"];
const C = { x: 550, y: 360 };
const RING_ARCS = [
  { from: "analyse", d: "M590.8 217.7 A148 148 0 0 1 692.3 319.2" },
  { from: "booking", d: "M692.3 400.8 A148 148 0 0 1 590.8 502.3" },
  { from: "reactivation", d: "M509.2 502.3 A148 148 0 0 1 407.7 400.8" },
  { from: "reviews", d: "M407.7 319.2 A148 148 0 0 1 509.2 217.7" },
];
const SEG = { analyse: "oklch(0.55 0.22 25)", booking: "oklch(0.55 0.18 155)", reactivation: "oklch(0.55 0.18 245)", reviews: "oklch(0.72 0.17 75)" };

/* spoke segments centre -> node, trimmed for air at both ends */
function spokeFor(id) {
  switch (id) {
    case "analyse":return { x1: 550, y1: 306, x2: 550, y2: 253 };
    case "booking":return { x1: 604, y1: 360, x2: 657, y2: 360 };
    case "reactivation":return { x1: 550, y1: 414, x2: 550, y2: 467 };
    case "reviews":return { x1: 496, y1: 360, x2: 443, y2: 360 };
    default:return { x1: 550, y1: 360, x2: 550, y2: 360 };
  }
}

/* external arrow segments (node <-> source), two per node — with air on both ends */
function arrowsFor(id) {
  switch (id) {
    case "analyse":return [];
    case "booking":return [{ x1: 786, y1: 354, x2: 941, y2: 354 }, { x1: 941, y1: 366, x2: 786, y2: 366 }];
    case "reactivation":return [{ x1: 544, y1: 658, x2: 544, y2: 600 }, { x1: 556, y1: 600, x2: 556, y2: 658 }];
    case "reviews":return [{ x1: 198, y1: 354, x2: 353, y2: 354 }, { x1: 353, y1: 366, x2: 198, y2: 366 }];
    default:return [];
  }
}

function Diagram({ active, sys, flow, outer, onOuter, onPick, onTip, offTip }) {
  const ia = outer ? null : active;
  return (
    <div className={"loop" + (sys ? " is-active" : "") + (flow ? "" : " no-flow")}>
      <svg className="loop__svg" viewBox="0 0 1100 720" aria-hidden="true">
        <defs>
          <marker id="ah" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" refX="8" refY="5.5" orient="auto-start-reverse">
            <path d="M0 0 L11 5.5 L0 11 Z" fill="context-stroke" />
          </marker>
          <marker id="ring-ah" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" refX="8.5" refY="6" orient="auto">
            <path d="M0 0 L12 6 L0 12 Z" fill="context-stroke" />
          </marker>
          <path id="loop-orbit" d="M 550 212 A 148 148 0 0 1 698 360 A 148 148 0 0 1 550 508 A 148 148 0 0 1 402 360 A 148 148 0 0 1 550 212 Z" />
          <linearGradient id="cometGrad" gradientUnits="userSpaceOnUse" x1="5" y1="0" x2="-34" y2="0">
            <stop offset="0%" stopColor="#26303f" stopOpacity="0.95" />
            <stop offset="55%" stopColor="#3a4a61" stopOpacity="0.35" />
            <stop offset="100%" stopColor="#3a4a61" stopOpacity="0" />
          </linearGradient>
          <filter id="cometGlow" x="-80%" y="-80%" width="260%" height="260%"><feGaussianBlur stdDeviation="1.1" /></filter>
        </defs>

        {/* clockwise curved arrows between the pillar circles */}
        {RING_ARCS.map((a) => {
          const pid = "arc-" + a.from;
          const showBalls = sys || ia === a.from;
          const ballFill = SEG[a.from];
          return (
            <g key={a.from} onMouseEnter={() => onPick(a.from)} style={{ cursor: "pointer" }}>
              <path className="loop__archit" d={a.d} />
              <path id={pid} className={"loop__ringarc" + (ia === a.from ? " lit" : "")} style={{ "--seg": SEG[a.from] }} d={a.d} markerEnd="url(#ring-ah)" />
              {showBalls && [{ d: 4.4, b: 0 }, { d: 5.3, b: 1.2 }, { d: 6.2, b: 2.4 }].map((m, i) => (
                <g key={i} filter="url(#cometGlow)" pointerEvents="none">
                  <path d="M4 0 C -3 -2.6, -10 -1.3, -13 0 C -10 1.3, -3 2.6, 4 0 Z" fill={ballFill} fillOpacity="0.38" />
                  <circle r="2.6" fill={ballFill} opacity="0.9" />
                  <animateMotion dur={m.d + "s"} begin={m.b + "s"} repeatCount="indefinite" rotate="auto" keyPoints="0;0.96" keyTimes="0;1" calcMode="linear"><mpath href={"#" + pid} /></animateMotion>
                </g>
              ))}
            </g>
          );
        })}
        <g pointerEvents="none" filter="url(#cometGlow)">
          <path d="M5 0 C -4 -3.2, -13 -1.6, -17 0 C -13 1.6, -4 3.2, 5 0 Z" fillOpacity="0.4">
            <animate attributeName="fill" values="oklch(0.55 0.22 25);oklch(0.55 0.18 155);oklch(0.55 0.18 245);oklch(0.72 0.17 75)" keyTimes="0;0.25;0.5;0.75" dur="20s" calcMode="discrete" repeatCount="indefinite" />
          </path>
          <circle r="3">
            <animate attributeName="fill" values="oklch(0.55 0.22 25);oklch(0.55 0.18 155);oklch(0.55 0.18 245);oklch(0.72 0.17 75)" keyTimes="0;0.25;0.5;0.75" dur="20s" calcMode="discrete" repeatCount="indefinite" />
          </circle>
          <animateMotion dur="20s" repeatCount="indefinite" rotate="auto"><mpath href="#loop-orbit" /></animateMotion>
        </g>

        {/* spokes from centre to each pillar */}
        {/* spokes removed per request */}

        {/* external arrows */}
        {NODES.map((nd) => arrowsFor(nd.id).map((a, i) =>
        <g key={"ar" + nd.id + i} className={"loop__arrowg--" + nd.id + " loop__arrowg--" + nd.id + "-" + i} style={{ "--seg": SEG[nd.id] }} onMouseEnter={() => onOuter(nd.id)}>
            <line className="loop__archit" x1={a.x1} y1={a.y1} x2={a.x2} y2={a.y2} />
            <line className={"loop__arrow" + (outer === nd.id || sys ? " lit" : "")} x1={a.x1} y1={a.y1} x2={a.x2} y2={a.y2} markerEnd="url(#ah)" />
          </g>
        ))}
        {(sys ? NODES.map((nd) => nd.id) : (outer ? [outer] : [])).map((oid) => arrowsFor(oid).map((a, ai) => (
          <g key={"obx" + oid + ai} pointerEvents="none">
            {[{ d: 3.2, b: 0 }, { d: 4.1, b: 1.6 }].map((m, i) => {
              const bc = (oid === "reactivation" && ai === 1) ? "#1F8A5B" : (oid === "reviews" && ai === 0) ? "#DC3D43" : SEG[oid];
              return (
              <g key={i} filter="url(#cometGlow)">
                <path d="M4 0 C -3 -2.6, -10 -1.3, -13 0 C -10 1.3, -3 2.6, 4 0 Z" fill={bc} fillOpacity="0.38" />
                <circle r="2.6" fill={bc} opacity="0.9" />
                <animateMotion dur={m.d + "s"} begin={m.b + "s"} repeatCount="indefinite" rotate="auto" keyPoints="0;0.94" keyTimes="0;1" calcMode="linear" path={"M " + a.x1 + " " + a.y1 + " L " + a.x2 + " " + a.y2} />
              </g>
              );
            })}
          </g>
        )))}
        {/* green feed: AI-resepsjonist (green) active → green balls flow into Eksterne systemer's right arrow */}
        {!sys && !outer && active === "booking" && arrowsFor("reactivation").map((a, ai) => ai === 1 && (
          <g key={"greenfeed"} pointerEvents="none">
            {[{ d: 3.2, b: 0 }, { d: 4.1, b: 1.6 }].map((m, i) => (
              <g key={i} filter="url(#cometGlow)">
                <path d="M4 0 C -3 -2.6, -10 -1.3, -13 0 C -10 1.3, -3 2.6, 4 0 Z" fill={SEG.booking} fillOpacity="0.38" />
                <circle r="2.6" fill={SEG.booking} opacity="0.9" />
                <animateMotion dur={m.d + "s"} begin={m.b + "s"} repeatCount="indefinite" rotate="auto" keyPoints="0;0.94" keyTimes="0;1" calcMode="linear" path={"M " + a.x1 + " " + a.y1 + " L " + a.x2 + " " + a.y2} />
              </g>
            ))}
          </g>
        ))}
        {/* blue feed: Reaktivering inner circle active → blue balls travel up Eksterne systemer's left arrow */}
        {!sys && !outer && active === "reactivation" && arrowsFor("reactivation").map((a, ai) => ai === 0 && (
          <g key={"bluefeed"} pointerEvents="none">
            {[{ d: 3.2, b: 0 }, { d: 4.1, b: 1.6 }].map((m, i) => (
              <g key={i} filter="url(#cometGlow)">
                <path d="M4 0 C -3 -2.6, -10 -1.3, -13 0 C -10 1.3, -3 2.6, 4 0 Z" fill={SEG.reactivation} fillOpacity="0.38" />
                <circle r="2.6" fill={SEG.reactivation} opacity="0.9" />
                <animateMotion dur={m.d + "s"} begin={m.b + "s"} repeatCount="indefinite" rotate="auto" keyPoints="0;0.94" keyTimes="0;1" calcMode="linear" path={"M " + a.x1 + " " + a.y1 + " L " + a.x2 + " " + a.y2} />
              </g>
            ))}
          </g>
        ))}
        {/* red feed: AI-resepsjonist (green) inner circle active → red balls flow on Kundehenvendelser's bottom arrow */}
        {!sys && !outer && active === "booking" && arrowsFor("booking").map((a, ai) => ai === 1 && (
          <g key={"redfeed"} pointerEvents="none">
            {[{ d: 3.2, b: 0 }, { d: 4.1, b: 1.6 }].map((m, i) => (
              <g key={i} filter="url(#cometGlow)">
                <path d="M4 0 C -3 -2.6, -10 -1.3, -13 0 C -10 1.3, -3 2.6, 4 0 Z" fill={SEG.analyse} fillOpacity="0.38" />
                <circle r="2.6" fill={SEG.analyse} opacity="0.9" />
                <animateMotion dur={m.d + "s"} begin={m.b + "s"} repeatCount="indefinite" rotate="auto" keyPoints="0;0.94" keyTimes="0;1" calcMode="linear" path={"M " + a.x1 + " " + a.y1 + " L " + a.x2 + " " + a.y2} />
              </g>
            ))}
          </g>
        ))}
        {/* yellow feed: Anmeldelsesmotor inner circle active → yellow balls flow on Kvalitetssløyfe's bottom arrow */}
        {!sys && !outer && active === "reviews" && arrowsFor("reviews").map((a, ai) => ai === 1 && (
          <g key={"yellowfeed"} pointerEvents="none">
            {[{ d: 3.2, b: 0 }, { d: 4.1, b: 1.6 }].map((m, i) => (
              <g key={i} filter="url(#cometGlow)">
                <path d="M4 0 C -3 -2.6, -10 -1.3, -13 0 C -10 1.3, -3 2.6, 4 0 Z" fill={SEG.reviews} fillOpacity="0.38" />
                <circle r="2.6" fill={SEG.reviews} opacity="0.9" />
                <animateMotion dur={m.d + "s"} begin={m.b + "s"} repeatCount="indefinite" rotate="auto" keyPoints="0;0.94" keyTimes="0;1" calcMode="linear" path={"M " + a.x1 + " " + a.y1 + " L " + a.x2 + " " + a.y2} />
              </g>
            ))}
          </g>
        ))}
      </svg>

      {/* external source tiles */}
      {NODES.map((nd) =>
      <button key={"e" + nd.id} className={"loop__ext" + ((outer === nd.id || sys || (nd.id === "analyse" && active === "analyse" && !outer)) ? " is-outer" : "") + (nd.extIcon ? "" : " loop__ext--bare")} data-pos={nd.pos} style={{ left: nd.e.l, top: nd.e.t, "--seg": SEG[nd.id] }}
      onMouseEnter={(ev) => {(nd.id === "analyse" ? onPick : onOuter)(nd.id);onTip(ev, nd.extTip);}} onMouseLeave={offTip}
      onFocus={(ev) => onTip(ev, nd.extTip)} onBlur={offTip}>
          {nd.extIcon && <span className="loop__tile" style={{ fontSize: "8px", fontWeight: "300" }}>{I[nd.extIcon]()}</span>}
          <span className="loop__extlabel"><span className="t">{nd.extLabel}</span><span className="s">{nd.extSub}</span>{nd.extBadges && <span className="loop__badges">{nd.extBadges.map((b) => <span key={b} className="loop__badge">{I[b]()}</span>)}</span>}</span>
        </button>
      )}

      {/* pillar nodes */}
      {NODES.map((nd) =>
      <button key={nd.id} className={"loop__node" + (ia === nd.id ? " is-active" : "")} data-pos={nd.pos}
      style={{ left: nd.n.l, top: nd.n.t, "--seg": SEG[nd.id] }}
      onMouseEnter={(ev) => {onPick(nd.id);onTip(ev, nd.tip);}} onMouseLeave={offTip}
      onFocus={(ev) => {onPick(nd.id);onTip(ev, nd.tip);}} onBlur={offTip}>
          <span className="loop__disc">{I[nd.icon]()}</span>
          {nd.id !== "analyse" && <span className="loop__label"><span className="t">{nd.label}</span><span className="s">{nd.short}</span></span>}
        </button>
      )}

      {/* centre hub */}
      <div className={"loop__sidebadges" + (active === "booking" || sys ? " is-on" : "")} style={{ left: "calc(63.45% + 40px)", top: "50%", "--seg": SEG.booking }}
        onMouseEnter={() => onPick("booking")}>
        {["phone", "whatsapp", "globe"].map((b) => <span key={b} className="loop__badge">{I[b]()}</span>)}
      </div>
      <button className="loop__hub" onMouseEnter={(ev) => {onPick("marketing");onTip(ev, HUB.tip);}} onMouseLeave={offTip}
      onFocus={(ev) => {onPick("marketing");onTip(ev, HUB.tip);}} onBlur={offTip} onClick={() => onPick("marketing")}>
        <img src="assets/alviss-mark-white.png" alt="Alviss" className="loop__hublogo" />
      </button>
    </div>);

}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [active, setActive] = useState("booking");
  const [sys, setSys] = useState(false);
  const [paused, setPaused] = useState(false);
  const [tip, setTip] = useState(null);
  const [outer, setOuter] = useState(null);
  const wrapRef = useRef(null);
  const shown = outer || active;
  const node = ALL.find((n) => n.id === shown);
  const isOuter = !!outer;
  const dTitle = isOuter && node.oTitle ? node.oTitle : node.title;
  const dTag = isOuter && node.oTag ? node.oTag : node.tag;
  const dDesc = isOuter && node.oDesc ? node.oDesc : node.desc;
  const mockKey = isOuter ? (MOCKS[shown + "-out"] ? shown + "-out" : shown) : active;

  useEffect(() => {
    const r = document.documentElement;
    r.style.setProperty("--loop-node", t.nodeSize + "px");
    r.style.setProperty("--loop-hub", t.hubSize + "px");
    r.style.setProperty("--accent", t.accent);
  }, [t.nodeSize, t.hubSize, t.accent]);

  useEffect(() => {
    if (paused || !t.flow) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const id = setInterval(() => setActive((c) => {
      const i = CYCLE.indexOf(c);
      return CYCLE[(i < 0 ? 0 : i + 1) % CYCLE.length];
    }), t.speed * 1000);
    return () => clearInterval(id);
  }, [paused, t.flow, t.speed]);

  const onTip = (ev, text) => {
    const wrap = wrapRef.current;if (!wrap) return;
    const wr = wrap.getBoundingClientRect();
    const br = ev.currentTarget.getBoundingClientRect();
    setTip({ text, x: br.left + br.width / 2 - wr.left, y: br.top - wr.top });
  };
  const offTip = () => setTip(null);

  const pick = (id) => {setActive(id);setSys(id === "marketing");setOuter(null);};

  return (
    <main className="page loop-page">

      <div ref={wrapRef} style={{ position: "relative" }}
      onMouseEnter={() => setPaused(true)}
      onMouseLeave={() => {setPaused(false);setSys(false);setOuter(null);}}>
        <Diagram active={active} sys={sys} flow={t.flow} outer={outer} onOuter={setOuter} onPick={pick} onTip={onTip} offTip={offTip} />

        {/* corner boxes removed */}

        <section className="loop-detail">
          <div className="loop-detail__text loop-anim" key={shown + (isOuter ? "-o" : "")}>
            <div className="loop-detail__head">
              <div className="loop-detail__icon">{I[isOuter && node.extIcon ? node.extIcon : node.icon]()}</div>
              <h2 className="loop-detail__title">{dTitle}</h2>
            </div>
            <p className="loop-detail__tag">{dTag}</p>
            <p className="loop-detail__desc">{dDesc}</p>
          </div>
          <div className="loop-anim" key={mockKey + "-m"}>{MOCKS[mockKey]}</div>
        </section>
      </div>

      <TweaksPanel>
        <TweakSection label="Størrelse" />
        <TweakSlider label="Pilar-sirkler" value={t.nodeSize} min={42} max={78} step={1} unit=" px"
        onChange={(v) => setTweak("nodeSize", v)} />
        <TweakSlider label="Senter (Markedsføring)" value={t.hubSize} min={64} max={140} step={2} unit=" px"
        onChange={(v) => setTweak("hubSize", v)} />
        <TweakSection label="Farge & animasjon" />
        <TweakColor label="Aksentfarge" value={t.accent}
        options={["#2B43C4", "#2A6FDB", "#1F8A5B", "#0E1A2B", "#7A5AE0"]}
        onChange={(v) => setTweak("accent", v)} />
        <TweakToggle label="Levende animasjon" value={t.flow} onChange={(v) => setTweak("flow", v)} />
        <TweakSlider label="Tempo" value={t.speed} min={2} max={6} step={0.2} unit=" s"
        onChange={(v) => setTweak("speed", v)} />
      </TweaksPanel>
    </main>);

}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);