/* Backentra — Preview page. window.BackentraPreview

   An infinite flowing screenshot rail: two staggered rows drifting in opposite directions,
   deliberately mixed sizes, hover to pause and lift, click to open the full frame.

   Why this shape rather than a carousel: a carousel shows one thing and asks permission for
   the next. A drifting rail shows the SHAPE of the product — that there is a lot of it, that
   the screens are dense and varied — before anybody reads a single caption. Two rows moving
   against each other reads as editorial rather than as a widget, and mixed widths stop it
   scanning as a grid on rails.

   The honest constraint is unchanged: there are no product captures in the project yet, only
   logos. Every tile is a labelled SLOT — striped, with its filename in monospace — because a
   hand-drawn approximation of a UI is the one thing this page must not contain. When real
   PNGs land, each tile stops being a slot and nothing else changes.

   Motion rules:
     · CSS animation on a duplicated track, so the loop is seamless and costs no JS per frame.
     · The whole row pauses on hover, not just the tile under the cursor — a tile that stops
       while its neighbours keep sliding looks broken, and you cannot read a caption that is
       still moving.
     · The hovered tile lifts and scales slightly; everything else in that row dims a little,
       so the eye is led rather than shouted at.
     · Focusing any tile by keyboard pauses its row too, otherwise tabbing through is a
       moving-target exercise.
     · prefers-reduced-motion stops the drift entirely and lays the rows out statically. */
const PV = window.BackentraDesignSystem_93cf3a;
const { Button: PvBtn, Icon: PvIcon } = PV;

const pvcontainer = { maxWidth: "var(--container-max)", margin: "0 auto", padding: "0 32px" };
const pveyebrow = {
  fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 11.5, letterSpacing: ".1em",
  textTransform: "uppercase", color: "var(--accent-text)",
};

/* "Built" means the screen works in the app today, not that it is finished — the same
   distinction the product uses internally. */
const PV_STATE = {
  built: ["Built", "var(--success-soft)", "var(--success)"],
  progress: ["In progress", "var(--orange-50)", "var(--accent-text)"],
  planned: ["Planned", "var(--gray-100)", "var(--text-muted)"],
};

/* `w` is the tile width in px. Mixed on purpose: equal widths make a rail read as a grid that
   happens to be moving, and the variation is what makes it feel edited. */
const PV_SCREENS = [
  { key: "myday", name: "My Day", state: "built", icon: "sun", w: 620, src: "./assets/preview/my-day.png",
    what: "What one person has on today — their jobs, appointments, and the tasks the system filed for them.",
    detail: "The first screen most people open. Everything on it is theirs, not the company's." },
  { key: "calendar", name: "Calendar", state: "built", icon: "calendar", src: "./assets/preview/calendar.png", w: 680,
    what: "The whole operation on one grid: crews down the side, the day across the top.",
    detail: "Seven views, conflict detection, and crew load. Drag a job and the crew's day recalculates." },
  { key: "job", name: "Workflow", state: "built", icon: "git-branch", src: "./assets/preview/workflow.png", w: 640,
    what: "Every job by the stage it is in — leads through to closed, one list per stage.",
    detail: "Reads the same records every other screen writes; nothing here is a separate pipeline." },
  { key: "performance", name: "Performance", state: "built", icon: "award", src: "./assets/preview/performance.png", w: 600,
    what: "How the team is doing, scored on what the system can actually measure.",
    detail: "No invented point system — a leaderboard built from real win rate, revenue and attendance." },
  { key: "invoice", name: "Home — Owner widgets", state: "built", icon: "layout-dashboard", src: "./assets/preview/invoices-payments.png", w: 640,
    what: "The owner's Home screen, with its default widget set — revenue, jobs, A/R, funnel, activity.",
    detail: "Rebuilt from the Widget Marketplace — every card here is one the owner preset actually ships with." },
  { key: "financials", name: "Accounts Receivable", state: "progress", icon: "bar-chart-3", src: "./assets/preview/receivables.png", w: 640,
    what: "Every invoice outstanding, aging, disputes and follow-ups in one place.",
    detail: "Reads the same records the crews create, so the numbers cannot drift." },
  { key: "field", name: "Field app", state: "built", icon: "hard-hat", src: "./assets/preview/field-home.png", w: 300,
    what: "The crew's phone view: today's stops, directions, photos and sign-off.",
    detail: "Deliberately small. A crew should not be operating a back office on a phone.", tall: true },
  { key: "sales", name: "Sales", state: "built", icon: "trending-up", src: "./assets/preview/sales.png", w: 620,
    what: "Pipeline and proposal follow-through — funnel, needs-attention, win rate.",
    detail: "Reads the same leads, estimates and proposals the rest of the app writes." },
];

