const { Button, IconButton, Icon, Card, Badge, Tag } = window.SuperiorPrintingHouseDesignSystem_81d178;

const TONES = ["mid", "light", "dark", "mid"];

/* Work rail. Full-bleed and always moving: the track carries the item list twice, so
   translating it by exactly one list-width lands on an identical frame and the loop is
   seamless. Motion, pause-on-hover and card width all live in responsive.css — this is
   a CSS marquee, so there is no step index, no resize listener and no rAF loop.
   Photography is still placeholder — supplied shots drop into `images`. */
function RailCard({ p, t, go, photoPath, rail }) {
  const v = cardView(t, p);
  return (
    <div className="rail-card" onClick={() => go("work", { rail: rail, cat: workCats(p)[0] })} style={{ display: "flex", flexDirection: "column", background: "var(--surface-card)", borderRadius: "var(--radius-2xl)", padding: 12, cursor: "pointer", boxShadow: "var(--elevation-panel)" }}>
      {/* Ph takes a fixed prop list and does not spread the rest, so the image tag
          rides on a plain wrapper. It carries no styles, so nothing moves. */}
      <div data-copy-image={photoPath}>
        <Ph label={v.label} images={p.images} focal={p.focal} fit="cover" tone="mid" radius="var(--radius-xl)" labelAlign="center" style={{ aspectRatio: "1 / 1" }} />
      </div>
      <div style={{ display: "flex", flexDirection: "column", gap: 8, padding: "20px 12px 12px" }}>
        {v.c && <span style={{ font: "var(--type-eyebrow)", letterSpacing: "var(--tr-label)", fontVariantNumeric: "lining-nums tabular-nums", textTransform: "uppercase", color: "var(--text-subtle)" }}>{v.c}</span>}
        <span style={{ fontFamily: "var(--font-display)", fontWeight: 500, fontSize: 20, lineHeight: 1.4, letterSpacing: "0", color: "var(--text-strong)" }}>{v.t}</span>
        <span style={{ fontSize: 14, lineHeight: 1.7, color: "var(--text-muted)" }}>{v.d}</span>
      </div>
    </div>
  );
}

