/* ============================================================
   Section pages — Blog (article grid) & Videos (embeds),
   with domain + tag filters. Plus the simple legal page.
   ============================================================ */
const { useState: uSs, useMemo: uMs } = React;

function SectionHeader({ showBack, back, tools, editTool }) {
  const { db } = useStore();
  const { go, lang, setLang, edit, auth } = useNav();
  const [menuOpen, setMenuOpen] = uSs(false);
  const p = db.profile;
  const t = useT();
  const editMode = !!(edit && auth);
  const homeDomain = db.domains.find((d) => d.route === p.defaultRoute) || db.domains[0];
  // back: true → "Back to site"; {label,to} → custom (e.g. "All other pages"); falsy → none.
  // `showBack` kept as a legacy alias for back=true.
  const backCfg = back === true || showBack ? { label: t.backToSite, to: p.defaultRoute } : back || null;
  const rightTool = editTool || tools || null; // `tools` legacy alias
  return (
    <>
      <header className="topbar" style={{ position: "static", paddingBottom: 0 }}>
        <div className="topleft">
          <a className="brand" onClick={() => go(p.defaultRoute)} style={{ cursor: "pointer" }}>
            <span className="b1">{p.first}</span>
            <span className="b2">{p.last}<span className="dot">.</span></span>
          </a>
          {backCfg && <button className="btn ghost sm header-back" onClick={() => typeof backCfg.to === "function" ? backCfg.to() : go(backCfg.to)}><Icon name="left" size={16} />{backCfg.label}</button>}
        </div>
        <div className="topright">
          {rightTool}
          <div className="lang">
            <button className={lang === "en" ? "on" : ""} onClick={() => setLang("en")}>EN</button>
            <button className={lang === "fr" ? "on" : ""} onClick={() => setLang("fr")}>FR</button>
          </div>
          <button className="menubtn" onClick={() => setMenuOpen(true)} aria-label="menu"><Icon name="menu" size={24} /></button>
          <IdentityControl />
        </div>
      </header>
      <MenuPanel open={menuOpen} onClose={() => setMenuOpen(false)} domain={homeDomain} editMode={editMode} goWindow={(n) => {setMenuOpen(false);go(p.defaultRoute + (n ? "?win=" + n : ""));}} />
    </>);

}

/* the orange, edit-mode-only "Edit page / Done" button shared by every static page.
   Lives in the header (right of the brand area, just left of EN/FR) so it never
   shifts the page content. */
function EditToggle({ editing, onToggle }) {
  return (
    <button className={"btn sm edit-toggle" + (editing ? " on" : "")} onClick={onToggle}>
      <Icon name="edit" size={14} /> {editing ? "Done" : "Edit page"}
    </button>);

}