/* Split so each row carries a different rhythm of widths rather than alternating evenly. */
const PV_TOP = [PV_SCREENS[1], PV_SCREENS[0], PV_SCREENS[3], PV_SCREENS[2]];
const PV_BOTTOM = [PV_SCREENS[5], PV_SCREENS[4], PV_SCREENS[7], PV_SCREENS[6]];

const pvFile = (s) => s.name.toLowerCase().replace(/\s+/g, "-") + ".png";

/* One tile. A slot, not a mockup — the stripes and the monospace filename make that
   unmistakable, which is better than a plausible-looking fake. */
function PvTile({ s, dim, onOpen, onHold, onRelease, phone }) {
  const [hot, setHot] = React.useState(false);
  const [label, bg, fg] = PV_STATE[s.state] || PV_STATE.planned;
  const enter = () => { setHot(true); onHold(); };
  const leave = () => { setHot(false); onRelease(); };
  return (
    <button
      className="pv-tile"
      onClick={() => onOpen(s)}
      onMouseEnter={enter} onMouseLeave={leave}
      onFocus={enter} onBlur={leave}
      aria-label={"Open " + s.name}
      style={{
        /* On the phone rail the wrapper owns the width (82vw); the tile fills it. */
        flex: "none", width: phone ? "100%" : s.w, padding: 0, border: "none", background: "transparent",
        cursor: "pointer", textAlign: "left", borderRadius: "var(--radius-lg)",
        /* Scale and lift on the tile itself; the row handles pausing. */
        transform: hot ? "scale(1.03) translateY(-4px)" : "none",
        opacity: dim && !hot ? 0.55 : 1,
        transition: "transform 260ms cubic-bezier(.2,.7,.3,1), opacity 220ms ease",
        outline: "none",
      }}>
      <span style={{
        display: "block", position: "relative", width: "100%",
        aspectRatio: s.tall ? "393 / 852" : "16 / 10",
        borderRadius: "var(--radius-lg)", overflow: "hidden",
        border: "1px solid " + (hot ? "var(--accent)" : "var(--border)"),
        background: "var(--gray-100)",
        /* Stripes only where no capture has landed. A real screenshot sits on plain white. */
        backgroundImage: s.src ? "none" : "repeating-linear-gradient(135deg, var(--gray-100) 0 12px, var(--surface-card) 12px 24px)",
        boxShadow: hot ? "var(--shadow-xl)" : "var(--shadow-sm)",
        transition: "box-shadow 260ms ease, border-color 200ms ease",
      }}>
        {s.src ? (
          /* Letterboxed, not cropped — captures vary in native proportions (a short wide
             dashboard vs. a tall narrow list), and a fixed 16:10 cover crop silently loses
             whichever edge doesn't fit. Contain shows every capture whole. */
          <img src={s.src} alt={s.name} loading="lazy" draggable="false"
            style={{ display: "block", width: "100%", height: "100%", objectFit: "contain", objectPosition: "center" }} />
        ) : (
          <span style={{
            position: "absolute", inset: 0, display: "flex", flexDirection: "column",
            alignItems: "center", justifyContent: "center", gap: 10, padding: 22, textAlign: "center",
          }}>
            <span style={{
              width: 46, height: 46, borderRadius: "var(--radius-md)", background: "var(--surface-card)",
              border: "1px solid var(--border)", display: "inline-flex", alignItems: "center", justifyContent: "center",
            }}>
              <PvIcon name={s.icon} size={21} color={hot ? "var(--accent-text)" : "var(--text-muted)"} />
            </span>
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-muted)" }}>{pvFile(s)}</span>
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-subtle)" }}>screen capture goes here</span>
          </span>
        )}
        <span style={{
          position: "absolute", top: 12, left: 12, display: "inline-flex", alignItems: "center",
          padding: "3px 10px", borderRadius: "var(--radius-pill)", background: bg, color: fg,
          fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 10.5, letterSpacing: ".04em",
        }}>{label}</span>
        {/* Only on hover, so the resting rail stays quiet. */}
        <span style={{
          position: "absolute", bottom: 12, right: 12, display: "inline-flex", alignItems: "center", gap: 6,
          padding: "5px 11px", borderRadius: "var(--radius-pill)", background: "var(--navy-900)", color: "#fff",
          fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 11.5,
          opacity: hot ? 1 : 0, transform: hot ? "none" : "translateY(4px)",
          transition: "opacity 200ms ease, transform 200ms ease", pointerEvents: "none",
        }}>
          <PvIcon name="maximize-2" size={12} /> Open
        </span>
      </span>
      <span style={{ display: "flex", alignItems: "baseline", gap: 9, paddingTop: 11 }}>
        <span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15, color: "var(--text-strong)" }}>{s.name}</span>
        <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-subtle)" }}>{label.toLowerCase()}</span>
      </span>
    </button>
  );
}

