// tweaks-app.jsx // Tweaks for the Kubbaan landing page. Mounts into #tweaks-root, // applies live updates by writing CSS variables on :root and data-* attrs on . // ------- Helpers ---------------------------------------------------------- // Compute a darker orange-ink shade for hover state (rough HSL shift). function shadeHex(hex, amt) { const m = hex.replace('#','').match(/.{2}/g); if (!m) return hex; const [r,g,b] = m.map((h) => parseInt(h, 16)); const f = (c) => Math.max(0, Math.min(255, Math.round(c + amt))).toString(16).padStart(2,'0'); return `#${f(r)}${f(g)}${f(b)}`; } // Inject a rule once that overrides the brand orange across the existing CSS. function ensureBrandStyle() { let el = document.getElementById('tweaks-brand'); if (!el) { el = document.createElement('style'); el.id = 'tweaks-brand'; document.head.appendChild(el); } return el; } function applyBrand(color) { const el = ensureBrandStyle(); const deep = shadeHex(color, -30); // Rewrite enough rules that the page genuinely re-colors without // chasing every individual selector. el.textContent = ` :root{ --orange:${color}; --orange-deep:${deep}; --orange-soft:${shadeHex(color, 40)}; } .brand-mark,.btn-primary,.app-card .tag,.feature-icon,.app-chip.active, .app-tabbar .fab,.cat.active,.pill.major,.province-pill.major, .featured-article .fa-tag,.fa-cta:hover,.fa-meta .avatar-sm,.legal-cta .btn, .laos-map .city,.laos-map .city-ring{ background:${color}; } .brand-mark{ box-shadow:0 4px 12px ${color}59; } .btn-primary{ box-shadow:0 6px 18px ${color}59; } .step-num{ background:linear-gradient(135deg, ${color} 0%, ${deep} 100%); box-shadow:0 10px 24px ${color}59; } .download{ background:radial-gradient(ellipse 50% 80% at 80% 50%, rgba(255,255,255,0.22), transparent 60%), linear-gradient(135deg, ${color} 0%, ${deep} 60%, ${shadeHex(color,-60)} 100%); } .accent,.eyebrow,.stars,.testimonial .quote::before,.testi blockquote::before, .ac-meta .sep,.feature-icon,.app-card .price,.pcard .price,.app-search .ico, .app-tab.active,.section-title span[style*="var(--orange)"], .stat .num .plus,.hero h1 .accent,.subhero h1 .accent, .nav-link[style],.fa-cat,.footer-col a:hover,.social:hover svg, .laos-map .city-label[style*="font-weight:700"]{ color:${color}; } .feature-icon{ color:${color}; background:linear-gradient(135deg, ${color}2E 0%, ${color}0F 100%); } .float-icon.orange{ background:${color}1F; color:${color}; } .pcard .tag,.app-card .tag{ background:${color}; } .laos-map .country{ stroke:${color}; } .laos-map .city{ filter:drop-shadow(0 0 6px ${color}B0); } .hero-badge{ background:${color}24; border-color:${color}52; } .hero-badge .dot{ background:${color}; box-shadow:0 0 0 4px ${color}33; } .nav-link:hover{ color:${color}; } .social:hover{ background:${color}; } `; } function applyHeadline(line1, line2, accent) { const h = document.getElementById('hero-headline'); if (!h) return; const l1 = h.querySelector('[data-h-line1]'); const l2 = h.querySelector('[data-h-line2]'); if (l1) { if (accent && line1.includes(accent)) { const parts = line1.split(accent); l1.innerHTML = parts[0] + `${accent}` + (parts.slice(1).join(accent) || ''); } else { l1.textContent = line1; } } if (l2) l2.textContent = line2; } // ------- App -------------------------------------------------------------- function App() { const [t, setTweak] = useTweaks(window.TWEAK_DEFAULTS); // Apply effects on every tweak change. React.useEffect(() => { applyBrand(t.brandColor); }, [t.brandColor]); React.useEffect(() => { document.body.setAttribute('data-hero-theme', t.heroTheme); }, [t.heroTheme]); React.useEffect(() => { document.documentElement.style.setProperty('--tweak-radius', t.cardRadius + 'px'); }, [t.cardRadius]); React.useEffect(() => { document.documentElement.style.setProperty('--tweak-section-pad', t.sectionPadding + 'px'); }, [t.sectionPadding]); React.useEffect(() => { document.body.setAttribute('data-show-floats', String(!!t.showFloats)); }, [t.showFloats]); React.useEffect(() => { document.body.setAttribute('data-show-stats', String(!!t.showStats)); }, [t.showStats]); React.useEffect(() => { applyHeadline(t.headlineLine1, t.headlineLine2, t.accentWord); }, [t.headlineLine1, t.headlineLine2, t.accentWord]); return ( setTweak('brandColor', v)} /> setTweak('heroTheme', v)} /> setTweak('headlineLine1', v)} /> setTweak('headlineLine2', v)} /> setTweak('accentWord', v)} /> setTweak('showFloats', v)} /> setTweak('showStats', v)} /> setTweak('cardRadius', v)} /> setTweak('sectionPadding', v)} /> ); } ReactDOM.createRoot(document.getElementById('tweaks-root')).render();