/* ---- tag picker: select from existing + add new ---- */
function TagPicker({ tags, all, onChange }) {
  const [input, setInput] = uSs("");
  const add = (t) => {const v = (t || "").trim().toLowerCase();if (v && !tags.includes(v)) onChange([...tags, v]);setInput("");};
  const remove = (t) => onChange(tags.filter((x) => x !== t));
  const suggestions = all.filter((t) => !tags.includes(t));
  return (
    <div className="col gap-8">
      <div className="row gap-8 wrap">
        {tags.length === 0 && <span className="dim mono" style={{ fontSize: 12 }}>No tags yet.</span>}
        {tags.map((t) =>
        <button type="button" key={t} className="chip on" onClick={() => remove(t)} title="remove">#{t} <Icon name="close" size={11} style={{ verticalAlign: "-1px" }} /></button>
        )}
      </div>
      <div className="row gap-8">
        <input value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => {if (e.key === "Enter") {e.preventDefault();add(input);}}} placeholder="Type a tag, press Enter…" />
        <button type="button" className="btn ghost sm" style={{ flex: "none" }} onClick={() => add(input)}><Icon name="plus" size={14} /> Add</button>
      </div>
      {suggestions.length > 0 &&
      <div className="row gap-8 wrap">
          <span className="dim mono" style={{ fontSize: 11, alignSelf: "center" }}>existing:</span>
          {suggestions.map((t) => <button type="button" key={t} className="chip" onClick={() => add(t)}>#{t}</button>)}
        </div>
      }
    </div>);

}

const AI_MODES = [
{ id: "", label: "Human-written" },
{ id: "augmented", label: "AI-augmented" },
{ id: "generated", label: "AI-created" },
{ id: "translated", label: "AI-translated" }];


/* ---- content editor modal (used on section pages in edit mode) ---- */
function ContentModal({ data, domains, kind, allTags = [], onClose, onSave, onDelete, onOpenArticle }) {
  const [c, setC] = uSs({ ...data.item, tags: [...(data.item.tags || [])], ai: data.item.ai || "" });
  const set = (k, v) => setC((p) => ({ ...p, [k]: v }));
  return (
    <Modal title={data.mode === "add" ? `New ${kind === "video" ? "video" : "article"}` : "Edit content"} onClose={onClose}
    footer={<>
        {data.mode === "edit" && <button className="btn ghost" style={{ color: "var(--c-coral)", marginRight: "auto" }} onClick={() => onDelete(c.id)}><Icon name="trash" size={16} /> Delete</button>}
        <button className="btn ghost" onClick={onClose}>Cancel</button>
        <button className="btn accent" onClick={() => c.title.trim() && onSave(c)}><Icon name="save" size={16} /> Save</button>
      </>}>
      <div><label>Title</label><input value={c.title} autoFocus onChange={(e) => set("title", e.target.value)} placeholder="Title" /></div>
      <div><label>Short description <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>shown under the title</span></label><textarea rows={2} value={c.description} onChange={(e) => set("description", e.target.value)} /></div>
      <div className="field-row">
        <div><label>Domain</label><select value={c.domain} onChange={(e) => set("domain", e.target.value)}>{domains.map((d) => <option key={d.id} value={d.id}>{d.label}</option>)}</select></div>
        {kind === "video" && <div><label>YouTube id</label><input value={c.youtube || ""} onChange={(e) => set("youtube", e.target.value)} placeholder="e.g. wTcMtvyXcSE" /></div>}
      </div>
      <div><label>AI transparency <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>shown to readers in the metadata</span></label>
        <div className="row gap-8 wrap">
          {AI_MODES.map((m) => <button type="button" key={m.id} className={"chip" + (c.ai === m.id ? " on" : "")} onClick={() => set("ai", m.id)}>{m.label}</button>)}
        </div>
      </div>
      <div><label>Tags</label><TagPicker tags={c.tags} all={allTags} onChange={(v) => set("tags", v)} /></div>
      <div className="src-note">
        <div className="row gap-8" style={{ alignItems: "flex-start" }}>
          <Icon name="edit" size={15} style={{ marginTop: 2, color: "var(--accent)" }} />
          <div>
            <b style={{ fontFamily: "var(--font-display)" }}>Body, sources &amp; linked media</b>
            <p className="dim" style={{ margin: "2px 0 0", fontSize: 13 }}>The full {kind === "video" ? "description, transcript" : "article body"}, <b>Sources</b> and multi-sensory alternatives (voice, music, video…) are written on the {kind === "video" ? "video" : "article"} page itself. Reader ratings are collected there too — they are not set here.</p>
            {data.mode === "edit" && onOpenArticle && <button type="button" className="btn ghost sm" style={{ marginTop: 10 }} onClick={() => onOpenArticle(c)}><Icon name="arrow" size={14} /> Open full {kind === "video" ? "video" : "article"} editor</button>}
          </div>
        </div>
      </div>
    </Modal>);

}

function SectionPage({ section }) {
  const { db, update } = useStore();
  const { route, go, edit, auth, lang, reviewMode, publishMode, testerMode, role, isAdmin, session } = useNav();
  const t = useT();
  const editing = !!(edit && auth);
  const collabReview = !!reviewMode;
  const collabPublish = !!publishMode;
  const collabTester = !!testerMode;
  const collabEdit = role === "editor" && edit; // editor's restricted working view
  const adminEdit = isAdmin && edit; // admin manages everything
  const inCollabMode = collabReview || collabPublish || collabTester;
  const [pubModal, setPubModal] = uSs(null); // content awaiting a publish/schedule decision
  const initDomain = route.query.domain || "all";
  const [domain, setDomain] = uSs(initDomain);
  const [tag, setTag] = uSs("all");
  const [q, setQ] = uSs("");
  const [sort, setSort] = uSs("newest");

  // create a fresh draft and jump straight into its full editor (no popup)
  const addContent = () => {
    const km = window.kindMeta(section.kind);
    const base = "New " + km.word;
    let ref = base.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
    if (db.content.some((c) => c.ref === ref)) ref = ref + "-" + Math.random().toString(36).slice(2, 6);
    const k = section.kind;
    const isImg = k === "image",isDocK = k === "document",isAud = window.isAudioContentKind(k);
    update((d) => d.content.unshift({
      id: uid("c"), ref, type: k, domain: d.domains[0].id, domains: [d.domains[0].id],
      title: base, description: "", tags: [], source: "Original", rating: 0, votes: 0, views: 0,
      ai: null, cover: null, alt: [], sources: [], related: [], subscribe: [],
      playlist: "", playlistKind: isAud ? "album" : "spotify", pdf: "", tr: {}, private: false,
      gallery: isImg ? [{ id: uid("gi"), src: null, caption: "", cover: true }] : [],
      galleryMode: "arrows", galleryInterval: 5,
      docs: isDocK ? [{ id: uid("dc"), label: "", pdf: "" }] : [],
      tracks: isAud ? [{ id: uid("tk"), title: "", assetId: "", locale: "" }] : [],
      published: false, status: "draft", statusByLang: { en: "draft", fr: "draft" }, publishedAt: "", createdAt: new Date().toISOString().slice(0, 10), updatedAt: new Date().toISOString().slice(0, 10),
      body: [{ id: uid("b"), type: "p", text: "" }], youtube: k === "video" ? "" : undefined
    }));
    go(`${section.route}/${ref}`);
  };

  // publish / schedule a piece (publisher & admin) — validated languages go live
  const doPublish = (whenDate, mode, whenTime) => {
    if (!pubModal) return;
    const todayStr = new Date().toISOString().slice(0, 10);
    const future = mode === "schedule" && whenDate > todayStr;
    update((d) => {
      const c = d.content.find((x) => x.id === pubModal.id);if (!c) return;
      c.statusByLang = c.statusByLang || {};
      if (future) {
        c.publishAt = whenDate; // scheduled — stays Validated until the date
        c.publishAtTime = whenTime || "06:00";
      } else {
        ["en", "fr"].forEach((lg) => {if (c.statusByLang[lg] === "validated") c.statusByLang[lg] = "published";});
        c.published = Object.values(c.statusByLang).some((v) => v === "published");
        c.status = c.statusByLang.en || c.status;
        c.publishedAt = whenDate || todayStr;c.publishAt = "";
        if (session) c.reviewedBy = c.reviewedBy || session.name;
        c.publishAtTime = "";
      }
      c.updatedAt = todayStr;
    });
    setPubModal(null);
  };

  const acc = accentFor(domain === "all" ? "datascientist" : domain);
  const styleVars = { "--accent": acc.accent, "--accent-ink": acc.ink, "--accent-wash": acc.wash };

  // Visibility per role (per-language workflow). The workflow is UNIQUE PER LANGUAGE,
  // and the collaborator works on ALL languages — so a piece appears in their queue if
  // ANY language is in the status they handle (draft for editors, review for reviewers).
  //  • admin in edit mode → everything (to manage)
  //  • editor in edit mode → anything with a DRAFT language (incl. EN published / FR draft)
  //  • reviewer in review mode → anything with a language IN REVIEW
  //  • public / tester → published (+ pre-released for testers), in this language
  const relevant = collabReview ? "review" : collabPublish ? "validated" : collabEdit ? "draft" : null;
  const previewStatuses = ["review", "validated"];
  const pool = db.content.filter((c) => {
    if (c.type !== section.kind) return false;
    if (adminEdit) return true; // admin manages everything, incl. the hidden template
    // editor / reviewer / publisher queue — per-piece, any language; private needs a grant
    if (relevant) return itemActionable(db, c, relevant, session, role, false);
    if (collabTester) {
      if (c.private) return false; // private content never previews publicly
      return ["en", "fr"].some((lg) => previewStatuses.includes(statusOfLang(c, lg)) && langExists(c, lg)) || isVisibleInLang(c, lang) && publishedInLang(c, lang);
    }
    if (c.private && !(session && ((session.grants || []).includes(c.id) || (session.editGrants || []).includes(c.id)))) return false; // PRIVATE — only via a grant
    if (c.ref === "template-toolkit") return false; // hidden reference — never on the public grid
    return readableInLang(c, lang);   // PUBLIC reading is role-independent (published only, fallback-aware)
  });
  const allTags = uMs(() => [...new Set(pool.flatMap((c) => c.tags || []))].sort(), [pool]);

  const items = pool.filter((c) =>
  (domain === "all" || domainsOf(c).includes(domain)) && (
  tag === "all" || (c.tags || []).includes(tag)) && (
  !q || (c.title + c.description + (c.tags || []).join(" ")).toLowerCase().includes(q.toLowerCase()))
  );

  const sorted = [...items].sort((a, b) => {
    if (sort === "best") return b.rating - a.rating || new Date(b.createdAt) - new Date(a.createdAt);
    const cmp = new Date(a.createdAt) - new Date(b.createdAt);
    return sort === "oldest" ? cmp : -cmp;
  });

  const SORTS = [{ id: "newest", label: "Newest" }, { id: "oldest", label: "Oldest" }, { id: "best", label: "Top rated" }];

  const domLabel = (id) => db.domains.find((d) => d.id === id)?.label || id;

  return (
    <div style={styleVars}>
      <SectionHeader />
      <div className="section-wrap">
        <div className="section-head" style={{ margin: "34px 0 48px" }}>
          <h1><Scribble>{secLabel(section, lang)}</Scribble></h1>
          <div className="row gap-12" style={{ alignItems: "center" }}>
            {editing && <button className="btn accent sm" onClick={addContent}><Icon name="plus" size={15} /> Add {section.kind === "blog" ? "article" : section.label.toLowerCase().replace(/s$/, "")}</button>}
            <div style={{ position: "relative" }}>
              <span style={{ position: "absolute", left: 11, top: 10, color: "var(--muted)" }}><Icon name="search" size={18} /></span>
              <input placeholder="Search…" value={q} onChange={(e) => setQ(e.target.value)} style={{ paddingLeft: 36, width: 220 }} />
            </div>
            <span className="mono dim">{items.length} {t.results}</span>
          </div>
        </div>

        {/* filters */}
        <div className="filters">
          <span className="mono dim" style={{ fontSize: 12, marginRight: 2 }}>{t.filterDomain}:</span>
          <button className={"chip" + (domain === "all" ? " on" : "")} onClick={() => setDomain("all")}>{t.all}</button>
          {db.domains.filter((d) => d.display).map((d) =>
          <button key={d.id} className={"chip" + (domain === d.id ? " on" : "")} onClick={() => setDomain(d.id)}
          style={domain === d.id ? null : { borderColor: accentFor(d.id).accent, color: accentFor(d.id).ink }}>{d.label}</button>
          )}
        </div>
        <div className="filters">
          <span className="mono dim" style={{ fontSize: 12, marginRight: 2 }}>{t.filterTag}:</span>
          <button className={"chip" + (tag === "all" ? " on" : "")} onClick={() => setTag("all")}>{t.all}</button>
          {allTags.map((tg) =>
          <button key={tg} className={"chip" + (tag === tg ? " on" : "")} onClick={() => setTag(tag === tg ? "all" : tg)}>#{tg}</button>
          )}
        </div>
        <div className="filters" style={{ marginBottom: 30 }}>
          <span className="mono dim" style={{ fontSize: 12, marginRight: 2 }}>Sort:</span>
          {SORTS.map((s) =>
          <button key={s.id} className={"chip" + (sort === s.id ? " on" : "")} onClick={() => setSort(s.id)}>{s.id === "best" && <Icon name="starF" size={11} style={{ verticalAlign: "-1px", marginRight: 3 }} />}{s.label}</button>
          )}
        </div>

        {/* grid */}
        <div className="content-grid">
          {sorted.map((it) => {
            const dispLang = (editing || inCollabMode) ? lang : publicDisplayLang(it, lang);
            const cl = wholeLang(it, dispLang);
            const coverSrc = it.cover || (it.type === "image" && window.galleryImgSrc ? window.galleryImgSrc(db, (it.gallery || []).find((g) => g.cover) || (it.gallery || [])[0]) : "");
            return (
              <article key={it.id} className="ccard" style={{ position: "relative" }} onClick={() => go(`${section.route}/${it.ref}`)}>
              {editing &&
                <div className="row gap-8" style={{ position: "absolute", top: 8, right: 8, zIndex: 3 }} onClick={(e) => e.stopPropagation()}>
                  {adminEdit && ["en", "fr"].some((lg) => statusOfLang(it, lg) === "validated" && langExists(it, lg)) &&
                  <button className="iconbtn" style={{ background: "rgba(255,255,255,.92)", color: "var(--c-green)" }} title="Publish / schedule" onClick={() => setPubModal(it)}><Icon name="check" size={15} /></button>
                  }
                  <button className="iconbtn" style={{ background: "rgba(255,255,255,.92)" }} title="Edit content" onClick={() => go(`${section.route}/${it.ref}`)}><Icon name="edit" size={15} /></button>
                  <button className="iconbtn danger" style={{ background: "rgba(255,255,255,.92)" }} onClick={() => update((d) => d.content = d.content.filter((x) => x.id !== it.id))}><Icon name="trash" size={15} /></button>
                </div>
                }
              {collabPublish && ["en", "fr"].some((lg) => statusOfLang(it, lg) === "validated" && langExists(it, lg)) &&
                <div style={{ position: "absolute", top: 8, right: 8, zIndex: 3 }} onClick={(e) => e.stopPropagation()}>
                  <button className="btn accent sm" style={{ boxShadow: "2px 2px 0 rgba(0,0,0,.18)" }} onClick={() => setPubModal(it)}><Icon name="check" size={14} /> {lang === "fr" ? "Publier" : "Publish"}</button>
                </div>
                }
              <div className="thumb">
                {(editing || inCollabMode) &&
                  <div className="thumb-pub">
                    <LangWfBadges item={it} relevant={relevant} />
                  </div>
                  }
                {!editing && !inCollabMode && <LangChips has={(l) => langExists(it, l) && publishedInLang(it, l)} className="on-thumb" />}
                {coverSrc ?
                  <img src={coverSrc} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} /> :
                  <div className="thumb-art" style={{ background: domainsOf(it).length > 1 ? "linear-gradient(135deg,#eaf0fb,#ffffff 72%)" : accentFor(primaryDomain(it)).wash }}><MultiDomainArt arts={domainsOf(it).map((id) => {const d = db.domains.find((x) => x.id === id);return { kind: d && d.art || (id === "human" ? "human" : "network"), accent: accentFor(id).accent };})} animate={false} /></div>}
                {it.type !== "blog" && it.type !== "document" && it.type !== "image" && <span className="thumb-kind"><Icon name={window.iconForKind(it.type)} size={44} /></span>}
                {it.type === "document" && <span className="thumb-kind doc"><Icon name="doc" size={40} /></span>}
                {it.private && <span className="thumb-priv"><Icon name="lock" size={11} style={{ verticalAlign: "-1px", marginRight: 3 }} />Private</span>}
              </div>
              <div className="cc-body">
                <div className="row gap-8 wrap" style={{ marginBottom: 2 }}>
                  {domainsOf(it).map((id) => <span key={id} className="chip" style={{ borderColor: accentFor(id).accent, color: accentFor(id).ink }}>{domLabel(id)}</span>)}
                  {it.source !== "Original" && <span className="chip" style={{ fontStyle: "italic" }}>{it.source}</span>}
                </div>
                <div className="cc-title">{cl.title}</div>
                <div className="cc-desc">{cl.description}</div>
                <div className="cc-meta">
                  <Stars n={it.rating} size={12} />
                  <span>{fmtDate(it.createdAt)}</span>
                  <span title="views"><Icon name="chart" size={12} style={{ verticalAlign: "-2px" }} /> {(it.views || 0).toLocaleString()}</span>
                  {(it.tags || []).map((tg) => <span key={tg} style={{ cursor: "pointer" }} onClick={(e) => {e.stopPropagation();setTag(tg);}}>#{tg}</span>)}
                </div>
              </div>
            </article>);

          })}
        </div>

        {items.length === 0 &&
        <div className="surface" style={{ padding: 40, textAlign: "center", color: "var(--muted)" }}>
            No {section.label.toLowerCase()} match these filters yet.
          </div>
        }
      </div>
      {pubModal && window.PublishModal && <window.PublishModal item={pubModal} onClose={() => setPubModal(null)} onConfirm={doPublish} />}
    </div>);

}

/* ---------- legal pages (editable when authenticated, bilingual) ---------- */
function LegalPage({ which }) {
  const { db, update } = useStore();
  const { go, auth, edit, lang, setLang } = useNav();
  const editMode = !!(auth && edit);
  const fallbackTitle = { mentions: "Legal notice", copyright: "Copyright", cgu: "Terms of use (CGU)" };
  const page = db.legal && db.legal[which] || window.LEGAL_SEED && window.LEGAL_SEED[which] || { title: fallbackTitle[which] || "Legal", body: "" };
  const KEYS = ["title", "body"];
  const [editing, setEditing] = uSs(false);
  const [saved, setSaved] = uSs(false);

  const L = objLangFields(page, lang, KEYS);
  const Lraw = objLangRaw(page, lang, KEYS);
  const Lother = objLangRaw(page, lang === "fr" ? "en" : "fr", KEYS);
  const enOn = objHasLang(page, "en", KEYS);
  const frOn = objHasLang(page, "fr", KEYS);
  const otherExists = lang === "fr" ? enOn : frOn;
  const langMissing = !objHasLang(page, lang, KEYS) && page.fallback !== "strict";

  const setField = (key, val) => update((d) => {d.legal = d.legal || {};d.legal[which] = d.legal[which] || { tr: {} };setObjLang(d.legal[which], lang, key, val);});
  const setFallback = (v) => update((d) => {d.legal[which].fallback = v;});
  const done = () => {setEditing(false);setSaved(true);setTimeout(() => setSaved(false), 1800);};

  // public visitor, strict page, missing language → friendly notice
  if (!editMode && lang === "fr" && page.fallback === "strict" && !frOn) {
    return (
      <div>
        <SectionHeader />
        <div className="section-wrap" style={{ maxWidth: 620, paddingTop: 56 }}>
          <div className="surface notinlang">
            <span className="nil-ic"><Icon name="globe" size={28} /></span>
            <h1 style={{ fontSize: "clamp(26px,4vw,38px)", lineHeight: 1.08 }}>Pas disponible en français</h1>
            <p className="dim" style={{ maxWidth: 420, margin: "10px auto 0" }}>Cette page n'a pas encore été traduite.</p>
            <div className="row gap-8" style={{ justifyContent: "center", marginTop: 18, flexWrap: "wrap" }}>
              <button className="btn" onClick={() => go("/")}><Icon name="left" size={16} /> Back to site</button>
              <button className="btn accent" onClick={() => setLang("en")}><Icon name="globe" size={16} /> Read in EN</button>
            </div>
          </div>
        </div>
      </div>);

  }

  return (
    <div>
      <SectionHeader back editTool={editMode ? <EditToggle editing={editing} onToggle={() => setEditing((e) => !e)} /> : null} />
      <div className="section-wrap" style={{ maxWidth: 780 }}>
        <div className="row between" style={{ margin: "26px 0 18px", alignItems: "flex-end" }}>
          <div>
            <div className="mono dim" style={{ fontSize: 12, letterSpacing: ".12em", marginBottom: 6 }}>/legal/{which}</div>
            <h1>{editing ? "Editing page" : L.title}</h1>
          </div>
          {saved && <span className="row gap-8" style={{ color: "var(--c-green)", fontWeight: 700, fontFamily: "var(--font-display)" }}><Icon name="check" size={16} /> Saved</span>}
        </div>
        {editing ?
        <div className="surface" style={{ padding: 28 }}>
            <div className="lang-edit-bar" style={{ marginBottom: 16 }}>
              <span className="leb-k">Editing in</span>
              <div className="leb-tabs">
                <button className={lang === "en" ? "on" : ""} onClick={() => setLang("en")}>EN</button>
                <button className={lang === "fr" ? "on" : ""} onClick={() => setLang("fr")}>FR</button>
              </div>
              <span className="grow" />
              <BilingualStatus enOn={enOn} frOn={frOn} fallback={page.fallback || "fallback"} editingLang={lang} />
            </div>
            <label>Title <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
            <input className={!Lraw.title.trim() && Lother.title ? "phantom-ph" : ""} value={Lraw.title} onChange={(e) => setField("title", e.target.value)} placeholder={Lother.title || "Page title"} style={{ fontFamily: "var(--font-display)", fontWeight: 700 }} />
            <label style={{ marginTop: 14 }}>Body <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
            <textarea className={!Lraw.body.trim() && Lother.body ? "phantom-ph" : ""} rows={12} value={Lraw.body} onChange={(e) => setField("body", e.target.value)} placeholder={Lother.body || "Page content…"} style={{ fontFamily: "var(--font-body)", lineHeight: 1.6 }} />
            <div style={{ marginTop: 18 }}>
              <LangFallbackToggle enOn={enOn} frOn={frOn} fallback={page.fallback || "fallback"} onChange={setFallback} />
            </div>
            <div className="row gap-8" style={{ justifyContent: "flex-end", marginTop: 18 }}>
              <button className="btn accent" onClick={done}><Icon name="check" size={16} /> Done</button>
            </div>
          </div> :

        <>
            {langMissing && lang === "fr" &&
          <div className="trans-notice"><Icon name="globe" size={15} /> Désolé, le contenu n'a pas encore été traduit — version anglaise affichée.</div>
          }
            <div className="surface" style={{ padding: 34 }}>
              <p style={{ color: "var(--ink-soft)", lineHeight: 1.75, whiteSpace: "pre-wrap", margin: 0 }}>{L.body}</p>
            </div>
          </>
        }
      </div>
    </div>);

}

/* ============================================================
   Form pages — Contact, Feedback, CV request.
   Each POSTs to the in-memory API (store).
   ============================================================ */
/* ---- reusable bilingual editor for the simple static form pages ---- */
function StaticPageEditor({ pageKey, fields, keys, maxWidth = 620 }) {
  const { db, update } = useStore();
  const { lang, setLang } = useNav();
  const page = db.pages[pageKey];
  const Lraw = objLangRaw(page, lang, keys);
  const Lother = objLangRaw(page, lang === "fr" ? "en" : "fr", keys);
  const enOn = objHasLang(page, "en", keys);
  const frOn = objHasLang(page, "fr", keys);
  const otherExists = lang === "fr" ? enOn : frOn;
  const setField = (k, v) => update((d) => setObjLang(d.pages[pageKey], lang, k, v));
  const setFallback = (v) => update((d) => d.pages[pageKey].fallback = v);
  const prefill = () => update((d) => keys.forEach((k) => {
    const other = objLangRaw(d.pages[pageKey], lang === "fr" ? "en" : "fr", [k])[k];
    if (other) setObjLang(d.pages[pageKey], lang, k, other);
  }));
  return (
    <div className="form-card surface" style={{ maxWidth }}>
      <div className="lang-edit-bar" style={{ marginBottom: 14 }}>
        <span className="leb-k">Editing in</span>
        <div className="leb-tabs">
          <button className={lang === "en" ? "on" : ""} onClick={() => setLang("en")}>EN</button>
          <button className={lang === "fr" ? "on" : ""} onClick={() => setLang("fr")}>FR</button>
        </div>
        <span className="grow" />
        <BilingualStatus enOn={enOn} frOn={frOn} fallback={page.fallback || "fallback"} editingLang={lang} />
      </div>
      <div className="row between" style={{ marginBottom: 14, flexWrap: "wrap", gap: 8 }}>
        <span className="dim" style={{ fontSize: 13 }}>Rename the page's title, intro and each field label.</span>
        <button className="btn ghost sm" onClick={prefill}><Icon name="globe" size={14} /> Fill {lang === "fr" ? "FR" : "EN"} from {lang === "fr" ? "EN" : "FR"}</button>
      </div>
      {fields.map(([k, lbl]) =>
      <div key={k} style={{ marginBottom: 12 }}>
          <label>{lbl} <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
          {k === "subtitle" ?
        <textarea className={!Lraw[k].trim() && Lother[k] ? "phantom-ph" : ""} rows={2} value={Lraw[k]} onChange={(e) => setField(k, e.target.value)} placeholder={Lother[k]} /> :
        <input className={!Lraw[k].trim() && Lother[k] ? "phantom-ph" : ""} value={Lraw[k]} onChange={(e) => setField(k, e.target.value)} placeholder={Lother[k]} />}
        </div>
      )}
      <div style={{ marginTop: 8 }}>
        <LangFallbackToggle enOn={enOn} frOn={frOn} fallback={page.fallback || "fallback"} onChange={setFallback} />
      </div>
    </div>);

}

function FormShell({ kicker, title, subtitle, children, maxWidth = 560 }) {
  return (
    <div>
      <SectionHeader showBack />
      <div className="form-page">
        <div className="form-card surface" style={{ maxWidth }}>
          {kicker && <div className="mono" style={{ color: "var(--accent)", letterSpacing: ".14em", fontSize: 12, marginBottom: 10 }}>{kicker}</div>}
          <h1 style={{ fontSize: "clamp(30px,4vw,44px)", marginBottom: 8 }}>{title}</h1>
          {subtitle && <p className="dim" style={{ marginTop: 0, marginBottom: 22 }}>{subtitle}</p>}
          {children}
        </div>
      </div>
    </div>);

}

function Field({ label, required, hint, children }) {
  return (
    <div>
      <label>{label}{required && <span style={{ color: "var(--c-coral)" }}> *</span>}</label>
      {children}
      {hint && <div className="dim" style={{ fontSize: 11, marginTop: 4 }}>{hint}</div>}
    </div>);

}

function Sent({ title, body, onBack }) {
  return (
    <div className="center col" style={{ textAlign: "center", gap: 12, padding: "20px 0 8px" }}>
      <span className="iconbtn" style={{ width: 60, height: 60, borderRadius: "50%", background: "var(--accent)", color: "#fff", border: "none" }}><Icon name="check" size={30} /></span>
      <h2 style={{ fontSize: 26 }}>{title}</h2>
      <p className="dim" style={{ maxWidth: 380 }}>{body}</p>
      <button className="btn" onClick={onBack} style={{ marginTop: 6 }}><Icon name="left" size={16} /> Back to site</button>
    </div>);

}

function ContactPage() {
  const { db, update } = useStore();
  const { go, edit, auth, lang } = useNav();
  const editMode = !!(edit && auth);
  const KEYS = ["title", "subtitle", "nameFirst", "nameLast", "email", "message", "btn"];
  const FIELDS = [["title", "Page title"], ["subtitle", "Intro line"], ["nameFirst", "First-name field"], ["nameLast", "Last-name field"], ["email", "Email field"], ["message", "Message field"], ["btn", "Send button"]];
  const L = objLangFields(db.pages.contact, lang, KEYS);
  const [editing, setEditing] = uSs(false);
  const [f, setF] = uSs({ first: "", last: "", email: "", message: "" });
  const [sent, setSent] = uSs(false);
  const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
  const valid = f.email.trim() && f.message.trim();
  const submit = (e) => {
    e.preventDefault();
    if (!valid) return;
    update((d) => {(d.contactMessages = d.contactMessages || []).unshift({ id: uid("msg"), ...f, date: new Date().toISOString().slice(0, 10) });});
    setSent(true);
  };
  return (
    <div>
      <SectionHeader back editTool={editMode ? <EditToggle editing={editing} onToggle={() => setEditing((e) => !e)} /> : null} />
      <div className="form-page">
        {editing ? <StaticPageEditor pageKey="contact" fields={FIELDS} keys={KEYS} /> :
        <div className="form-card surface" style={{ maxWidth: 560 }}>
            <h1 style={{ fontSize: "clamp(30px,4vw,44px)", marginBottom: 8 }}>{L.title}</h1>
            <p className="dim" style={{ marginTop: 0, marginBottom: 22 }}>{L.subtitle}</p>
            {sent ? <Sent title={lang === "fr" ? "Message envoyé" : "Message sent"} body={lang === "fr" ? "Merci — je vous réponds bientôt par e-mail." : "Thanks — I'll get back to you by email shortly."} onBack={() => go("/")} /> :
          <form className="col gap-16" onSubmit={submit}>
                <div className="field-row">
                  <Field label={L.nameFirst}><input value={f.first} onChange={(e) => set("first", e.target.value)} /></Field>
                  <Field label={L.nameLast}><input value={f.last} onChange={(e) => set("last", e.target.value)} /></Field>
                </div>
                <Field label={L.email} required><input type="email" value={f.email} onChange={(e) => set("email", e.target.value)} placeholder="you@company.com" /></Field>
                <Field label={L.message} required><textarea rows={5} value={f.message} onChange={(e) => set("message", e.target.value)} placeholder={lang === "fr" ? "Qu'avez-vous en tête ?" : "What's on your mind?"} /></Field>
                <button className="btn accent" type="submit" disabled={!valid} style={{ alignSelf: "flex-start", opacity: valid ? 1 : .5 }}><Icon name="arrow" size={18} /> {L.btn}</button>
              </form>
          }
          </div>
        }
      </div>
    </div>);

}

function FeedbackPage() {
  const { db, update } = useStore();
  const { go, edit, auth, lang } = useNav();
  const editMode = !!(edit && auth);
  const KEYS = ["title", "subtitle", "name", "rating", "message", "btn"];
  const FIELDS = [["title", "Page title"], ["subtitle", "Intro line"], ["name", "Name field"], ["rating", "Rating field"], ["message", "Message field"], ["btn", "Send button"]];
  const L = objLangFields(db.pages.feedback, lang, KEYS);
  const [editing, setEditing] = uSs(false);
  const [f, setF] = uSs({ name: "", rating: 5, message: "" });
  const [sent, setSent] = uSs(false);
  const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
  const valid = f.message.trim();
  const submit = (e) => {
    e.preventDefault();
    if (!valid) return;
    update((d) => {
      d.feedbacks.unshift({ id: uid("f"), name: f.name.trim() || "Anon", date: new Date().toISOString().slice(0, 10), rating: f.rating, text: f.message });
      d.notifications.unshift({ id: uid("n"), tile: "t3", time: new Date().toISOString().slice(0, 16).replace("T", " "), text: `New feedback received (${f.rating}★).` });
    });
    setSent(true);
  };
  return (
    <div>
      <SectionHeader back editTool={editMode ? <EditToggle editing={editing} onToggle={() => setEditing((e) => !e)} /> : null} />
      <div className="form-page">
        {editing ? <StaticPageEditor pageKey="feedback" fields={FIELDS} keys={KEYS} /> :
        <div className="form-card surface" style={{ maxWidth: 560 }}>
            <h1 style={{ fontSize: "clamp(30px,4vw,44px)", marginBottom: 8 }}>{L.title}</h1>
            <p className="dim" style={{ marginTop: 0, marginBottom: 22 }}>{L.subtitle}</p>
            {sent ? <Sent title={lang === "fr" ? "Feedback reçu" : "Feedback received"} body={lang === "fr" ? "Merci ! Il vient d'arriver sur mon tableau de bord." : "Thank you! It just landed on my board."} onBack={() => go("/")} /> :
          <form className="col gap-16" onSubmit={submit}>
                <Field label={L.name} hint={lang === "fr" ? "Optionnel — laissez vide pour rester anonyme." : "Optional — leave blank to stay anonymous."}><input value={f.name} onChange={(e) => set("name", e.target.value)} /></Field>
                <Field label={L.rating}>
                  <div className="row gap-8" style={{ color: "var(--c-amber)" }}>
                    {[1, 2, 3, 4, 5].map((i) =>
                <button type="button" key={i} onClick={() => set("rating", i)} style={{ background: "none", border: "none", padding: 2, color: "inherit" }}>
                        <Icon name={i <= f.rating ? "starF" : "star"} size={28} />
                      </button>
                )}
                  </div>
                </Field>
                <Field label={L.message} required><textarea rows={4} value={f.message} onChange={(e) => set("message", e.target.value)} placeholder={lang === "fr" ? "Votre feedback…" : "Your feedback…"} /></Field>
                <button className="btn accent" type="submit" disabled={!valid} style={{ alignSelf: "flex-start", opacity: valid ? 1 : .5 }}><Icon name="star" size={18} /> {L.btn}</button>
              </form>
          }
          </div>
        }
      </div>
    </div>);

}

const CV_LABELS = {
  title: "Request my CV",
  subtitle: "A private, link-only page. Tell me a little about you and I'll email my up-to-date CV.",
  first: "First name", last: "Last name", email: "Email", company: "Company", role: "Role", why: "Why are you interested?"
};
function cvLabels(page, lang) {
  const base = { ...CV_LABELS, ...(page && page.form || {}) };
  if (lang === "fr" && page && page.tr && page.tr.fr && page.tr.fr.form) {
    const fr = page.tr.fr.form;
    Object.keys(base).forEach((k) => {if (fr[k] && String(fr[k]).trim()) base[k] = fr[k];});
  }
  return base;
}

function CVRequestPage({ domain }) {
  const { db, update } = useStore();
  const { go, edit, auth, lang, setLang, prevPath } = useNav();
  const editMode = !!(edit && auth);
  const fr = lang === "fr";
  const acc = accentFor(domain || "datascientist");
  const styleVars = { "--accent": acc.accent, "--accent-ink": acc.ink, "--accent-wash": acc.wash };
  const page = (db.otherPages || []).find((p) => p.id === "cv");
  const [editing, setEditing] = uSs(false);
  const [f, setF] = uSs({ first: "", last: "", email: "", company: "", role: "", why: "" });
  const [sent, setSent] = uSs(false);
  const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
  const L = cvLabels(page, lang);
  const FIELDS = [["title", "Page title"], ["subtitle", "Intro line"], ["first", "First-name field"], ["last", "Last-name field"], ["email", "Email field"], ["company", "Company field"], ["role", "Role field"], ["why", "Why-interested field"]];
  const rawLabel = (k) => lang === "fr" ? page && page.tr && page.tr.fr && page.tr.fr.form && page.tr.fr.form[k] || "" : (page && page.form && page.form[k]) != null ? page.form[k] : CV_LABELS[k];
  const phLabel = (k) => lang === "fr" ? page && page.form && page.form[k] || CV_LABELS[k] : CV_LABELS[k];
  const setLabel = (k, v) => update((d) => {
    const p = (d.otherPages || []).find((x) => x.id === "cv");if (!p) return;
    if (lang === "fr") {p.tr = p.tr || {};p.tr.fr = p.tr.fr || {};p.tr.fr.form = p.tr.fr.form || {};p.tr.fr.form[k] = v;} else
    {p.form = p.form || {};p.form[k] = v;}
  });
  const headerBack = prevPath === "/shared" ?
  { label: fr ? "Contenus partagés" : "Shared content", to: "/shared" } :
  prevPath === "/other" ?
  { label: "All other pages", to: "/other" } :
  { label: fr ? "Retour" : "Back", to: () => {if (prevPath && prevPath !== "/other") go(prevPath);else if (window.history.length > 1) window.history.back();else go("/" + (domain || "datascientist"));} };
  const headerEdit = editMode ? <EditToggle editing={editing} onToggle={() => setEditing((e) => !e)} /> : null;
  const valid = f.first.trim() && f.last.trim() && f.email.trim() && f.company.trim();
  const submit = (e) => {
    e.preventDefault();
    if (!valid) return;
    update((d) => {
      (d.cvRequests = d.cvRequests || []).unshift({ id: uid("cv"), domain: domain || "datascientist", ...f, date: new Date().toISOString().slice(0, 10) });
      d.notifications.unshift({ id: uid("n"), tile: "t1", time: new Date().toISOString().slice(0, 16).replace("T", " "), text: `CV requested by ${f.first} ${f.last} (${f.company}).` });
    });
    setSent(true);
  };

  if (editing) {
    const enOn = !!(page && page.form); // EN always has defaults; "ready" once any override saved is irrelevant — EN baseline exists
    const frOn = !!(page && page.tr && page.tr.fr && page.tr.fr.form && Object.values(page.tr.fr.form).some((v) => v && String(v).trim()));
    return (
      <div style={styleVars}>
        <SectionHeader back={headerBack} editTool={headerEdit} />
        <div className="form-page">
          <div className="form-card surface" style={{ maxWidth: 620 }}>
            <div className="lang-edit-bar" style={{ marginBottom: 16 }}>
              <span className="leb-k">Editing in</span>
              <div className="leb-tabs">
                <button className={lang === "en" ? "on" : ""} onClick={() => setLang("en")}>EN</button>
                <button className={lang === "fr" ? "on" : ""} onClick={() => setLang("fr")}>FR</button>
              </div>
              <span className="grow" />
              <BilingualStatus enOn={true} frOn={frOn} fallback="fallback" editingLang={lang} />
            </div>
            <p className="dim" style={{ fontSize: 13, marginTop: -4, marginBottom: 14 }}>Rename the form's title and each field label. {lang === "fr" ? "Leave a field empty to fall back to its English label." : "These are the English labels (shown by default)."}</p>
            {FIELDS.map(([k, lbl]) =>
            <div key={k} style={{ marginBottom: 12 }}>
                <label>{lbl} <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
                {k === "subtitle" ?
              <textarea className={!String(rawLabel(k)).trim() && phLabel(k) ? "phantom-ph" : ""} rows={2} value={rawLabel(k)} onChange={(e) => setLabel(k, e.target.value)} placeholder={phLabel(k)} /> :
              <input className={!String(rawLabel(k)).trim() && phLabel(k) ? "phantom-ph" : ""} value={rawLabel(k)} onChange={(e) => setLabel(k, e.target.value)} placeholder={phLabel(k)} />}
              </div>
            )}
            <div style={{ marginTop: 18 }}>
              <LangFallbackToggle enOn={enOn} frOn={frOn} fallback={page && page.fallback || "fallback"} onChange={(v) => update((d) => {const p = (d.otherPages || []).find((x) => x.id === "cv");if (p) p.fallback = v;})} />
            </div>
          </div>
        </div>
      </div>);

  }

  return (
    <div style={styleVars}>
      <SectionHeader back={headerBack} editTool={headerEdit} />
      <div className="form-page">
        <div className="form-card surface" style={{ maxWidth: 560 }}>
          <h1 style={{ fontSize: "clamp(30px,4vw,44px)", marginBottom: 8 }}>{L.title}</h1>
          <p className="dim" style={{ marginTop: 0, marginBottom: 22 }}>{L.subtitle}</p>
          {sent ? <Sent title="Request received" body="Thanks! My CV is on its way to your inbox." onBack={() => go("/datascientist")} /> :
          <form className="col gap-16" onSubmit={submit}>
              <div className="field-row">
                <Field label={L.first} required><input value={f.first} onChange={(e) => set("first", e.target.value)} /></Field>
                <Field label={L.last} required><input value={f.last} onChange={(e) => set("last", e.target.value)} /></Field>
              </div>
              <Field label={L.email} required><input type="email" value={f.email} onChange={(e) => set("email", e.target.value)} placeholder="you@company.com" /></Field>
              <Field label={L.company} required hint="Required — who would I be sharing it with?"><input value={f.company} onChange={(e) => set("company", e.target.value)} /></Field>
              <div className="field-row">
                <Field label={L.role}><input value={f.role} onChange={(e) => set("role", e.target.value)} placeholder="Optional" /></Field>
              </div>
              <Field label={L.why}><textarea rows={3} value={f.why} onChange={(e) => set("why", e.target.value)} placeholder="Optional" /></Field>
              <button className="btn accent" type="submit" disabled={!valid} style={{ alignSelf: "flex-start", opacity: valid ? 1 : .5 }}><Icon name="user" size={18} /> {L.title}</button>
            </form>
          }
        </div>
      </div>
    </div>);

}

/* ---------- About me page (editable when authenticated) ---------- */
const ABOUT_SEED = {
  title: "Hi, I'm Cannelle Richter.",
  lead: "Data scientist by trade, maker and alpinist by temperament. I'm fascinated by data, the brain, and the slow craft of getting a little better at things over time.",
  why: "I built this site as one place to gather the different hats I wear — and to keep a public notebook of what I'm learning.\n\nMost portfolios pick a lane. I never could: the same curiosity that pulls me into a dataset pulls me up a ridge line or into a half-finished electronics build. This is where those worlds get to sit next to each other, cross-link, and occasionally borrow each other's tools.\n\nExpect articles, videos and experiments — some polished, some rough. If something here is useful to you, that's the whole point.",
  portrait: null, embed: null, fallback: "fallback",
  tr: { fr: {
      title: "Bonjour, je suis Cannelle Richter.",
      lead: "Data scientist de métier, bricoleuse et alpiniste de tempérament. Je suis fascinée par les données, le cerveau, et l'art patient de progresser un peu, avec le temps.",
      why: "J'ai construit ce site comme un seul endroit où rassembler les différentes casquettes que je porte — et tenir un carnet public de ce que j'apprends.\n\nLa plupart des portfolios choisissent une voie. Je n'ai jamais pu : la même curiosité qui me plonge dans un jeu de données me pousse sur une arête ou dans un montage électronique inachevé. C'est ici que ces mondes se côtoient, se relient et s'empruntent parfois leurs outils.\n\nAttendez-vous à des articles, des vidéos et des expériences — certains aboutis, d'autres bruts. Si quelque chose ici vous est utile, c'est tout l'intérêt."
    } }
};

function AboutMePage() {
  const { db, update } = useStore();
  const { go, auth, edit, lang, setLang } = useNav();
  const canEdit = !!(auth && edit);
  const about = db.about || ABOUT_SEED;
  const KEYS = ["title", "lead", "why"];
  const [editing, setEditing] = uSs(false);
  const [saved, setSaved] = uSs(false);
  const L = objLangFields(about, lang, KEYS);
  const Lraw = objLangRaw(about, lang, KEYS);
  const Lother = objLangRaw(about, lang === "fr" ? "en" : "fr", KEYS);
  const enOn = objHasLang(about, "en", KEYS);
  const frOn = objHasLang(about, "fr", KEYS);
  const otherExists = lang === "fr" ? enOn : frOn;
  const langMissing = !objHasLang(about, lang, KEYS) && about.fallback !== "strict";
  const ensure = (d) => {d.about = d.about || JSON.parse(JSON.stringify(ABOUT_SEED));if (!d.about.tr) d.about.tr = {};return d.about;};
  const setField = (k, v) => update((d) => {const a = ensure(d);setObjLang(a, lang, k, v);});
  const setShared = (k, v) => update((d) => {const a = ensure(d);a[k] = v;});
  const setFallback = (v) => update((d) => {ensure(d).fallback = v;});
  const prefill = () => update((d) => {const a = ensure(d);KEYS.forEach((k) => {const o = objLangRaw(a, lang === "fr" ? "en" : "fr", [k])[k];if (o) setObjLang(a, lang, k, o);});});
  const done = () => {setEditing(false);setSaved(true);setTimeout(() => setSaved(false), 1800);};
  // portrait can be a photo, an image gallery, a video, or an embedded app/page
  const portraitKind = about.portraitKind || (about.embed ? "embed" : about.portraitVideo ? "video" : about.portraitGallery && about.portraitGallery.length ? "gallery" : "photo");
  const setPortraitKind = (k) => update((d) => {ensure(d).portraitKind = k;});
  const aboutGalleryItem = { gallery: about.portraitGallery || [], galleryMode: about.portraitGalleryMode || "arrows", galleryInterval: about.portraitGalleryInterval || 5 };
  const patchAboutGallery = (fn) => update((d) => {const a = ensure(d);const draft = { gallery: a.portraitGallery || [], galleryMode: a.portraitGalleryMode || "arrows", galleryInterval: a.portraitGalleryInterval || 5 };fn(draft);a.portraitGallery = draft.gallery;a.portraitGalleryMode = draft.galleryMode;a.portraitGalleryInterval = draft.galleryInterval;});
  const domains = db.domains.filter((d) => d.display);
  return (
    <div>
      <SectionHeader back editTool={canEdit ? <EditToggle editing={editing} onToggle={() => setEditing((e) => !e)} /> : null} />
      <div className="section-wrap" style={{ maxWidth: 880 }}>
        <div className="row between" style={{ alignItems: "center", margin: "20px 0 6px" }}>
          <div className="mono dim" style={{ fontSize: 12, letterSpacing: ".14em" }}>{lang === "fr" ? "À PROPOS DE MOI" : "ABOUT ME"}</div>
          {saved && <span className="row gap-8" style={{ color: "var(--c-green)", fontWeight: 700, fontFamily: "var(--font-display)" }}><Icon name="check" size={16} /> Saved</span>}
        </div>

        {editing ?
        <div className="surface" style={{ padding: 28, marginTop: 12 }}>
            <div className="lang-edit-bar" style={{ marginBottom: 16 }}>
              <span className="leb-k">Editing in</span>
              <div className="leb-tabs">
                <button className={lang === "en" ? "on" : ""} onClick={() => setLang("en")}>EN</button>
                <button className={lang === "fr" ? "on" : ""} onClick={() => setLang("fr")}>FR</button>
              </div>
              <span className="grow" />
              <BilingualStatus enOn={enOn} frOn={frOn} fallback={about.fallback || "fallback"} editingLang={lang} />
            </div>
            <div className="row" style={{ justifyContent: "flex-end", marginBottom: 12 }}>
              <button className="btn ghost sm" onClick={prefill}><Icon name="globe" size={14} /> Fill {lang === "fr" ? "FR" : "EN"} from {lang === "fr" ? "EN" : "FR"}</button>
            </div>
            <label>Heading <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
            <input className={!Lraw.title.trim() && Lother.title ? "phantom-ph" : ""} value={Lraw.title} onChange={(e) => setField("title", e.target.value)} placeholder={Lother.title} />
            <label style={{ marginTop: 14 }}>Lead <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
            <textarea className={!Lraw.lead.trim() && Lother.lead ? "phantom-ph" : ""} rows={3} value={Lraw.lead} onChange={(e) => setField("lead", e.target.value)} placeholder={Lother.lead} />
            <label style={{ marginTop: 14 }}>Body <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
            <textarea className={!Lraw.why.trim() && Lother.why ? "phantom-ph" : ""} rows={10} value={Lraw.why} onChange={(e) => setField("why", e.target.value)} style={{ lineHeight: 1.6 }} placeholder={Lother.why} />
            <label style={{ marginTop: 14 }}>Portrait <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>shared across languages — a photo, an image gallery, a video, or an embedded app</span></label>
            <div className="row gap-8 wrap" style={{ marginBottom: 10 }}>
              {[{ id: "photo", l: "Photo" }, { id: "gallery", l: "Image gallery" }, { id: "video", l: "Video" }, { id: "embed", l: "Embed app" }].map((o) =>
            <button type="button" key={o.id} className={"chip" + (portraitKind === o.id ? " on" : "")} onClick={() => setPortraitKind(o.id)}>{o.l}</button>
            )}
            </div>
            {portraitKind === "photo" &&
          <ImageUpload value={about.portrait} onChange={(v) => {setShared("portrait", v);}} label="upload portrait photo" aspect="4/5" />
          }
            {portraitKind === "gallery" && window.ImageGalleryEditor &&
          <window.ImageGalleryEditor item={aboutGalleryItem} onPatch={patchAboutGallery} />
          }
            {portraitKind === "video" &&
          <div className="col gap-8">
                <input className="mono" value={about.portraitVideo || ""} onChange={(e) => setShared("portraitVideo", e.target.value)} placeholder="Video URL — .mp4 / .webm or an embeddable link" />
                <label className="btn ghost sm" style={{ cursor: "pointer", alignSelf: "flex-start" }}>
                  <Icon name="save" size={13} /> Upload a video file
                  <input type="file" accept="video/*" style={{ display: "none" }} onChange={(e) => {const f = e.target.files && e.target.files[0];if (!f) return;const r = new FileReader();r.onload = () => setShared("portraitVideo", r.result);r.readAsDataURL(f);}} />
                </label>
              </div>
          }
            {portraitKind === "embed" &&
          <input className="mono" value={about.embed || ""} onChange={(e) => setShared("embed", e.target.value)} placeholder="embed an app / page: https://…" />
          }
            <div style={{ marginTop: 18 }}>
              <LangFallbackToggle enOn={enOn} frOn={frOn} fallback={about.fallback || "fallback"} onChange={setFallback} />
            </div>
            <div className="row gap-8" style={{ justifyContent: "flex-end", marginTop: 18 }}>
              <button className="btn accent" onClick={done}><Icon name="check" size={16} /> Done</button>
            </div>
          </div> :

        <>
            {langMissing && lang === "fr" && <div className="trans-notice"><Icon name="globe" size={15} /> Pas encore traduit — version anglaise affichée.</div>}
            <div className="aboutme-hero">
              <div>
                <h1 style={{ lineHeight: 1.04, marginBottom: 18 }}>
                  <span style={{ display: "block", fontSize: "clamp(15px,1.8vw,20px)", fontWeight: 700, fontFamily: "var(--font-mono)", color: "var(--muted)", letterSpacing: "0", marginBottom: 8 }}>{lang === "fr" ? "Bonjour, je suis" : "Hi, I'm"}</span>
                  <span style={{ display: "block", fontSize: "clamp(32px,4.6vw,54px)" }}>{L.title.replace(/^(Hi, I'?m|Bonjour, je suis)\s*/i, "")}</span>
                </h1>
                <p style={{ fontSize: "clamp(17px,2vw,21px)", color: "var(--ink-soft)", lineHeight: 1.5, fontWeight: 500 }}>{L.lead}</p>
              </div>
              {portraitKind === "embed" && about.embed ?
            <iframe className="about-embed" src={about.embed} title="about" loading="lazy" style={{ width: "100%", aspectRatio: "4/5", border: "1.5px solid var(--line)", borderRadius: "var(--r-md)", background: "#fff" }} /> :
            portraitKind === "video" && about.portraitVideo ?
            /\.(mp4|webm|ogg)(\?|$)/i.test(about.portraitVideo) || /^data:video/.test(about.portraitVideo) ?
            <video src={about.portraitVideo} controls style={{ width: "100%", aspectRatio: "4/5", objectFit: "cover", borderRadius: "var(--r-md)", border: "1.5px solid var(--line)", background: "#000" }} /> :
            <iframe className="about-embed" src={about.portraitVideo} title="about video" loading="lazy" allowFullScreen style={{ width: "100%", aspectRatio: "4/5", border: "1.5px solid var(--line)", borderRadius: "var(--r-md)", background: "#000" }} /> :
            portraitKind === "gallery" && about.portraitGallery && about.portraitGallery.length && window.ImageGalleryReader ?
            <div className="about-gallery"><window.ImageGalleryReader item={aboutGalleryItem} /></div> :
            about.portrait ?
            <img src={about.portrait} alt="portrait" style={{ width: "100%", aspectRatio: "4/5", objectFit: "cover", borderRadius: "var(--r-md)", border: "1.5px solid var(--line)" }} /> :
            <Placeholder label="portrait photo" tag="replaceable" style={{ width: "100%", aspectRatio: "4/5" }} />}
            </div>

            <div className="article-body" style={{ marginTop: 34, maxWidth: 720 }}>
              <h3 style={{ fontSize: 24, marginBottom: 12 }}>{lang === "fr" ? "Pourquoi ce site" : "Why this site"}</h3>
              <p style={{ whiteSpace: "pre-wrap" }}>{L.why}</p>
            </div>

            <div style={{ marginTop: 40 }}>
              <div className="mono dim" style={{ fontSize: 12, letterSpacing: ".12em", marginBottom: 14 }}>{lang === "fr" ? "MES CASQUETTES" : "THE HATS I WEAR"}</div>
              <div className="content-grid">
                {domains.map((d) =>
              <a key={d.id} className="ccard" onClick={() => go(d.route)} style={{ padding: 18, gap: 8 }}>
                    <div className="row gap-8" style={{ alignItems: "center" }}>
                      <span style={{ width: 14, height: 14, borderRadius: 4, background: accentFor(d.id).accent, flex: "none" }} />
                      <span style={{ fontFamily: "var(--font-display)", fontWeight: 800, fontSize: 19 }}>{d.label}</span>
                    </div>
                    <div className="cc-desc">{domLangFields(d, lang).presBody.slice(0, 96)}…</div>
                    <div className="row gap-8" style={{ color: accentFor(d.id).ink, fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 13 }}>{lang === "fr" ? "Visiter" : "Visit"} <Icon name="arrow" size={14} /></div>
                  </a>
              )}
              </div>
            </div>

            <div className="readnext" style={{ marginTop: 40 }} onClick={() => go("/contact")}>
              <div>
                <div className="mono dim" style={{ fontSize: 12, letterSpacing: ".12em" }}>{lang === "fr" ? "DITES BONJOUR" : "SAY HELLO"}</div>
                <div className="rn-title">{lang === "fr" ? "Me contacter" : "Get in touch"}</div>
              </div>
              <span className="rn-arrow"><Icon name="arrow" size={26} /></span>
            </div>
          </>
        }
      </div>
    </div>);

}

/* ---------- "Other" hub — edit-mode index of created pages ---------- */
function OtherIndex() {
  const { db, update } = useStore();
  const { go, edit, auth, lang, role, isAdmin, reviewMode, publishMode, testerMode, session } = useNav();
  const editMode = !!(edit && auth);
  const collabEdit = role === "editor" && edit;
  const collabReview = !!reviewMode;
  const collabPublish = !!publishMode;
  const collabTester = !!testerMode;
  const [pubModal, setPubModal] = uSs(null);
  const visibleOther = (pg) => {
    if (isAdmin && edit) return true;
    if (collabEdit) return itemActionable(db, pg, "draft", session, role, false);
    if (collabReview) return itemActionable(db, pg, "review", session, role, false);
    if (collabPublish) return itemActionable(db, pg, "validated", session, role, false);
    if (collabTester) return ["en", "fr"].some((lg) => ["review", "validated", "published"].includes(statusOfLang(pg, lg)) && pageHasLang(pg, lg));
    return pageVisibleInLang(pg, lang) && canSeeStatus(role, statusOfLang(pg, lang));
  };
  const doPublishPage = (whenDate, mode, whenTime) => {
    if (!pubModal) return;
    const todayStr = new Date().toISOString().slice(0, 10);
    const future = mode === "schedule" && whenDate > todayStr;
    update((d) => {
      const p = (d.otherPages || []).find((x) => x.id === pubModal.id);if (!p) return;
      p.statusByLang = p.statusByLang || {};
      if (future) {p.publishAt = whenDate;p.publishAtTime = whenTime || "06:00";} else
      {
        ["en", "fr"].forEach((lg) => {if (p.statusByLang[lg] === "validated") p.statusByLang[lg] = "published";});
        p.published = Object.values(p.statusByLang).some((v) => v === "published");
        p.status = p.statusByLang.en || p.status;p.publishAt = "";p.publishAtTime = "";
      }
    });
    setPubModal(null);
  };
  const acc = accentFor("datascientist");
  const styleVars = { "--accent": acc.accent, "--accent-ink": acc.ink, "--accent-wash": acc.wash };
  const pages = db.otherPages || [];
  const addPage = () => {
    const id = uid("pg");
    update((d) => (d.otherPages = d.otherPages || []).push({ id, label: "New page", route: "/other/" + id, kind: "page", desc: "A dynamic page — code, form, survey or mini-app.", html: "" }));
    go("/other/" + id);
  };
  return (
    <div style={styleVars}>
      <SectionHeader back />
      <div className="section-wrap" style={{ maxWidth: 980 }}>
        <div className="section-head" style={{ margin: "26px 0 26px" }}>
          <div>
            <div className="mono dim" style={{ fontSize: 12, letterSpacing: ".14em", marginBottom: 6 }}>OTHER · CREATED PAGES</div>
            <h1 style={{ fontSize: "clamp(30px,4.2vw,48px)", marginBottom: 16, whiteSpace: "nowrap" }}><Scribble>Other pages</Scribble></h1>
          </div>
          {editMode && <button className="btn accent sm" onClick={addPage}><Icon name="plus" size={15} /> New page</button>}
        </div>
        <p className="dim" style={{ maxWidth: 620, marginBottom: 26 }}>
          Dynamic pages — a bit of code, a form, a reader survey or a mini-app. They are <b>not</b> shown in the public menu;
          each one gives you an <b>embeddable URL</b> you can drop into a blog article or onto a domain illustration.
        </p>
        <div className="content-grid">
          {pages.filter((pg) => visibleOther(pg)).map((pg) => {
            const pl = pageLangFields(pg, lang);
            return (
              <a key={pg.id} className="ccard" onClick={() => go(pg.route)} style={{ padding: 18, gap: 10 }}>
              <div className="row gap-8" style={{ alignItems: "center", flexWrap: "wrap" }}>
                <span className="logo" style={{ width: 30, height: 30, borderRadius: 9, background: pg.kind === "secret" ? "var(--c-violet)" : "var(--accent)", color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", flex: "none" }}><Icon name={pg.kind === "secret" ? "lock" : "compass"} size={15} /></span>
                <span style={{ fontFamily: "var(--font-display)", fontWeight: 800, fontSize: 18 }}>{pl.label}</span>
                <span className="grow" />
                {(editMode || collabEdit || collabReview || collabPublish) && <LangWfBadges item={pg} relevant={collabReview ? "review" : collabPublish ? "validated" : collabEdit ? "draft" : null} />}
              </div>
              <div className="cc-desc">{pl.desc || "—"}</div>
              <div className="row between" style={{ marginTop: "auto" }}>
                <span className="mono dim" style={{ fontSize: 11 }}>{pg.route}</span>
                {collabPublish && ["en", "fr"].some((lg) => statusOfLang(pg, lg) === "validated" && pageHasLang(pg, lg)) && pg.kind !== "secret" ?
                  <button className="btn accent sm" onClick={(e) => {e.stopPropagation();setPubModal(pg);}}><Icon name="check" size={13} /> {lang === "fr" ? "Publier" : "Publish"}</button> :
                  <span className="row gap-8" style={{ color: "var(--accent-ink)", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 13 }}>{editMode ? "Edit" : "Open"} <Icon name="arrow" size={14} /></span>}
              </div>
            </a>);

          })}
        </div>
      </div>
      {pubModal && window.PublishModal && <window.PublishModal item={pubModal} isPage onClose={() => setPubModal(null)} onConfirm={doPublishPage} />}
    </div>);

}

/* ---------- "Other" pages: secret / survey / game / custom ---------- */
function OtherPage({ which }) {
  const { db, update } = useStore();
  const { go, edit, auth, lang, setLang, prevPath, role, reviewMode, isAdmin, session } = useNav();
  const editMode = !!(edit && auth);
  const fr = lang === "fr";
  const acc = accentFor("datascientist");
  const styleVars = { "--accent": acc.accent, "--accent-ink": acc.ink, "--accent-wash": acc.wash };
  // "All other pages" only makes sense when the visitor actually arrived from the
  // /other hub. Reached through an integration (article link, domain bubble, embed),
  // show a plain Back instead.
  const cameFromOther = prevPath === "/other";
  const backToList = { label: "All other pages", to: "/other" };
  const backShared = { label: fr ? "Contenus partagés" : "Shared content", to: "/shared" };
  const backGeneric = { label: fr ? "Retour" : "Back", to: () => {if (prevPath && prevPath !== "/other") go(prevPath);else if (window.history.length > 1) window.history.back();else go(db.profile.defaultRoute);} };
  const headerBackCfg = prevPath === "/shared" ? backShared : cameFromOther ? backToList : backGeneric;
  const page = (db.otherPages || []).find((p) => p.id === which);
  const [editing, setEditing] = uSs(false);
  const patch = (fn) => update((d) => {const p = (d.otherPages || []).find((x) => x.id === which);if (p) fn(p);});
  // language-specific page fields (label/desc/html): EN = base, FR = tr.fr
  const setPL = (key, value) => update((d) => {
    const pg = (d.otherPages || []).find((x) => x.id === which);if (!pg) return;
    if (lang === "fr") {pg.tr = pg.tr || {};pg.tr.fr = pg.tr.fr || {};pg.tr.fr[key] = value;} else
    pg[key] = value;
  });

  if (which === "secret") {
    const unlocked = !!db.secretUnlocked;
    const plRaw = pageLangFieldsRaw(page, lang);
    const plOther = pageLangFieldsRaw(page, lang === "fr" ? "en" : "fr");
    const secretBack = headerBackCfg;
    const secretEdit = editMode ? <EditToggle editing={editing} onToggle={() => setEditing((e) => !e)} /> : null;
    return (
      <div style={styleVars}>
        <SectionHeader back={secretBack} editTool={secretEdit} />
        <div className="section-wrap" style={{ maxWidth: 720 }}>
          {editing ?
          <div className="surface" style={{ padding: 26 }}>
              <div className="lang-edit-bar" style={{ marginBottom: 16 }}>
                <span className="leb-k">Editing in</span>
                <div className="leb-tabs">
                  <button className={lang === "en" ? "on" : ""} onClick={() => setLang("en")}>EN</button>
                  <button className={lang === "fr" ? "on" : ""} onClick={() => setLang("fr")}>FR</button>
                </div>
                <span className="grow" />
                <BilingualStatus enOn={pageHasLang(page, "en")} frOn={pageHasLang(page, "fr")} fallback={page.fallback || "en"} editingLang={lang} />
              </div>
              <label>Title <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
              <input className={!plRaw.label.trim() && plOther.label ? "phantom-ph" : ""} value={plRaw.label} onChange={(e) => setPL("label", e.target.value)} placeholder={plOther.label || "Title"} />
              {!plRaw.label.trim() && <div className="miss-note"><Icon name="bell" size={12} /> Title required — missing content in {lang === "fr" ? "FR" : "EN"}</div>}
              <label style={{ marginTop: 14 }}>Description <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
              <textarea className={!plRaw.desc.trim() && plOther.desc ? "phantom-ph" : ""} rows={2} value={plRaw.desc} onChange={(e) => setPL("desc", e.target.value)} placeholder={plOther.desc || ""} />
              <label style={{ marginTop: 14 }}>Route <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>shared</span></label>
              <input className="mono" value={page.route} onChange={(e) => patch((p) => p.route = e.target.value)} />
              <div className="row between" style={{ marginTop: 16 }}>
                <span style={{ fontSize: 13, fontWeight: 600, fontFamily: "var(--font-display)", color: page.published === false ? "#b56b08" : "var(--c-green)" }}>{page.published === false ? "Draft — hidden" : "Published — live"}</span>
                <button className={"toggle" + (page.published !== false ? " on" : "")} onClick={() => patch((p) => p.published = !(p.published !== false))} />
              </div>
            </div> :

          <div className="secret-page surface">
            {unlocked ?
            <>
                <div className="mono" style={{ color: "var(--accent)", letterSpacing: ".18em", fontSize: 12, marginBottom: 14 }}>CANNELLE’S SECRET · UNLOCKED</div>
                <h1 style={{ fontSize: "clamp(30px,5vw,52px)", lineHeight: 1.05 }}>You really thought it would be that simple?</h1>
                <p style={{ fontSize: "clamp(17px,2vw,21px)", color: "var(--ink-soft)", lineHeight: 1.6, marginTop: 18 }}>
                  Solving one little median puzzle and the door swings open? Cute. I expect a lot more from you than that.
                </p>
                <p className="dim" style={{ marginTop: 14 }}>Come back when you’ve earned the next clue. — C.</p>
                <div style={{ marginTop: 26 }}><button className="btn ghost sm" onClick={() => go("/datascientist")}><Icon name="left" size={15} /> Back to the data</button></div>
              </> :

            <div className="center col gap-12" style={{ textAlign: "center", padding: "30px 0" }}>
                <span className="iconbtn" style={{ width: 58, height: 58, borderRadius: "50%", color: "var(--muted)" }}><Icon name="lock" size={26} /></span>
                <h1 style={{ fontSize: 30 }}>Still locked</h1>
                <p className="dim" style={{ maxWidth: 380 }}>Solve the puzzle hidden in the data illustration on the home page to open this.</p>
                <button className="btn accent" onClick={() => go("/datascientist")} style={{ marginTop: 6 }}><Icon name="arrow" size={16} /> Take me to the puzzle</button>
              </div>
            }
          </div>
          }
        </div>
      </div>);

  }

  const embedUrl = page ? `cannellerichter.fr/#${page.route}` : "";
  const pl = page ? pageLangFields(page, lang) : { label: "Page", desc: "", html: "" };
  const plRaw = page ? pageLangFieldsRaw(page, lang) : { label: "", desc: "", html: "" };
  const plOther = page ? pageLangFieldsRaw(page, lang === "fr" ? "en" : "fr") : { label: "", desc: "", html: "" };
  const enOn = page ? pageHasLang(page, "en") : false;
  const frOn = page ? pageHasLang(page, "fr") : false;
  const otherCode = lang === "fr" ? "EN" : "FR";
  const langMissing = page ? !pageHasLang(page, lang) && pageVisibleInLang(page, lang) : false;
  // header tools — fixed on the site, not part of the page content
  const otherBack = headerBackCfg;
  const otherEdit = editMode ? <EditToggle editing={editing} onToggle={() => setEditing((e) => !e)} /> : null;

  // public visitor reaching a strict page in a missing language
  if (page && !editMode && lang === "fr" && !pageVisibleInLang(page, "fr")) {
    return (
      <div style={styleVars}>
        <SectionHeader />
        <div className="section-wrap" style={{ maxWidth: 620, paddingTop: 56 }}>
          <div className="surface notinlang">
            <span className="nil-ic"><Icon name="globe" size={28} /></span>
            <h1 style={{ fontSize: "clamp(26px,4vw,38px)", lineHeight: 1.08 }}>Pas disponible en français</h1>
            <p className="dim" style={{ maxWidth: 420, margin: "10px auto 0" }}>Cette page n'a pas encore été traduite.</p>
            <div className="row gap-8" style={{ justifyContent: "center", marginTop: 18, flexWrap: "wrap" }}>
              <button className="btn" onClick={() => go("/")}><Icon name="left" size={16} /> Back to site</button>
              <button className="btn accent" onClick={() => setLang("en")}><Icon name="globe" size={16} /> Read in EN</button>
            </div>
          </div>
        </div>
      </div>);

  }

  // status gate — an unpublished page is only public to those allowed to see its
  // workflow status (testers/admin for pre-released) or to whom it's been shared.
  const grantedHere = !!(session && (session.grants || []).includes(which));
  if (page && !editMode && !reviewMode && !isAdmin && !grantedHere && !canSeeStatus(role, statusOfLang(page, lang))) {
    return (
      <div style={styleVars}>
        <SectionHeader back={headerBackCfg} />
        <div className="section-wrap" style={{ maxWidth: 620, paddingTop: 56 }}>
          <div className="surface notinlang">
            <span className="nil-ic"><Icon name="lock" size={26} /></span>
            <h1 style={{ fontSize: "clamp(26px,4vw,38px)", lineHeight: 1.08 }}>{fr ? "Pas encore disponible" : "Not available yet"}</h1>
            <p className="dim" style={{ maxWidth: 420, margin: "10px auto 0" }}>{fr ? "Cette page n'est pas encore publi\u00e9e." : "This page isn't published yet."}</p>
            <div className="row gap-8" style={{ justifyContent: "center", marginTop: 18 }}>
              <button className="btn" onClick={() => go(db.profile.defaultRoute)}><Icon name="left" size={16} /> {fr ? "Retour au site" : "Back to site"}</button>
            </div>
          </div>
        </div>
      </div>);

  }

  return (
    <div style={styleVars}>
      <SectionHeader back={otherBack} editTool={otherEdit} />
      <div className="section-wrap" style={{ maxWidth: 880 }}>
        <div className="row between" style={{ alignItems: "center", margin: "18px 0 6px" }}>
          <div className="mono dim" style={{ fontSize: 12, letterSpacing: ".14em" }}>OTHER · {(pl.label || which).toUpperCase()}</div>
        </div>

        {editing ?
        <div className="surface" style={{ padding: 26, marginTop: 10 }}>
            {/* which language am I editing + symmetric status */}
            <div className="lang-edit-bar" style={{ marginBottom: 16 }}>
              <span className="leb-k">Editing in</span>
              <div className="leb-tabs">
                <button className={lang === "en" ? "on" : ""} onClick={() => setLang("en")}>EN</button>
                <button className={lang === "fr" ? "on" : ""} onClick={() => setLang("fr")}>FR</button>
              </div>
              <span className="grow" />
              <BilingualStatus enOn={enOn} frOn={frOn} fallback={page.fallback || "en"} editingLang={lang} />
            </div>

            <div className="row between" style={{ marginBottom: 16, paddingBottom: 14, borderBottom: "1px dashed var(--line)" }}>
              <span style={{ fontSize: 13, fontWeight: 600, fontFamily: "var(--font-display)", color: page.published === false ? "#b56b08" : "var(--c-green)" }}>{page.published === false ? "Draft — hidden" : "Published — live"}</span>
              <button className={"toggle" + (page.published !== false ? " on" : "")} onClick={() => patch((p) => p.published = !(p.published !== false))} />
            </div>

            <label>Title <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
            <input className={!plRaw.label.trim() && plOther.label ? "phantom-ph" : ""} value={plRaw.label} onChange={(e) => setPL("label", e.target.value)} placeholder={plOther.label || (lang === "fr" ? "Titre (FR)" : "Title (EN)")} />
            {!plRaw.label.trim() && <div className="miss-note"><Icon name="bell" size={12} /> Title required — missing content in {lang === "fr" ? "FR" : "EN"}</div>}
            <label style={{ marginTop: 14 }}>Description <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR" : "EN"}</span></label>
            <textarea className={!plRaw.desc.trim() && plOther.desc ? "phantom-ph" : ""} rows={2} value={plRaw.desc} onChange={(e) => setPL("desc", e.target.value)} placeholder={plOther.desc || ""} />
            <label style={{ marginTop: 14 }}>Route <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>shared · this page's address</span></label>
            <input className="mono" value={page.route} onChange={(e) => patch((p) => p.route = e.target.value)} />
            <label style={{ marginTop: 14 }}>Dynamic content <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>{lang === "fr" ? "FR · " : "EN · "}code / app / form / survey — paste any HTML or an &lt;iframe&gt;</span></label>
            <textarea className={!plRaw.html.trim() && plOther.html ? "phantom-ph" : ""} rows={6} style={{ fontSize: 12 }} value={plRaw.html} onChange={(e) => setPL("html", e.target.value)} placeholder={plOther.html || "<iframe src='…'></iframe>  or  <form>…</form>  or  any HTML / SVG"} />

            {/* language fallback behavior — symmetric */}
            <div style={{ marginTop: 18 }}>
              <LangFallbackToggle enOn={pageHasLang(page, "en")} frOn={pageHasLang(page, "fr")} fallback={page.fallback || "fallback"} onChange={(v) => patch((p) => p.fallback = v)} />
            </div>

            <div className="src-note" style={{ marginTop: 16 }}>
              <div className="mono dim" style={{ fontSize: 11, marginBottom: 4 }}>EMBEDDABLE URL — drop into a blog article or a domain illustration</div>
              <div className="mono" style={{ fontSize: 13, color: "var(--accent-ink)", wordBreak: "break-all" }}>{embedUrl}</div>
            </div>
          </div> :

        <>
            {langMissing &&
          <div className="trans-notice"><Icon name="globe" size={15} /> {lang === "fr" ?
            "Désolé, le contenu n'a pas encore été traduit — version anglaise affichée." :
            "This hasn't been translated to English yet — showing the French version."}</div>
          }
            <h1 style={{ fontSize: "clamp(30px,5vw,52px)", marginTop: 6 }}>{pl.label || "Page"}</h1>
            <p className="dim" style={{ marginTop: 10 }}>{pl.desc || "This page is part of the \u201cOther\u201d collection."}</p>
            <div className="surface" style={{ padding: pl.html ? 0 : 30, marginTop: 20, overflow: "hidden" }}>
              {pl.html ?
            <div className="other-embed" dangerouslySetInnerHTML={{ __html: pl.html }} /> :
            <Placeholder label={which === "game" ? "interactive mini-game" : which === "survey" ? "reader survey form" : "custom page content"} tag="add dynamic content in edit mode" style={{ width: "100%", height: 220 }} />}
            </div>
          </>
        }
      </div>
    </div>);

}

Object.assign(window, { SectionPage, SectionHeader, LegalPage, ContactPage, FeedbackPage, CVRequestPage, AboutMePage, OtherPage, OtherIndex, ABOUT_SEED, TagPicker });