/* A row. The track is rendered twice and translated by exactly -50%, so the loop is seamless
   — the second copy is under the cursor at the instant the first finishes. */
function PvRow({ screens, dir, secs, paused, onHold, onRelease, onOpen, still }) {
  const track = screens.concat(screens);
  return (
    <div style={{ overflow: "hidden", padding: "6px 0" }}>
      <div style={{
        display: "flex", gap: 26, width: "max-content",
        animation: still ? "none" : "pvdrift-" + dir + " " + secs + "s linear infinite",
        animationPlayState: paused ? "paused" : "running",
        /* Static fallback under reduced motion: let it scroll rather than animate. */
        ...(still ? { width: "auto", overflowX: "auto", paddingBottom: 8 } : null),
      }}>
        {(still ? screens : track).map((s, i) => (
          <PvTile key={s.key + "-" + i} s={s} dim={paused} onOpen={onOpen} onHold={onHold} onRelease={onRelease} />
        ))}
      </div>
    </div>
  );
}

/* Clicking a tile opens the full frame plus its caption. Centred, since this is the one place
   the page asks for undivided attention. */
function PvLightbox({ s, onClose, onStep }) {
  React.useEffect(() => {
    const k = (e) => {
      if (e.key === "Escape") onClose();
      if (e.key === "ArrowRight") onStep(1);
      if (e.key === "ArrowLeft") onStep(-1);
    };
    document.addEventListener("keydown", k);
    return () => document.removeEventListener("keydown", k);
  }, [onClose, onStep]);
  if (!s) return null;
  const [label, bg, fg] = PV_STATE[s.state] || PV_STATE.planned;
  return (
    <div onClick={onClose} role="dialog" aria-label={s.name}
      style={{
        position: "fixed", inset: 0, zIndex: 900, background: "rgba(15, 30, 46, 0.62)",
        backdropFilter: "blur(3px)", display: "flex", alignItems: "center", justifyContent: "center", padding: 28,
      }}>
      <div onClick={(e) => e.stopPropagation()}
        style={{
          width: "min(1080px, 100%)", maxHeight: "92vh", overflowY: "auto", background: "var(--surface-card)",
          borderRadius: "var(--radius-xl)", boxShadow: "var(--shadow-xl)", padding: 22,
        }}>
        <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 16, marginBottom: 16 }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 5 }}>
              <h2 style={{ fontSize: 25, margin: 0 }}>{s.name}</h2>
              <span style={{
                display: "inline-flex", padding: "3px 10px", borderRadius: "var(--radius-pill)", background: bg, color: fg,
                fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 10.5, letterSpacing: ".04em",
              }}>{label}</span>
            </div>
            <p style={{ margin: 0, fontSize: 15.5, lineHeight: 1.6, color: "var(--text-body)", maxWidth: 760, textWrap: "pretty" }}>{s.what}</p>
          </div>
          <div style={{ display: "flex", gap: 7, flex: "none" }}>
            {[["chevron-left", -1, "Previous screen"], ["chevron-right", 1, "Next screen"]].map(([ic, d, lab]) => (
              <button key={lab} onClick={() => onStep(d)} aria-label={lab}
                style={{ width: 36, height: 36, borderRadius: "var(--radius-md)", border: "1px solid var(--border)", background: "var(--surface-card)", color: "var(--text-body)", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
                <PvIcon name={ic} size={18} />
              </button>
            ))}
            <button onClick={onClose} aria-label="Close"
              style={{ width: 36, height: 36, borderRadius: "var(--radius-md)", border: "1px solid var(--border)", background: "var(--surface-card)", color: "var(--text-muted)", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
              <PvIcon name="x" size={18} />
            </button>
          </div>
        </div>

        <div style={{
          position: "relative", width: "100%",
          aspectRatio: s.src ? (s.tall ? undefined : "1280 / 900") : (s.tall ? "9 / 14" : "16 / 9"),
          display: s.tall ? "flex" : undefined, justifyContent: s.tall ? "center" : undefined,
          borderRadius: s.tall ? 0 : "var(--radius-lg)", overflow: s.tall ? "visible" : "hidden",
          border: s.tall ? "none" : "1px solid var(--border)",
          background: s.tall ? "transparent" : "var(--gray-100)",
          backgroundImage: s.tall ? "none" : "repeating-linear-gradient(135deg, var(--gray-100) 0 14px, var(--surface-card) 14px 28px)",
        }}>
          {s.src ? (
            s.tall ? (
              /* A phone frame, not a full-width stretch — the raw capture is native device
                 width (393dp); blowing it up to the modal's width was what forced scrolling.
                 Fixed to a real phone footprint and centered instead. */
              <div style={{ position: "relative", width: 300, flex: "none", background: "#0B0B0C", borderRadius: 38, padding: 9, boxShadow: "0 24px 50px -14px rgba(8,15,30,0.45)" }}>
                <div style={{ position: "absolute", top: 9, left: "50%", transform: "translateX(-50%)", width: 90, height: 24, background: "#0B0B0C", borderRadius: 14, zIndex: 2 }} />
                <div style={{ width: "100%", aspectRatio: "393 / 852", borderRadius: 29, overflow: "hidden", background: "#fff" }}>
                  <img src={s.src} alt={s.name} style={{ display: "block", width: "100%", height: "100%", objectFit: "contain", objectPosition: "top center" }} />
                </div>
              </div>
            ) : (
              /* Contain, not cover: the lightbox is where somebody wants the WHOLE screen, so
                 nothing may be cropped away here. */
              <img src={s.src} alt={s.name} style={{ display: "block", width: "100%", height: "100%", objectFit: "contain", objectPosition: "top center", background: "#fff" }} />
            )
          ) : (
            <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12 }}>
              <PvIcon name={s.icon} size={28} color="var(--text-muted)" />
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 13.5, color: "var(--text-muted)" }}>{pvFile(s)}</span>
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 11.5, color: "var(--text-subtle)" }}>full-size capture goes here</span>
            </div>
          )}
        </div>

        <p style={{ margin: "16px 0 0", fontSize: 14.5, lineHeight: 1.6, color: "var(--text-muted)", textWrap: "pretty" }}>{s.detail}</p>
      </div>
    </div>
  );
}

