// app.jsx — Jaxon Stickler portfolio shell.
// Views: about (landing + diagram) · index (horizontal grid) · project

const { useState, useEffect, useRef } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "locationStyle": "both",
  "indexCardMode": "title",
  "imageLimit": 6,
  "navPosition": "bottom-left",
  "bubbleOpacity": 1,
  "bubbleNavGraphic": false,
  "nodesLocked": true
} /*EDITMODE-END*/;

// hover readout for the diagram — positioned dynamically below the bio panel
function KeepAlive({ active, children }) {
  // Keeps a subtree mounted (images/iframes stay loaded) but visually removed
  // when inactive — so switching pages never re-downloads or re-renders it.
  return <div style={{ display: active ? 'contents' : 'none' }}>{children}</div>;
}

function DiagramReadout({ p, top }) {
  return (
    <div style={{
      position: 'fixed', left: 43, top: top, width: 300, zIndex: 26, pointerEvents: 'none',
      fontFamily: FONT, color: '#fff', fontSize: 12, lineHeight: 1.5,
      opacity: p ? 1 : 0, transform: p ? 'translateY(0)' : 'translateY(6px)',
      transition: 'opacity .3s ease, transform .3s ease',
      textShadow: '0 1px 8px rgba(0,0,0,0.9)'
    }}>
      {p &&
      <React.Fragment>
          <div style={{ fontWeight: 600, letterSpacing: '0.04em' }}>{p.title}</div>
          <div style={{ opacity: 0.7 }}>{p.year}</div>
        </React.Fragment>
      }
    </div>);

}

// hover readout for the index grid (when cards are off)
function IndexReadout({ p }) {
  return (
    <div style={{
      position: 'fixed', left: 43, bottom: 78, width: 320, zIndex: 38, pointerEvents: 'none',
      fontFamily: FONT, color: '#fff', fontSize: 12, lineHeight: 1.5,
      opacity: p ? 1 : 0, transform: p ? 'translateY(0)' : 'translateY(6px)',
      transition: 'opacity .3s ease, transform .3s ease',
      textShadow: '0 1px 6px rgba(0,0,0,0.7)'
    }}>
      {p &&
      <React.Fragment>
          <div style={{ fontWeight: 600, letterSpacing: '0.04em' }}>{p.title}</div>
          <div style={{ opacity: 0.65 }}>{p.year}</div>
          {p.keywords && <div style={{ opacity: 0.5, marginTop: 6 }}>{p.keywords.join(', ')}</div>}
        </React.Fragment>
      }
    </div>);

}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const isMobile = useMobile();
  const [view, setView] = useState(() => location.hash.slice(1) || (window.innerWidth < 768 ? 'index' : 'about'));
  const [hovered, setHovered] = useState(null);
  const bioRef = useRef(null);
  const [readoutTop, setReadoutTop] = useState(500);

  useEffect(() => {
    const onHash = () => {setView(location.hash.slice(1) || 'about');setHovered(null);};
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);

  useEffect(() => {window.scrollTo(0, 0);}, [view]);

  // Mobile has no About page — always show Index
  useEffect(() => {
    if (isMobile && view === 'about') go('index');
  }, [isMobile, view]);

  // Track bio panel bottom for DiagramReadout positioning
  useEffect(() => {
    if (!bioRef.current) return;
    const update = () => {
      if (bioRef.current) {
        const r = bioRef.current.getBoundingClientRect();
        setReadoutTop(Math.round(r.bottom) + 20);
      }
    };
    update();
    const ro = new ResizeObserver(update);
    ro.observe(bioRef.current);
    return () => ro.disconnect();
  }, [view]);

  const go = (v) => {location.hash = v === 'about' ? '' : v;};
  const openProject = (p) => {location.hash = p.id;};

  const project = view !== 'about' && view !== 'index' ?
  window.PROJECTS.find((p) => p.id === view) : null;
  const isIndex = view === 'index';
  const isAbout = !project && !isIndex;

  let footerLeft = [];

  // bubble appearance globals (read by BubbleNav + bubble backgrounds)
  window.__navGraphic = t.bubbleNavGraphic;
  if (typeof document !== 'undefined') {
    document.documentElement.style.setProperty('--bubble-op', t.bubbleOpacity);
  }


  return (
    <div style={{ fontFamily: FONT }}>
      <DepthCloud />
      <AnimatedEntity />
      <CustomCursor />

      {isAbout && !isMobile &&
      <React.Fragment>
          <BioAbout ref={bioRef} locationStyle={t.locationStyle} />
          <AboutDiagram
          projects={window.PROJECTS}
          locked={t.nodesLocked}
          onOpen={openProject}
          onHover={setHovered}
          hoveredId={hovered ? hovered.id : null} />
          <DiagramReadout p={hovered} top={readoutTop} />
        </React.Fragment>
      }

      {/* Entity 2 stays mounted across pages so it never pops back in */}
      {!isMobile &&
      <KeepAlive active={isAbout}>
          <LargeEntity locked={t.nodesLocked} />
        </KeepAlive>
      }

      {/* Index stays mounted so its images never re-load when returning */}
      {!isMobile &&
      <KeepAlive active={isIndex}>
          <IndexGrid
          cardMode={t.indexCardMode}
          imageLimit={t.imageLimit}
          locked={t.nodesLocked}
          onOpen={openProject}
          onHover={setHovered}
          isMobile={false}
          locationStyle={t.locationStyle} />
        </KeepAlive>
      }

      {isIndex && isMobile &&
      <IndexGrid
        cardMode={t.indexCardMode}
        imageLimit={t.imageLimit}
        locked={t.nodesLocked}
        onOpen={openProject}
        onHover={setHovered}
        isMobile={true}
        locationStyle={t.locationStyle} />
      }

      {isIndex && !isMobile &&
      <React.Fragment>
          <BioShort locationStyle={t.locationStyle} top={19} contact={false} />
          {t.indexCardMode === 'off' && <IndexReadout p={hovered} />}
        </React.Fragment>
      }

      {project &&
      <ProjectPage p={project} locationStyle={t.locationStyle} locked={t.nodesLocked} mobile={isMobile} />
      }

      <Footer left={footerLeft} navPosition={t.navPosition} />

      <TweaksPanel>
        <TweakSection label="Authoring" />
        <TweakToggle label="Lock all positions" value={t.nodesLocked}
        onChange={(v) => setTweak('nodesLocked', v)} />

        <TweakSection label="Navigation" />
        <TweakRadio label="Nav position"
        value={t.navPosition}
        options={['bottom-left', 'top-right', 'top-center']}
        onChange={(v) => setTweak('navPosition', v)} />

        <TweakSection label="Bubble" />
        <TweakSlider label="Bubble opacity"
        value={t.bubbleOpacity} min={0.3} max={1} step={0.05}
        onChange={(v) => setTweak('bubbleOpacity', v)} />
        <TweakToggle label="Graphic nav glyph" value={t.bubbleNavGraphic}
        onChange={(v) => setTweak('bubbleNavGraphic', v)} />

        <TweakSection label="Live location" />
        <TweakRadio label="Signifier" value={t.locationStyle}
        options={['pulse', 'clock', 'both']}
        onChange={(v) => setTweak('locationStyle', v)} />

        <TweakSection label="Index" />
        <TweakRadio label="Project cards"
        value={t.indexCardMode}
        options={['off', 'title']}
        onChange={(v) => setTweak('indexCardMode', v)} />
        <TweakSlider label="Default preview images"
        value={t.imageLimit} min={1} max={20} step={1}
        onChange={(v) => setTweak('imageLimit', v)} />
      </TweaksPanel>
    </div>);

}

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