'use strict';
// ═══════════════════════════════════════════════
// js/profile.js — My Profile modal + buddy profiles
// ═══════════════════════════════════════════════

const _AVATAR_EMOJIS = [
  // Big cats & canines
  '🐺','🦁','🐯','🦊','🐻','🐼','🐻‍❄️','🦝','🦦','🐆','🐅',
  // Small mammals
  '🐶','🐱','🐭','🐹','🐰','🦔','🐿️','🦡','🦦','🦥','🦨',
  // Hooved & large
  '🦌','🦬','🐂','🐴','🦓','🦒','🐘','🦏','🦛','🦘','🦙',
  // Birds
  '🦅','🦆','🦉','🦚','🦜','🦩','🐧','🦤','🦢','🕊️','🦃',
  // Reptiles & sea
  '🐸','🦎','🐢','🐊','🦈','🐬','🐳','🦭','🐙','🦑','🦀','🦞','🦐',
  // Bugs
  '🦋','🐝','🪲','🕷️','🦂',
  // Plants
  '🌱','🌿','🍀','🌳','🌲','🌴','🎋','🍃','🌾','🌵','🎍','🪴','🌷','🌹','🪷',
  // Weather & sky
  '🌊','🔥','⚡','🌙','☀️','⭐','🌟','💫','🌈','❄️','🌪️','🌩️','⛅','🌤️','🌬️',
  // More flora
  '🌻','🌺','🌸','🍄','🪨','🪸','🌍','🪵','🍂','🍁',
  // Landscapes & scenes
  '🏔️','🗻','🏕️','🌄','🌅','🌇','🌆','🌃','🌌','🏞️','🎑','🏜️','🏝️','🌁','🌉','⛺',
  // Space
  '🚀','🛸','🔭','🌑','🌒','🌓','🌔','🌕','🪐','☄️','🌋','🛰️','🌠',
  // Gems & tools
  '💎','🔮','⚗️','🧪','⚙️','🧲','💡','🕯️','🏺',
  // Combat & adventure
  '⚔️','🛡️','🏹','🪃','🗡️','🔱','⚜️','🎯','🧭',
  // Sports — individual
  '🏋️','🧗','🏃','🚴','🏊','🤸','🧘','🤿','🪂','🏄','🎿','🏂','🏇','⛷️','🤺','🥊','🎣','🚵','🛹','🛷',
  // Sports — ball & racket
  '⛳','🎾','🏐','🏈','🏉','🎱','🏓','🏸','🥏','🎳','🥅','⚾',
  // Jobs & roles
  '👷','💂','🧑‍⚕️','🧑‍🍳','🧑‍🌾','🧑‍🏭','🧑‍💻','🧑‍🚒','🧑‍✈️','🧑‍🚀','🧑‍⚖️','🧑‍🎨','🧑‍🏫','🧑‍🔧','🧑‍💼',
  // Arts & craft
  '🎸','🎨','🖼️','🎭','🎪','🎬','📚','🔬','🧬','🏆','🎖️','🥇',
];

function _emojiPickerHTML() {
  return `<div style="max-height:180px;overflow-y:auto;display:flex;flex-wrap:wrap;gap:3px">
    ${_AVATAR_EMOJIS.map(e =>
      `<button type="button" data-action="pickAvatar" data-a0="${e}" style="font-size:20px;padding:3px 5px;background:none;border:1px solid transparent;border-radius:5px;cursor:pointer;line-height:1" title="${e}">${e}</button>`
    ).join('')}
  </div>`;
}



function pickAvatar(emoji) {
  const preview = document.getElementById('pf-avatar-preview');
  const val = document.getElementById('pf-avatar-val');
  const picker = document.getElementById('pf-avatar-picker');
  if (preview) preview.textContent = emoji;
  if (val) val.value = emoji;
  if (picker) picker.classList.add('hidden');
}

function _miniChart(data, maxY, color, gradId, title) {
  if (!data.length) return '';
  if (data.length === 1) {
    return `<div style="margin-bottom:14px">
      <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:2px;text-transform:uppercase;letter-spacing:.06em">${title}</div>
      <div style="font-size:13px">${data[0].val}</div>
    </div>`;
  }
  const W = 540, H = 72, pL = 28, pR = 8, pT = 8, pB = 20;
  const iW = W - pL - pR, iH = H - pT - pB;
  const vals = data.map(d => d.val);
  const minV = Math.min(...vals), maxV = Math.max(...vals, minV + 1);
  const range = maxV - minV;
  function px(i) { return pL + (i / (data.length - 1)) * iW; }
  function py(v) { return pT + iH - ((v - minV) / range) * iH; }
  const line = data.map((d, i) => `${px(i)},${py(d.val)}`).join(' ');
  const area = `${px(0)},${pT + iH} ` + line + ` ${px(data.length - 1)},${pT + iH}`;
  const dots = data.map((d, i) => `<circle cx="${px(i)}" cy="${py(d.val)}" r="2.5" fill="${color}"/>`).join('');
  return `<div style="margin-bottom:16px">
    <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:4px;text-transform:uppercase;letter-spacing:.06em">${title}</div>
    <svg viewBox="0 0 ${W} ${H}" style="width:100%;height:${H}px;display:block">
      <defs><linearGradient id="${gradId}" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stop-color="${color}" stop-opacity=".2"/>
        <stop offset="100%" stop-color="${color}" stop-opacity="0"/>
      </linearGradient></defs>
      <text x="${pL - 3}" y="${pT + 6}" text-anchor="end" font-family="monospace" font-size="8" fill="var(--muted2)">${maxV}</text>
      <text x="${pL - 3}" y="${pT + iH}" text-anchor="end" font-family="monospace" font-size="8" fill="var(--muted2)">${minV}</text>
      <line x1="${pL}" y1="${pT}" x2="${pL}" y2="${pT + iH}" stroke="var(--border2)" stroke-width="1"/>
      <line x1="${pL}" y1="${pT + iH}" x2="${W - pR}" y2="${pT + iH}" stroke="var(--border2)" stroke-width="1"/>
      <polygon points="${area}" fill="url(#${gradId})"/>
      <polyline fill="none" stroke="${color}" stroke-width="1.5" points="${line}"/>
      ${dots}
      <text x="${pL}" y="${H}" font-family="monospace" font-size="7" fill="var(--muted2)">${data[0].date}</text>
      <text x="${W - pR}" y="${H}" font-family="monospace" font-size="7" fill="var(--muted2)" text-anchor="end">${data[data.length - 1].date}</text>
    </svg>
  </div>`;
}

async function openProfile() {
  // Dismiss any stuck dropdown backdrop that could block the modal
  if (typeof _hideDropdownBackdrop === 'function') _hideDropdownBackdrop();
  const modal = document.getElementById('profileModal');
  if (!modal) return;
  try {
    modal.innerHTML = await _renderProfileBox();
  } catch(e) {
    console.error('[profile] render failed:', e);
    modal.innerHTML = `<div class="modal-box" style="max-width:420px"><div class="modal-hdr"><span>My Profile</span><button data-action="closeModal" data-a0="profileModal">✕</button></div><div class="modal-body"><p style="color:var(--red)">Failed to load profile: ${typeof esc === 'function' ? esc(e.message || String(e)) : String(e)}</p></div></div>`;
  }
  modal.classList.add('open');
  // Async sections rendered after DOM is ready
  if (typeof renderDevicesList === 'function') renderDevicesList();
  // Hydrate the mirrored Network toggle bank in My Devices.
  if (typeof updateNetworkToggleUI === 'function') updateNetworkToggleUI();
}

