Site Builder
Editing:
index7.php
writable 0666
<?php /** -------------------------------------------------------------- * Promptinator v6 (Full) – SELF‑CONTAINED TEMPLATE (v6.4) * ------------------------------------------------------------- * • Root‑anchored business path /ph/<10‑digit‑phone>/business.json * • Meta tags always render (biz description fallback; prompt ⇢ desc) * • ?debug=1 prints path(s) tried + flags in a red box * • FULL JavaScript restored (no more “omitted”) so tokens convert * and AI buttons populate. * ------------------------------------------------------------- */ // ---------- 1. BOOTSTRAP INPUTS ------------------------------------ $ph = $_GET['ph'] ?? null; // phone‑slug directory (10‑digit) $idx = (int) ($_GET['idx'] ?? 0); // prompt index (0‑based) $debug = isset($_GET['debug']); // ---------- 2. LOAD BUSINESS JSON ---------------------------------- $biz = []; $pathsTried=[]; if ($ph) { $root = rtrim($_SERVER['DOCUMENT_ROOT'] ?? __DIR__, '/'); $pathsTried[] = $p1 = "$root/ph/$ph/business.json"; $pathsTried[] = $p2 = __DIR__ . "/ph/$ph/business.json"; foreach ($pathsTried as $p) if (is_readable($p)) { $biz = json_decode(file_get_contents($p), true) ?? []; break; } } // alias lat/lon ➜ latitude/longitude for uniformity if (isset($biz['lat']) && !isset($biz['latitude'])) $biz['latitude'] = $biz['lat']; if (isset($biz['lon']) && !isset($biz['longitude'])) $biz['longitude'] = $biz['lon']; // ---------- 3. LOAD PROMPTS ---------------------------------------- $prompts = []; $promptsPath = __DIR__ . '/prompts.json'; if (is_readable($promptsPath)) { $arr = json_decode(file_get_contents($promptsPath), true); if (is_array($arr)) $prompts = array_column($arr, 'prompt'); } $currentPrompt = $prompts[$idx] ?? ''; $currentIndex = $idx; // ---------- 4. TOKEN RESOLUTION (for meta tags) -------------------- $resolveTokens = function(string $tpl, array $biz): string { return preg_replace_callback('/\[(?:[ABCDI]?)-?([^~|\-\]]+)/', function($m) use ($biz) { $lab = trim($m[1]); if ($lab === 'service' && !empty($biz['tags'])) return implode(' • ', $biz['tags']); if ($lab === 'location' && !empty($biz['location_tags'])) return implode(' • ', $biz['location_tags']); return $biz[$lab] ?? ''; }, $tpl); }; $resolvedPrompt = $resolveTokens($currentPrompt, $biz); $first4Words = trim(implode(' ', array_slice(preg_split('/\s+/', strip_tags($resolvedPrompt)), 0, 4))); // ---------- 5. CANONICAL URL --------------------------------------- $chronicleUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']==='on' ? 'https://' : 'http://') . ($_SERVER['HTTP_HOST'] ?? 'localhost') . strtok($_SERVER['REQUEST_URI'], '?'); $safe = fn($k)=>htmlspecialchars((string)($biz[$k] ?? ''), ENT_QUOTES); $metaDescRaw = $resolvedPrompt ?: ($biz['description'] ?? ''); $metaDesc = htmlspecialchars(trim($metaDescRaw), ENT_QUOTES); $keywords = htmlspecialchars(implode(', ', array_unique(array_merge($biz['tags'] ?? [], $biz['location_tags'] ?? []))), ENT_QUOTES); $titleBase = $safe('name') ?: 'Prompt Playground'; $titleFull = $first4Words ? "$titleBase | $first4Words" : "$titleBase | Prompt Playground"; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title><?= $titleFull ?></title> <!-- SEO & Social --> <?php if ($metaDesc): ?> <meta name="description" content="<?= $metaDesc ?>"> <?php endif; ?> <?php if ($keywords): ?> <meta name="keywords" content="<?= $keywords ?>"> <?php endif; ?> <?php if ($biz): ?> <meta property="og:title" content="<?= $titleFull ?>"> <meta property="og:description" content="<?= $metaDesc ?>"> <meta property="og:url" content="<?= htmlspecialchars($chronicleUrl) ?>"> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:title" content="<?= $titleFull ?>"> <meta name="twitter:description" content="<?= $metaDesc ?>"> <meta name="geo.region" content="US-<?= $safe('state') ?>"> <meta name="geo.placename" content="<?= $safe('city') ?>"> <?php if (!empty($biz['latitude']) && !empty($biz['longitude'])): ?> <meta name="geo.position" content="<?= $biz['latitude'] . ',' . $biz['longitude'] ?>"> <meta name="ICBM" content="<?= $biz['latitude'] . ',' . $biz['longitude'] ?>"> <?php endif; ?> <?php endif; ?> <!-- ---------- 5. DESIGN RESOURCES -------------------------------- --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"> <script src="https://cdn.jsdelivr.net/npm/marked@12/marked.min.js"></script> <!-- ---------- 6. INLINE CSS (unchanged) -------------------------- --> <style> body { max-width:900px; margin:2rem auto; font-family:system-ui,sans-serif; } h1 { text-align:center; margin-bottom:1.5rem; } .biz-card{ background:#f9fafb; border:1px solid #ccc; border-radius:6px; padding:1rem; margin-bottom:1rem; display:flex; align-items:center; gap:1rem; } .biz-card h2{ margin:0; font-size:1.5rem; } .biz-details{ display:flex; flex-direction:column; gap:.25rem; } .biz-details p{ margin:0; font-size:1rem; } .biz-icon{ font-size:1.5rem; color:#6c48ff; } .nav-buttons{ display:flex; justify-content:space-between; margin:1rem 0; } .nav-buttons button{ padding:.75rem 1.5rem; font-size:1rem; border:none; border-radius:6px; background:#6c48ff; color:#fff; cursor:pointer; } .nav-buttons button:disabled{ background:#ccc; cursor:default; } .prompt-card{ position:relative; background:#fff; border:1px solid #ddd; border-radius:8px; padding:1rem; margin-bottom:1.5rem; } .prompt-text{ white-space:pre-wrap; line-height:1.6; } .prompt-text h2{ font-size:1.25rem; margin:1rem 0 .5rem; } .prompt-text ul,.prompt-text ol{ margin:.5rem 0 .5rem 1.25rem; } .prompt-text li{ margin:.25rem 0; } .prompt-text strong{ font-weight:700; } .token{ display:inline-flex; background:#e8e0ff; color:#5c3bff; padding:.3rem .6rem; border-radius:4px; cursor:pointer; user-select:none; flex:0 1 auto; min-width:0; white-space:normal; overflow-wrap:break-word; word-break:break-word; margin-bottom:.3rem; } .token.placeholder{ opacity:.6; } .toolbar{ position:absolute; background:#fff; border:1px solid #ccc; border-radius:6px; box-shadow:0 2px 12px rgba(0,0,0,.1); padding:.8rem; display:flex; flex-direction:column; gap:.6rem; z-index:10; width:300px; } .toolbar label{ font-size:.9rem; font-weight:600; margin-bottom:.3rem; } .toolbar input[type="text"], .toolbar select, .toolbar textarea{ width:100%; padding:.4rem; border:1px solid #999; border-radius:4px; font-size:1rem; } .toolbar select{ max-height:200px; overflow:auto; } .toolbar textarea{ resize:vertical; } .checkbox-list{ display:flex; flex-direction:column; gap:.4rem; max-height:200px; overflow:auto; padding-right:.2rem; } .checkbox-list label{ display:flex; align-items:center; gap:.6rem; padding:.4rem .6rem; background:#f0f4ff; border-radius:4px; cursor:pointer; transition:background .2s; } .checkbox-list label:hover{ background:#e0e8ff; } .checkbox-list input[type="checkbox"]{ accent-color:#3b82f6; width:1.2em; height:1.2em; flex-shrink:0; } .ai-btns{ display:flex; gap:.6rem; margin-top:1rem; } .ai-btns a{ flex:1; text-align:center; padding:.7rem; border-radius:6px; color:#fff; text-decoration:none; font-weight:600; } .ai-btns a.chatgpt{ background:#6c48ff; } .ai-btns a.perplexity{ background:#1ca7ec; } .ai-btns a.copilot{ background:#00c853; color:#173E22; } .ai-btns a:hover{ filter:brightness(1.1); } </style> </head> <body> <h1>Promptinator By Melanie AI</h1> <!-- --------- 7. BUSINESS PROFILE CARD ----------------------------- --> <div id="biz-profile" class="biz-card" <?= $biz ? '' : 'hidden' ?>> <div class="biz-icon">🏢</div> <div class="biz-details"> <h2><?= $safe('name') ?></h2> <p>📍 <span class="biz-address"><?= $safe('address') ?></span></p> <p>📞 <span class="biz-phone"><?= $safe('phone') ?></span></p> </div> </div> <!-- Navigation --> <div class="nav-buttons"> <button id="prev-btn" disabled>Previous</button> <button id="next-btn" disabled>Next</button> </div> <!-- Prompt Container --> <div id="app"></div> <!-- ---------- 8. PASS PHP DATA TO JS ------------------------------ --> <script>window.bizFromPHP = <?= json_encode($biz, JSON_HEX_TAG|JSON_HEX_APOS|JSON_HEX_AMP|JSON_HEX_QUOT) ?>;</script> <script> (async () => { const SEP = ' • '; const RX = /\[(?:([ABCDI]?)-)?([^~|\-\]]+)(?:-([^~]*?))?(?:~([^~]*?)~)?\]/g; /* --- Use server‑side business first; fallback to fetch if none --- */ let biz = window.bizFromPHP || {}; if (!biz.name) { const ph = new URLSearchParams(location.search).get('ph') || ''; if (ph) { try { biz = await fetch(`/ph/${ph}/business.json`).then(r=>r.json()); } catch {} } } /* --- Load prompts (client‑side) ---------------------------------- */ let prompts = []; try { prompts = await fetch('prompts.json').then(r=>r.json()).then(a=>a.map(o=>o.prompt)); } catch {} if (!prompts.length) return; const app = document.getElementById('app'); const cards = []; const formatPhone = n => { const d = (n || '').replace(/\D/g,''); return d.length===10 ? `(${d.slice(0,3)}) ${d.slice(3,6)}-${d.slice(6)}` : n; }; if (biz.name) { document.querySelector('#biz-profile .biz-address').textContent = biz.address || ''; document.querySelector('#biz-profile .biz-phone' ).textContent = formatPhone(biz.phone); } /* --- Helper: place token toolbar --------------------------------- */ const placeToolbar = (tb, tok) => { const card = tok.closest('.prompt-card'); const tr = tok.getBoundingClientRect(); const cr = card.getBoundingClientRect(); tb.style.top = (tr.bottom - cr.top) + 'px'; tb.style.left = (tr.left - cr.left) + 'px'; }; /* --- Build prompt cards ------------------------------------------ */ for (const tpl of prompts) { const meta = []; const state = {}; ['name','slogan','description','address','city','state','zip','phone','website'].forEach(k=>state[k]=biz[k]||''); state.service = Array.isArray(biz.tags) ? biz.tags.slice(0,1) : []; state.location = Array.isArray(biz.location_tags) ? biz.location_tags.slice(0,1) : []; tpl.replace(RX, (m, cmd='', lab, ops='', def='') => { lab = lab.trim(); let opts = []; if (cmd==='C') { opts = lab==='service' ? biz.tags : lab==='location' ? biz.location_tags : ops.split('|').filter(Boolean); state[lab] = def ? def.split(',').map(v=>v.trim()) : (opts.length ? [opts[0]] : []); } else if (cmd==='B') { opts = ops.split('|').filter(Boolean); state[lab] = def || opts[0] || ''; } else if (cmd==='D') { state[lab] = def || state[lab] || ''; } else if (cmd==='I') { state[lab] = def || state[lab] || ''; } else { state[lab] = def || state[lab] || ''; } meta.push({cmd: cmd || 'A', lab, opts, rawOps: ops, defaultVal: def}); return ''; }); /* --- Card skeleton --- */ const card = document.createElement('div'); card.className='prompt-card'; card.innerHTML=`<div class="prompt-text"></div><div class="ai-btns"><a class="chatgpt" data-base="https://chatgpt.com/?prompt=" target="_blank">ChatGPT</a><a class="perplexity" data-base="https://www.perplexity.ai/search?q=" target="_blank">Perplexity</a><a class="copilot" data-base="https://copilot.microsoft.com/?q=" target="_blank">Copilot</a></div>`; app.append(card); cards.push(card); /* --- Hidden toolbar --- */ const tb = document.createElement('div'); tb.className='toolbar'; tb.style.display='none'; card.append(tb); /* --- Render helpers --- */ const renderText = () => { const cont = card.querySelector('.prompt-text'); const substituted = tpl.replace(RX, (m, cmd='', lab) => { const idx = meta.findIndex(x=>x.lab===lab.trim()); return cmd==='C' ? (state[lab]||[]).map(tag=>`<span class="token" data-i="${idx}">${tag}</span>`).join(' ') : `<span class="token" data-i="${idx}">${state[lab]||''}</span>`; }); cont.innerHTML = marked.parse(substituted); cont.querySelectorAll('.token').forEach(t=>t.onclick=onTokenClick); }; const rebuild = () => { renderText(); const out = tpl.replace(RX, (m, cmd='', lab)=> cmd==='C' ? (state[lab.trim()]||[]).join(SEP) : (state[lab.trim()]||'')); card.querySelectorAll('.ai-btns a').forEach(a=>a.href = a.dataset.base + encodeURIComponent(out)); }; renderText(); rebuild(); /* --- Token click logic --- */ function onTokenClick(e) { const tok = e.currentTarget; const idx = +tok.dataset.i; const {cmd, lab, opts, rawOps, defaultVal} = meta[idx]; tb.innerHTML=''; placeToolbar(tb,tok); tb.style.display='flex'; document.addEventListener('mousedown', function off(ev){ if(!tb.contains(ev.target)&&ev.target!==tok){ tb.style.display='none'; document.removeEventListener('mousedown', off); } }); const label=document.createElement('label'); label.textContent=lab.replace(/_/g,' '); tb.append(label); if(cmd==='C'){ // multi‑select const list=document.createElement('div'); list.className='checkbox-list'; opts.forEach(o=>{ const lb=document.createElement('label'); const cb=document.createElement('input'); cb.type='checkbox'; cb.value=o; cb.checked=(state[lab]||[]).includes(o); cb.onchange=()=>{ state[lab]=[...list.querySelectorAll('input:checked')].map(i=>i.value); rebuild(); }; lb.append(cb,' ',o); list.append(lb); }); tb.append(list); } else if(cmd==='B'){ // radio opts.forEach(o=>{ const lb=document.createElement('label'); const rd=document.createElement('input'); rd.type='radio'; rd.name=lab; rd.value=o; rd.checked=(state[lab]===o); rd.onchange=()=>{ state[lab]=o; rebuild(); tb.style.display='none'; }; lb.append(rd,' ',o); tb.append(lb); }); } else if(cmd==='D'){ // dropdown const sel=document.createElement('select'); if(opts.length){ opts.forEach(o=>{ const op=document.createElement('option'); op.value=o; op.textContent=o; if(o===state[lab]) op.selected=true; sel.append(op); }); } else { const max=parseInt(rawOps||defaultVal,10)||0; for(let i=1;i<=max;i++){ const op=document.createElement('option'); op.value=String(i); op.textContent=i; if(String(i)===state[lab]) op.selected=true; sel.append(op);} } sel.onchange=()=>{ state[lab]=sel.value; rebuild(); tb.style.display='none'; }; tb.append(sel); } else if(cmd==='I'){ // iframe picker or numeric if(opts.length){ const select=document.createElement('select'); const ph=document.createElement('option'); ph.disabled=true; ph.selected=true; ph.textContent=state[lab]||'Pick a site'; select.append(ph); opts.forEach(url=>{ const op=document.createElement('option'); op.value=url; op.textContent=url; select.append(op); }); const iframe=document.createElement('iframe'); iframe.hidden=true; iframe.style='width:100%;height:200px'; const btn=document.createElement('button'); btn.textContent='Use site'; btn.style.marginTop='.6rem'; select.onchange=()=>{ iframe.hidden=false; iframe.src=select.value; }; btn.onclick=()=>{ state[lab]=select.value; rebuild(); tb.style.display='none'; }; tb.append(select, iframe, btn); } else { const inp=document.createElement('input'); inp.type='number'; inp.value=state[lab]||''; inp.oninput=()=>{ state[lab]=inp.value; rebuild(); }; tb.append(inp); } } else { // free text const field = cmd==='A' ? (()=>{ const t=document.createElement('textarea'); t.rows=4; return t; })() : (()=>{ const i=document.createElement('input'); i.type='text'; return i; })(); field.value=state[lab]||''; field.oninput=()=>{ state[lab]=field.value; rebuild(); }; tb.append(field); } } } /* --- Navigation -------------------------------------------------- */ const prevBtn=document.getElementById('prev-btn'); const nextBtn=document.getElementById('next-btn'); let current=<?= $currentIndex ?>; const show=idx=>{ cards.forEach((c,i)=>c.style.display=(i===idx?'block':'none')); prevBtn.disabled = idx===0; nextBtn.disabled = idx===cards.length-1; prevBtn.textContent = idx>0 ? '← '+prompts[idx-1].split(/\s+/).slice(0,4).join(' ')+'…' : 'Previous'; nextBtn.textContent = idx<prompts.length-1 ? prompts[idx+1].split(/\s+/).slice(0,4).join(' ')+'… →' : 'Next'; }; prevBtn.onclick=()=>{ if(current>0) show(--current); }; nextBtn.onclick=()=>{ if(current<cards.length-1) show(++current); }; show(current); /* --- Shortcode tutorial ------------------------------------------ */ const tutorial=document.createElement('section'); tutorial.style='margin-top:2rem;padding:1rem;border-top:1px solid #ddd;'; tutorial.innerHTML=`<h2>Shortcode Tutorial</h2><ul><li><strong>A-tokens</strong> – <code>[A-key-~default~]</code> (text)</li><li><strong>B-tokens</strong> – <code>[B-key-|opt1|opt2|]</code> (single)</li><li><strong>C-tokens</strong> – <code>[C-key-|opt1|opt2|]</code> (multi)</li><li><strong>D-tokens</strong> – <code>[D-key-5~3~]</code> (dropdown)</li><li><strong>I-tokens</strong> – <code>[I-key-|url1|url2|]</code> (iframe)</li></ul>`; app.append(tutorial); })(); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel