Site Builder
Editing:
index7.php
writable 0666
<!doctype html> <html lang="en" data-theme="amber"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>TruthCase™ — The Case for Truth | RF SAFE</title> <meta name="description" content="Pick your phone model and get instant Buy on Amazon and Buy Now links. Models load from truthcases.json. Designed for clarity and speed with an amber, low‑blue aesthetic." /> <style> /* ---------- AMBER (LOW-BLUE) THEME ---------- */ :root{ --brand:#ffb020; --brand-ink:#ffe1a6; --ink:#ffe8c8; --muted:#d7c9ad; --ring: rgba(255,176,32,.35); --bg:linear-gradient(180deg,#0e0b06 0%, #171208 100%); --card:#151109; --border:#372b1a; --shadow:0 14px 36px rgba(0,0,0,.45); --soft:rgba(255,176,32,.10); --soft2:rgba(255,176,32,.08); --radius:22px; } *{box-sizing:border-box} html,body{ margin:0;background:var(--bg);color:var(--ink); font:16px/1.75 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","DejaVu Sans Mono","Courier New",monospace; letter-spacing:.2px; } a{color:var(--brand);text-decoration:none} a:hover{text-decoration:underline} .wrap{max-width:1180px;margin:0 auto;padding:28px 18px 120px} /* NAV */ .nav{position:sticky;top:0;z-index:40;backdrop-filter:saturate(1.1) blur(8px); background:rgba(24,18,10,.68);border-bottom:1px solid var(--border)} .navin{max-width:1180px;margin:0 auto;display:flex;gap:12px;align-items:center;justify-content:space-between;padding:10px 18px} .brand{display:flex;gap:10px;align-items:center;font-weight:900;color:var(--brand-ink)} .dot{width:12px;height:12px;border-radius:50%;background:var(--brand);box-shadow:0 0 18px var(--ring)} .row{display:flex;gap:10px;align-items:center;flex-wrap:wrap} .btn{display:inline-flex;align-items:center;gap:10px;border-radius:999px;padding:11px 16px;font-weight:900;border:2px solid var(--brand); background:transparent;color:var(--brand);box-shadow:0 8px 20px var(--ring);transition:.2s;cursor:pointer} .btn:hover{transform:translateY(-1px)} .btn.primary{background:var(--brand);color:#1a1309} .btn.ghost{border-color:var(--border);color:var(--brand)} .btn.small{padding:8px 12px;font-size:.92rem} /* HERO */ .hero{ margin-top:18px;background: radial-gradient(900px 320px at 10% -20%, var(--soft), transparent 60%), radial-gradient(900px 320px at 90% -40%, var(--soft), transparent 60%), var(--card); border-radius:28px;box-shadow:var(--shadow);padding:28px } .eyebrow{display:inline-flex;align-items:center;gap:8px;font-weight:900;letter-spacing:.08em;text-transform:uppercase;color:var(--brand); border:1px solid var(--border);background:var(--soft2);border-radius:999px;padding:6px 12px} h1{margin:10px 0 8px;font-size:clamp(28px,4.8vw,54px);line-height:1.06;color:var(--brand-ink)} .subtitle{color:var(--muted);max-width:980px} .badges{display:flex;flex-wrap:wrap;gap:10px;margin-top:10px} .badge{font-size:12px;font-weight:900;border:1px solid var(--border);background:var(--soft2);color:var(--brand); border-radius:999px;padding:6px 10px} .cta-row{display:flex;gap:12px;flex-wrap:wrap;margin-top:16px} /* CONTENT CARDS */ .section{margin-top:44px} .card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:22px;border:1px solid var(--border)} .grid{display:grid;gap:18px} @media(min-width:980px){.cols-2{grid-template-columns:1fr 1fr}.cols-3{grid-template-columns:1fr 1fr 1fr}} /* ===== MODAL (enhanced visibility, mobile sheet, accessibility) ===== */ .modal-backdrop{ position:fixed; inset:0; z-index:100; background: radial-gradient(900px 420px at 50% 10%, rgba(255,176,32,.18), rgba(0,0,0,.78) 58%), rgba(0,0,0,.62); display:none; opacity:0; pointer-events:none; transition: opacity .25s ease; } .modal{ position:fixed; inset:0; z-index:101; display:none; place-items:center; pointer-events:none; opacity:0; transition: opacity .25s ease; } .modal-panel{ width:min(720px,94vw); background:#1a140b; /* a touch lighter to stand out from cards */ border-radius:20px; border:1px solid #6b4a1f; box-shadow: 0 22px 60px rgba(0,0,0,.6), 0 0 0 2px rgba(255,176,32,.18) inset, 0 0 0 6px rgba(255,176,32,.10); padding:18px; pointer-events:auto; transform: translateY(6px) scale(.985); transition: transform .28s ease, box-shadow .28s ease, background .28s ease; position:relative; } .modal-panel::before{ content:""; position:absolute; inset:-2px; border-radius:22px; pointer-events:none; box-shadow: 0 0 0 2px rgba(255,176,32,.25), 0 0 40px rgba(255,176,32,.14); } .modal-head{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:8px} .modal-title{font-weight:900;color:#ffe1a6;margin:0} .close{appearance:none;border:1px solid #5a3f1c;background:#120d07;color:#ffb020; border-radius:999px;padding:8px 12px;font-weight:900;cursor:pointer;box-shadow:0 6px 18px rgba(0,0,0,.35)} .close:hover{transform:translateY(-1px)} .form-row{display:grid;gap:14px;margin-top:12px} @media(min-width:700px){ .form-row{grid-template-columns:1fr 1fr} } label{font-weight:700} select{ width:100%;padding:12px;border:1px solid #5a3f1c;border-radius:12px; background:#0f0c06;color:#ffe8c8;font-size:16px; /* avoid iOS zoom */ box-shadow: 0 8px 20px rgba(0,0,0,.25) inset; } select:focus-visible{ outline:2px solid rgba(255,176,32,.55) } .modal-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:18px} .helper{font-size:.9rem;color:var(--muted)} /* open state toggled on body */ body.modal-open .modal-backdrop{ display:block; opacity:1; pointer-events:auto } body.modal-open .modal{ display:grid; opacity:1; pointer-events:auto } body.modal-open .modal-panel{ transform: translateY(0) scale(1) } /* blur/dim page when modal open */ body.modal-open .wrap, body.modal-open .nav{ filter: blur(1.2px) saturate(.85); opacity:.85; transform: scale(.998); transition: filter .2s ease, opacity .2s ease, transform .2s ease; } /* mobile: turn into a bottom sheet with sticky actions */ @media (max-width: 560px){ .modal{ align-items:end; } .modal-panel{ width:100vw; max-height:85vh; overflow:auto; border-radius:16px 16px 0 0; padding:16px 16px 20px; } .modal-panel::after{ content:""; display:block; width:44px; height:4px; border-radius:999px; background:#d7c9ad; opacity:.6; margin:4px auto 8px; } .modal-actions{ position: sticky; bottom:-16px; background: linear-gradient(180deg, rgba(26,20,11,0) 0%, rgba(26,20,11,.96) 42%); padding-top:10px; padding-bottom:4px; } } /* REVEAL */ .reveal{opacity:0;transform:translateY(12px);transition:all .5s ease} .reveal.in{opacity:1;transform:none} /* motion reduction */ @media (prefers-reduced-motion: reduce){ .modal-backdrop, .modal, .modal-panel, body.modal-open .wrap, body.modal-open .nav{ transition: none !important; } } </style> </head> <body> <!-- NAV --> <div class="nav" role="navigation" aria-label="Primary"> <div class="navin"> <div class="brand"><span class="dot"></span><span>RF SAFE • TruthCase™</span></div> <div class="row"> <a class="btn ghost" href="#why">Why</a> <a class="btn ghost" href="#how">How</a> <button class="btn primary" id="openModalBtn" type="button">Select Your Phone</button> </div> </div> </div> <div class="wrap"> <!-- HERO --> <header class="hero reveal"> <span class="eyebrow">RF SAFE • Since the 1990s</span> <h1>TruthCase™ — the case that tells the truth</h1> <p class="subtitle"> RF SAFE pioneered exposure‑reduction cases in the <strong>1990s</strong>. Pick your phone and get two clean options: <strong>Buy on Amazon</strong> or <strong>Buy Now</strong>. Everything else—models, prices, affiliate settings—loads from a simple <code>truthcases.json</code>. </p> <div class="badges" role="list"> <span class="badge" role="listitem">Design • Usage • Policy • Procedure</span> <span class="badge" role="listitem">No metal loops • No magnets</span> <span class="badge" role="listitem">Shield between you & phone</span> <span class="badge" role="listitem">Antenna‑aware & ultra‑thin</span> </div> <div class="cta-row"> <button class="btn primary" id="openModalBtn2" type="button">Select Your Phone</button> <a class="btn" href="#how">Learn how TruthCase™ reduces exposure</a> </div> </header> <section id="why" class="section card reveal"> <h2 style="margin:0 0 8px;color:var(--brand-ink)">Why this matters</h2> <p>Many “anti‑radiation” cases include design features that can <strong>increase</strong> exposure by detuning antennas (metal loops, magnets, plate steel, thick stacks). TruthCase™ is built to reduce exposure in real use and keep radios efficient—without gimmicks or “99% everywhere” claims.</p> </section> <section id="how" class="section grid cols-2 reveal"> <div class="card"> <h3 style="margin:0 0 6px;color:var(--brand-ink)">What lowers exposure</h3> <ul> <li><strong>Shield between you & phone</strong> for calls and pocket carry</li> <li><strong>Thin, antenna‑aware design</strong> to avoid power boosts</li> <li><strong>No large ear‑side apertures</strong> breaking continuity</li> <li><strong>Habits</strong>: speaker/wired for long calls; distance at night</li> </ul> </div> <div class="card"> <h3 style="margin:0 0 6px;color:var(--brand-ink)">What often raises exposure</h3> <ul> <li>Metal strap loops or decorative hardware near antennas</li> <li>Magnetic detachable wallets & steel plates behind the phone</li> <li>Over‑thick materials near antenna zones</li> <li>“99%” fabric swatch numbers ≠ everyday protection</li> </ul> </div> </section> </div> <!-- MODAL (manufacturer → model → 2 buttons) --> <div class="modal-backdrop" id="backdrop" aria-hidden="true"></div> <div class="modal" id="modal" aria-hidden="true"> <div class="modal-panel" role="dialog" aria-modal="true" aria-labelledby="modalTitle"> <div class="modal-head"> <h3 id="modalTitle" class="modal-title">Select Your Phone</h3> <button class="close" id="closeModalBtn" type="button" aria-label="Close">✕</button> </div> <div class="form-row" id="manuRow" style="display:none"> <div> <label for="manuSelect">Manufacturer</label> <select id="manuSelect" aria-describedby="manuHelp"></select> <div id="manuHelp" class="helper">Choose a brand to filter models.</div> </div> <div> <label for="modelSelect">Model</label> <select id="modelSelect" aria-describedby="modelHelp" disabled></select> <div id="modelHelp" class="helper">Pick your specific phone model.</div> </div> </div> <div class="form-row" id="modelOnlyRow" style="display:none"> <div style="grid-column:1/-1"> <label for="modelSelectSolo">Model</label> <select id="modelSelectSolo" aria-describedby="modelOnlyHelp"></select> <div id="modelOnlyHelp" class="helper">Models loaded from <code>truthcases.json</code>.</div> </div> </div> <div class="modal-actions" id="buyButtons" style="display:none"> <a class="btn" id="buyAmazonBtn" href="#" target="_blank" rel="noopener">Buy on Amazon</a> <a class="btn primary" id="buyDirectBtn" href="#" target="_blank" rel="noopener">Buy Now</a> </div> </div> </div> <script> /* ---------------------- UTILITIES ---------------------- */ const $ = sel => document.querySelector(sel); function unique(arr){ return [...new Set(arr)]; } function getQS(name){ try{ return new URL(location.href).searchParams.get(name); }catch{return null;} } function appendParam(urlStr, key, val){ if(!val) return urlStr; try{ const u = new URL(urlStr, location.href); u.searchParams.set(key, val); return u.toString(); }catch{ const sep = urlStr.includes('?') ? '&' : '?'; return urlStr + sep + encodeURIComponent(key) + '=' + encodeURIComponent(val); } } const norm = s => (s||'').toString().toLowerCase().replace(/\s+/g,' ').trim(); /* ---------------------- STATE ---------------------- */ let DATA = { settings:{}, products:[] }; let manufacturers = []; // discovered list let selectedManufacturer = ""; // current selection let filteredProducts = []; // products for selected manufacturer let affiliateRef = ""; // direct-buy affiliate code (from URL/localStorage/JSON fallback) let amazonTag = ""; // amazon tag (from URL/localStorage/JSON fallback) /* ---------------------- DOM refs ---------------------- */ const openModalBtn = $('#openModalBtn'); const openModalBtn2 = $('#openModalBtn2'); const backdrop = $('#backdrop'); const modal = $('#modal'); const closeModalBtn = $('#closeModalBtn'); const manuRow = $('#manuRow'); const manuSelect = $('#manuSelect'); const modelRowSolo = $('#modelOnlyRow'); const modelSelect = $('#modelSelect'); const modelSelectSolo = $('#modelSelectSolo'); const buyButtons = $('#buyButtons'); const buyDirectBtn = $('#buyDirectBtn'); const buyAmazonBtn = $('#buyAmazonBtn'); /* ===== OPEN/CLOSE (with body class, focus trap, and scroll lock) ===== */ const focusableSelectors = ` a[href], button:not([disabled]), select:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"]) `; let lastFocused = null; function trapFocus(e){ if(e.key !== 'Tab') return; const focusables = Array.from(modal.querySelectorAll(focusableSelectors)) .filter(el => el.offsetParent !== null); if(!focusables.length) return; const first = focusables[0], last = focusables[focusables.length - 1]; if(e.shiftKey && document.activeElement === first){ last.focus(); e.preventDefault(); } else if(!e.shiftKey && document.activeElement === last){ first.focus(); e.preventDefault(); } } function escToClose(e){ if(e.key === 'Escape') closeModal(); } function openModal(){ lastFocused = document.activeElement; document.body.classList.add('modal-open'); document.body.style.overflow = 'hidden'; backdrop.style.display = 'block'; modal.style.display = 'grid'; modal.setAttribute('aria-hidden','false'); requestAnimationFrame(()=>{ backdrop.style.opacity = '1'; modal.style.opacity = '1'; }); // focus first control setTimeout(()=>{ if(manuRow.style.display!=='none') manuSelect.focus(); else modelSelectSolo.focus(); }, 30); document.addEventListener('keydown', trapFocus); document.addEventListener('keydown', escToClose); } function closeModal(){ document.removeEventListener('keydown', trapFocus); document.removeEventListener('keydown', escToClose); document.body.classList.remove('modal-open'); document.body.style.overflow = ''; backdrop.style.opacity = '0'; modal.style.opacity = '0'; modal.setAttribute('aria-hidden','true'); setTimeout(()=>{ backdrop.style.display = 'none'; modal.style.display = 'none'; }, 200); if(lastFocused && typeof lastFocused.focus === 'function'){ lastFocused.focus(); } } backdrop.addEventListener('click', closeModal); closeModalBtn.addEventListener('click', closeModal); document.addEventListener('keydown', e=>{ if(e.key==='Escape' && modal.getAttribute('aria-hidden')==='false') closeModal(); }); openModalBtn.addEventListener('click', openModal); openModalBtn2 && openModalBtn2.addEventListener('click', openModal); /* ---------------------- DATA LOADING ---------------------- */ async function loadData(){ try{ const res = await fetch('truthcases.json', {cache:'no-store'}); if(!res.ok) throw new Error('HTTP '+res.status); const j = await res.json(); DATA = Object.assign({settings:{}, products:[]}, j||{}); if(!DATA.products || !Array.isArray(DATA.products) || !DATA.products.length) throw new Error('No products'); }catch(err){ console.warn('truthcases.json not found or invalid, using demo data.', err); DATA = { settings:{ defaultAffiliateParam:'ref', defaultAmazonTagParam:'tag' }, products:[ { manufacturer:'Apple', title:'TruthCase™ for iPhone 15 Pro Max (Black)', sku:'QC-IP15PM-BLK', asin:'B0DEMO1234', price:59.95, salePrice:49.95, buyUrl:'https://www.rfsafe.com/product/truthcase-iphone-15-pro-max/', affiliateId:'RFS_DEFAULT', amazonTag:'rfsafe-20' }, { manufacturer:'Samsung', title:'TruthCase™ for Galaxy S24 Ultra (Black)', sku:'QC-S24U-BLK', asin:'', price:57.95, salePrice:null, buyUrl:'https://www.rfsafe.com/product/truthcase-s24-ultra/', affiliateId:'RFS_DEFAULT', amazonTag:'' } ] }; } } /* ---------------------- MANUFACTURERS ---------------------- */ function buildManufacturers(){ manufacturers = unique( (DATA.products||[]) .map(p => (p.manufacturer || '').trim()) .filter(Boolean) ); } /* ---------------------- UI BUILDERS ---------------------- */ function renderManufacturerSelect(){ manuSelect.innerHTML = ''; const ph = document.createElement('option'); ph.value = ''; ph.textContent = 'Choose manufacturer…'; manuSelect.appendChild(ph); manufacturers.forEach(m=>{ const opt = document.createElement('option'); opt.value = m; opt.textContent = m; manuSelect.appendChild(opt); }); manuSelect.value = selectedManufacturer || ''; } function renderModelSelect(list, el){ el.innerHTML = ''; const ph = document.createElement('option'); ph.value = ''; ph.textContent = 'Choose model…'; el.appendChild(ph); list.forEach((p, idx)=>{ const opt = document.createElement('option'); opt.value = String(idx); const manu = (p.manufacturer||'').trim(); opt.textContent = (el===modelSelectSolo && manu ? `${manu} — ${p.title}` : (p.title||`Model ${idx+1}`)); el.appendChild(opt); }); el.value = ''; } /* ---------------------- LINK BUILDERS ---------------------- */ function currentAffiliateRef(p){ return (affiliateRef || p.affiliateId || '').trim(); } function currentAmazonTag(p){ return (amazonTag || p.amazonTag || '').trim(); } function buildDirectUrl(p){ const base = (p.buyUrl||'').trim(); if(!base) return ''; const paramKey = (DATA.settings && DATA.settings.defaultAffiliateParam) || 'ref'; const code = currentAffiliateRef(p); return appendParam(base, paramKey, code || ''); } function buildAmazonUrl(p){ const asin = (p.asin||'').trim(); if(!asin) return ''; const tagKey = (DATA.settings && DATA.settings.defaultAmazonTagParam) || 'tag'; const tagVal = currentAmazonTag(p); let base = `https://www.amazon.com/dp/${encodeURIComponent(asin)}`; return tagVal ? appendParam(base, tagKey, tagVal) : base; } /* ---------------------- STATE UPDATE ---------------------- */ function showButtonsForProduct(p){ const dir = buildDirectUrl(p); const amz = buildAmazonUrl(p); if(amz){ buyAmazonBtn.href = amz; buyAmazonBtn.style.display='inline-flex'; } else { buyAmazonBtn.removeAttribute('href'); buyAmazonBtn.style.display='none'; } if(dir){ buyDirectBtn.href = dir; buyDirectBtn.style.display='inline-flex'; } else { buyDirectBtn.removeAttribute('href'); buyDirectBtn.style.display='none'; } buyButtons.style.display = (amz || dir) ? 'flex' : 'none'; } /* ---------------------- EVENTS ---------------------- */ manuSelect && manuSelect.addEventListener('change', ()=>{ selectedManufacturer = manuSelect.value; filteredProducts = selectedManufacturer ? DATA.products.filter(p => (p.manufacturer||'').trim() === selectedManufacturer) : DATA.products.slice(); modelSelect.disabled = false; renderModelSelect(filteredProducts, modelSelect); buyButtons.style.display = 'none'; }); modelSelect && modelSelect.addEventListener('change', ()=>{ const idx = parseInt(modelSelect.value, 10); if(Number.isFinite(idx) && idx>=0){ const p = filteredProducts[idx]; if(p) showButtonsForProduct(p); } else { buyButtons.style.display = 'none'; } }); modelSelectSolo && modelSelectSolo.addEventListener('change', ()=>{ const idx = parseInt(modelSelectSolo.value, 10); if(Number.isFinite(idx) && idx>=0){ const p = DATA.products[idx]; if(p) showButtonsForProduct(p); } else { buyButtons.style.display = 'none'; } }); /* ---------------------- DEEP-LINK PRESELECT ---------------------- */ function findProductFromDeepLink(){ const qSku = getQS('sku'); const qAsin = getQS('asin'); const qManu = getQS('manu') || getQS('manufacturer'); const qModel= getQS('model') || getQS('title'); // 1) Exact SKU if(qSku){ const p = DATA.products.find(pr => pr.sku && norm(pr.sku) === norm(qSku)); if(p) return p; } // 2) Exact ASIN if(qAsin){ const p = DATA.products.find(pr => pr.asin && norm(pr.asin) === norm(qAsin)); if(p) return p; } // 3) Exact title, then partial includes if(qModel){ let p = DATA.products.find(pr => norm(pr.title) === norm(qModel)); if(!p) p = DATA.products.find(pr => norm(pr.title).includes(norm(qModel))); if(p) return p; } // 4) Manufacturer only (no model) if(qManu){ // Return a marker object for manu-only; the caller will handle it. return { __manuOnly: true, manufacturer: qManu }; } return null; } /* ---------------------- INIT ---------------------- */ (async function init(){ // Capture affiliate overrides (URL → persist → use silently) const qRef = getQS('ref'); const qTag = getQS('atag'); if(qRef){ localStorage.setItem('truthcase_ref', qRef); } if(qTag){ localStorage.setItem('truthcase_atag', qTag); } affiliateRef = qRef || localStorage.getItem('truthcase_ref') || ''; amazonTag = qTag || localStorage.getItem('truthcase_atag') || ''; await loadData(); buildManufacturers(); // Build base UI const hasManu = manufacturers.length > 0; if(hasManu){ manuRow.style.display = ''; modelRowSolo.style.display = 'none'; selectedManufacturer = ''; renderManufacturerSelect(); renderModelSelect([], modelSelect); } else { manuRow.style.display = 'none'; modelRowSolo.style.display = ''; renderModelSelect(DATA.products, modelSelectSolo); } // Deep-link handling const pick = findProductFromDeepLink(); if(pick){ // Always open modal if deep-linked openModal(); if(pick.__manuOnly){ // Preselect manufacturer if present in list const manuName = manufacturers.find(m => norm(m) === norm(pick.manufacturer)) || pick.manufacturer; selectedManufacturer = manuName; if(hasManu){ manuSelect.value = selectedManufacturer || ''; manuSelect.dispatchEvent(new Event('change')); // populates models } // No model chosen yet, keep buttons hidden until user chooses model buyButtons.style.display = 'none'; } else { // We have a concrete product const manuName = pick.manufacturer || ''; if(hasManu){ // Choose manufacturer selectedManufacturer = manuName; manuSelect.value = selectedManufacturer || ''; manuSelect.dispatchEvent(new Event('change')); // populates filteredProducts // Find index within filtered list by title const idx = filteredProducts.findIndex(pr => norm(pr.title) === norm(pick.title)); if(idx >= 0){ modelSelect.value = String(idx); // Show buttons for this product showButtonsForProduct(filteredProducts[idx]); } else { // fallback: try includes const idx2 = filteredProducts.findIndex(pr => norm(pr.title).includes(norm(pick.title))); if(idx2 >= 0){ modelSelect.value = String(idx2); showButtonsForProduct(filteredProducts[idx2]); } } } else { // No manufacturer field in JSON; select directly in the solo list const idx = DATA.products.findIndex(pr => norm(pr.title) === norm(pick.title)); if(idx >= 0){ modelSelectSolo.value = String(idx); showButtonsForProduct(DATA.products[idx]); } } } } // reveal-on-scroll effect for content const io=new IntersectionObserver(es=>es.forEach(e=>{ if(e.isIntersecting){ e.target.classList.add('in'); io.unobserve(e.target);} }),{threshold:.12}); document.querySelectorAll('.reveal').forEach(el=>io.observe(el)); })(); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel