// Portfolio — Kirsten Kelleher

const { useState, useMemo, useEffect } = React;

const LABEL_FONTS = {
  "Geist Mono":      `"Geist Mono", ui-monospace, monospace`,
  "IBM Plex Mono":   `"IBM Plex Mono", ui-monospace, monospace`,
  "Fragment Mono":   `"Fragment Mono", ui-monospace, monospace`,
  "Space Mono":      `"Space Mono", ui-monospace, monospace`,
  "JetBrains Mono":  `"JetBrains Mono", ui-monospace, monospace`,
  "Tracked Sans":    `"Geist", -apple-system, sans-serif`,
};

// ─── Project data ────────────────────────────────────────────────────────────
// Add each project as an entry below. Item shapes:
//   { kind: "screenshot", src: "<folder>/<image>.png", h: "Headline.", em: "highlighted words", p: "Body copy." }
//   { kind: "video",      src: "<folder>/<clip>.mp4",   h: "Headline.", em: "highlighted words", p: "Body copy." }
//   { kind: "video",      src: "https://www.youtube.com/embed/<id>", h: "...", em: "...", p: "..." }
// Put media files in a folder named after the project id (e.g. design/example/shot-01.png).
// Optional per-project "type":
//   "with_descriptions" (default) — one full pane at a time, media + caption side by side.
//   "panel_only"                  — media only, showing 1.5 panes so the next pane peeks in.
const PROJECTS = [
  {
    id: "climate-report",
    name: "Climate Report",
    tag: "Editor",
    type: "panel_only",
    items: [
      { kind: "video", src: "https://www.youtube.com/embed/iRkc9BaCeU0" },
      { kind: "video", src: "https://www.youtube.com/embed/792Mf1bNZXs" },
      { kind: "video", src: "https://www.youtube.com/embed/HBo4Vxp9SmU" },
    ],
  },
  {
    id: "jpr",
    name: "Journal of Postgraduate Research, Trinity College Dublin",
    tag: "Editor",
    type: "panel_only",
    items: [
      { kind: "screenshot", src: "jpr/001.jpg" },
      { kind: "screenshot", src: "jpr/002.jpg" },
      { kind: "screenshot", src: "jpr/003.jpg" },
      { kind: "screenshot", src: "jpr/004.jpg" },
    ],
  },
  {
    id: "trinity-news",
    name: "Trinity News, Trinity College Dublin",
    tag: "Science Editor & Writer",
    type: "panel_only",
    items: [
      { kind: "screenshot", src: "trinitynews/tn001.jpg" },
      { kind: "screenshot", src: "trinitynews/tn002.jpg" },
      { kind: "screenshot", src: "trinitynews/tn003.jpg" },
      { kind: "screenshot", src: "trinitynews/tn004.jpg" },
      { kind: "screenshot", src: "trinitynews/tn005.jpg" },
      { kind: "screenshot", src: "trinitynews/tn006.jpg" },
    ],
  },
  {
    id: "rheinische-post",
    name: "Rheinische Post, Wissenschaftsressort",
    tag: "Praktikantin",
    type: "panel_only",
    items: [
      { kind: "screenshot", src: "rheinischepost/rp1.jpg" },
      { kind: "screenshot", src: "rheinischepost/rp2.jpg" },
      { kind: "screenshot", src: "rheinischepost/rp3.jpg" },
      { kind: "screenshot", src: "rheinischepost/rp4.jpg" },
    ],
  },
];

// ─── Tile ────────────────────────────────────────────────────────────────────
function Tile({ item }) {
  if (item.kind === "video" && item.src) {
    const isYouTube = /youtube\.com|youtu\.be/.test(item.src);
    return (
      <div className="tile video embed">
        {isYouTube ? (
          <iframe
            src={item.src}
            title="Embedded video"
            frameBorder="0"
            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
            allowFullScreen
          />
        ) : (
          <video src={item.src} controls playsInline preload="metadata" />
        )}
      </div>
    );
  }
  if (item.kind === "screenshot" && item.src) {
    return (
      <div className="tile still embed">
        <img src={item.src} alt="" loading="lazy" />
      </div>
    );
  }
  return (
    <div className={`tile ${item.kind === "video" ? "video" : "still"}`}>
      <div className="tex"></div>
      {item.kind === "video" && (
        <>
          <div className="pulse"></div>
          <div className="play">
            <svg viewBox="0 0 24 24" fill="currentColor">
              <path d="M8 5l12 7-12 7V5z"/>
            </svg>
          </div>
        </>
      )}
    </div>
  );
}

// ─── Highlight (no italics) ──────────────────────────────────────────────────
function withEm(text, em) {
  if (!em || !text.includes(em)) return text;
  const parts = text.split(em);
  return (
    <>
      {parts[0]}
      <span className="em">{em}</span>
      {parts.slice(1).join(em)}
    </>
  );
}