/* The phone rail. One row, swiped by hand, instead of the desktop's two drifting ones.
   Three reasons the desktop treatment cannot simply be scaled down:
     · Auto-drift and a finger fight each other. Touch has no hover, so the pause-on-hover
       that makes the desktop rail readable does not exist — the tile you reach for is
       already leaving.
     · Two rows at phone width means two things moving in a space that fits one, and each
       tile is then too small to read as anything but a smudge.
     · A continuously animating full-width layer is the most expensive thing on the page
       for a phone GPU, and it never stops.
   So: the same tiles, the same order, laid out as a native scroll-snap strip. 82vw wide
   leaves the next tile peeking, which is what tells a thumb there is more to the right. */
function PvPhoneRail({ screens, onOpen }) {
  return (
    <div
      className="pv-phone-rail"
      style={{
        display: "flex", gap: 14, overflowX: "auto", overflowY: "hidden",
        scrollSnapType: "x mandatory", WebkitOverflowScrolling: "touch",
        /* Padding inside the scroller, so the first and last tiles still snap to a margin
           rather than butting against the screen edge. */
        padding: "6px 18px 14px",
        /* The rail is the one thing on the page allowed to run to the edges. */
        scrollbarWidth: "none",
        /* Mixed heights sit on a common centre line rather than a common top edge. */
        alignItems: "center",
      }}>
      {screens.map((s) => (
        /* A phone capture is 393x852 — at the same width as a desktop one it stands three
           times taller and the rail has to reserve that height for every tile, which is what
           made it look broken. Narrower, so the two shapes land in the same height band and
           the strip reads as one row of mixed sizes, which is the desktop intent anyway. */
        <div key={s.key} style={{
          flex: "none",
          width: s.tall ? "46vw" : "82vw",
          maxWidth: s.tall ? 240 : 460,
          scrollSnapAlign: "center",
        }}>
          <PvTile s={s} dim={false} onOpen={onOpen} onHold={() => {}} onRelease={() => {}} phone />
        </div>
      ))}
    </div>
  );
}

