v1.0 - launch: full nav on gallery, chat widget auto-open, comparison logos, checkout fixes
This commit is contained in:
+479
-62
@@ -58,6 +58,7 @@ fn page_open() -> String {
|
||||
font-family: var(--body);
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body::before {
|
||||
@@ -75,8 +76,11 @@ fn page_open() -> String {
|
||||
.container { max-width: 1280px; margin: 0 auto; padding: 0 2.5rem; }
|
||||
.container-md { max-width: 900px; margin: 0 auto; padding: 0 2.5rem; }
|
||||
.container-lg { max-width: 1100px; margin: 0 auto; padding: 0 2.5rem; }
|
||||
@media (max-width: 480px) {
|
||||
.container, .container-md, .container-lg { padding: 0 1.25rem; }
|
||||
}
|
||||
|
||||
section { position: relative; z-index: 1; }
|
||||
section { position: relative; z-index: 1; overflow-x: clip; }
|
||||
|
||||
.label {
|
||||
font-family: var(--body);
|
||||
@@ -244,6 +248,28 @@ fn page_open() -> String {
|
||||
.nav-link.cta { color: var(--navy); }
|
||||
.nav-link.cta:hover { color: #0078D4; }
|
||||
|
||||
/* ── Nav dropdown ─────────────────────────────────────────────────────── */
|
||||
.nav-dropdown { position: relative; display: flex; align-items: center; }
|
||||
.nav-dropdown-btn {
|
||||
background: none; border: none; cursor: pointer; padding: 0;
|
||||
font-family: var(--body); font-size: 0.8125rem; font-weight: 400;
|
||||
letter-spacing: 0.02em; color: var(--t2); transition: color 200ms;
|
||||
}
|
||||
.nav-dropdown-btn:hover { color: var(--t1); }
|
||||
.nav-dropdown-menu {
|
||||
display: none; position: absolute; top: calc(100% + 0.75rem); left: 50%;
|
||||
transform: translateX(-50%); background: #fff;
|
||||
border: 1px solid var(--border2); box-shadow: 0 8px 32px rgba(0,0,0,.10);
|
||||
min-width: 160px; flex-direction: column; z-index: 200; padding: 0.375rem 0;
|
||||
}
|
||||
.nav-dropdown.open .nav-dropdown-menu { display: flex; }
|
||||
.nav-dropdown-item {
|
||||
font-family: var(--body); font-size: 0.8125rem; font-weight: 400;
|
||||
color: var(--t2); text-decoration: none; padding: 0.5rem 1.25rem;
|
||||
transition: background 150ms, color 150ms; white-space: nowrap;
|
||||
}
|
||||
.nav-dropdown-item:hover { background: var(--bg2); color: var(--t1); }
|
||||
|
||||
.nav-cta {
|
||||
font-family: var(--body);
|
||||
font-size: 0.675rem;
|
||||
@@ -352,7 +378,7 @@ fn page_open() -> String {
|
||||
/* ── HERO ───────────────────────────────────────────────────────────────── */
|
||||
|
||||
#hero {
|
||||
padding: clamp(7rem, 18vh, 11rem) 2.5rem clamp(5rem, 12vh, 8rem);
|
||||
padding: clamp(7rem, 18vh, 11rem) clamp(1.25rem, 5vw, 2.5rem) clamp(5rem, 12vh, 8rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -569,11 +595,11 @@ fn page_open() -> String {
|
||||
color: var(--navy);
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.15em;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.13em;
|
||||
text-transform: uppercase;
|
||||
color: var(--t1);
|
||||
color: var(--navy);
|
||||
}
|
||||
.stat-body {
|
||||
font-family: var(--body);
|
||||
@@ -629,11 +655,11 @@ fn page_open() -> String {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.efficiency-title {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.15em;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.13em;
|
||||
text-transform: uppercase;
|
||||
color: var(--t1);
|
||||
color: var(--navy);
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.efficiency-rule {
|
||||
@@ -709,7 +735,7 @@ fn page_open() -> String {
|
||||
.enterprise-box {
|
||||
border-top: 2px solid rgba(0,82,160,.15);
|
||||
background: rgba(0,82,160,.03);
|
||||
padding: 3rem;
|
||||
padding: clamp(1.5rem, 4vw, 3rem);
|
||||
}
|
||||
|
||||
.ent-who-label {
|
||||
@@ -847,6 +873,10 @@ fn page_open() -> String {
|
||||
padding: 2rem 0 2rem 3rem;
|
||||
border-left: 1px solid rgba(0,82,160,.10);
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.problem-item { padding: 2rem 0; }
|
||||
.problem-item:nth-child(even) { padding: 2rem 0; border-left: none; }
|
||||
}
|
||||
.problems-grid-bottom { border-top: 1px solid rgba(0,82,160,.15); grid-column: 1 / -1; }
|
||||
.problem-label {
|
||||
font-size: 0.75rem;
|
||||
@@ -1136,6 +1166,8 @@ fn page_open() -> String {
|
||||
color: var(--t3);
|
||||
max-width: 68rem;
|
||||
margin: 0 auto;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.founding-banner {
|
||||
@@ -1431,12 +1463,33 @@ fn page_open() -> String {
|
||||
max-width: calc(100vw - 2rem);
|
||||
background: var(--card);
|
||||
border: 1px solid rgba(0,82,160,.20);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 16px 64px rgba(0,0,0,.15), 0 4px 16px rgba(0,82,160,.12);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 70vh;
|
||||
overflow: hidden;
|
||||
animation: demoSlideIn 200ms cubic-bezier(0.16,1,0.3,1) both;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
#neuron-demo-panel {
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 88vh;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
border-radius: 1.25rem 1.25rem 0 0;
|
||||
box-shadow: 0 -8px 40px rgba(0,0,0,.18);
|
||||
}
|
||||
#neuron-demo-btn {
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
@keyframes demoSlideIn {
|
||||
from { opacity: 0; transform: translateY(16px) scale(0.97); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
@@ -1450,6 +1503,7 @@ fn page_open() -> String {
|
||||
background: var(--navy);
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
border-radius: 1rem 1rem 0 0;
|
||||
}
|
||||
#neuron-demo-header-left {
|
||||
display: flex;
|
||||
@@ -1474,22 +1528,24 @@ fn page_open() -> String {
|
||||
font-family: var(--body);
|
||||
font-size: 0.625rem;
|
||||
letter-spacing: 0.08em;
|
||||
color: rgba(255,255,255,.60);
|
||||
color: rgba(255,255,255,.85);
|
||||
text-transform: uppercase;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
#neuron-demo-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255,255,255,.7);
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
transition: color 200ms;
|
||||
padding: 0.375rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.65;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
#neuron-demo-close:hover { color: #fff; }
|
||||
#neuron-demo-close:hover { opacity: 1; }
|
||||
#neuron-demo-close svg { display: block; }
|
||||
|
||||
#neuron-demo-messages {
|
||||
flex: 1;
|
||||
@@ -1504,30 +1560,109 @@ fn page_open() -> String {
|
||||
}
|
||||
|
||||
.demo-msg {
|
||||
max-width: 92%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
font-family: var(--body);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
.demo-msg-user {
|
||||
align-self: flex-end;
|
||||
background: var(--navy);
|
||||
color: #fff;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0;
|
||||
.demo-msg-user { align-self: flex-end; flex-direction: row-reverse; }
|
||||
.demo-msg-ai { align-self: flex-start; width: 100%; }
|
||||
.demo-msg-ai-body { display: flex; flex-direction: column; gap: 0; flex: 1; min-width: 0; }
|
||||
.demo-msg-bubble { width: fit-content; }
|
||||
.demo-msg-avatar {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
.demo-msg-ai {
|
||||
align-self: flex-start;
|
||||
.demo-msg-ai .demo-msg-avatar { background: #fff; border: 1px solid var(--border); }
|
||||
.demo-msg-ai .demo-msg-avatar img { width: 1.125rem; height: 1.125rem; display: block; object-fit: contain; }
|
||||
.demo-msg-user .demo-msg-avatar { background: var(--bg2); border: 1px solid var(--border); }
|
||||
.demo-msg-user .demo-msg-avatar svg { color: var(--t3); }
|
||||
.demo-msg-bubble {
|
||||
max-width: calc(100% - 2.25rem);
|
||||
padding: 0.625rem 0.875rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
.demo-msg-ai .demo-msg-bubble {
|
||||
background: var(--bg2);
|
||||
color: var(--t1);
|
||||
padding: 0.875rem 1.125rem;
|
||||
border-left: 2px solid rgba(0,82,160,.30);
|
||||
border-radius: 0.25rem 1rem 1rem 1rem;
|
||||
}
|
||||
.demo-msg-ai p { margin: 0; }
|
||||
.demo-msg-ai p + p { margin-top: 0.75rem; }
|
||||
.demo-msg-ai ul, .demo-msg-ai ol { margin: 0.5rem 0 0.5rem 1.25rem; }
|
||||
.demo-msg-ai li + li { margin-top: 0.25rem; }
|
||||
.demo-msg-ai strong { font-weight: 600; color: var(--t1); }
|
||||
.demo-msg-user .demo-msg-bubble {
|
||||
background: var(--navy);
|
||||
color: #fff;
|
||||
border-radius: 1rem 0.25rem 1rem 1rem;
|
||||
}
|
||||
.demo-msg-ai .demo-msg-bubble p { margin: 0; }
|
||||
.demo-msg-ai .demo-msg-bubble p + p { margin-top: 0.75rem; }
|
||||
.demo-msg-ai .demo-msg-bubble ul, .demo-msg-ai .demo-msg-bubble ol { margin: 0.5rem 0 0.5rem 1.25rem; }
|
||||
.demo-msg-ai .demo-msg-bubble li + li { margin-top: 0.25rem; }
|
||||
.demo-msg-ai .demo-msg-bubble strong { font-weight: 600; }
|
||||
.demo-msg-share {
|
||||
position: relative;
|
||||
margin-top: 0.375rem;
|
||||
align-self: flex-start;
|
||||
margin-left: 2.5rem;
|
||||
}
|
||||
.demo-msg-share-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--t3);
|
||||
font-family: var(--body);
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
padding: 0.2rem 0.4rem;
|
||||
transition: color 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.demo-msg-share-btn:hover { color: var(--navy); }
|
||||
.demo-share-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
background: #fff;
|
||||
border: 1px solid var(--border2);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,.12);
|
||||
padding: 0.5rem;
|
||||
display: none;
|
||||
gap: 0.25rem;
|
||||
flex-direction: column;
|
||||
min-width: 160px;
|
||||
z-index: 9999;
|
||||
}
|
||||
.demo-share-menu.open { display: flex; }
|
||||
.demo-share-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-family: var(--body);
|
||||
font-size: 0.8125rem;
|
||||
color: var(--t1);
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
transition: background 0.15s;
|
||||
text-decoration: none;
|
||||
}
|
||||
.demo-share-item:hover { background: var(--bg2); }
|
||||
.demo-msg-thinking {
|
||||
align-self: flex-start;
|
||||
color: var(--t3);
|
||||
@@ -1535,6 +1670,25 @@ fn page_open() -> String {
|
||||
letter-spacing: 0.08em;
|
||||
font-style: italic;
|
||||
}
|
||||
.demo-share-pill {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--t3);
|
||||
cursor: pointer;
|
||||
padding: 0.2rem 0;
|
||||
font-family: var(--body);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.04em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin-top: 0.3rem;
|
||||
align-self: flex-start;
|
||||
transition: color 0.15s;
|
||||
line-height: 1;
|
||||
}
|
||||
.demo-share-pill:hover { color: var(--navy); }
|
||||
|
||||
#neuron-demo-input-row {
|
||||
display: flex;
|
||||
@@ -1568,6 +1722,11 @@ fn page_open() -> String {
|
||||
}
|
||||
#neuron-demo-send:hover { background: #0078D4; }
|
||||
#neuron-demo-send:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
@media (max-width: 600px) {
|
||||
#neuron-demo-text { font-size: 1rem; padding: 1rem; }
|
||||
#neuron-demo-send { padding: 1rem 1.25rem; min-width: 64px; }
|
||||
#neuron-demo-messages { padding: .75rem 1rem; }
|
||||
}
|
||||
|
||||
.text-gradient-gold {
|
||||
background: linear-gradient(135deg, #003d7a 0%, #0078D4 50%, #0052A0 100%);
|
||||
@@ -1592,6 +1751,7 @@ fn page_open() -> String {
|
||||
|
||||
</style>
|
||||
<script src=\"https://cdn.jsdelivr.net/npm/marked/marked.min.js\"></script>
|
||||
<script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\" async defer></script>
|
||||
<noscript><style>.reveal { opacity: 1 !important; transform: none !important; }</style></noscript>
|
||||
|
||||
<!-- Google Analytics -->
|
||||
@@ -1607,7 +1767,7 @@ fn page_open() -> String {
|
||||
<meta property=\"og:type\" content=\"website\">
|
||||
<meta property=\"og:url\" content=\"https://neurontechnologies.ai\">
|
||||
<meta property=\"og:title\" content=\"Neuron - The AI That Remembers You\">
|
||||
<meta property=\"og:description\" content=\"Every other AI forgets you. Neuron doesn't. Runs on your machine, builds a memory graph over time, and gets sharper the longer you use it. Built by one person. April 22, 2026 — the meeting. April 25 — you're looking at the proof.\">
|
||||
<meta property=\"og:description\" content=\"Every other AI forgets you. Neuron doesn't. Runs on your machine, builds a memory over time, and gets sharper the longer you use it. Built by one person. April 22, 2026 — the meeting. April 25 — you're looking at the proof.\">
|
||||
<meta property=\"og:image\" content=\"https://neurontechnologies.ai/assets/brand/neuron-wordmark-on-light@2x.png\">
|
||||
|
||||
<!-- Twitter / X -->
|
||||
@@ -1626,14 +1786,14 @@ fn page_open() -> String {
|
||||
\"@graph\": [
|
||||
{
|
||||
\"@type\": \"Organization\",
|
||||
\"name\": \"Neuron Technologies\",
|
||||
\"name\": \"Neuron, LLC\",
|
||||
\"url\": \"https://neurontechnologies.ai\",
|
||||
\"founder\": {
|
||||
\"@type\": \"Person\",
|
||||
\"name\": \"Will Anderson\",
|
||||
\"jobTitle\": \"Founder\"
|
||||
},
|
||||
\"description\": \"Neuron Technologies builds AI that runs on your machine, builds a memory graph over time, and gets sharper the longer you use it. One builder. Built different.\",
|
||||
\"description\": \"Neuron builds AI that runs on your machine, builds a memory over time, and gets sharper the longer you use it. One builder. Built different.\",
|
||||
\"foundingDate\": \"2026\",
|
||||
\"sameAs\": [\"https://github.com/neuron-technologies\"]
|
||||
},
|
||||
@@ -1664,11 +1824,11 @@ fn page_open() -> String {
|
||||
\"priceCurrency\": \"USD\"
|
||||
}
|
||||
],
|
||||
\"description\": \"The AI that remembers you. Runs locally on your machine. Builds a persistent memory graph over every conversation. Gets sharper the longer you use it. Your data never leaves your device.\",
|
||||
\"description\": \"The AI that remembers you. Runs locally on your machine. Builds a persistent memory over every conversation. Gets sharper the longer you use it. Your data never leaves your device.\",
|
||||
\"featureList\": [
|
||||
\"Persistent memory graph — runs locally, never resets\",
|
||||
\"Bring your own API keys (OpenAI, Anthropic, Groq)\",
|
||||
\"Local inference via Ollama — free, offline, zero cloud\",
|
||||
\"Persistent memory — runs locally, never resets\",
|
||||
\"Bring your own API keys (OpenAI, Anthropic, Grok)\",
|
||||
\"Local inference via Ollama — coming, offline, zero cloud\",
|
||||
\"Neuron Inference coming soon — priced below major APIs\",
|
||||
\"Two devices included per plan\",
|
||||
\"Enterprise deployment — Q1 2027\"
|
||||
@@ -1682,7 +1842,7 @@ fn page_open() -> String {
|
||||
\"name\": \"What is Neuron?\",
|
||||
\"acceptedAnswer\": {
|
||||
\"@type\": \"Answer\",
|
||||
\"text\": \"Neuron is an AI that runs on your machine and builds a persistent memory graph over time. Every other AI forgets you when you close the tab. Neuron doesn't. The longer you use it, the less you have to explain.\"
|
||||
\"text\": \"Neuron is an AI that runs on your machine and builds a persistent memory over time. Every other AI forgets you when you close the tab. Neuron doesn't. The longer you use it, the less you have to explain.\"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1690,7 +1850,7 @@ fn page_open() -> String {
|
||||
\"name\": \"Does Neuron send my data to the cloud?\",
|
||||
\"acceptedAnswer\": {
|
||||
\"@type\": \"Answer\",
|
||||
\"text\": \"No. Your memory graph lives on your machine and never leaves it. Neuron does not collect your data, train on your conversations, or require cloud storage. For inference you choose: local via Ollama, your own API keys, or Neuron Inference (coming soon).\"
|
||||
\"text\": \"No. Your memory lives on your machine and never leaves it. Neuron does not collect your data, train on your conversations, or require cloud storage. For inference you choose: local via Ollama, your own API keys, or Neuron Inference (coming soon).\"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1745,6 +1905,12 @@ fn page_close() -> String {
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
// Auto-open chat if ?open=chat in URL
|
||||
if (typeof URLSearchParams !== 'undefined' && new URLSearchParams(window.location.search).get('open') === 'chat') {
|
||||
// Wait for widget to initialize, then open
|
||||
setTimeout(function() { if (typeof neuronDemoToggle === 'function') neuronDemoToggle(); }, 600);
|
||||
}
|
||||
|
||||
// Scroll reveal via IntersectionObserver
|
||||
var revealEls = document.querySelectorAll('.reveal');
|
||||
if ('IntersectionObserver' in window) {
|
||||
@@ -1801,6 +1967,17 @@ fn page_close() -> String {
|
||||
pollFoundingCount();
|
||||
setInterval(pollFoundingCount, 90000);
|
||||
|
||||
// Hide chat on checkout, account, legal pages — not relevant there
|
||||
if (window.location.pathname.indexOf('/checkout') === 0 ||
|
||||
window.location.pathname.indexOf('/account') === 0 ||
|
||||
window.location.pathname.indexOf('/legal') === 0 ||
|
||||
window.location.pathname.indexOf('/marketplace/success') === 0) {
|
||||
var demoBtn = document.getElementById('neuron-demo-btn');
|
||||
var demoPanel = document.getElementById('neuron-demo-panel');
|
||||
if (demoBtn) demoBtn.style.display = 'none';
|
||||
if (demoPanel) demoPanel.style.display = 'none';
|
||||
}
|
||||
|
||||
// Checkout buttons - navigate to integrated payment page
|
||||
var checkoutBtns = document.querySelectorAll('[data-checkout]');
|
||||
checkoutBtns.forEach(function(btn) {
|
||||
@@ -1830,10 +2007,18 @@ fn page_close() -> String {
|
||||
<div id=\"neuron-demo-header-sub\">Live Demo</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick=\"neuronDemoToggle()\" aria-label=\"Close\">✕</button>
|
||||
<div style=\"display:flex;align-items:center;gap:0.75rem\">
|
||||
<span id=\"neuron-demo-countdown\" style=\"font-family:var(--body);font-size:0.65rem;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;color:rgba(255,255,255,0.90)\"></span>
|
||||
<button id=\"neuron-demo-close\" onclick=\"neuronDemoToggle()\" aria-label=\"Close\">
|
||||
<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"rgba(255,255,255,.85)\" stroke-width=\"1.75\" stroke-linecap=\"round\">
|
||||
<line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id=\"neuron-demo-messages\"></div>
|
||||
<div id=\"neuron-demo-input-row\">
|
||||
<div id=\"neuron-demo-turnstile\" style=\"padding:0.75rem 1rem 0;transition:opacity 0.6s,max-height 0.6s;overflow:hidden;max-height:80px\"></div>
|
||||
<div id=\"neuron-demo-input-row\" style=\"display:none\">
|
||||
<input type=\"text\" id=\"neuron-demo-text\" placeholder=\"Ask me anything...\" autocomplete=\"off\">
|
||||
<button id=\"neuron-demo-send\" onclick=\"neuronDemoSend()\">Send</button>
|
||||
</div>
|
||||
@@ -1841,34 +2026,242 @@ fn page_close() -> String {
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
if (typeof marked !== 'undefined') { marked.setOptions({ breaks: true, gfm: true }); }
|
||||
var TURNSTILE_SITE_KEY = '0x4AAAAAADHAZXyuRb3yD9mr';
|
||||
var turnstileToken = '';
|
||||
var turnstileWidgetId = null;
|
||||
var turnstileVerified = false;
|
||||
var isOpen = false;
|
||||
var msgCount = 0;
|
||||
var MAX = 10;
|
||||
var MAX = 5;
|
||||
|
||||
// Persistent session storage - survives page refreshes
|
||||
function loadSession() {
|
||||
try {
|
||||
var s = localStorage.getItem('neuron_demo_session');
|
||||
return s ? JSON.parse(s) : { messages: [], count: 0, context: '' };
|
||||
} catch(e) { return { messages: [], count: 0, context: '' }; }
|
||||
}
|
||||
function saveSession(session) {
|
||||
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
|
||||
}
|
||||
function clearSession() {
|
||||
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
|
||||
}
|
||||
|
||||
function _mg(s) { return s._m || { nodes: [], edges: [] }; }
|
||||
|
||||
function _um(s, nn, ne) {
|
||||
if (!nn || !nn.length) return;
|
||||
var g = _mg(s), nm = {}, ek = function(e) { return e.from+'->'+e.to; }, em = {};
|
||||
g.nodes.forEach(function(n) { nm[n.id] = n; });
|
||||
(nn || []).forEach(function(n) {
|
||||
if (nm[n.id]) { nm[n.id].w = Math.min(1.0, (nm[n.id].w || 0.5) + 0.08); }
|
||||
else { nm[n.id] = n; }
|
||||
});
|
||||
g.nodes = Object.values(nm);
|
||||
g.edges.forEach(function(e) { em[ek(e)] = e; });
|
||||
(ne || []).forEach(function(e) {
|
||||
var k = ek(e);
|
||||
if (em[k]) { em[k].weight = Math.min(1.0, (em[k].weight || 0.5) + 0.05); }
|
||||
else { em[k] = e; }
|
||||
});
|
||||
g.edges = Object.values(em);
|
||||
s._m = g; saveSession(s);
|
||||
}
|
||||
|
||||
function _ra(g, q) {
|
||||
if (!g || !g.nodes || !g.nodes.length) return [];
|
||||
var words = q.toLowerCase().split(/\s+/).filter(function(w) { return w.length > 3; });
|
||||
var sc = {};
|
||||
g.nodes.forEach(function(n) {
|
||||
var t = (n.content || '').toLowerCase();
|
||||
sc[n.id] = words.filter(function(w) { return t.indexOf(w) !== -1; }).length * 0.6 + (n.w || 0.5) * 0.4;
|
||||
});
|
||||
(g.edges || []).forEach(function(e) {
|
||||
if (sc[e.from] > 0.1) sc[e.to] = (sc[e.to] || 0) + sc[e.from] * (e.weight || 0.5) * 0.4;
|
||||
});
|
||||
return g.nodes.filter(function(n) { return sc[n.id] > 0.2; })
|
||||
.sort(function(a,b) { return sc[b.id]-sc[a.id]; }).slice(0,5)
|
||||
.map(function(n) { return { id: n.id, content: n.content, score: sc[n.id] }; });
|
||||
}
|
||||
|
||||
// ?reset=1 clears the session and reloads clean
|
||||
if (window.location.search.indexOf('reset=1') !== -1) {
|
||||
clearSession();
|
||||
var clean = window.location.pathname;
|
||||
window.history.replaceState({}, '', clean);
|
||||
}
|
||||
|
||||
var session = loadSession();
|
||||
// Ensure every user has a stable unique session ID.
|
||||
if (!session.uid) {
|
||||
session.uid = 'u' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
||||
saveSession(session);
|
||||
}
|
||||
var msgCount = session.count || 0;
|
||||
|
||||
function updateCountdown() {
|
||||
var el = document.getElementById('neuron-demo-countdown');
|
||||
if (!el) return;
|
||||
var remaining = MAX - msgCount;
|
||||
el.textContent = remaining + ' question' + (remaining === 1 ? '' : 's') + ' left';
|
||||
el.style.color = remaining <= 5 ? '#c44' : 'rgba(255,255,255,0.45)';
|
||||
}
|
||||
|
||||
window.neuronDemoReset = function() {
|
||||
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
|
||||
session = { messages: [], count: 0, context: '' };
|
||||
msgCount = 0;
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
if (msgs) msgs.innerHTML = '';
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
if (input) { input.disabled = false; input.placeholder = 'Ask me anything...'; }
|
||||
var btn = document.getElementById('neuron-demo-send');
|
||||
if (btn) btn.disabled = false;
|
||||
addMsg('ai', 'Hi. I am Neuron. You get 5 questions.', true);
|
||||
};
|
||||
|
||||
window.neuronDemoToggle = function() {
|
||||
isOpen = !isOpen;
|
||||
var panel = document.getElementById('neuron-demo-panel');
|
||||
if (panel) panel.style.display = isOpen ? 'flex' : 'none';
|
||||
// Hide launch button on mobile when panel is open (panel covers it)
|
||||
var btn = document.getElementById('neuron-demo-btn');
|
||||
if (btn) btn.style.display = isOpen ? 'none' : '';
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
if (isOpen && msgs && msgs.children.length === 0) {
|
||||
addMsg('ai', 'Hi! How are you?');
|
||||
if (isOpen && turnstileVerified && msgs && msgs.style.display !== 'none' && msgs.children.length === 0) {
|
||||
// Restore previous conversation if it exists
|
||||
if (session.messages && session.messages.length > 0) {
|
||||
session.messages.forEach(function(m) { addMsg(m.role, m.text, true); });
|
||||
var remaining = MAX - msgCount;
|
||||
if (remaining <= 0) {
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
if (input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
|
||||
}
|
||||
} else {
|
||||
addMsg('ai', 'Hi. I am Neuron. You get 5 questions.', true);
|
||||
}
|
||||
}
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
if (isOpen && input) input.focus();
|
||||
if (isOpen && input && !input.disabled) input.focus();
|
||||
// Init Turnstile on first open
|
||||
updateCountdown();
|
||||
if (isOpen && !turnstileWidgetId && typeof turnstile !== 'undefined') {
|
||||
var container = document.getElementById('neuron-demo-turnstile');
|
||||
if (container) {
|
||||
turnstileWidgetId = turnstile.render(container, {
|
||||
sitekey: TURNSTILE_SITE_KEY,
|
||||
size: 'compact',
|
||||
callback: function(token) {
|
||||
turnstileToken = token;
|
||||
turnstileVerified = true;
|
||||
// Destroy the widget completely
|
||||
if (typeof turnstile !== 'undefined' && turnstileWidgetId !== null) {
|
||||
try { turnstile.remove(turnstileWidgetId); } catch(e) {}
|
||||
turnstileWidgetId = null;
|
||||
}
|
||||
// Swap gate for chat
|
||||
var gate = document.getElementById('neuron-demo-gate');
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
var inputRow = document.getElementById('neuron-demo-input-row');
|
||||
if (gate) gate.style.display = 'none';
|
||||
if (msgs) msgs.style.display = 'flex';
|
||||
if (inputRow) inputRow.style.display = 'flex';
|
||||
// Show opening message
|
||||
addMsg('ai', 'Hi. I am Neuron. You get 5 questions.', true);
|
||||
updateCountdown();
|
||||
var inp = document.getElementById('neuron-demo-text');
|
||||
if (inp) inp.focus();
|
||||
},
|
||||
'expired-callback': function() {
|
||||
turnstileToken = '';
|
||||
turnstileVerified = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function addMsg(role, text) {
|
||||
function addMsg(role, text, skipSave) {
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
if (!msgs) return null;
|
||||
var el = document.createElement('div');
|
||||
el.className = 'demo-msg demo-msg-' + role;
|
||||
if (role === 'ai' && typeof marked !== 'undefined') {
|
||||
try { el.innerHTML = marked.parse(text); } catch(e) { el.textContent = text; }
|
||||
// Avatar - use DOM API to avoid quote escaping issues
|
||||
var avatar = document.createElement('div');
|
||||
avatar.className = 'demo-msg-avatar';
|
||||
if (role === 'ai') {
|
||||
var img = document.createElement('img');
|
||||
img.src = '/assets/neuron-icon.png';
|
||||
img.alt = 'Neuron';
|
||||
avatar.appendChild(img);
|
||||
} else {
|
||||
el.textContent = text;
|
||||
var svgNS = 'http://www.w3.org/2000/svg';
|
||||
var svg = document.createElementNS(svgNS, 'svg');
|
||||
svg.setAttribute('width', '14'); svg.setAttribute('height', '14');
|
||||
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('fill', 'none');
|
||||
svg.setAttribute('stroke', 'currentColor'); svg.setAttribute('stroke-width', '2');
|
||||
var p1 = document.createElementNS(svgNS, 'path');
|
||||
p1.setAttribute('d', 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2');
|
||||
var c1 = document.createElementNS(svgNS, 'circle');
|
||||
c1.setAttribute('cx', '12'); c1.setAttribute('cy', '7'); c1.setAttribute('r', '4');
|
||||
svg.appendChild(p1); svg.appendChild(c1);
|
||||
avatar.appendChild(svg);
|
||||
}
|
||||
// Bubble
|
||||
var bubble = document.createElement('div');
|
||||
bubble.className = 'demo-msg-bubble';
|
||||
if (role === 'ai' && typeof marked !== 'undefined') {
|
||||
try { bubble.innerHTML = marked.parse(text); } catch(e) { bubble.textContent = text; }
|
||||
} else {
|
||||
bubble.textContent = text;
|
||||
}
|
||||
if (role === 'ai') {
|
||||
var bodyWrap = document.createElement('div');
|
||||
bodyWrap.className = 'demo-msg-ai-body';
|
||||
bodyWrap.appendChild(bubble);
|
||||
if (!skipSave) {
|
||||
var shareBtn = document.createElement('button');
|
||||
shareBtn.className = 'demo-share-pill';
|
||||
shareBtn.title = 'Share this response';
|
||||
shareBtn.textContent = 'Share ↗';
|
||||
shareBtn.onclick = async function() {
|
||||
var prevUser = '';
|
||||
if (session.messages) {
|
||||
for (var i = session.messages.length - 1; i >= 0; i--) {
|
||||
if (session.messages[i].role === 'user') { prevUser = session.messages[i].text; break; }
|
||||
}
|
||||
}
|
||||
shareBtn.style.opacity = '0.4';
|
||||
try {
|
||||
var r = await fetch('/api/share', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({question: prevUser, answer: text})
|
||||
});
|
||||
var d = await r.json();
|
||||
if (d.id) window.open('/share/' + d.id, '_blank');
|
||||
} catch(e) {}
|
||||
shareBtn.style.opacity = '1';
|
||||
};
|
||||
bodyWrap.appendChild(shareBtn);
|
||||
}
|
||||
el.appendChild(avatar);
|
||||
el.appendChild(bodyWrap);
|
||||
} else {
|
||||
el.appendChild(avatar);
|
||||
el.appendChild(bubble);
|
||||
}
|
||||
msgs.appendChild(el);
|
||||
msgs.scrollTop = msgs.scrollHeight;
|
||||
// Persist to localStorage unless restoring
|
||||
if (!skipSave && role !== 'thinking') {
|
||||
session.messages = session.messages || [];
|
||||
session.messages.push({ role: role, text: text });
|
||||
// Keep last 40 messages to avoid storage bloat
|
||||
if (session.messages.length > 40) session.messages = session.messages.slice(-40);
|
||||
saveSession(session);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
@@ -1884,23 +2277,47 @@ fn page_close() -> String {
|
||||
addMsg('user', msg);
|
||||
var thinking = addMsg('thinking', 'thinking...');
|
||||
if (thinking) thinking.className = 'demo-msg demo-msg-thinking';
|
||||
msgCount++;
|
||||
if (msgCount >= MAX && input) {
|
||||
input.disabled = true;
|
||||
input.placeholder = 'Interaction limit reached';
|
||||
}
|
||||
if (turnstileVerified && !session._cfSent) { session._cfSent = true; }
|
||||
try {
|
||||
// Build history from session for soul context
|
||||
var hist = (session.messages || []).slice(-20).filter(function(m){ return m.role !== 'thinking'; }).map(function(m){
|
||||
return {role: m.role === 'ai' ? 'assistant' : 'user', content: m.text};
|
||||
});
|
||||
// Activate local engram nodes relevant to this message
|
||||
var activated_nodes = _ra(session._m, msg);
|
||||
var r = await fetch('/api/demo', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({message: msg})
|
||||
body: JSON.stringify({
|
||||
message: msg,
|
||||
history: hist,
|
||||
cf_token: turnstileVerified && !session._cfSent ? turnstileToken : '',
|
||||
uid: session.uid || '',
|
||||
activated_nodes: activated_nodes,
|
||||
engram_node_count: (session._m && session._m.nodes) ? session._m.nodes.length : 0
|
||||
})
|
||||
});
|
||||
var d = await r.json();
|
||||
if (thinking) thinking.remove();
|
||||
addMsg('ai', d.response || d.reply || d.message || 'No response');
|
||||
// Merge session nodes returned by soul into local engram
|
||||
_um(session, d.sn, d.se);
|
||||
var reply = d.response || d.reply || d.message || '';
|
||||
var isError = !reply || reply === 'Stepped out for a moment. Try again.';
|
||||
if (!isError) {
|
||||
// Only count as an interaction on a real response
|
||||
msgCount++;
|
||||
session.count = msgCount;
|
||||
saveSession(session);
|
||||
updateCountdown();
|
||||
if (msgCount >= MAX && input) {
|
||||
input.disabled = true;
|
||||
input.placeholder = 'Interaction limit reached';
|
||||
}
|
||||
}
|
||||
addMsg('ai', reply || 'Stepped out for a moment. Try again.');
|
||||
} catch(e) {
|
||||
if (thinking) thinking.remove();
|
||||
addMsg('ai', 'Connection error. Try again.');
|
||||
addMsg('ai', 'Stepped out for a moment. Try again.');
|
||||
}
|
||||
if (msgCount < MAX && btn) btn.disabled = false;
|
||||
if (input) input.focus();
|
||||
|
||||
Reference in New Issue
Block a user