forked from Goutam/lynkeduppro-crm
feat(landing): port the full marketing site into the CRM (static)
Copies the goutam-pages landing (React marketing home + /page/1..19 + assets) into /public and adds beforeFiles rewrites so '/' serves the marketing home and '/1'..'/19' + '/thanks' serve the static pages — reproducing the landing's vercel.json clean URLs. /portal/* (login/register) is untouched.
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
/* LynkedUp Pro v3 — interactivity */
|
||||
|
||||
// ---------- Mobile nav (hamburger toggle) ----------
|
||||
(function() {
|
||||
const toggle = document.getElementById('navToggle');
|
||||
const links = document.getElementById('navLinks');
|
||||
const overlay = document.getElementById('navOverlay');
|
||||
if (!toggle || !links) return;
|
||||
|
||||
function setOpen(open) {
|
||||
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
toggle.classList.toggle('is-open', open);
|
||||
links.classList.toggle('is-open', open);
|
||||
if (overlay) overlay.classList.toggle('is-open', open);
|
||||
document.body.classList.toggle('nav-locked', open);
|
||||
}
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
const open = toggle.getAttribute('aria-expanded') !== 'true';
|
||||
setOpen(open);
|
||||
});
|
||||
if (overlay) overlay.addEventListener('click', () => setOpen(false));
|
||||
// Close on any link tap inside the panel
|
||||
links.querySelectorAll('a').forEach(a => {
|
||||
a.addEventListener('click', () => setOpen(false));
|
||||
});
|
||||
// Close when crossing back to desktop width
|
||||
const mq = window.matchMedia('(min-width: 1024px)');
|
||||
mq.addEventListener('change', e => { if (e.matches) setOpen(false); });
|
||||
// Close on Escape
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') setOpen(false);
|
||||
});
|
||||
})();
|
||||
|
||||
// ---------- Theme toggle (light/dark) ----------
|
||||
(function() {
|
||||
const root = document.documentElement;
|
||||
const key = 'lp-theme';
|
||||
const saved = localStorage.getItem(key);
|
||||
if (saved === 'light' || saved === 'dark') {
|
||||
root.setAttribute('data-theme', saved);
|
||||
} else {
|
||||
root.setAttribute('data-theme', 'dark'); // default
|
||||
}
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (btn) {
|
||||
btn.addEventListener('click', () => {
|
||||
const current = root.getAttribute('data-theme') || 'dark';
|
||||
const next = current === 'dark' ? 'light' : 'dark';
|
||||
root.setAttribute('data-theme', next);
|
||||
localStorage.setItem(key, next);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// ---------- Countdown timer (both final card + sticky bar) ----------
|
||||
(function() {
|
||||
let secs = 42, mins = 27, hours = 14, days = 3;
|
||||
const get = id => document.getElementById(id);
|
||||
const elFinal = { s: get('cdS'), m: get('cdM'), h: get('cdH'), d: get('cdD') };
|
||||
const elStk = { s: get('ssS'), m: get('ssM'), h: get('ssH'), d: get('ssD') };
|
||||
|
||||
function pad(n) { return String(n).padStart(2, '0'); }
|
||||
function render() {
|
||||
const v = { d: pad(days), h: pad(hours), m: pad(mins), s: pad(secs) };
|
||||
for (const k of ['d','h','m','s']) {
|
||||
if (elFinal[k]) elFinal[k].textContent = v[k];
|
||||
if (elStk[k]) elStk[k].textContent = v[k];
|
||||
}
|
||||
}
|
||||
setInterval(() => {
|
||||
secs--;
|
||||
if (secs < 0) { secs = 59; mins--;
|
||||
if (mins < 0) { mins = 59; hours--;
|
||||
if (hours < 0) { hours = 23; days = Math.max(0, days - 1); }
|
||||
}
|
||||
}
|
||||
render();
|
||||
}, 1000);
|
||||
render();
|
||||
})();
|
||||
|
||||
// ---------- Sticky CTA reveal/hide ----------
|
||||
(function() {
|
||||
const el = document.getElementById('stickyCta');
|
||||
const close = document.getElementById('stickyClose');
|
||||
if (!el || !close) return;
|
||||
let dismissed = false;
|
||||
window.addEventListener('scroll', () => {
|
||||
if (dismissed) return;
|
||||
if (window.scrollY > 700) el.classList.add('show');
|
||||
else el.classList.remove('show');
|
||||
});
|
||||
close.addEventListener('click', () => {
|
||||
dismissed = true;
|
||||
el.classList.remove('show');
|
||||
document.body.style.paddingBottom = '0';
|
||||
});
|
||||
})();
|
||||
|
||||
// ---------- Video player ----------
|
||||
(function() {
|
||||
const frame = document.getElementById('videoFrame');
|
||||
const playBtn = document.getElementById('videoPlay');
|
||||
const videoEl = document.getElementById('videoEl');
|
||||
if (!frame || !playBtn || !videoEl) return;
|
||||
|
||||
function play(at) {
|
||||
frame.classList.add('playing');
|
||||
if (typeof at === 'number' && !isNaN(at)) {
|
||||
try { videoEl.currentTime = at; } catch (e) {}
|
||||
}
|
||||
videoEl.play().catch(() => {});
|
||||
}
|
||||
playBtn.addEventListener('click', () => play());
|
||||
document.querySelectorAll('.vc[data-seek]').forEach(chip => {
|
||||
chip.addEventListener('click', () => {
|
||||
const at = parseFloat(chip.dataset.seek);
|
||||
play(at);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
// ---------- Dashboard product-view tabs ----------
|
||||
(function() {
|
||||
const wrap = document.getElementById('dashWindow');
|
||||
if (!wrap) return;
|
||||
const tabs = wrap.querySelectorAll('.dw-tab');
|
||||
const shots = wrap.querySelectorAll('.dw-shot');
|
||||
const urlText = document.getElementById('dwUrlText');
|
||||
|
||||
function activate(name) {
|
||||
tabs.forEach(t => {
|
||||
const on = t.dataset.tab === name;
|
||||
t.classList.toggle('is-active', on);
|
||||
t.setAttribute('aria-selected', on ? 'true' : 'false');
|
||||
if (on && urlText && t.dataset.url) urlText.textContent = t.dataset.url;
|
||||
});
|
||||
shots.forEach(s => s.classList.toggle('is-active', s.dataset.shot === name));
|
||||
}
|
||||
|
||||
tabs.forEach(t => {
|
||||
t.addEventListener('click', () => activate(t.dataset.tab));
|
||||
});
|
||||
})();
|
||||
|
||||
// ---------- Smooth in-page nav ----------
|
||||
document.querySelectorAll('a[href^="#"]').forEach(a => {
|
||||
a.addEventListener('click', (e) => {
|
||||
const id = a.getAttribute('href');
|
||||
if (id.length < 2) return;
|
||||
const t = document.querySelector(id);
|
||||
if (!t) return;
|
||||
e.preventDefault();
|
||||
window.scrollTo({ top: t.getBoundingClientRect().top + window.scrollY - 80, behavior: 'smooth' });
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- Tweaks panel ----------
|
||||
(function() {
|
||||
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
|
||||
"accentOrange": "#f97316",
|
||||
"accentCyan": "#22d3ee",
|
||||
"bgIntensity": 0.65,
|
||||
"showGrid": true
|
||||
}/*EDITMODE-END*/;
|
||||
|
||||
let state = { ...TWEAK_DEFAULTS };
|
||||
let active = false;
|
||||
const root = document.getElementById('tweaks-root');
|
||||
|
||||
function applyState() {
|
||||
document.documentElement.style.setProperty('--orange', state.accentOrange);
|
||||
document.documentElement.style.setProperty('--cyan', state.accentCyan);
|
||||
document.documentElement.style.setProperty('--cyan-2', state.accentCyan);
|
||||
const orbs = document.querySelectorAll('.bg-orb');
|
||||
orbs.forEach(o => o.style.opacity = state.bgIntensity);
|
||||
const grid = document.querySelector('.bg-grid');
|
||||
if (grid) grid.style.display = state.showGrid ? '' : 'none';
|
||||
}
|
||||
applyState();
|
||||
|
||||
function render() {
|
||||
if (!active) { root.innerHTML = ''; return; }
|
||||
root.innerHTML = `
|
||||
<div style="width:300px; background:rgba(5,15,36,.96); backdrop-filter:blur(20px); border:1px solid rgba(56,189,248,.3); border-radius:16px; box-shadow:0 30px 60px -20px rgba(0,0,0,.8); padding:18px; font-family:'Plus Jakarta Sans'; color:#e6edf6;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
|
||||
<strong style="font-size:14px; letter-spacing:-.01em; color:#fff;">Tweaks</strong>
|
||||
<button id="tw-close" style="border:0; background:transparent; cursor:pointer; color:#94a3b8; font-size:18px;">×</button>
|
||||
</div>
|
||||
|
||||
<div style="font-size:11px; color:#94a3b8; font-weight:600; text-transform:uppercase; letter-spacing:.05em; margin-bottom:8px;">CTA Orange</div>
|
||||
<div style="display:flex; gap:6px; margin-bottom:14px;">
|
||||
${['#f97316','#ef4444','#fbbf24','#ec4899'].map(c => `<button data-orange="${c}" style="flex:1; height:32px; border-radius:8px; border:2px solid ${state.accentOrange===c?'#fff':'transparent'}; background:${c}; cursor:pointer;"></button>`).join('')}
|
||||
</div>
|
||||
|
||||
<div style="font-size:11px; color:#94a3b8; font-weight:600; text-transform:uppercase; letter-spacing:.05em; margin-bottom:8px;">Accent Cyan</div>
|
||||
<div style="display:flex; gap:6px; margin-bottom:14px;">
|
||||
${['#22d3ee','#38bdf8','#a78bfa','#10b981'].map(c => `<button data-cyan="${c}" style="flex:1; height:32px; border-radius:8px; border:2px solid ${state.accentCyan===c?'#fff':'transparent'}; background:${c}; cursor:pointer;"></button>`).join('')}
|
||||
</div>
|
||||
|
||||
<div style="font-size:11px; color:#94a3b8; font-weight:600; text-transform:uppercase; letter-spacing:.05em; margin-bottom:8px;">Background glow · ${Math.round(state.bgIntensity*100)}%</div>
|
||||
<input type="range" min="0" max="1" step="0.05" value="${state.bgIntensity}" id="tw-bg" style="width:100%; margin-bottom:14px;">
|
||||
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:13px; color:#cbd5e1; cursor:pointer;">
|
||||
<input type="checkbox" id="tw-grid" ${state.showGrid?'checked':''}/>
|
||||
Show background grid
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
root.querySelector('#tw-close').onclick = () => {
|
||||
active = false; render();
|
||||
window.parent.postMessage({type:'__edit_mode_dismissed'}, '*');
|
||||
};
|
||||
root.querySelectorAll('[data-orange]').forEach(b => b.onclick = () => {
|
||||
state.accentOrange = b.dataset.orange; applyState(); render();
|
||||
window.parent.postMessage({type:'__edit_mode_set_keys', edits:{accentOrange: state.accentOrange}}, '*');
|
||||
});
|
||||
root.querySelectorAll('[data-cyan]').forEach(b => b.onclick = () => {
|
||||
state.accentCyan = b.dataset.cyan; applyState(); render();
|
||||
window.parent.postMessage({type:'__edit_mode_set_keys', edits:{accentCyan: state.accentCyan}}, '*');
|
||||
});
|
||||
root.querySelector('#tw-bg').oninput = (e) => {
|
||||
state.bgIntensity = parseFloat(e.target.value); applyState(); render();
|
||||
window.parent.postMessage({type:'__edit_mode_set_keys', edits:{bgIntensity: state.bgIntensity}}, '*');
|
||||
};
|
||||
root.querySelector('#tw-grid').onchange = (e) => {
|
||||
state.showGrid = e.target.checked; applyState(); render();
|
||||
window.parent.postMessage({type:'__edit_mode_set_keys', edits:{showGrid: state.showGrid}}, '*');
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener('message', (e) => {
|
||||
if (e.data?.type === '__activate_edit_mode') { active = true; render(); }
|
||||
if (e.data?.type === '__deactivate_edit_mode') { active = false; render(); }
|
||||
});
|
||||
window.parent.postMessage({type:'__edit_mode_available'}, '*');
|
||||
})();
|
||||
Reference in New Issue
Block a user