async function _renderProfileBox() {
  const profile = getProfile();
  const identity = await getIdentity();
  const entries = getEntries().sort((a, b) => b.date.localeCompare(a.date));
  const sortedAsc = [...entries].sort((a, b) => a.date.localeCompare(b.date));

  const pubProfile = ((typeof getPublicProfileSettings === 'function') ? getPublicProfileSettings() : null) || {};

  const latest = entries[0] || null;
  const oldest = sortedAsc[0] || null;
  const currentCI = latest?.ci ?? null;

  // Years restoring
  let yearsStr = null;
  if (profile.startDate) {
    const ms = Date.now() - new Date(profile.startDate).getTime();
    const yrs = ms / (365.25 * 24 * 3600 * 1000);
    yearsStr = yrs >= 1 ? yrs.toFixed(1) + ' yrs' : Math.round(yrs * 12) + ' mo';
  }

  // Devices: compare latest entry vs all-time
  const latestTugDevs = new Set(latest?.tuggingDevices || []);
  const latestRetDevs = new Set(latest?.retainingDevices || []);
  const allTugDevs = new Set();
  const allRetDevs = new Set();
  entries.forEach(e => {
    (e.tuggingDevices || []).forEach(d => allTugDevs.add(d));
    (e.retainingDevices || []).forEach(d => allRetDevs.add(d));
  });

  function devBadge(d, active) {
    return `<span style="font-family:var(--mono);font-size:12px;padding:3px 8px;border-radius:4px;border:1px solid ${active ? 'var(--greenb)' : 'var(--border2)'};background:${active ? 'var(--greenl)' : 'var(--surface2)'};color:${active ? 'var(--green)' : 'var(--muted2)'}">${esc(d)}</span>`;
  }

  const name = identity?.displayName || 'Me';
  const avatar = profile.avatar || '';
  const ownTimer = (typeof getActiveTimer === 'function') ? getActiveTimer() : null;
  const ownTimerBadge = ownTimer
    ? `<span class="buddy-timer-badge ${ownTimer.type === 'tugging' ? 'tug' : 'ret'}" style="display:inline-flex;align-items:center;gap:4px;font-family:var(--mono);font-size:12px;font-weight:600">${ownTimer.type === 'tugging' ? '🟢 tugging' : '🔵 retaining'}</span>`
    : '';

  return `<div class="modal-box" style="max-width:620px;max-height:90vh;overflow-y:auto">
    <div class="modal-hdr">
      <span>My Profile</span>
      <button data-action="closeModal" data-a0="profileModal">✕</button>
    </div>
    <div class="modal-body">

      <!-- Avatar + stats -->
      ${(() => {
        const ownBadges = (typeof computeBadges === 'function') ? computeBadges() : [];
        const largeBadge = ownBadges.find(b => b.large);
        const restBadges = ownBadges.filter(b => !b.large);
        return `<div style="display:flex;gap:16px;align-items:flex-start;margin-bottom:18px">
          <div style="flex-shrink:0;width:110px;display:flex;flex-direction:column;align-items:center;gap:8px">
            <div style="width:110px;height:110px;border-radius:8px;background:var(--surface2);border:1px solid var(--border2);display:flex;align-items:center;justify-content:center">
              <span style="font-size:64px;line-height:1">${avatar ? esc(avatar) : '🌿'}</span>
            </div>
            ${ownBadges.length ? `<div style="display:flex;flex-wrap:wrap;gap:5px;justify-content:center">
              ${largeBadge ? `<span title="${esc(largeBadge.tooltip)}" style="display:inline-flex;align-items:center;gap:4px;background:var(--surface2);border:1px solid var(--border2);border-radius:10px;padding:3px 9px 3px 6px;cursor:default;white-space:nowrap"><span style="font-size:16px;line-height:1">${largeBadge.emoji}</span><span style="font-family:var(--mono);font-size:11px;color:var(--muted2)">${esc(largeBadge.label)}</span></span>` : ''}
              ${restBadges.map(b => `<span title="${esc(b.tooltip)}" style="display:inline-flex;align-items:center;gap:4px;background:var(--surface2);border:1px solid var(--border2);border-radius:10px;padding:3px 9px 3px 6px;cursor:default;white-space:nowrap"><span style="font-size:14px;line-height:1">${b.emoji}</span><span style="font-family:var(--mono);font-size:11px;color:var(--muted2)">${esc(b.label)}</span></span>`).join('')}
            </div>` : ''}
            ${ownTimerBadge}
            ${(() => {
              const showAllImgs = localStorage.getItem('frj_dash_show_all_imgs') === '1';
              const latestImgKey = latest && latest.imgFront ? latest.imgFront : null;
              if (!latestImgKey) return '';
              const peekSrc = typeof imgURL === 'function' ? imgURL(latestImgKey) : null;
              // Dashboard photo rules:
              if (peekSrc && showAllImgs) {
                return `<div style="width:110px;margin-top:4px">
                  <div style="width:110px;border-radius:8px;overflow:hidden;background:var(--surface2);border:1px solid var(--border2);cursor:pointer" data-action="tabJournal">
                    <img src="${esc(peekSrc)}" style="width:100%;display:block;aspect-ratio:3/4;object-fit:cover" data-no-context="1" data-no-drag="1" data-peek-src="${esc(peekSrc)}" alt="Latest">
                  </div>
                  ${latest.date ? `<div style="font-family:var(--mono);font-size:10px;color:var(--muted2);text-align:center;margin-top:3px">${esc(latest.date)}</div>` : ''}
                </div>`;
              }
              if (peekSrc) {
                return `<div style="width:110px;margin-top:4px;display:flex;justify-content:center">
                  <button class="btn btn-ghost btn-sm" data-peek-src="${esc(peekSrc)}" title="Hold to preview photo" style="font-size:22px;padding:8px 14px;line-height:1;border-color:transparent">📷</button>
                </div>`;
              }
              // Photo expected but not cached yet — dim placeholder
              return `<div style="width:110px;margin-top:4px;display:flex;justify-content:center">
                <button class="btn btn-ghost btn-sm" title="Photo available — loading" style="font-size:22px;padding:8px 14px;line-height:1;border-color:transparent;opacity:.5">📷</button>
              </div>`;
            })()}
          </div>
          <div style="flex:1;min-width:0">
            <div style="font-size:18px;font-weight:600;margin-bottom:12px">${esc(name)}</div>
            <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;font-size:13px;margin-bottom:10px">
              <div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">CURRENT CI</div>
                ${currentCI != null ? `<span class="ci-badge">CI-${currentCI}</span>` : `<span style="color:var(--muted2)">-</span>`}
              </div>
              <div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">TARGET CI</div>
                ${profile.goalCI != null ? `<span class="ci-badge">CI-${profile.goalCI}</span>` : `<span style="color:var(--muted2)">-</span>`}
              </div>
              <div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">STARTING CI</div>
                ${profile.startingCI != null ? `<span class="ci-badge">CI-${profile.startingCI}</span>` : `<span style="color:var(--muted2)">-</span>`}
              </div>
              <div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">TIME RESTORING</div>
                <span>${yearsStr || '-'}</span>
              </div>
              ${profile.ageRange ? `<div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">AGE RANGE</div>
                <span>${esc(profile.ageRange)}</span>
              </div>` : ''}
              ${(profile.ageCircumcised != null || profile.fullyRestoredOn) ? `
                ${profile.ageCircumcised != null ? `<div>
                  <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">AGE CIRCUMCISED</div>
                  <span>${profile.ageCircumcised === 0 ? 'At birth' : `Age ${profile.ageCircumcised}`}</span>
                </div>` : '<div></div>'}
                ${profile.fullyRestoredOn ? (() => {
                  try {
                    const [yr, mo] = profile.fullyRestoredOn.split('-');
                    const moName = new Date(+yr, +mo - 1, 1).toLocaleString('default', { month: 'long' });
                    return `<div>
                      <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">FULLY RESTORED</div>
                      <span>👑 ${moName} ${yr}</span>
                    </div>`;
                  } catch(e) { return '<div></div>'; }
                })() : '<div></div>'}
              ` : ''}
              ${(profile.circumStyle || profile.circumMethod) ? `
                ${profile.circumStyle ? `<div>
                  <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">CIRCUMCISION STYLE</div>
                  <span>${esc(profile.circumStyle)}</span>
                </div>` : '<div></div>'}
                ${profile.circumMethod ? `<div>
                  <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">CIRCUMCISION METHOD</div>
                  <span>${esc(profile.circumMethod)}</span>
                </div>` : '<div></div>'}
              ` : ''}
            </div>
            ${entries.length ? `<div style="font-family:var(--mono);font-size:11px;color:var(--muted2)">${entries.length} update${entries.length !== 1 ? 's' : ''} · since ${esc(oldest.date)}</div>` : ''}
            ${profile.notes ? `<div style="margin-top:10px;font-size:13px;color:var(--text);white-space:pre-wrap;line-height:1.5">${esc(profile.notes)}</div>` : ''}
          </div>
        </div>`;
      })()}

      <!-- Devices -->
      ${(allTugDevs.size || allRetDevs.size) ? `
      <div style="margin-bottom:16px">
        <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:6px;text-transform:uppercase;letter-spacing:.05em">
          Devices Used <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--muted2)">(green = in latest update · grey = past)</span>
        </div>
        ${allTugDevs.size ? `<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:5px">${[...allTugDevs].map(d => devBadge(d, latestTugDevs.has(d))).join('')}</div>` : ''}
        ${allRetDevs.size ? `<div style="display:flex;flex-wrap:wrap;gap:4px">${[...allRetDevs].map(d => devBadge(d, latestRetDevs.has(d))).join('')}</div>` : ''}
      </div>` : ''}

      <!-- Edit profile -->
      <details style="margin-bottom:16px">
        <summary style="font-family:var(--mono);font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--muted2);cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;padding:8px 0;border-top:1px solid var(--border2);outline:none">
          Edit Profile Details <span style="font-weight:400">▾</span>
        </summary>
        <div class="prof-edit-grid" style="padding-top:10px;display:grid;gap:10px">
          <div class="fg" style="margin:0;grid-column:1/-1">
            <label>Avatar</label>
            <div style="display:flex;align-items:center;gap:10px;margin-bottom:6px">
              <span id="pf-avatar-preview" style="font-size:28px;line-height:1;min-width:36px">${avatar ? esc(avatar) : '-'}</span>
              <button class="btn btn-ghost btn-sm" data-action="toggleAvatarPicker">Pick avatar</button>
              ${avatar ? `<button class="btn btn-ghost btn-sm" data-action="clearAvatar">Clear</button>` : ''}
            </div>
            <input type="hidden" id="pf-avatar-val" value="${esc(avatar)}">
            <div id="pf-avatar-picker" class="hidden" style="background:var(--surface2);border:1px solid var(--border2);border-radius:8px;padding:8px;margin-top:4px">
              ${_emojiPickerHTML()}
            </div>
          </div>
          <div class="fg" style="margin:0;grid-column:1/-1">
            <label>Display Name</label>
            <div style="display:flex;gap:8px">
              <input type="text" id="pf-displayname" class="input" placeholder="Your display name" value="${esc(identity?.displayName || '')}">
            </div>
          </div>
          <div class="fg" style="margin:0">
            <label>Starting CI</label>
            <select id="pf-startci" class="input">
              <option value="">- not set -</option>
              ${[0,1,2,3,4,5,6,7,8,9,10].map(n =>
                `<option value="${n}" ${profile.startingCI == n ? 'selected' : ''}>CI-${n}: ${CI_LABELS[n] || ''}</option>`
              ).join('')}
            </select>
            <button type="button" class="ciref-open-btn" data-action="openCiRef" data-a0="pf-startci">Illustrated guide</button>
          </div>
          <div class="fg" style="margin:0">
            <label>Age Range</label>
            <select id="pf-agerange" class="input">
              <option value="">- not set -</option>
              ${['18–25','25–40','40–55','55–70','70+'].map(r =>
                `<option value="${r}" ${profile.ageRange === r ? 'selected' : ''}>${r}</option>`
              ).join('')}
            </select>
          </div>
          <div class="fg" style="margin:0">
            <label>Age Circumcised</label>
            <input type="number" id="pf-agecircd" class="input" min="0" max="80" inputmode="numeric" placeholder="0 = at birth" value="${profile.ageCircumcised ?? ''}">
          </div>
          <div class="fg" style="margin:0">
            <label>Started Restoring</label>
            <input type="date" id="pf-startdate" class="input" value="${profile.startDate || ''}">
          </div>
          <div class="fg" style="margin:0">
            <label>Fully Restored On</label>
            <input type="date" id="pf-fullyrestoredon" class="input" value="${profile.fullyRestoredOn || ''}">
          </div>
          <div class="fg" style="margin:0">
            <label>Circumcision Style</label>
            <select id="pf-circumstyle" class="input">
              <option value="">- not set -</option>
              ${['None (intact)','Average','High & Tight','High & Loose','Low & Tight','Low & Loose'].map(s =>
                `<option value="${s}" ${profile.circumStyle === s ? 'selected' : ''}>${s}</option>`
              ).join('')}
            </select>
          </div>
          <div class="fg" style="margin:0">
            <label>Circumcision Method</label>
            <select id="pf-circummethod" class="input">
              <option value="">- not set -</option>
              ${['None (intact)','Gomco','Plastibell','Mogen','Freehand','Shang Ring','Smart Klamp','Circumplast','PrePex','Unknown','Other'].map(m =>
                `<option value="${m}" ${profile.circumMethod === m ? 'selected' : ''}>${m}</option>`
              ).join('')}
            </select>
          </div>
          <div class="fg" style="margin:0;grid-column:1/-1">
            <label>Goal CI</label>
            <select id="pf-goalci" class="input">
              <option value="">- not set -</option>
              ${[0,1,2,3,4,5,6,7,8,9,10].map(n =>
                `<option value="${n}" ${profile.goalCI == n ? 'selected' : ''}>CI-${n}: ${CI_LABELS[n] || ''}</option>`
              ).join('')}
            </select>
            <button type="button" class="ciref-open-btn" data-action="openCiRef" data-a0="pf-goalci">Illustrated guide</button>
          </div>
        </div>
        <div class="fg" style="margin:10px 0 0">
          <label>Notes</label>
          <textarea id="pf-notes" class="input" rows="5" maxlength="2000" style="resize:vertical;width:100%;box-sizing:border-box" placeholder="Personal notes…">${esc(profile.notes || '')}</textarea>
        </div>
        <div style="margin-top:10px">
          <button class="btn btn-primary btn-sm" data-action="saveProfileForm">Save</button>
        </div>
      </details>

      <!-- Settings -->
      <!-- Public Profile visibility -->
      <details style="margin-bottom:16px">
        <summary style="font-family:var(--mono);font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--muted2);cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;padding:8px 0;border-top:1px solid var(--border2);outline:none">
          Public Profile <span style="font-weight:400">▾</span>
        </summary>
        <div style="margin-top:10px">
          <p style="font-size:12px;color:var(--muted2);margin:0 0 12px;line-height:1.5">Choose what others can see when you're in Public mode in the buddy network. Nothing is shared while you're Private or Offline.</p>
          ${[
            ['showName',         'Name'],
            ['showCurrentCI',    'Current CI'],
            ['showTargetCI',     'Target CI'],
            ['showStartingCI',   'Starting CI'],
            ['showTimeRestoring','Time restoring'],
            ['showAchievements', 'Badges'],
          ].map(([key, label]) => `<label style="display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;margin-bottom:7px">
            <input type="checkbox" ${pubProfile[key] ? 'checked' : ''} data-pub-field="${key}">
            ${label}
          </label>`).join('')}
        ${(() => {
          const allBadges = (typeof computeBadges === 'function') ? computeBadges() : [];
          if (!allBadges.length) return '';
          const hidden = Array.isArray(pubProfile.hiddenPublicBadges) ? pubProfile.hiddenPublicBadges : [];
          // Static badge labels for display (key → label), with fallback to emoji
          const BADGE_LABELS = { journey: 'Journey', ci_gained: 'CI Progress', restored: 'Fully Restored', multi_method: 'Multi-Method', buddies: '5+ Buddies', trusted: 'Trusted Buddies', weekly_streak: 'Weekly Streak', photos: 'Photos', timer_streak: 'Daily Streak' };
          return `<div style="margin-top:10px;border-top:1px solid var(--border2);padding-top:10px">
            <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em">Which badges appear publicly</div>
            ${allBadges.map(b => `<label style="display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;margin-bottom:6px">
              <input type="checkbox" ${!hidden.includes(b.key) ? 'checked' : ''} data-hide-badge="${esc(b.key)}">
              <span style="font-size:16px;line-height:1">${b.emoji}</span>
              <span>${BADGE_LABELS[b.key] || b.key}</span>
            </label>`).join('')}
          </div>`;
        })()}
        </div>
      </details>

      <!-- Actions -->
      <div style="display:flex;gap:8px;flex-wrap:wrap">
        <button class="btn btn-ghost btn-sm" data-action="tabJournal">Journal</button>
      </div>

    </div>
  </div>`;
}

// ── Sample profile (generated demo data) ──────────────────────────────────────
// A deterministic example journal: three years of consistent work, one CI level
// gained per year (CI-2 to CI-5), 6-10 h/day tugging and 8-10 h/day retaining.
// Opened from the Chats empty state so new users can see what a filled-in
// profile looks like. Never stored, never syncs; the "photo" is the
// public-domain CI reference art.
const _DEMO_KEY = '_demo_';

function _demoRnd(i) { const x = Math.sin(i * 12.9898) * 43758.5453; return x - Math.floor(x); }

function _demoJournal() {
  const MONTHS = 37;
  const now = new Date();
  const entries = [];
  const yearNotes = [
    'Starting out. Manual tugging to learn what gentle tension feels like.',
    'One year in. The rollover is starting to stay put on its own.',
    'Two years in. Coverage holds most of the day now; retaining feels normal.',
    'Three years in. Reached CI-5. Slow and steady works.'
  ];
  for (let m = 0; m < MONTHS; m++) {
    const monthsAgo = MONTHS - 1 - m;
    const d = new Date(now.getFullYear(), now.getMonth() - monthsAgo, 5 + Math.floor(_demoRnd(m) * 15));
    entries.push({
      id: 'demo-' + m,
      date: `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`,
      ci: Math.min(5, 2 + Math.floor(m / 12)),
      pctTugged: 80 + Math.round(_demoRnd(m * 3 + 1) * 20),
      pctRetained: 85 + Math.round(_demoRnd(m * 7 + 2) * 15),
      avgHrsTugged: 6 + Math.round(_demoRnd(m * 5 + 3) * 4),
      avgHrsRetained: 8 + Math.round(_demoRnd(m * 11 + 4) * 2),
      tuggingDevices: m < 5 ? ['Manual'] : m < 22 ? ['Manual', 'DTR'] : ['DTR', 'TLC-X'],
      retainingDevices: m < 9 ? [] : m < 24 ? ['O-ring'] : ['O-ring', 'Stealth Retainer'],
      notes: m % 12 === 0 ? yearNotes[m / 12] : '',
      hasImgFront: false
    });
  }
  return {
    avatar: '🌱',
    profile: {
      displayName: 'Sample Restorer',
      startingCI: 2,
      goalCI: 8,
      startDate: entries[0].date,
      ageRange: '35-44',
      ageCircumcised: 0
    },
    entries,
    _demoPhoto: 'img/CIref/CI-5.png'
  };
}