// ─── Project pane ────────────────────────────────────────────────────────────
function Pane({ project, nextId }) {
  const items = project.items;
  const panelOnly = project.type === "panel_only";
  const max = Math.max(0, items.length - 1);
  const [pos, setPos] = useState(0);
  const trackRef = React.useRef(null);

  // panel_only is a native scroll-snap carousel (so touch/trackpad swiping works
  // and the peek can be sized responsively in CSS). Its position follows the
  // scroll offset; with_descriptions stays a transform-driven slideshow.
  const scrollToIndex = (i) => {
    const el = trackRef.current;
    const slide = el?.children[i];
    if (slide) el.scrollTo({ left: slide.offsetLeft, behavior: "smooth" });
  };

  const advance = () => {
    const next = pos >= max ? 0 : pos + 1;
    if (panelOnly) scrollToIndex(next);
    else setPos(next);
  };

  const onTrackScroll = (e) => {
    const el = e.currentTarget;
    const w = el.children[0]?.getBoundingClientRect().width || el.clientWidth;
    setPos(Math.max(0, Math.min(max, Math.round(el.scrollLeft / w))));
  };

  const total = items.length;
  const progDeg = max > 0 ? (pos / max) * 360 : 0;

  return (
    <section
      className="pane"
      id={project.id}
      data-screen-label={project.name}
    >
      <header className="pane-head">
        <h2>{withEm(project.name, project.em)}</h2>
        <p className="tag">{project.tag}</p>
      </header>

      {total === 0 ? (
        <div className="stage">
          <div className="window">
            <div className="empty">
              <p>Coming soon.</p>
            </div>
          </div>
        </div>
      ) : (
        <div className="stage">
          <div className="window">
            <div
              ref={trackRef}
              className={`track${panelOnly ? " panel-only" : ""}`}
              style={panelOnly ? undefined : { transform: `translateX(${-pos * 100}%)` }}
              onScroll={panelOnly ? onTrackScroll : undefined}
            >
              {items.map((it, i) => (
                <div className="slide" key={i}>
                  <div className="slide-media">
                    <Tile item={it} />
                  </div>
                  {!panelOnly && (
                    <aside className="slide-caption">
                      {it.h && (
                        <>
                          <h3>{withEm(it.h, it.em)}</h3>
                          <p>{it.p}</p>
                        </>
                      )}
                    </aside>
                  )}
                </div>
              ))}
            </div>
          </div>

          <div className="plus-wrap">
            <button className="plus" onClick={advance} aria-label="Show next" disabled={max === 0}>
              <span className="ring" style={{ "--prog-deg": `${progDeg}deg` }}></span>
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
                <path d="M12 4v16M4 12h16"/>
              </svg>
            </button>
            <span className="count">
              <span className="cur">{String(pos + 1).padStart(2, "0")}</span>
              <span> / {String(total).padStart(2, "0")}</span>
            </span>
          </div>
        </div>
      )}

      <footer className="pane-foot">
        {nextId && (
          <a className="more" href={`#${nextId}`}>
            <span>More</span>
            <svg className="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
              <path d="M6 9l6 6 6-6"/>
            </svg>
          </a>
        )}
      </footer>
    </section>
  );
}

// ─── Fit headings to a line budget ────────────────────────────────────────────
// Shrinks each pane heading only as far as needed to fit its line budget: one
// line on desktop, two on mobile. Titles that already fit keep their full size.
const fitHeadings = () => {
  const maxLines = window.innerWidth <= 720 ? 2 : 1;
  document.querySelectorAll(".pane-head h2").forEach((el) => {
    el.style.fontSize = "";
    let fs = parseFloat(getComputedStyle(el).fontSize);
    const prevWhiteSpace = el.style.whiteSpace;
    el.style.whiteSpace = "nowrap";
    const lineUnit = el.getBoundingClientRect().height / fs;
    el.style.whiteSpace = prevWhiteSpace;
    let guard = 0;
    while (
      el.getBoundingClientRect().height > fs * lineUnit * maxLines + 2 &&
      fs > 12 &&
      guard++ < 200
    ) {
      fs -= 1;
      el.style.fontSize = `${fs}px`;
    }
  });
};

// ─── App ─────────────────────────────────────────────────────────────────────
function App() {
  useEffect(() => {
    fitHeadings();
    window.addEventListener("resize", fitHeadings);
    if (document.fonts?.ready) document.fonts.ready.then(fitHeadings);
    return () => window.removeEventListener("resize", fitHeadings);
  }, []);

  return (
    <>
      <header className="top">
        <div className="top-left">
          <a className="name" href={`#${PROJECTS[0]?.id ?? ""}`}>Kirsten Kelleher</a>
          <span className="title">Portfolio</span>
        </div>
        <nav className="top-right">
          <a className="icon-link" href="https://www.linkedin.com/in/kirsten-kelleher-476084131/" target="_blank" rel="noopener noreferrer" aria-label="LinkedIn" title="LinkedIn">
            <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
              <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.063 2.063 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
            </svg>
          </a>
          <a className="email" href="mailto:kirstenbkelleher@gmail.com">kirstenbkelleher@gmail.com</a>
        </nav>
      </header>
      <main>
        {PROJECTS.map((p, i) => (
          <Pane key={p.id} project={p} nextId={PROJECTS[i + 1]?.id} />
        ))}
      </main>
    </>
  );
}

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