<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8" />

  <title>Art Book Builder Dashboard</title>

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <style>

    :root {

      --bg: #f5f2ec;

      --text: #1a1a1a;

      --accent: #a38b4d;

      --accent-dark: #4a2f1b;

      --border: #c7c2ba;

      --muted: #8a8478;

      --danger: #b14a3c;

      --radius: 6px;

    }

    * {

      box-sizing: border-box;

      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

    }

    body {

      margin: 0;

      background: var(--bg);

      color: var(--text);

    }

    header {

      padding: 12px 16px;

      border-bottom: 1px solid var(--border);

      background: #fdfbf8;

      position: sticky;

      top: 0;

      z-index: 10;

    }

    header h1 {

      margin: 0;

      font-size: 20px;

      letter-spacing: 0.04em;

    }

    header p {

      margin: 4px 0 0;

      font-size: 12px;

      color: var(--muted);

    }

    .app {

      display: flex;

      flex-direction: row;

      min-height: calc(100vh - 60px);

    }

    @media (max-width: 800px) {

      .app {

        flex-direction: column;

      }

    }

    .sidebar {

      width: 260px;

      padding: 12px;

      border-right: 1px solid var(--border);

      background: #faf7f2;

    }

    @media (max-width: 800px) {

      .sidebar {

        width: 100%;

        border-right: none;

        border-bottom: 1px solid var(--border);

      }

    }

    .main {

      flex: 1;

      padding: 12px;

    }

    h2 {

      margin: 8px 0;

      font-size: 14px;

      text-transform: uppercase;

      letter-spacing: 0.12em;

      color: var(--accent-dark);

    }

    .small-label {

      font-size: 11px;

      text-transform: uppercase;

      letter-spacing: 0.14em;

      color: var(--muted);

      margin-bottom: 4px;

    }

    button, select, input, textarea {

      font-size: 13px;

    }

    button {

      border-radius: var(--radius);

      border: 1px solid var(--accent);

      background: var(--accent);

      color: #fdfbf8;

      padding: 6px 10px;

      cursor: pointer;

    }

    button.secondary {

      background: transparent;

      color: var(--accent-dark);

      border-color: var(--border);

    }

    button.danger {

      border-color: var(--danger);

      background: transparent;

      color: var(--danger);

    }

    button:disabled {

      opacity: 0.6;

      cursor: default;

    }

    input[type="text"], input[type="number"], textarea, select {

      width: 100%;

      border-radius: var(--radius);

      border: 1px solid var(--border);

      padding: 5px 8px;

      background: #fdfbf8;

      color: var(--text);

    }

    textarea {

      resize: vertical;

      min-height: 60px;

    }

    .section-list {

      max-height: 350px;

      overflow-y: auto;

      padding-right: 4px;

    }

    .section-item {

      border-radius: var(--radius);

      border: 1px solid var(--border);

      padding: 6px 8px;

      margin-bottom: 6px;

      background: white;

      cursor: pointer;

    }

    .section-item.active {

      border-color: var(--accent);

      box-shadow: 0 0 0 1px rgba(163, 139, 77, 0.2);

    }

    .section-title {

      font-size: 13px;

      font-weight: 600;

    }

    .section-meta {

      font-size: 11px;

      color: var(--muted);

      margin-top: 2px;

    }

    .pill {

      display: inline-block;

      padding: 1px 6px;

      border-radius: 999px;

      font-size: 10px;

      border: 1px solid var(--border);

      margin-right: 4px;

    }

    .pill.status-done {

      border-color: #4c8c52;

      color: #2d6a34;

    }

    .pill.status-inprogress {

      border-color: #d08b2e;

      color: #a46a1b;

    }

    .pill.status-notstarted {

      border-color: var(--border);

      color: var(--muted);

    }

    .toolbar {

      display: flex;

      gap: 8px;

      margin-bottom: 8px;

      flex-wrap: wrap;

      align-items: center;

    }

    .toolbar span.hint {

      font-size: 11px;

      color: var(--muted);

    }

    .card {

      border-radius: var(--radius);

      border: 1px solid var(--border);

      padding: 8px;

      background: #fdfbf8;

      margin-bottom: 10px;

    }

    .card-header {

      display: flex;

      align-items: center;

      justify-content: space-between;

      margin-bottom: 4px;

    }

    .card-title {

      font-size: 13px;

      font-weight: 600;

    }

    .field {

      margin-bottom: 6px;

    }

    .field label {

      font-size: 11px;

      text-transform: uppercase;

      letter-spacing: 0.12em;

      color: var(--muted);

      display: block;

      margin-bottom: 3px;

    }

    .columns {

      display: flex;

      gap: 10px;

      flex-wrap: wrap;

    }

    .col-2 {

      flex: 1 1 200px;

    }

    .badge {

      font-size: 10px;

      padding: 1px 6px;

      border-radius: 999px;

      background: #eee4d7;

      color: var(--accent-dark);

    }

    .page-list {

      margin-top: 8px;

    }

    .page-item {

      border-radius: var(--radius);

      border: 1px solid var(--border);

      padding: 6px;

      background: white;

      margin-bottom: 6px;

    }

    .page-top {

      display: flex;

      justify-content: space-between;

      align-items: center;

      flex-wrap: wrap;

      gap: 4px;

    }

    .page-meta {

      font-size: 11px;

      color: var(--muted);

      margin-top: 2px;

    }

    .status-select {

      width: auto;

      min-width: 110px;

    }

    .tag {

      display: inline-block;

      font-size: 10px;

      color: var(--muted);

      border-radius: 999px;

      border: 1px solid var(--border);

      padding: 1px 6px;

      margin-right: 4px;

      margin-top: 2px;

    }

    .summary {

      font-size: 11px;

      color: var(--muted);

      margin-top: 4px;

    }

    .divider {

      border-top: 1px dashed var(--border);

      margin: 8px 0;

    }

    .topbar {

      display: flex;

      justify-content: space-between;

      align-items: center;

      gap: 8px;

      flex-wrap: wrap;

      margin-bottom: 8px;

    }

    .topbar-left {

      display: flex;

      flex-direction: column;

      gap: 2px;

    }

    .topbar-left span {

      font-size: 11px;

      color: var(--muted);

    }

    .chip-group {

      display: flex;

      flex-wrap: wrap;

      gap: 4px;

      margin-top: 4px;

    }

    .chip {

      border-radius: 999px;

      border: 1px solid var(--border);

      padding: 2px 8px;

      font-size: 11px;

      background: white;

      cursor: pointer;

    }

    .chip.active {

      border-color: var(--accent);

      background: #f3ece0;

    }

  </style>

</head>

<body>

<header>

  <h1>Art Book Builder</h1>

  <p>Plan sections, assign pages, and track your mythic art book from concept to print.</p>

</header>

 

<div class="app">

  <aside class="sidebar">

    <div class="toolbar">

      <button id="addSectionBtn">+ New Section</button>

      <button class="secondary" id="resetDataBtn">Reset All</button>

    </div>

    <div class="small-label">Sections</div>

    <div id="sectionList" class="section-list"></div>

  </aside>

 

  <main class="main">

    <div class="topbar">

      <div class="topbar-left">

        <strong style="font-size:13px;">Section Detail & Page Planner</strong>

        <span id="sectionHint">Select a section on the left or create a new one.</span>

      </div>

      <div>

        <button class="secondary" id="exportJsonBtn">Export JSON</button>

      </div>

    </div>

 

    <div id="sectionDetail"></div>

  </main>

</div>

 

<script>

  // ---- Data Model ----

  const STORAGE_KEY = "artbook_dashboard_data_v1";

 

  let state = {

    sections: [],

    selectedSectionId: null

  };

 

  function loadState() {

    try {

      const saved = localStorage.getItem(STORAGE_KEY);

      if (saved) {

        state = JSON.parse(saved);

      }

    } catch (e) {

      console.warn("Could not load saved state", e);

    }

    render();

  }

 

  function saveState() {

    localStorage.setItem(STORAGE_KEY, JSON.stringify(state));

  }

 

  function createSection() {

    const id = "sec_" + Date.now();

    const section = {

      id,

      title: "New Section",

      subtitle: "",

      type: "content", // content / frontmatter / backmatter

      order: state.sections.length + 1,

      notes: "",

      status: "notstarted", // notstarted / inprogress / done

      pages: []

    };

    state.sections.push(section);

    state.selectedSectionId = id;

    saveState();

    render();

  }

 

  function deleteSection(id) {

    if (!confirm("Delete this section and all its pages?")) return;

    state.sections = state.sections.filter(s => s.id !== id);

    if (state.selectedSectionId === id) {

      state.selectedSectionId = state.sections[0]?.id || null;

    }

    saveState();

    render();

  }

 

  function updateSection(id, updates) {

    const idx = state.sections.findIndex(s => s.id === id);

    if (idx === -1) return;

    state.sections[idx] = { ...state.sections[idx], ...updates };

    saveState();

    render();

  }

 

  function addPageToSection(sectionId, presetType) {

    const section = state.sections.find(s => s.id === sectionId);

    if (!section) return;

    const page = {

      id: "pg_" + Date.now() + "_" + Math.floor(Math.random() * 1000),

      pageNumber: "", // you can assign later

      layoutType: presetType, // "full-art", "diptych", etc.

      title: "",

      contentRef: "",

      notes: "",

      status: "notstarted"

    };

    section.pages.push(page);

    saveState();

    render();

  }

 

  function updatePage(sectionId, pageId, updates) {

    const section = state.sections.find(s => s.id === sectionId);

    if (!section) return;

    const idx = section.pages.findIndex(p => p.id === pageId);

    if (idx === -1) return;

    section.pages[idx] = { ...section.pages[idx], ...updates };

    saveState();

    render();

  }

 

  function deletePage(sectionId, pageId) {

    const section = state.sections.find(s => s.id === sectionId);

    if (!section) return;

    section.pages = section.pages.filter(p => p.id !== pageId);

    saveState();

    render();

  }

 

  function resetAll() {

    if (!confirm("This will erase all sections and pages. Continue?")) return;

    state = { sections: [], selectedSectionId: null };

    saveState();

    render();

  }

 

  function exportJson() {

    const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(state, null, 2));

    const a = document.createElement("a");

    a.setAttribute("href", dataStr);

    a.setAttribute("download", "artbook-dashboard-export.json");

    document.body.appendChild(a);

    a.click();

    a.remove();

  }

 

  // ---- Rendering ----

 

  function render() {

    renderSectionList();

    renderSectionDetail();

  }

 

  function renderSectionList() {

    const container = document.getElementById("sectionList");

    container.innerHTML = "";

    const sections = [...state.sections].sort((a, b) => a.order - b.order);

 

    if (!sections.length) {

      container.innerHTML = `<div style="font-size:12px;color:var(--muted);margin-top:6px;">

        No sections yet. Click <strong>+ New Section</strong> to start planning your book.

      </div>`;

      return;

    }

 

    sections.forEach(sec => {

      const div = document.createElement("div");

      div.className = "section-item" + (sec.id === state.selectedSectionId ? " active" : "");

      div.onclick = () => {

        state.selectedSectionId = sec.id;

        saveState();

        render();

      };

 

      const pagesDone = sec.pages.filter(p => p.status === "done").length;

      const pagesTotal = sec.pages.length;

      const statusPillClass =

        sec.status === "done" ? "status-done" :

        sec.status === "inprogress" ? "status-inprogress" :

        "status-notstarted";

 

      div.innerHTML = `

        <div class="section-title">${sec.title || "Untitled Section"}</div>

        <div class="section-meta">

          <span class="pill ${statusPillClass}">${sec.status === "done" ? "Done" : sec.status === "inprogress" ? "In Progress" : "Planning"}</span>

          <span>${pagesTotal} page${pagesTotal === 1 ? "" : "s"}</span>

          ${pagesTotal ? ` • ${pagesDone}/${pagesTotal} ready` : ""}

        </div>

      `;

      container.appendChild(div);

    });

  }

 

  function renderSectionDetail() {

    const container = document.getElementById("sectionDetail");

    const hint = document.getElementById("sectionHint");

 

    if (!state.selectedSectionId || !state.sections.length) {

      container.innerHTML = "";

      hint.textContent = "Select a section on the left or create a new one.";

      return;

    }

 

    const section = state.sections.find(s => s.id === state.selectedSectionId);

    if (!section) {

      container.innerHTML = "";

      hint.textContent = "Select a section on the left or create a new one.";

      return;

    }

 

    hint.textContent = `You’re editing: ${section.title || "Untitled Section"}`;

 

    const pages = section.pages;

    const pagesDone = pages.filter(p => p.status === "done").length;

    const pagesInProgress = pages.filter(p => p.status === "inprogress").length;

 

    container.innerHTML = `

      <div class="card">

        <div class="card-header">

          <div class="card-title">Section Info</div>

          <button class="danger" id="deleteSectionBtn">Delete Section</button>

        </div>

        <div class="columns">

          <div class="col-2">

            <div class="field">

              <label>Section Title</label>

              <input type="text" id="secTitleInput" value="${section.title || ""}" placeholder="e.g., Thresholds, Portraits of Becoming" />

            </div>

            <div class="field">

              <label>Subtitle / Invocation Phrase</label>

              <input type="text" id="secSubtitleInput" value="${section.subtitle || ""}" placeholder="Short phrase that sets the tone" />

            </div>

          </div>

          <div class="col-2">

            <div class="field">

              <label>Section Type</label>

              <select id="secTypeSelect">

                <option value="frontmatter" ${section.type === "frontmatter" ? "selected" : ""}>Front Matter</option>

                <option value="content" ${section.type === "content" ? "selected" : ""}>Content</option>

                <option value="backmatter" ${section.type === "backmatter" ? "selected" : ""}>Back Matter</option>

              </select>

            </div>

            <div class="field">

              <label>Status</label>

              <select id="secStatusSelect">

                <option value="notstarted" ${section.status === "notstarted" ? "selected" : ""}>Planning</option>

                <option value="inprogress" ${section.status === "inprogress" ? "selected" : ""}>In Progress</option>

                <option value="done" ${section.status === "done" ? "selected" : ""}>Complete</option>

              </select>

            </div>

            <div class="field">

              <label>Internal Notes</label>

              <textarea id="secNotesInput" placeholder="Concept, mood, ritual frame for this section...">${section.notes || ""}</textarea>

            </div>

          </div>

        </div>

        <div class="summary">

          Pages: ${pages.length} • Ready: ${pagesDone} • In progress: ${pagesInProgress}

        </div>

      </div>

 

      <div class="card">

        <div class="card-header">

          <div class="card-title">Add Pages to this Section</div>

        </div>

        <div class="chip-group" id="presetChipGroup">

          <div class="chip" data-layout="full-art">+ Full-page artwork</div>

          <div class="chip" data-layout="single-image">+ Single image ritual page</div>

          <div class="chip" data-layout="diptych">+ Diptych spread</div>

          <div class="chip" data-layout="triptych">+ Triptych grid</div>

          <div class="chip" data-layout="text-only">+ Text / Written echoes</div>

          <div class="chip" data-layout="process">+ Process / Ritual notes</div>

          <div class="chip" data-layout="custom">+ Custom layout</div>

        </div>

      </div>

 

      <div class="card">

        <div class="card-header">

          <div class="card-title">Pages in this Section</div>

        </div>

        <div class="page-list" id="pageList"></div>

      </div>

    `;

 

    // Attach handlers for section info

    document.getElementById("deleteSectionBtn").onclick = () => deleteSection(section.id);

    document.getElementById("secTitleInput").onchange = (e) => updateSection(section.id, { title: e.target.value });

    document.getElementById("secSubtitleInput").onchange = (e) => updateSection(section.id, { subtitle: e.target.value });

    document.getElementById("secTypeSelect").onchange = (e) => updateSection(section.id, { type: e.target.value });

    document.getElementById("secStatusSelect").onchange = (e) => updateSection(section.id, { status: e.target.value });

    document.getElementById("secNotesInput").onchange = (e) => updateSection(section.id, { notes: e.target.value });

 

    // Chip handlers

    const chipGroup = document.getElementById("presetChipGroup");

    chipGroup.querySelectorAll(".chip").forEach(chip => {

      chip.onclick = () => {

        const layout = chip.getAttribute("data-layout");

        addPageToSection(section.id, layout);

      };

    });

 

    // Render pages

    const pageList = document.getElementById("pageList");

    if (!pages.length) {

      pageList.innerHTML = `<div style="font-size:12px;color:var(--muted);">

        No pages yet. Use the layout chips above to add your first page.

      </div>`;

    } else {

      pages.forEach(page => {

        const div = document.createElement("div");

        div.className = "page-item";

 

        const layoutLabel = layoutTypeToLabel(page.layoutType);

        const statusLabel =

          page.status === "done" ? "Ready" :

          page.status === "inprogress" ? "In progress" :

          "Planning";

 

        div.innerHTML = `

          <div class="page-top">

            <div>

              <div style="font-size:13px;font-weight:600;">

                ${page.title || "(Untitled)"}

                ${page.pageNumber ? `<span class="badge">p. ${page.pageNumber}</span>` : ""}

              </div>

              <div class="page-meta">

                <span class="tag">${layoutLabel}</span>

                <span class="tag">${statusLabel}</span>

                ${page.contentRef ? `<span class="tag">Ref: ${escapeHtml(page.contentRef)}</span>` : ""}

              </div>

            </div>

            <div>

              <select class="status-select" data-pageid="${page.id}">

                <option value="notstarted" ${page.status === "notstarted" ? "selected" : ""}>Planning</option>

                <option value="inprogress" ${page.status === "inprogress" ? "selected" : ""}>In Progress</option>

                <option value="done" ${page.status === "done" ? "selected" : ""}>Ready</option>

              </select>

              <button class="danger" data-delpageid="${page.id}" style="margin-left:4px;">×</button>

            </div>

          </div>

          <div class="divider"></div>

          <div class="columns">

            <div class="col-2">

              <div class="field">

                <label>Page Number (in book)</label>

                <input type="number" min="1" value="${page.pageNumber || ""}" data-field="pageNumber" data-pageid="${page.id}" />

              </div>

              <div class="field">

                <label>Page Title / Label</label>

                <input type="text" value="${page.title || ""}" data-field="title" data-pageid="${page.id}" placeholder="e.g., Threshold Figure I" />

              </div>

              <div class="field">

                <label>Content Reference</label>

                <input type="text" value="${page.contentRef || ""}" data-field="contentRef" data-pageid="${page.id}" placeholder="e.g., Artwork file name, text piece ID" />

              </div>

            </div>

            <div class="col-2">

              <div class="field">

                <label>Notes / Concept</label>

                <textarea data-field="notes" data-pageid="${page.id}" placeholder="What happens on this page? Ritual, symbolism, layout notes...">${page.notes || ""}</textarea>

              </div>

            </div>

          </div>

        `;

 

        pageList.appendChild(div);

      });

 

      // Attach listeners for page inputs

      pageList.querySelectorAll("input[data-pageid], textarea[data-pageid]").forEach(el => {

        el.onchange = (e) => {

          const pageId = el.getAttribute("data-pageid");

          const field = el.getAttribute("data-field");

          let value = el.value;

          if (field === "pageNumber" && value !== "") {

            value = parseInt(value, 10);

          }

          updatePage(section.id, pageId, { [field]: value });

        };

      });

 

      // Status dropdowns

      pageList.querySelectorAll("select.status-select").forEach(sel => {

        sel.onchange = (e) => {

          const pageId = sel.getAttribute("data-pageid");

          updatePage(section.id, pageId, { status: sel.value });

        };

      });

 

      // Delete buttons

      pageList.querySelectorAll("button[data-delpageid]").forEach(btn => {

        btn.onclick = () => {

          const pageId = btn.getAttribute("data-delpageid");

          deletePage(section.id, pageId);

        };

      });

    }

  }

 

  function layoutTypeToLabel(type) {

    switch (type) {

      case "full-art": return "Full-page artwork";

      case "single-image": return "Single image ritual page";

      case "diptych": return "Diptych spread";

      case "triptych": return "Triptych grid";

      case "text-only": return "Text / Written echoes";

      case "process": return "Process / Ritual notes";

      case "custom": return "Custom layout";

      default: return "Layout";

    }

  }

 

  function escapeHtml(str) {

    if (!str) return "";

    return str.replace(/[&<>"']/g, function(m) {

      return {

        "&": "&amp;",

        "<": "&lt;",

        ">": "&gt;",

        '"': "&quot;",

        "'": "&#39;"

      }[m];

    });

  }

 

  // ---- Event bindings ----

  document.getElementById("addSectionBtn").onclick = createSection;

  document.getElementById("resetDataBtn").onclick = resetAll;

  document.getElementById("exportJsonBtn").onclick = exportJson;

 

  loadState();

</script>

</body>

</html>