function showDemoProfile() {
  const modal = document.getElementById('profileModal');
  if (!modal) return;
  if (typeof _hideDropdownBackdrop === 'function') _hideDropdownBackdrop();
  modal.innerHTML = _renderBuddyProfileBox({ pubKeyB64: _DEMO_KEY, nickname: 'Sample Restorer' }, _demoJournal(), _DEMO_KEY);
  modal.classList.add('open');
}

function openBuddyProfile(pubKeyB64) {
  const c = (typeof getContacts === 'function' ? getContacts() : []).find(x => x.pubKeyB64 === pubKeyB64);
  if (!c) return;
  // Dismiss any stuck dropdown backdrop that could block the modal
  if (typeof _hideDropdownBackdrop === 'function') _hideDropdownBackdrop();
  const journal = typeof getPeerJournal === 'function' ? getPeerJournal(pubKeyB64) : null;
  const modal = document.getElementById('profileModal');
  if (!modal) return;
  modal.innerHTML = _renderBuddyProfileBox(c, journal, pubKeyB64);
  modal.classList.add('open');
}

function _renderBuddyProfileBox(c, journal, pubKeyB64) {
  const entries = ((journal?.entries || []).sort((a, b) => b.date.localeCompare(a.date)));
  const sortedAsc = [...entries].sort((a, b) => a.date.localeCompare(b.date));
  const latest = entries[0] || null;
  const oldest = sortedAsc[0] || null;
  const currentCI = latest?.ci ?? null;
  const prof = journal?.profile || {};

  // Use shared profile data where available, fall back to inferring from entries
  const startCI = prof.startingCI ?? oldest?.ci ?? null;
  const goalCI = prof.goalCI ?? null;

  // Time restoring: prefer shared startDate, fall back to first entry date
  let yearsStr = null;
  const startDateStr = prof.startDate || oldest?.date;
  if (startDateStr) {
    const ms = Date.now() - new Date(startDateStr + 'T00:00:00').getTime();
    const yrs = ms / (365.25 * 24 * 3600 * 1000);
    yearsStr = yrs >= 1 ? yrs.toFixed(1) + ' yrs' : Math.round(yrs * 12) + ' mo';
  }

  // Latest cached photo
  const latestImgEntry = entries.find(e => imgURL(`c_${pubKeyB64.slice(0, 16)}_${e.id}_front`));
  const latestImgSrc = latestImgEntry ? imgURL(`c_${pubKeyB64.slice(0, 16)}_${latestImgEntry.id}_front`) : null;
  const bOnline = typeof isOnline === 'function' && isOnline(pubKeyB64);
  const bIdle = bOnline && typeof isPeerIdle === 'function' && isPeerIdle(pubKeyB64);
  const mutualTrust = !!(c.trusted && c.senderTrusts);
  const hasPhotoExpected = entries.some(e => e.hasImgFront);

  // Devices: green = in latest, grey = past
  const latestTugDevs = new Set(latest?.tuggingDevices || []);
  const latestRetDevs = new Set(latest?.retainingDevices || []);
  const allTugDevs = new Set(), allRetDevs = new Set();
  entries.forEach(e => {
    (e.tuggingDevices || []).forEach(d => allTugDevs.add(d));
    (e.retainingDevices || []).forEach(d => allRetDevs.add(d));
  });

  function devBadge(d, active) {
    return `<span style="font-family:var(--mono);font-size:12px;padding:3px 8px;border-radius:4px;border:1px solid ${active ? 'var(--greenb)' : 'var(--border2)'};background:${active ? 'var(--greenl)' : 'var(--surface2)'};color:${active ? 'var(--green)' : 'var(--muted2)'}">${esc(d)}</span>`;
  }

  // Charts
  const chartData = [...entries].reverse();
  const ciData  = chartData.map(e => ({ date: e.date, val: e.ci }));
  const tugData = chartData.filter(e => e.pctTugged != null).map(e => ({ date: e.date, val: e.pctTugged }));
  const hrsData = chartData.filter(e => e.avgHrsTugged != null).map(e => ({ date: e.date, val: e.avgHrsTugged }));

  const buddyAvatar = journal?.avatar || '';
  const _bPermsSnap = (typeof getBuddyPermissions === 'function') ? getBuddyPermissions() : {};
  const _buddyTimerObj = (_bPermsSnap.showBuddyTimerStatus && typeof getBuddyTimerStatus === 'function') ? getBuddyTimerStatus(pubKeyB64) : null;
  const buddyTimerBadge = _buddyTimerObj
    ? `<span class="buddy-timer-badge ${_buddyTimerObj.type === 'tugging' ? 'tug' : 'ret'}" style="display:inline-flex;align-items:center;gap:4px;font-family:var(--mono);font-size:12px;font-weight:600">${_buddyTimerObj.type === 'tugging' ? '🟢 tugging' : '🔵 retaining'}</span>`
    : '';

  return `<div class="modal-box" style="max-width:620px;max-height:90vh;overflow-y:auto">
    <div class="modal-hdr">
      <span>${buddyAvatar ? esc(buddyAvatar) + ' ' : ''}${esc(c.nickname || 'Buddy')}'s Profile</span>
      <button data-action="closeModal" data-a0="profileModal">✕</button>
    </div>
    <div class="modal-body">
      ${pubKeyB64 === _DEMO_KEY ? `<div style="background:var(--accent2);border:1px solid var(--accent);border-radius:8px;padding:8px 12px;margin-bottom:14px;font-size:12px;color:var(--accent);font-family:var(--mono)">Example data. This is what a profile looks like after three years of consistent journaling.</div>` : ''}

      <!-- Avatar + stats -->
      ${(() => {
        const bBadges = (entries.length && typeof computeBadgesFrom === 'function') ? computeBadgesFrom(entries, prof, []) : [];
        const largeBadge = bBadges.find(b => b.large);
        const restBadges = bBadges.filter(b => !b.large);
        return `<div style="display:flex;gap:16px;align-items:flex-start;margin-bottom:18px">
          <div style="flex-shrink:0;width:110px;display:flex;flex-direction:column;align-items:center;gap:8px">
            <div style="width:110px;height:110px;border-radius:8px;background:var(--surface2);border:1px solid var(--border2);display:flex;align-items:center;justify-content:center">
              <span style="font-size:64px;line-height:1">${buddyAvatar ? esc(buddyAvatar) : '🌿'}</span>
            </div>
            ${bBadges.length ? `<div style="display:flex;flex-wrap:wrap;gap:5px;justify-content:center">
              ${largeBadge ? `<span title="${esc(largeBadge.tooltip)}" style="display:inline-flex;align-items:center;gap:4px;background:var(--surface2);border:1px solid var(--border2);border-radius:10px;padding:3px 9px 3px 6px;cursor:default;white-space:nowrap"><span style="font-size:16px;line-height:1">${largeBadge.emoji}</span><span style="font-family:var(--mono);font-size:11px;color:var(--muted2)">${esc(largeBadge.label)}</span></span>` : ''}
              ${restBadges.map(b => `<span title="${esc(b.tooltip)}" style="display:inline-flex;align-items:center;gap:4px;background:var(--surface2);border:1px solid var(--border2);border-radius:10px;padding:3px 9px 3px 6px;cursor:default;white-space:nowrap"><span style="font-size:14px;line-height:1">${b.emoji}</span><span style="font-family:var(--mono);font-size:11px;color:var(--muted2)">${esc(b.label)}</span></span>`).join('')}
            </div>` : ''}
            ${buddyTimerBadge}
            ${(() => {
              if (journal && journal._demoPhoto) {
                return `<div style="width:110px;margin-top:4px">
                  <div style="width:110px;border-radius:8px;overflow:hidden;background:#fff;border:1px solid var(--border2)">
                    <img src="${esc(journal._demoPhoto)}" style="width:100%;display:block;aspect-ratio:3/4;object-fit:contain" alt="CI reference illustration">
                  </div>
                  <div style="font-family:var(--mono);font-size:10px;color:var(--muted2);text-align:center;margin-top:3px">reference art</div>
                </div>`;
              }
              const showAllImgs = localStorage.getItem('frj_dash_show_all_imgs') === '1';
              const photoEntry = entries.find(e => e.hasImgFront);
              if (!photoEntry) return '';
              const photoKey = `c_${pubKeyB64.slice(0, 16)}_${photoEntry.id}_front`;
              const peekSrc = typeof imgURL === 'function' ? imgURL(photoKey) : null;
              // Dashboard photo rules:
              if (peekSrc && showAllImgs) {
                return `<div style="width:110px;margin-top:4px">
                  <div style="width:110px;border-radius:8px;overflow:hidden;background:var(--surface2);border:1px solid var(--border2);cursor:pointer" data-action="viewBuddyJournal" data-a0="${esc(pubKeyB64)}">
                    <img src="${esc(peekSrc)}" style="width:100%;display:block;aspect-ratio:3/4;object-fit:cover" data-no-context="1" data-no-drag="1" data-peek-src="${esc(peekSrc)}" alt="Latest">
                  </div>
                  ${photoEntry?.date ? `<div style="font-family:var(--mono);font-size:10px;color:var(--muted2);text-align:center;margin-top:3px">${esc(photoEntry.date)}</div>` : ''}
                </div>`;
              }
              if (peekSrc) {
                return `<div style="width:110px;margin-top:4px;display:flex;justify-content:center">
                  <button class="btn btn-ghost btn-sm" data-peek-src="${esc(peekSrc)}" title="Hold to preview photo" style="font-size:22px;padding:8px 14px;line-height:1;border-color:transparent">📷</button>
                </div>`;
              }
              return `<div style="width:110px;margin-top:4px;display:flex;justify-content:center">
                <button class="btn btn-ghost btn-sm" title="Photo available — loading" style="font-size:22px;padding:8px 14px;line-height:1;border-color:transparent;opacity:.5">📷</button>
              </div>`;
            })()}
          </div>
          <div style="flex:1;min-width:0">
            <div style="font-size:18px;font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;flex-wrap:wrap">
              ${esc(c.nickname || 'Buddy')}
              <span title="${bOnline ? (bIdle ? 'Idle' : 'Active') : 'Offline'}" style="width:10px;height:10px;border-radius:50%;flex-shrink:0;display:inline-block;background:${!bOnline ? 'var(--muted2)' : bIdle ? '#f97316' : 'var(--green)'}"></span>
              ${mutualTrust
                ? `<span style="background:#7c3aed22;border:1px solid #7c3aed55;color:#7c3aed;font-family:var(--mono);font-size:11px;padding:3px 9px;border-radius:10px;font-weight:600">Trusted</span>`
                : c.trusted
                  ? `<span style="background:#3b82f622;border:1px solid #3b82f655;color:#3b82f6;font-family:var(--mono);font-size:11px;padding:3px 9px;border-radius:10px;font-weight:600">Trusting</span>`
                  : ''}
            </div>
            <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;font-size:13px;margin-bottom:10px">
              <div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">CURRENT CI</div>
                ${currentCI != null ? `<span class="ci-badge">CI-${currentCI}</span>` : `<span style="color:var(--muted2)">-</span>`}
              </div>
              <div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">TARGET CI</div>
                ${goalCI != null ? `<span class="ci-badge">CI-${goalCI}</span>` : `<span style="color:var(--muted2)">-</span>`}
              </div>
              <div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">STARTING CI</div>
                ${startCI != null ? `<span class="ci-badge">CI-${startCI}</span>` : `<span style="color:var(--muted2)">-</span>`}
              </div>
              <div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">TIME RESTORING</div>
                <span>${yearsStr || '-'}</span>
              </div>
              ${prof.ageRange ? `<div>
                <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">AGE RANGE</div>
                <span>${esc(prof.ageRange)}</span>
              </div>` : ''}
              ${(prof.ageCircumcised != null || prof.fullyRestoredOn) ? `
                ${prof.ageCircumcised != null ? `<div>
                  <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">AGE CIRCUMCISED</div>
                  <span>${prof.ageCircumcised === 0 ? 'At birth' : `Age ${prof.ageCircumcised}`}</span>
                </div>` : '<div></div>'}
                ${prof.fullyRestoredOn ? (() => {
                  const [yr, mo] = prof.fullyRestoredOn.split('-');
                  const moName = new Date(+yr, +mo - 1, 1).toLocaleString('default', { month: 'long' });
                  return `<div>
                    <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">FULLY RESTORED</div>
                    <span>👑 ${moName} ${yr}</span>
                  </div>`;
                })() : '<div></div>'}
              ` : ''}
              ${(prof.circumStyle || prof.circumMethod) ? `
                ${prof.circumStyle ? `<div>
                  <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">CIRCUMCISION STYLE</div>
                  <span>${esc(prof.circumStyle)}</span>
                </div>` : '<div></div>'}
                ${prof.circumMethod ? `<div>
                  <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:3px">CIRCUMCISION METHOD</div>
                  <span>${esc(prof.circumMethod)}</span>
                </div>` : '<div></div>'}
              ` : ''}
            </div>
            ${entries.length ? `<div style="font-family:var(--mono);font-size:11px;color:var(--muted2)">${entries.length} update${entries.length !== 1 ? 's' : ''} · since ${esc(oldest.date)}</div>` : ''}
          </div>
        </div>`;
      })()}

      <!-- Devices -->
      ${(allTugDevs.size || allRetDevs.size) ? `
      <div style="margin-bottom:16px">
        <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:6px;text-transform:uppercase;letter-spacing:.05em">
          Devices Used <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--muted2)">(green = in latest update · grey = past)</span>
        </div>
        ${allTugDevs.size ? `<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:5px">${[...allTugDevs].map(d => devBadge(d, latestTugDevs.has(d))).join('')}</div>` : ''}
        ${allRetDevs.size ? `<div style="display:flex;flex-wrap:wrap;gap:4px">${[...allRetDevs].map(d => devBadge(d, latestRetDevs.has(d))).join('')}</div>` : ''}
      </div>` : ''}
      ${!journal ? `<div class="empty"><p>Not yet synced. Connect with this buddy first.</p></div>` : ''}

      <!-- Buddy list -->
      ${(() => {
        if (!c.buddyList || !c.buddyList.length) return '';
        const visible = c.buddyList.filter(b =>
          !(typeof _ownPubKeyHash !== 'undefined' && _ownPubKeyHash && b.hash === _ownPubKeyHash)
        );
        if (!visible.length) return '';
        const chips = visible.map(b => {
          const myContacts = typeof getContacts === 'function' ? getContacts() : [];
          const alreadyBuddy = myContacts.some(mc => !mc.removedAt && !mc.forgottenAt && mc._pubKeyHash === b.hash);
          if (alreadyBuddy) {
            return `<span style="font-size:13px;padding:3px 8px;border-radius:12px;background:var(--surface2);color:var(--muted2)">${esc(b.emoji || '🌿')} ${esc(b.name || 'Restorer')}</span>`;
          }
          return `<button class="btn btn-ghost btn-sm" style="font-size:13px;padding:3px 8px;border-radius:12px"
            data-action="openRestorerProfile" data-a0="${esc(b.hash)}" data-a1="${esc(b.emoji || '🌿')}" data-a2="${esc(b.name || 'Restorer')}" data-a3="0">
            ${esc(b.emoji || '🌿')} ${esc(b.name || 'Restorer')}
          </button>`;
        }).join('');
        return `<div style="margin-bottom:16px;padding-top:12px;border-top:1px solid var(--border2)">
          <div style="font-family:var(--mono);font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted2);margin-bottom:8px">${esc(c.nickname || 'Buddy')}'s Buddies</div>
          <div style="display:flex;flex-wrap:wrap;gap:6px">${chips}</div>
        </div>`;
      })()}

      <!-- Actions (hidden for the generated sample profile) -->
      ${pubKeyB64 === _DEMO_KEY ? '' : `<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;padding-top:8px;border-top:1px solid var(--border2)">
        <button class="btn btn-primary btn-sm" data-action="viewBuddyJournal" data-a0="${esc(pubKeyB64)}">View Journal</button>
        <button class="btn btn-ghost btn-sm" data-action="viewBuddyContacts">Back to Buddies</button>
        ${mutualTrust ? `<button class="hdr-lock-btn" style="margin-left:auto" data-action="openBuddyTimeline" data-a0="${esc(pubKeyB64)}" title="${esc(c.nickname || 'Buddy')}'s timeline">📈</button>` : `<span style="margin-left:auto"></span>`}
        ${mutualTrust ? `<button class="hdr-lock-btn" data-action="qcProposeModal" data-a0="${esc(pubKeyB64)}" title="Quick Check schedule">📅</button>` : ''}
        <button class="hdr-lock-btn" data-action="openBuddyGallery" data-a0="${esc(pubKeyB64)}" title="${esc(c.nickname || 'Buddy')}'s gallery">🖼️</button>
        <button class="hdr-lock-btn" data-action="openChatPopup" data-a0="${esc(pubKeyB64)}" title="Chat with ${esc(c.nickname || 'Buddy')}">💬</button>
      </div>`}

    </div>
  </div>`;
}

