Diff -- /var/www/vhosts/3dshawn.com/crm.3dshawn.com/TEMPLATES/cf_marketing.html
Diff
/var/www/vhosts/3dshawn.com/crm.3dshawn.com/TEMPLATES/cf_marketing.html
added on local at 2026-07-11 08:17:32
Added
+0
lines
Removed
-0
lines
Context
118
unchanged
Blobs
from 54b8b2837cf7
to 54b8b2837cf7
to 54b8b2837cf7
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | 1 | <!DOCTYPE html> |
| 2 | 2 | <html lang="en"> |
| 3 | 3 | <head> |
| 4 | 4 | <meta charset="UTF-8"> |
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | 6 | <!-- Anti-cache: belt-and-suspenders to the Cache-Control HTTP headers |
| 7 | 7 | the CGIs already emit. Some browser back/forward caches (bfcache) |
| 8 | 8 | still serve stale HTML even with no-cache headers; these meta tags |
| 9 | 9 | are the second line of defense. --> |
| 10 | 10 | <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0"> |
| 11 | 11 | <meta http-equiv="Pragma" content="no-cache"> |
| 12 | 12 | <meta http-equiv="Expires" content="0"> |
| 13 | 13 | <link rel="icon" type="image/svg+xml" href="/assets/images/favicon.svg"> |
| 14 | 14 | <title>$title · $website_title</title> |
| 15 | 15 | $meta_tags |
| 16 | 16 | <link rel="preconnect" href="https://fonts.googleapis.com"> |
| 17 | 17 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| 18 | 18 | <link href="https://fonts.googleapis.com/css2?family=Inter:wght\@400;500;600;700&family=Space+Grotesk:wght\@500;600;700&family=JetBrains+Mono:wght\@400;600&display=swap" rel="stylesheet"> |
| 19 | 19 | <link rel="stylesheet" href="$assets_css/site.css?v=2026-06-12-fixbrand"> |
| 20 | 20 | $extra_css |
| 21 | 21 | </head> |
| 22 | 22 | <body> |
| 23 | 23 | $body |
| 24 | 24 | <script src="$assets_js/site.js?v=2026-06-12-fixbrand"></script> |
| 25 | 25 | <!-- In-app popup helper (confirm/alert/prompt/toast). Same script the |
| 26 | 26 | dashboard uses, loaded on marketing pages too so logged-out flows |
| 27 | 27 | get consistent overlays instead of native alert(). --> |
| 28 | 28 | <script src="$assets_js/confirm.js?v=1"></script> |
| 29 | 29 | <!-- Live chat widget: only loaded on logged-out marketing pages so |
| 30 | 30 | prospects can reach support. The authenticated dashboard wrapper |
| 31 | 31 | does NOT load this script -- in-app help lives at /help.cgi |
| 32 | 32 | and /support.cgi instead. --> |
| 33 | 33 | [if:!$heatmap_view]<script src="$assets_js/track.js?v=5"></script>[/if] |
| 34 | 34 | <!-- ContactForge visitor analytics -- captures pageviews, sessions, |
| 35 | 35 | referrer + UTM, browser + OS + device + country. Admin views the |
| 36 | 36 | results at /admin_visitors.cgi, /admin_traffic.cgi, |
| 37 | 37 | /admin_heatmap.cgi. Suppressed when the admin is previewing a |
| 38 | 38 | page inside an iframe (heatmap_view=1) so admin clicks don't |
| 39 | 39 | pollute visitor data. --> |
| 40 | 40 | [if:!$heatmap_view]<script async src="/t.js?v=20260610"></script>[/if] |
| 41 | 41 | $extra_js |
| 42 | 42 | |
| 43 | 43 | <!-- Anonymous path tracker. Only included from the marketing wrapper, so |
| 44 | 44 | logged-in app pages never call /t.cgi. --> |
| 45 | 45 | <script> |
| 46 | 46 | (function(){ |
| 47 | 47 | try { |
| 48 | 48 | var u = new URLSearchParams(location.search); |
| 49 | 49 | var qs = '?p=' + encodeURIComponent(location.pathname + location.search) |
| 50 | 50 | + '&r=' + encodeURIComponent(document.referrer || '') |
| 51 | 51 | + '&us=' + encodeURIComponent(u.get('utm_source') || '') |
| 52 | 52 | + '&um=' + encodeURIComponent(u.get('utm_medium') || '') |
| 53 | 53 | + '&uc=' + encodeURIComponent(u.get('utm_campaign') || ''); |
| 54 | 54 | var img = new Image(); |
| 55 | 55 | img.referrerPolicy = 'no-referrer-when-downgrade'; |
| 56 | 56 | img.src = '/t.cgi' + qs; |
| 57 | 57 | } catch (e) { /* silent */ } |
| 58 | 58 | })(); |
| 59 | 59 | </script> |
| 60 | 60 | <script> |
| 61 | 61 | /* sidebar-scroll-preserve: keeps .sidebar scrollTop across page loads */ |
| 62 | 62 | (function(){ |
| 63 | 63 | var sb = document.querySelector('.sidebar'); |
| 64 | 64 | if (!sb) return; |
| 65 | 65 | var KEY = 'crm.3dshawn.com-sidebar-scroll'; |
| 66 | 66 | try { var s = parseInt(localStorage.getItem(KEY), 10) || 0; if (s > 0) sb.scrollTop = s; } catch(e){} |
| 67 | 67 | var t = null; |
| 68 | 68 | sb.addEventListener('scroll', function(){ |
| 69 | 69 | if (t) clearTimeout(t); |
| 70 | 70 | t = setTimeout(function(){ try { localStorage.setItem(KEY, sb.scrollTop); } catch(e){} }, 80); |
| 71 | 71 | }, { passive: true }); |
| 72 | 72 | })(); |
| 73 | 73 | </script> |
| 74 | 74 | <!-- Viewer-local timestamp renderer (portfolio-canonical). |
| 75 | 75 | Server emits `<span class="ts" data-ts="EPOCH" data-fmt="…">FALLBACK</span>`. |
| 76 | 76 | Formats: datetime | date | date-short | time | relative |
| 77 | 77 | Original text is the no-JS fallback. Re-run via window.tzLocalize() after AJAX. --> |
| 78 | 78 | <script> |
| 79 | 79 | (function(){ |
| 80 | 80 | function fmt(el){ |
| 81 | 81 | var e = parseInt(el.getAttribute('data-ts'), 10); |
| 82 | 82 | if (!e) return; |
| 83 | 83 | var d = new Date(e * 1000); |
| 84 | 84 | var kind = el.getAttribute('data-fmt') || 'datetime'; |
| 85 | 85 | var out; |
| 86 | 86 | switch (kind) { |
| 87 | 87 | case 'date': out = d.toLocaleDateString(undefined, {year:'numeric',month:'short',day:'numeric'}); break; |
| 88 | 88 | case 'date-short': out = d.toLocaleDateString(undefined, {month:'short',day:'numeric'}); break; |
| 89 | 89 | case 'time': out = d.toLocaleTimeString(undefined, {hour:'2-digit',minute:'2-digit'}); break; |
| 90 | 90 | case 'relative': out = rel(e); break; |
| 91 | 91 | default: out = d.toLocaleDateString(undefined, {year:'numeric',month:'short',day:'numeric'}) |
| 92 | 92 | + ' ' + d.toLocaleTimeString(undefined, {hour:'2-digit',minute:'2-digit'}); |
| 93 | 93 | } |
| 94 | 94 | el.textContent = out; |
| 95 | 95 | if (!el.title) el.title = d.toString(); |
| 96 | 96 | } |
| 97 | 97 | function rel(e){ |
| 98 | 98 | var s = Math.floor(Date.now()/1000) - e, future = s < 0; s = Math.abs(s); |
| 99 | 99 | var n, u; |
| 100 | 100 | if (s < 60) { n = s; u = 'second'; } |
| 101 | 101 | else if (s < 3600) { n = Math.floor(s/60); u = 'minute'; } |
| 102 | 102 | else if (s < 86400) { n = Math.floor(s/3600); u = 'hour'; } |
| 103 | 103 | else if (s < 2592000){ n = Math.floor(s/86400); u = 'day'; } |
| 104 | 104 | else if (s < 31536000){n = Math.floor(s/2592000); u = 'month'; } |
| 105 | 105 | else { n = Math.floor(s/31536000);u = 'year'; } |
| 106 | 106 | var p = n === 1 ? '' : 's'; |
| 107 | 107 | return future ? ('in ' + n + ' ' + u + p) : (n + ' ' + u + p + ' ago'); |
| 108 | 108 | } |
| 109 | 109 | function localize(root){ (root||document).querySelectorAll('span.ts[data-ts], time[data-ts]').forEach(fmt); } |
| 110 | 110 | window.tzLocalize = localize; |
| 111 | 111 | if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ localize(); }); |
| 112 | 112 | else localize(); |
| 113 | 113 | window.addEventListener('ts-refresh', function(){ localize(); }); |
| 114 | 114 | })(); |
| 115 | 115 | </script> |
| 116 | 116 | |
| 117 | 117 | </body> |
| 118 | 118 | </html> |