function WorkSlider({ t , go, title, titleTh, text, items, photoPathPrefix, aside, more }) {
  const revealHead = useReveal();
  /* Constant speed regardless of list length: seconds per card, not per loop. */
  const duration = `${items.length * 7}s`;
  const track = React.useRef(null);
  const tween = React.useRef(0);

  /* The arrows steer the marquee itself rather than adding a second transform:
     one loop covers exactly one list, so one card is duration/items.length of
     animation time. Tweening currentTime keeps the CSS animation the only owner
     of the track's position, so a nudge cannot desync from the auto-scroll and
     the infinite loop handles the wrap for free.
     ponytail: 420ms ease-out by hand because currentTime is not a CSS property
     and so cannot be transitioned. */
  const nudge = (dir) => {
    const el = track.current;
    if (!el) return;
    const an = el.getAnimations()[0];
    if (!an) {                       // reduced-motion: no animation, native scroll instead
      const card = el.querySelector(".rail-card");
      el.parentElement.scrollBy({ left: dir * (card.offsetWidth + 16), behavior: "smooth" });
      return;
    }
    cancelAnimationFrame(tween.current);   // impatient clicks chain from wherever we are
    const total = an.effect.getTiming().duration;
    const step = total / items.length;
    /* Minus, not plus: the keyframe runs `reverse`, so a rising currentTime slides
       the track to the RIGHT and walks the list backwards. The right arrow has to
       lower it to travel forward. Measured, not assumed — one step of +7000ms moved
       the track from -2338px to -2041px. */
    let from = Number(an.currentTime), to = from - dir * step;
    /* currentTime cannot go negative, and in the first seconds of the loop a
       backwards nudge would. The animation is periodic, so lifting both ends by a
       whole loop lands on the identical frame and keeps the travel to one card. */
    if (to < 0) { const bump = Math.ceil(-to / total) * total; from += bump; to += bump; an.currentTime = from; }
    an.pause();
    const t0 = performance.now();
    const tick = (now) => {
      const k = Math.min(1, (now - t0) / 420);
      an.currentTime = from + (to - from) * (1 - Math.pow(1 - k, 3));
      if (k < 1) tween.current = requestAnimationFrame(tick); else an.play();
    };
    tween.current = requestAnimationFrame(tick);
  };

  return (
    <section>
      {/* Title and rule share a baseline row; the description sits under both.
         The head keeps the page inset — only the rail below runs full-bleed. */}
      <div ref={revealHead} className="rail-head pad-x reveal" style={{ display: "flex", flexDirection: "column", gap: 18, marginBottom: 40, padding: "0 56px" }}>
        <div style={{ display: "flex", alignItems: "baseline", gap: 12 }}>
          <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 500, fontSize: 34, lineHeight: 1.3,
            letterSpacing: "-.005em", color: "var(--text-strong)", margin: 0, flexShrink: 0 }}>{title}</h2>
          <span aria-hidden="true" style={{ color: "var(--line-strong)", flexShrink: 0, fontSize: 20, marginTop: 2 }}>|</span>
          <span style={{ fontSize: 15, color: "var(--text-subtle)", flexShrink: 0, marginTop: 4 }}>{titleTh}</span>
          <span aria-hidden="true" className="rail-rule" style={{ flex: 1, height: 1, background: "var(--line-subtle)", alignSelf: "center", marginLeft: 12 }} />
        </div>
        <p style={{ fontSize: 15, lineHeight: 1.8, color: "var(--text-muted)", margin: 0, maxWidth: 720 }}>{text}</p>
      </div>
      {aside}

      {/* Full-bleed: the rail is the page width, so cards run off both edges. The
         shell already clips, so nothing here can produce a horizontal scrollbar. */}
      <div className="rail-viewport">
        <div className="rail-track" ref={track} style={{ animationDuration: duration }}>
          {items.map((p, n) => <RailCard key={p.t} p={p} t={t} go={go} rail={photoPathPrefix} photoPath={photoPathPrefix ? photoPathPrefix + "." + n : undefined} />)}
          {/* Second pass is the seam. Hidden from assistive tech so the list is not
             announced twice; it exists only so the loop has somewhere to land. */}
          <div className="rail-half" aria-hidden="true">
            {items.map((p, n) => <RailCard key={p.t} p={p} t={t} go={go} rail={photoPathPrefix} photoPath={photoPathPrefix ? photoPathPrefix + "." + n : undefined} />)}
          </div>
        </div>
        {/* Over the rail's own edges, up to full strength only on hover — at rest the
           cards stay the focus. The loop is infinite, so neither arrow ever disables. */}
        <div className="rail-nav rail-nav--prev">
          <IconButton icon="arrow-left" label={t.work.prev} variant="outline" size="lg"
            onClick={() => nudge(-1)} style={{ background: "var(--white)", boxShadow: "var(--shadow-md)" }} />
        </div>
        <div className="rail-nav rail-nav--next">
          <IconButton icon="arrow-right" label={t.work.next} variant="outline" size="lg"
            onClick={() => nudge(1)} style={{ background: "var(--white)", boxShadow: "var(--shadow-md)" }} />
        </div>
      </div>
      {/* The rail loops forever, so it never reaches an end that says "there is more
          over here". This does. Only the main rail carries it — one way out is a
          route, three is a wall. */}
      {more && (
        <div style={{ display: "flex", justifyContent: "center", marginTop: 32 }}>
          {/* A text link, not a Button: every Button in the kit is a pill
              (--radius-control is --radius-pill), and a third pill under a wall of
              cards reads as another card action rather than the way out. */}
          <button type="button" className="see-more" onClick={() => go("work", { rail: photoPathPrefix })}>
            {more}<Icon name="arrow-right" size={16} />
          </button>
        </div>
      )}
    </section>
  );
}

/* The proposition band: what we do differently, said once, between the client marks
   and the work itself. Three columns — the claim, a photograph of it in use, and the
   three reasons behind it.

   Numbered rather than icon-led: readme.md rules icons out as a bullet substitute,
   and the sanctioned glyph set has nothing that reads as "colour accuracy", so mono
   lining numerals index the list instead.

   No background of its own — .trust-section directly above already spends the page's
   one tinted band, and two in a row read as a single block. */