async function saveProfileForm() {
  const profile = getProfile();
  const avatarVal = (document.getElementById('pf-avatar-val')?.value || '').trim();
  if (avatarVal) profile.avatar = avatarVal;
  else delete profile.avatar;
  const displayName = (document.getElementById('pf-displayname')?.value || '').trim();
  if (displayName && typeof setDisplayName === 'function') await setDisplayName(displayName);
  const startCI = document.getElementById('pf-startci')?.value;
  const ageCirVal = document.getElementById('pf-agecircd')?.value;
  const startDate = document.getElementById('pf-startdate')?.value;

  if (startCI !== '') profile.startingCI = parseInt(startCI);
  else delete profile.startingCI;

  const ageRange = document.getElementById('pf-agerange')?.value || '';
  if (ageRange) profile.ageRange = ageRange;
  else delete profile.ageRange;

  if (ageCirVal !== '') profile.ageCircumcised = parseInt(ageCirVal);
  else delete profile.ageCircumcised;

  if (startDate) profile.startDate = startDate;
  else delete profile.startDate;

  const circumStyle = document.getElementById('pf-circumstyle')?.value;
  if (circumStyle) profile.circumStyle = circumStyle;
  else delete profile.circumStyle;

  const circumMethod = document.getElementById('pf-circummethod')?.value || '';
  if (circumMethod) profile.circumMethod = circumMethod;
  else delete profile.circumMethod;

  const goalCI = document.getElementById('pf-goalci')?.value;
  if (goalCI !== '') profile.goalCI = parseInt(goalCI);
  else delete profile.goalCI;

  const fullyRestoredOn = document.getElementById('pf-fullyrestoredon')?.value;
  if (fullyRestoredOn) profile.fullyRestoredOn = fullyRestoredOn;
  else delete profile.fullyRestoredOn;

  const notes = (document.getElementById('pf-notes')?.value || '').trim().slice(0, 2000);
  if (notes) profile.notes = notes;
  else delete profile.notes;

  profile.updatedAt = Date.now();
  saveProfile(profile);
  if (typeof syncAfterSave === 'function') syncAfterSave();
  if (typeof p2pBroadcastManifest === 'function') p2pBroadcastManifest();
  else if (typeof p2pBroadcastJournal === 'function') p2pBroadcastJournal();
  toast('Profile saved');
  openProfile();
}

function savePublicProfileField(key, value) {
  if (typeof getPublicProfileSettings !== 'function') return;
  const s = getPublicProfileSettings();
  s[key] = value;
  savePublicProfileSettings(s);
}

// ── Settings page ─────────────────────────────────────────────────────────────

async function renderSettingsPage() {
  const el = document.getElementById('settings-content');
  if (!el) return;

  const vanish   = ((typeof getVanishSettings === 'function') ? getVanishSettings() : null) || { enabled: true, ttl: 90 };
  const wm       = ((typeof getWatermarkSettings === 'function') ? getWatermarkSettings() : null) || { style: 'none', opacity: 'medium', customText: '' };
  const encEnabled   = (typeof idbMetaGet === 'function') ? await idbMetaGet('encryptionEnabled')   : null;
  const encSetupDone = (typeof idbMetaGet === 'function') ? await idbMetaGet('encryptionSetupDone') : null;
  const schemaV      = (typeof getSchemaVersion === 'function') ? await getSchemaVersion() : null;
  const peerUsesEnc  = lsGet('frj_peer_uses_encryption', false);

  const entries = (typeof getEntries === 'function') ? getEntries().sort((a, b) => b.date.localeCompare(a.date)) : [];
  const latestWithPhoto = entries.find(e => e.imgFront);
  const previewKey = latestWithPhoto ? latestWithPhoto.imgFront : null;
  const wmText = (wm.customText || '').trim() ||
    ((typeof getProfile === 'function') ? (getProfile().displayName || '') : '') ||
    'Your Name';

  const ttlOpts = [30, 60, 90, 120, 180, 365];

  const theme = (typeof getTheme === 'function') ? getTheme() : 'light';

  // Storage health facts for the Data safety panel
  const shPersisted = navigator.storage?.persisted ? await navigator.storage.persisted().catch(() => null) : null;
  const shEst = navigator.storage?.estimate ? await navigator.storage.estimate().catch(() => null) : null;
  const abHandle = (typeof idbMetaGet === 'function') ? await idbMetaGet('autoBackupHandle').catch(() => null) : null;
  const abLast = parseInt(localStorage.getItem('frj_autobackup_last') || '0', 10);
  const fsaSupported = !!window.showSaveFilePicker;

  el.innerHTML = `<div style="max-width:680px;margin:0 auto;display:flex;flex-direction:column;gap:1.5rem;padding:0 0 2rem">

    <!-- Appearance -->
    <div class="panel">
      <div class="ph"><h2>Appearance</h2></div>
      <div class="pb">
        <p style="font-size:13px;color:var(--muted);margin:0 0 12px;line-height:1.6">Light is the default. Dark is easier on the eyes in the evening; System follows your device setting.</p>
        <div style="display:flex;gap:8px;flex-wrap:wrap">
          <button class="btn btn-sm ${theme === 'light' ? 'btn-primary' : 'btn-ghost'}" data-action="setTheme" data-a0="light">Light</button>
          <button class="btn btn-sm ${theme === 'dark' ? 'btn-primary' : 'btn-ghost'}" data-action="setTheme" data-a0="dark">Dark</button>
          <button class="btn btn-sm ${theme === 'system' ? 'btn-primary' : 'btn-ghost'}" data-action="setTheme" data-a0="system">System</button>
        </div>
      </div>
    </div>

    <!-- Encryption -->
    <div class="panel">
      <div class="ph"><h2>Encryption</h2></div>
      <div class="pb">
        ${(() => {
          if (!encEnabled || !encSetupDone) {
            const nudge = peerUsesEnc ? `<p style="font-size:12px;color:#f59e0b;margin:0 0 10px;line-height:1.5">⚠ Your paired device uses encryption. Set it up here to keep both devices secure.</p>` : '';
            return `${nudge}<p style="font-size:13px;color:var(--muted);margin:0 0 12px;line-height:1.6">Your journal data is stored <strong>unencrypted</strong>. Set a password to protect it on this device.</p>
              <button class="btn btn-primary btn-sm" data-action="openEncryptionSettings">Set up encryption</button>`;
          }
          if (schemaV !== 2) {
            return `<p style="font-size:13px;color:var(--muted);margin:0 0 12px;line-height:1.6">Encryption is enabled but your existing data hasn't been migrated yet.</p>
              <div style="display:flex;gap:8px;flex-wrap:wrap">
                <button class="btn btn-ghost btn-sm" data-action="openEncryptionSettings">Change password</button>
                <button class="btn btn-primary btn-sm" data-action="showMigrationModal">Encrypt local data</button>
              </div>`;
          }
          return `<p style="font-size:13px;color:var(--muted2);margin:0 0 12px;line-height:1.6">✓ Your data is fully encrypted on this device.</p>
            <button class="btn btn-ghost btn-sm" data-action="openEncryptionSettings">Change password</button>`;
        })()}
      </div>
    </div>

    <!-- Data safety -->
    <div class="panel">
      <div class="ph"><h2>Data safety</h2></div>
      <div class="pb">
        <p style="font-size:13px;color:var(--muted);margin:0 0 10px;line-height:1.6">Your journal lives in this browser's storage, and browsers can clear that storage. Keep more than one copy: export backups, pair a second device, or set an automatic backup file.</p>
        <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:12px">
          Permanent storage: ${shPersisted === true ? `<span style="color:var(--green)">granted</span>` : shPersisted === false ? `<span style="color:var(--amber)">not granted, the browser may evict data</span>` : 'unknown'}
          ${shEst?.usage != null ? ` · Using ${(shEst.usage / 1048576).toFixed(1)} MB${shEst.quota ? ' of ' + (shEst.quota / 1073741824).toFixed(1) + ' GB allowed' : ''}` : ''}
        </div>
        ${fsaSupported ? `
          <div style="font-size:13px;font-weight:600;margin-bottom:4px">Automatic backup file</div>
          <p style="font-size:12px;color:var(--muted2);margin:0 0 10px;line-height:1.5">Pick a file on your disk and the app keeps an encrypted backup written to it in the background. Even if the browser wipes itself, that file survives.</p>
          <div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
            ${abHandle ? `
              <button class="btn btn-ghost btn-sm" data-action="autoBackupSetup">Change file</button>
              <button class="btn btn-danger btn-sm" data-action="autoBackupDisable">Disable</button>
              <span id="autobackup-status" style="font-family:var(--mono);font-size:11px;color:var(--muted2)">${abLast ? 'Last written: ' + new Date(abLast).toLocaleString() : 'Not written yet'}</span>
            ` : `<button class="btn btn-secondary btn-sm" data-action="autoBackupSetup">Set up automatic backup file</button>`}
          </div>
        ` : `<p style="font-size:12px;color:var(--muted2);margin:0">This browser cannot keep an automatic backup file (desktop Chrome and Edge only). Export backups regularly, or pair a second device so each holds a full copy.</p>`}
        <div style="border-top:1px solid var(--border2);margin-top:14px;padding-top:12px">
          <div style="font-size:13px;font-weight:600;margin-bottom:4px">Download an offline copy</div>
          <p style="font-size:12px;color:var(--muted2);margin:0 0 10px;line-height:1.5">Save the whole app to your computer so you can run it with no internet, keep it if this site ever goes away, and read every line of its code. Nothing about how the app works is hidden.</p>
          <a class="btn btn-ghost btn-sm" href="forestorers-offline.zip" download>Download offline copy (.zip)</a>
        </div>
      </div>
    </div>

    <!-- My Devices -->
    <div class="panel">
      <div class="ph"><h2>My Devices</h2></div>
      <div class="pb">
        <p style="font-size:13px;color:var(--muted);margin:0 0 12px;line-height:1.6">Pair another device to sync your journal automatically. Both devices share the same identity.</p>
        <div id="devices-list"><span style="font-size:12px;color:var(--muted2)">Loading…</span></div>
      </div>
    </div>

    <!-- App Version -->
    <div class="panel">
      <div class="ph"><h2>App version</h2></div>
      <div class="pb">
        <p style="font-size:13px;color:var(--muted);margin:0 0 12px;line-height:1.6">If you're not seeing the latest features (especially in the iOS home-screen app), tap to check for updates. Your data stays untouched.</p>
        <button class="btn btn-secondary btn-sm" data-action="checkForUpdates">Check for updates</button>
        <a href="CHANGELOG.md" target="_blank" rel="noopener" class="ciref-open-btn" style="margin-left:10px">Release notes</a>
        <div id="sw-update-status" style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-top:8px"></div>
      </div>
    </div>

    <!-- Vanish Mechanic -->
    <div class="panel">
      <div class="ph"><h2>Fade</h2></div>
      <div class="pb">
        <p style="font-size:13px;color:var(--muted);margin:0 0 14px;line-height:1.6">All your data — journal entries, stats, photos, and cached images — auto-deletes from buddies' devices after you stop syncing. This is the full erasure timer. Cached photo timers are set separately per tier in the Buddies permissions.</p>
        <div style="display:flex;align-items:center;gap:16px;flex-wrap:wrap">
          <label style="display:flex;align-items:center;gap:7px;font-size:13px;cursor:pointer">
            <input type="checkbox" id="st-vanish-on" ${vanish.enabled !== false ? 'checked' : ''}>
            Enabled
          </label>
          <div style="display:flex;align-items:center;gap:8px">
            <span style="font-size:13px;color:var(--muted)">Clear after</span>
            <select id="st-vanish-ttl" class="input" style="width:auto">
              ${ttlOpts.map(d => `<option value="${d}" ${(vanish.ttl || 90) == d ? 'selected' : ''}>${d === 365 ? '1 year' : d + ' days'}</option>`).join('')}
            </select>
            <span style="font-size:13px;color:var(--muted)">of no sync</span>
          </div>
        </div>
      </div>
    </div>

    <!-- Photo Watermark -->
    <div class="panel collapsed">
      <div class="ph" style="cursor:pointer;user-select:none" data-action="toggleCollapsed" title="Click to expand/collapse">
        <h2>Photo Watermark</h2>
        <span style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-left:auto">▾</span>
      </div>
      <div class="pb" id="wm-panel-body">
        <p style="font-size:13px;color:var(--muted);margin:0 0 14px;line-height:1.6">
          Controls how your name appears on photos you share with buddies.
          The <strong>forestorers.org</strong> watermark is always baked into shared photos.
          The name watermark below is also baked in when sharing.
        </p>
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px">
          <div class="fg" style="margin:0">
            <label>Style</label>
            <select id="st-wm-style" class="input">
              <option value="none" ${wm.style === 'none' ? 'selected' : ''}>None</option>
              <option value="center" ${wm.style === 'center' ? 'selected' : ''}>Name centered</option>
              <option value="corner" ${wm.style === 'corner' ? 'selected' : ''}>Corner</option>
              <option value="tiled" ${wm.style === 'tiled' ? 'selected' : ''}>Tiled repeat</option>
              <option value="combined" ${wm.style === 'combined' ? 'selected' : ''}>Name + tiled</option>
            </select>
          </div>
          <div class="fg" style="margin:0">
            <label>Opacity</label>
            <select id="st-wm-opacity" class="input">
              <option value="light" ${wm.opacity === 'light' ? 'selected' : ''}>Light</option>
              <option value="medium" ${wm.opacity === 'medium' ? 'selected' : ''}>Medium</option>
              <option value="strong" ${wm.opacity === 'strong' ? 'selected' : ''}>Strong</option>
            </select>
          </div>
        </div>
        <div class="fg" style="margin:0 0 14px">
          <label>Watermark text <span style="font-weight:400;color:var(--muted2)">(leave blank to use your display name)</span></label>
          <input type="text" id="st-wm-text" class="input" placeholder="${esc(wmText)}" value="${esc(wm.customText || '')}">
        </div>
        ${previewKey ? `
        <div style="margin-bottom:14px">
          <div style="font-family:var(--mono);font-size:10px;color:var(--muted2);margin-bottom:6px;text-transform:uppercase;letter-spacing:.06em">Live Preview (your latest photo)</div>
          <div id="wm-preview-wrap" style="max-width:300px">
            ${typeof photoCanvas === 'function' ? photoCanvas(previewKey, { watermarkName: wmText, lightbox: false }) : ''}
          </div>
        </div>` : `<div style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-bottom:14px">Add a journal entry with a photo to see a live preview here.</div>`}
        <button class="btn btn-primary btn-sm" data-action="saveWatermarkSettingsPage">Save Watermark Settings</button>
      </div>
    </div>

    ${typeof renderNotifSettingsPanel === 'function' ? renderNotifSettingsPanel() : ''}

    <!-- View settings -->
    <div class="panel collapsed">
      <div class="ph" style="cursor:pointer;user-select:none" data-action="toggleCollapsed" title="Click to expand/collapse">
        <h2>View</h2>
        <span style="font-family:var(--mono);font-size:11px;color:var(--muted2);margin-left:auto">▾</span>
      </div>
      <div class="pb">
        <label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer;margin-bottom:4px">
          <input type="checkbox" id="st-dash-show-all-imgs" ${localStorage.getItem('frj_dash_show_all_imgs') === '1' ? 'checked' : ''} style="margin-top:2px;flex-shrink:0">
          <span style="font-size:12px;color:var(--text)">Show all dashboard images instead of 📷 to look</span>
        </label>
        <p style="font-size:11px;color:var(--muted2);margin:2px 0 14px 22px;line-height:1.5">When enabled, buddy photos on the dashboard load automatically instead of requiring a click. May use more data on mobile.</p>

        <label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer;margin-bottom:4px">
          <input type="checkbox" id="st-hide-buddy-data" ${localStorage.getItem('frj_hide_buddy_data') === '1' ? 'checked' : ''} style="margin-top:2px;flex-shrink:0">
          <span style="font-size:12px;color:var(--text)">Hide all buddy data</span>
        </label>
        <p style="font-size:11px;color:var(--muted2);margin:2px 0 0 22px;line-height:1.5">Hides buddy entries, photos, and activity from the dashboard, journal, activity feed, and timeline. Useful when others might see your screen. Buddies remain connecte, just hidden from view.</p>
      </div>
    </div>

    <!-- Delete all data -->
    <div class="panel">
      <div class="ph"><h2>Delete all data from my device</h2></div>
      <div class="pb">
        <p style="font-size:13px;color:var(--muted);margin:0 0 14px;line-height:1.6">Permanently wipes all journal entries, photos, contacts, settings, and encryption keys from this device. This cannot be undone.</p>
        <button class="btn btn-sm" style="background:#ef444420;color:#f87171;border:1px solid #ef444440" data-action="wipeAllData">Delete everything</button>
      </div>
    </div>

  </div>`;

  if (typeof renderDevicesList === 'function') renderDevicesList();
  if (typeof updateDataEncBadge === 'function') updateDataEncBadge();
}

function _settingsWmChange() {
  _updateWmPreview();
}

function _updateWmPreview() {
  const canvas = document.querySelector('#wm-preview-wrap canvas[data-img-key]');
  if (!canvas) return;
  const text = (document.getElementById('st-wm-text')?.value || '').trim() ||
    ((typeof getProfile === 'function') ? (getProfile().displayName || 'Your Name') : 'Your Name');
  const style   = document.getElementById('st-wm-style')?.value   || 'none';
  const opacity = document.getElementById('st-wm-opacity')?.value || 'medium';
  canvas.dataset.wmName = text;
  // Update live settings for the redraw
  if (typeof saveWatermarkSettings === 'function') {
    const cur = typeof getWatermarkSettings === 'function' ? getWatermarkSettings() : {};
    saveWatermarkSettings({ ...cur, style, opacity });
  }
  delete canvas.dataset.rendered;
  if (typeof _drawPhotoCanvas === 'function') _drawPhotoCanvas(canvas);
}

function saveWatermarkSettingsPage() {
  const style      = document.getElementById('st-wm-style')?.value   || 'none';
  const opacity    = document.getElementById('st-wm-opacity')?.value || 'medium';
  const customText = (document.getElementById('st-wm-text')?.value   || '').trim();
  if (typeof saveWatermarkSettings === 'function') saveWatermarkSettings({ style, opacity, customText });
  _updateWmPreview();
  if (typeof toast === 'function') toast('Watermark settings saved ✓');
  if (typeof updateDataEncBadge === 'function') updateDataEncBadge();
}

async function _wipeAllData() {
  if (!confirm('This will permanently erase ALL your data: journal entries, photos, contacts, devices, and encryption keys. This cannot be undone.')) return;
  if (!confirm('Are you sure? Everything on this device will be deleted.')) return;
  localStorage.clear();
  // Close the open IDB connection so deleteDatabase doesn't get blocked
  if (typeof _idb !== 'undefined' && _idb) {
    try { _idb.close(); } catch(e) {}
    // eslint-disable-next-line no-global-assign
    _idb = null;
  }
  await new Promise(resolve => {
    const r = indexedDB.deleteDatabase('frj_db');
    r.onsuccess = resolve;
    r.onerror   = resolve;
    r.onblocked = () => setTimeout(resolve, 800); // fallback: reload after brief wait
  });
  location.reload();
}

function _saveVanishSettings() {
  const enabled = document.getElementById('st-vanish-on')?.checked ?? true;
  const ttl     = parseInt(document.getElementById('st-vanish-ttl')?.value || '90', 10);
  if (typeof saveVanishSettings === 'function') saveVanishSettings({ enabled, ttl });
  if (typeof stampSettingsTs     === 'function') stampSettingsTs();
  if (typeof p2pBroadcastSettingsPush === 'function') p2pBroadcastSettingsPush();
  else if (typeof p2pBroadcastJournal === 'function') p2pBroadcastJournal();
  if (typeof syncAfterSave       === 'function') syncAfterSave();
  if (typeof toast               === 'function') toast('Vanish settings saved ✓');
}