function PreviewPage({ onNav, onJoin }) {
  React.useEffect(() => {
    window.BackentraSetMeta && window.BackentraSetMeta({
      path: "/preview",
      title: "Preview — Backentra",
      description: "Real screens from the running application: scheduling, invoicing, the field crew app, and the office dashboard.",
    });
  }, []);
  const counts = PV_SCREENS.reduce((a, s) => { a[s.state] = (a[s.state] || 0) + 1; return a; }, {});
  const [holdTop, setHoldTop] = React.useState(0);
  const [holdBottom, setHoldBottom] = React.useState(0);
  const [open, setOpen] = React.useState(null);
  const [still, setStill] = React.useState(false);
  React.useEffect(() => {
    setStill(!!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches));
  }, []);

  /* Touch gets a different rail entirely — see PvPhoneRail.
     The test is `hover: none` first and width second, because width is the wrong question.
     What the drifting rail actually depends on is hover-to-pause: without it the tile you
     reach for is already leaving. An iPad has 768 or 1024 comfortable pixels and no hover
     at all, so a width-only rule hands it the one layout it cannot operate. Any pointer
     that can hover — laptop, desktop, a mouse plugged into anything — keeps the drift.
     Kept live rather than read once, so a tablet rotating across the boundary re-lands. */
  const [phone, setPhone] = React.useState(false);
  React.useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia("(hover: none), (max-width: 720px)");
    const sync = () => setPhone(mq.matches);
    sync();
    /* addListener is the pre-Safari-14 spelling; still needed for older iOS. */
    if (mq.addEventListener) { mq.addEventListener("change", sync); return () => mq.removeEventListener("change", sync); }
    mq.addListener(sync); return () => mq.removeListener(sync);
  }, []);

  /* A counter rather than a boolean: moving between two adjacent tiles fires leave-then-enter,
     and a boolean would flicker the row back into motion for a frame. */
  const hold = (set) => () => set((n) => n + 1);
  const release = (set) => () => set((n) => Math.max(0, n - 1));

  /* The lightbox pauses both rows — nothing should be drifting behind a modal. */
  const step = (d) => setOpen((cur) => {
    const i = PV_SCREENS.indexOf(cur);
    return PV_SCREENS[(i + d + PV_SCREENS.length) % PV_SCREENS.length];
  });

  return (
    <div style={{ background: "var(--surface-page)", minHeight: "100vh" }}>
      <window.BackentraSiteHeader current="preview" onNav={onNav} />

      <section style={{ background: "var(--gray-100)", padding: "56px 0 40px" }}>
        <div style={{ ...pvcontainer, maxWidth: 780 }}>
          <div style={{ ...pveyebrow, marginBottom: 12 }}>
            Preview · {counts.built || 0} built, {counts.progress || 0} in progress, {counts.planned || 0} planned
          </div>
          <h1 style={{ fontSize: 44, lineHeight: 1.06, margin: "0 0 14px" }}>See it before you decide.</h1>
          <p style={{ fontSize: 17, lineHeight: 1.6, color: "var(--text-body)", margin: 0, textWrap: "pretty" }}>
            The screens your team would actually use, with the build state of each stated plainly. Hover to
            hold one still, click to open it. Every capture is the real application running on a real
            company's data — where a capture is missing you get an empty frame rather than an artist's
            impression.
          </p>
        </div>
      </section>

      {/* The rail. Full-bleed, because a drifting row that stops at a container edge reads as
          a broken carousel rather than as something continuous. */}
      <section style={{ background: "var(--surface-card)", padding: "38px 0 30px", overflow: "hidden" }}>
        {phone ? (
          /* One rail, both rows' screens in their original order, swiped rather than driven. */
          <PvPhoneRail screens={PV_TOP.concat(PV_BOTTOM)} onOpen={setOpen} />
        ) : (
          <React.Fragment>
            <PvRow screens={PV_TOP} dir="left" secs={64} still={still}
              paused={holdTop > 0 || !!open} onHold={hold(setHoldTop)} onRelease={release(setHoldTop)} onOpen={setOpen} />
            <div style={{ height: 30 }} />
            <PvRow screens={PV_BOTTOM} dir="right" secs={72} still={still}
              paused={holdBottom > 0 || !!open} onHold={hold(setHoldBottom)} onRelease={release(setHoldBottom)} onOpen={setOpen} />
          </React.Fragment>
        )}
      </section>

      <section style={{ padding: "26px 0 56px", background: "var(--surface-card)" }}>
        <div style={{ ...pvcontainer }}>
          <div style={{ display: "flex", alignItems: "flex-start", gap: 11, padding: "16px 18px", borderRadius: "var(--radius-md)", background: "var(--surface-page)", border: "1px solid var(--border)" }}>
            <PvIcon name="info" size={17} color="var(--text-muted)" style={{ flex: "none", marginTop: 1 }} />
            <p style={{ margin: 0, fontSize: 14, lineHeight: 1.6, color: "var(--text-body)", textWrap: "pretty" }}>
              A screen marked <strong>Built</strong> works in the application today. That is not the same as
              finished — copy, layout and behaviour are all still moving, and we would rather show you a
              working screen mid-change than a polished one that does nothing.
            </p>
          </div>
        </div>
      </section>

      <section style={{ background: "var(--navy-900)", padding: "48px 0" }}>
        <div style={{ ...pvcontainer, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 24, flexWrap: "wrap" }}>
          <div style={{ minWidth: 0 }}>
            <h2 style={{ color: "#fff", fontSize: 26, margin: "0 0 7px" }}>Want to use it rather than look at it?</h2>
            <p style={{ color: "var(--gray-300)", fontSize: 15.5, lineHeight: 1.6, margin: 0, maxWidth: 620, textWrap: "pretty" }}>
              Founding members get in first, keep 25% off for life, and have a say in what gets built next.
            </p>
          </div>
          <PvBtn variant="primary" onClick={onJoin} iconRight={<PvIcon name="arrow-right" size={18} />}>Join the list</PvBtn>
        </div>
      </section>

      <window.BackentraSiteFooter onNav={onNav} />
      <PvLightbox s={open} onClose={() => setOpen(null)} onStep={step} />
    </div>
  );
}

window.BackentraPreview = PreviewPage;