function Why({ t, go }) {
  const w = t.statement;
  const reveal = useReveal();
  return (
    /* Inside the rail now, between its heading and its track — so the spacing is the
       rail's business, not the page grid's: marginBottom mirrors .rail-head's own.
       .grid-3 for its breakpoints alone (3 -> 2 -> 1 at 900 and 560), .pad-x to keep
       the same inset as the heading directly above it.

       Centred, not top-aligned: the photograph is the tallest thing here, so starting
       the text at the top left it stranded against a much longer edge with a hole
       underneath. */
    <div ref={reveal} className="pad-x grid-3 reveal"
      style={{ padding: "0 56px", marginBottom: 40, display: "grid",
               gridTemplateColumns: "repeat(3,1fr)", gap: 40, alignItems: "center" }}>
      <div style={{ display: "flex", flexDirection: "column", gap: 18, maxWidth: 420 }}>
        <span style={{ font: "var(--type-eyebrow)", letterSpacing: "var(--tr-label)", textTransform: "uppercase", color: "var(--text-subtle)" }}>{w.eyebrow}</span>
        <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 500, fontSize: 40, lineHeight: 1.2,
          letterSpacing: "-.01em", color: "var(--text-strong)", margin: 0 }}>{w.title}</h2>
        <p style={{ fontSize: 15, lineHeight: 1.8, color: "var(--text-muted)", margin: 0 }}>{w.text}</p>
        <div style={{ display: "flex", alignItems: "center", gap: 20, marginTop: 6, flexWrap: "wrap" }}>
          {/* Scoped like every other way in: there is no page that mixes the rails. */}
          <Button variant="ghost" iconRight="arrow-up-right" onClick={() => go("work", { rail: "work.projects" })}>{w.cta2}</Button>
        </div>
      </div>

      {/* Ph takes a fixed prop list and does not spread the rest, so the admin's
          image hook rides on a plain wrapper.

          The frame is a fixed 2016x1512 — 4:3 landscape. Written as the full pair
          rather than 4/3 so the intended plate size stays legible. object-fit is
          cover, so a portrait photograph is cropped to it; the focal control in the
          panel is how you choose which part survives. */}
      <div data-copy-image="statement.images.0">
        <Ph label={w.media} images={w.images} focal={w.focal} fit="cover" tone="mid"
            radius="var(--radius-2xl)" style={{ aspectRatio: "2016 / 1512" }} />
      </div>

      <div style={{ display: "flex", flexDirection: "column", justifyContent: "center" }}>
        {w.points.map((pt, n) => (
          <div key={pt.t} style={{ display: "grid", gridTemplateColumns: "auto 1fr", gap: "0 18px",
            padding: "22px 0", borderTop: n ? "var(--border-hairline)" : "none" }}>
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 12, fontVariantNumeric: "lining-nums tabular-nums",
              color: "var(--text-subtle)", paddingTop: 6 }}>{"0" + (n + 1)}</span>
            <span style={{ fontFamily: "var(--font-display)", fontWeight: 500, fontSize: 20, lineHeight: 1.4,
              letterSpacing: "0", color: "var(--text-strong)" }}>{pt.t}</span>
            <span style={{ gridColumn: 2, fontSize: 14, lineHeight: 1.75, color: "var(--text-muted)", marginTop: 8 }}>{pt.d}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

/* Hero slideshow. One slide renders exactly as the old static hero — no dots, no
   timer — so a single-slide site is unchanged. Auto-advance pauses on hover and
   never starts under prefers-reduced-motion. */
function Hero({ t, go }) {
  const slides = t.hero.slides || [];
  const [i, setI] = React.useState(0);
  const [hold, setHold] = React.useState(false);
  const at = Math.min(i, Math.max(0, slides.length - 1));
  const s = slides[at] || {};
  const reduce = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
  React.useEffect(() => {
    if (slides.length < 2 || hold || reduce) return;
    const id = setTimeout(() => setI((at + 1) % slides.length), 7000);
    return () => clearTimeout(id);
  }, [at, hold, slides.length, reduce]);
  return (
    <section className="premium-reveal" onMouseEnter={() => setHold(true)} onMouseLeave={() => setHold(false)}>
      <div data-theme="inverse" className="hero-panel" data-copy-image={`hero.slides.${at}.bannerSrc`} style={{ position: "relative", isolation: "isolate", background: `var(--ink-900) url(${s.bannerSrc}) ${focalOrDefault(s.focal)} / cover no-repeat`, padding: "72px 64px 40px", minHeight: 620, display: "flex", flexDirection: "column", boxShadow: "var(--elevation-panel-hero)", containerType: "inline-size" }}>
        <div aria-hidden="true" style={{ position: "absolute", inset: 0, zIndex: -1, borderRadius: "inherit", background: "linear-gradient(180deg, rgba(23,22,18,.62) 0%, rgba(23,22,18,.34) 45%, rgba(23,22,18,.74) 100%)" }} />
        <div key={at} className="hero-slide" style={{ margin: "auto", display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center", gap: 20, maxWidth: 720 }}>
          <span style={{ fontSize: 11, letterSpacing: ".18em", fontVariantNumeric: "lining-nums tabular-nums", textTransform: "uppercase", color: "rgba(255,255,255,.62)", lineHeight: 1.7 }}>{s.eyebrow[0]}<br />{s.eyebrow[1]}</span>
          <h1 style={{ fontFamily: "var(--font-display)", fontWeight: 600, fontSize: "clamp(28px,3.4cqw,42px)", lineHeight: 1.4, letterSpacing: "-.005em", color: "var(--white)", margin: 0, textWrap: "balance" }}>
            {s.title[0]}<br />{s.title[1]}
          </h1>
          <p style={{ fontSize: 15, lineHeight: 1.8, color: "rgba(255,255,255,.74)", margin: 0, maxWidth: 560 }}>{s.lede[0]}<br />{s.lede[1]}</p>
          <div style={{ display: "flex", gap: 10, marginTop: 8, flexWrap: "wrap", justifyContent: "center" }}>
            <Button className="line-btn" variant="outline" size="lg" href={lineHref(t)} target="_blank" rel="noopener" iconRight="arrow-up-right" style={{ color: "var(--white)", borderColor: "rgba(255,255,255,.32)" }}>{s.cta}</Button>
            <Button variant="outline" size="lg" onClick={() => go("work", { rail: "work.projects" })} style={{ color: "var(--white)", borderColor: "rgba(255,255,255,.32)" }}>{s.cta2}</Button>
          </div>
        </div>
        {slides.length > 1 && (
          <div className="hero-dots">
            {slides.map((_, n) => (
              <button key={n} type="button" aria-label={`${n + 1}`} className={n === at ? "is-on" : ""} onClick={() => setI(n)} />
            ))}
          </div>
        )}
        <div className="hero-meta" style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 28, flexWrap: "wrap", fontSize: 12, color: "rgba(255,255,255,.6)", borderTop: "1px solid rgba(255,255,255,.16)", paddingTop: 20 }}>
          <span>{t.hero.social[0]} {t.hero.social[1]}</span>
        </div>
      </div>
    </section>
  );
}

function Home({ go, t }) {
  const revealTrust = useReveal();
  return (
    /* One rhythm, one owner: the wrapper's rowGap is the only vertical space between
       sections, so adjacent paddings can never add up. Sections carry inset only.
       minmax(0,1fr) is load-bearing: an auto grid column sizes to its widest item, so
       without it the full-bleed rail's max-content track drags the whole page wide. */
    <div style={{ display: "grid", gridTemplateColumns: "minmax(0,1fr)", rowGap: 64, paddingBottom: 20 }}>
      {/* ---- hero ---- */}
      {/* Photo-led banner: the image carries the page, so the type sits centred and
         deliberately quiet — small caps eyebrow, a modest headline weight, and the
         supporting meta demoted to one hairline-separated line at the foot. */}
      <Hero t={t} go={go} />

      {/* ---- trusted by ---- */}
      {/* Client marks are the supplied artwork, shown at full colour and uniform height. */}
      <section ref={revealTrust} className="trust-section reveal" style={{ marginTop: -40, background: "var(--surface-card)", padding: "48px 56px", display: "flex", flexDirection: "column", gap: 40 }}>
        <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 500, fontSize: 29, lineHeight: 1.45, letterSpacing: "0", color: "var(--ink-900)", margin: 0, textAlign: "center" }}>{t.trust.title}</h2>
        <p style={{ fontSize: 13, lineHeight: 1.8, color: "var(--text-subtle)", margin: "-24px 0 0", textAlign: "center" }}>{t.trust.text}</p>
        <div className="trust-grid" style={{ display: "grid", gridTemplateColumns: "repeat(5,1fr)", gap: "28px 32px", alignItems: "center", justifyItems: "center" }}>
          {t.clients.map((c, i) => (
            <div key={c.src + i} style={{ display: "flex", alignItems: "center", justifyContent: "center", width: "100%", height: 96 }}>
              <img src={c.src} alt={c.name} title={c.name} className="client-mark" data-copy-image={`clients.${i}.src`}
                style={{ height: c.h, width: "auto", maxWidth: "82%", objectFit: "contain" }} />
            </div>
          ))}
        </div>
      </section>

      {/* ---- work carousel ---- */}
      {/* The proposition rides inside this rail, under its heading and above its
         cards — only this one, not the category rails below. */}
      <WorkSlider t={t} go={go} title={t.work.showcaseTitle} titleTh={t.work.showcaseTitleTh} text={t.work.showcaseText} items={t.work.projects} photoPathPrefix="work.projects" more={t.work.seeMore}
        aside={<Why t={t} go={go} />} />

      {/* ---- category carousels ---- */}
      {/* display:contents keeps these as direct grid items, so the wrapper does not
         disturb the wrapper grid's rowGap rhythm. */}
      <div style={{ display: "contents" }}>
        {t.showcases.map((s, si) => (
          <WorkSlider key={s.title} t={t} go={go} title={s.title} titleTh={s.titleTh} text={s.text} items={s.projects} photoPathPrefix={`showcases.${si}.projects`} />
        ))}
      </div>

    </div>
  );
}

Object.assign(window, { Home });