feat(native-el-ui): convert all component files to el-html vessel API
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 4m48s
Dev — Build & local smoke test / build-smoke (pull_request) Failing after 4m48s
Replaced raw HTML heredoc returns with native el_ function calls across all 21 component files. styles.el intentionally excluded.
This commit is contained in:
+282
-227
@@ -12,6 +12,200 @@
|
||||
// anchors, which the HTML5 parser resolves via the adoption agency algorithm,
|
||||
// producing mismatched </div> tags that break gallery-grid's closing tag and
|
||||
// pull sibling elements into the grid as spurious grid items.
|
||||
|
||||
extern fn el_html_doc(lang: String, head_html: String, body_html: String) -> String
|
||||
extern fn el_meta_charset(charset: String) -> String
|
||||
extern fn el_meta(name: String, content: String) -> String
|
||||
extern fn el_link_stylesheet(href: String) -> String
|
||||
extern fn el_title(text: String) -> String
|
||||
extern fn el_script_src(src: String, defer_load: Bool) -> String
|
||||
extern fn el_script_inline(js: String) -> String
|
||||
extern fn el_div(attrs: String, children: String) -> String
|
||||
extern fn el_nav(attrs: String, children: String) -> String
|
||||
extern fn el_a(href: String, attrs: String, children: String) -> String
|
||||
extern fn el_img(src: String, alt: String, attrs: String) -> String
|
||||
extern fn el_button(attrs: String, label: String) -> String
|
||||
extern fn el_span(attrs: String, children: String) -> String
|
||||
extern fn el_p(attrs: String, children: String) -> String
|
||||
extern fn el_h1(attrs: String, text: String) -> String
|
||||
extern fn el_h2(attrs: String, text: String) -> String
|
||||
extern fn el_input(type_attr: String, attrs: String) -> String
|
||||
extern fn el_br() -> String
|
||||
|
||||
fn gallery_css_base() -> String {
|
||||
"*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #FAFAF8; --bg2: #F0F0EC; --card: #FFFFFF;
|
||||
--navy: #0052A0; --navy-65: rgba(0,82,160,.65); --navy-85: rgba(0,82,160,.85);
|
||||
--t1: #0D0D14; --t2: #3A3A4A; --t3: #6B6B7E;
|
||||
--border: rgba(0,0,0,.07); --border2: rgba(0,0,0,.13);
|
||||
--up: #2E7D32; --down: #C62828;
|
||||
--head: 'Playfair Display', Georgia, serif;
|
||||
--body: 'IBM Plex Sans', system-ui, sans-serif;
|
||||
}
|
||||
html { scroll-behavior: smooth; }
|
||||
html, body { background: var(--bg); color: var(--t1); font-family: var(--body); -webkit-font-smoothing: antialiased; }
|
||||
body::before {
|
||||
content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;
|
||||
background-image: linear-gradient(rgba(0,0,0,.022) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,0,0,.022) 1px, transparent 1px);
|
||||
background-size: 48px 48px;
|
||||
}
|
||||
.label { font-family: var(--body); font-size: .75rem; font-weight: 500; letter-spacing: .2em; text-transform: uppercase; color: var(--navy-65); }
|
||||
.display-lg { font-family: var(--head); font-size: clamp(2rem,4vw,3.25rem); font-weight: 600; line-height: 1.1; letter-spacing: -.02em; color: var(--t1); }
|
||||
.btn-primary { display: inline-flex; align-items: center; gap: .75rem; font-family: var(--body); font-size: .75rem; font-weight: 500; letter-spacing: .15em; text-transform: uppercase; text-decoration: none; color: #fff; background: var(--navy); padding: 1rem 2rem; border: none; cursor: pointer; transition: background 300ms; }
|
||||
.btn-primary:hover { background: #0078D4; }
|
||||
.btn-ghost { display: inline-flex; align-items: center; gap: .75rem; font-family: var(--body); font-size: .75rem; font-weight: 400; letter-spacing: .15em; text-transform: uppercase; text-decoration: none; color: var(--t2); background: transparent; padding: 1rem 2rem; border: 1px solid var(--border2); cursor: pointer; transition: border-color 300ms, color 300ms; }
|
||||
.btn-ghost:hover { border-color: rgba(0,82,160,.35); color: var(--t1); }"
|
||||
}
|
||||
|
||||
fn gallery_css_nav() -> String {
|
||||
"#nav { position: fixed; top: 0; left: 0; right: 0; z-index: 100; transition: background 500ms, border-color 500ms; }
|
||||
#nav.scrolled { background: rgba(250,250,248,.95); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-bottom: 1px solid var(--border); }
|
||||
.nav-inner { max-width: 1280px; margin: 0 auto; padding: 0 2rem; height: 4rem; display: flex; align-items: center; justify-content: space-between; position: relative; }
|
||||
.nav-logo img { height: 2rem; width: auto; display: block; }
|
||||
.nav-links { display: flex; align-items: center; gap: 1.25rem; flex-wrap: nowrap; }
|
||||
.nav-link { font-family: var(--body); font-size: .675rem; font-weight: 400; letter-spacing: .16em; text-transform: uppercase; color: var(--t3); text-decoration: none; white-space: nowrap; transition: color 200ms; }
|
||||
.nav-link:hover { color: var(--t1); }
|
||||
.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: .675rem; font-weight: 400; letter-spacing: .16em; text-transform: uppercase; color: var(--t3); transition: color 200ms; white-space: nowrap; }
|
||||
.nav-dropdown-btn:hover { color: var(--t1); }
|
||||
.nav-dropdown-menu { display: none; position: absolute; top: calc(100% + .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: .375rem 0; }
|
||||
.nav-dropdown.open .nav-dropdown-menu { display: flex; }
|
||||
.nav-dropdown-item { font-family: var(--body); font-size: .8125rem; font-weight: 400; color: var(--t2); text-decoration: none; padding: .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: .675rem; font-weight: 500; letter-spacing: .15em; text-transform: uppercase; color: #fff; background: var(--navy); text-decoration: none; padding: .575rem 1.1rem; white-space: nowrap; box-shadow: 0 2px 12px rgba(0,82,160,.20); transition: background 300ms; }
|
||||
.nav-cta:hover { background: #0078D4; }
|
||||
.nav-hamburger { display: none; flex-direction: column; justify-content: center; gap: 5px; background: none; border: none; cursor: pointer; padding: 6px 4px; z-index: 101; -webkit-tap-highlight-color: transparent; }
|
||||
.nav-hamburger span { display: block; width: 22px; height: 2px; background: var(--t1); border-radius: 1px; transition: transform 280ms ease, opacity 200ms ease; transform-origin: center; }
|
||||
.nav-hamburger[aria-expanded=\"true\"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
|
||||
.nav-hamburger[aria-expanded=\"true\"] span:nth-child(2) { opacity: 0; transform: scaleX(0); }
|
||||
.nav-hamburger[aria-expanded=\"true\"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
|
||||
.nav-mobile { display: none; position: absolute; top: 4rem; left: 0; right: 0; background: rgba(250,250,248,.98); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border-bottom: 1px solid var(--border); padding: .5rem 0 1.5rem; flex-direction: column; box-shadow: 0 8px 32px rgba(0,0,0,.08); }
|
||||
.nav-mobile.open { display: flex; }
|
||||
.nav-mobile-link { display: block; padding: .875rem 2rem; font-family: var(--body); font-size: .75rem; font-weight: 400; letter-spacing: .16em; text-transform: uppercase; color: var(--t2); text-decoration: none; border-bottom: 1px solid rgba(0,0,0,.05); transition: color 150ms, background 150ms; }
|
||||
.nav-mobile-link:last-child { border-bottom: none; }
|
||||
.nav-mobile-link:hover { color: var(--t1); background: rgba(0,82,160,.03); }
|
||||
.nav-mobile-cta { display: block; margin: 1rem 2rem 0; padding: .8rem 1.5rem; text-align: center; font-family: var(--body); font-size: .75rem; font-weight: 500; letter-spacing: .15em; text-transform: uppercase; color: #fff; background: var(--navy); text-decoration: none; }
|
||||
@media (max-width: 1060px) { .nav-links { display: none; } .nav-hamburger { display: flex; } }
|
||||
@media (min-width: 1061px) { .nav-mobile { display: none !important; } .nav-hamburger { display: none !important; } }"
|
||||
}
|
||||
|
||||
fn gallery_css_gallery() -> String {
|
||||
".gallery-wrap { max-width: 1100px; margin: 0 auto; padding: 7rem 2.5rem 5rem; position: relative; z-index: 1; }
|
||||
.gallery-header { margin-bottom: 3rem; }
|
||||
.gallery-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.25rem; }
|
||||
@media (max-width: 900px) { .gallery-grid { grid-template-columns: 1fr 1fr; } }
|
||||
@media (max-width: 600px) { .gallery-grid { grid-template-columns: 1fr; } }
|
||||
.gal-card { background: var(--card); border: 1px solid var(--border); padding: 1.5rem; display: flex; flex-direction: column; gap: .875rem; transition: border-color .15s, box-shadow .15s; }
|
||||
.gal-card:hover { border-color: rgba(0,82,160,.25); box-shadow: 0 4px 20px rgba(0,82,160,.07); }
|
||||
.gal-link { text-decoration: none; color: inherit; display: flex; flex-direction: column; gap: .875rem; flex: 1; }
|
||||
.gal-q { font-family: var(--body); font-weight: 500; font-size: .875rem; color: var(--navy); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.gal-a { font-family: var(--body); font-weight: 300; font-size: .8125rem; color: var(--t2); line-height: 1.7; display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; flex: 1; max-height: 11rem; }
|
||||
.gal-a p { margin: 0; }
|
||||
.gal-a p + p { margin-top: .35rem; }
|
||||
.gal-a ol, .gal-a ul { padding-left: 1.25rem; margin: .25rem 0; }
|
||||
.gal-a li + li { margin-top: .15rem; }
|
||||
.gal-a strong { font-weight: 600; color: var(--t1); }
|
||||
.gal-a em { font-style: italic; }
|
||||
.gal-a code { background: rgba(0,0,0,0.05); padding: .1em .3em; border-radius: 3px; font-family: ui-monospace, 'IBM Plex Mono', Menlo, monospace; font-size: .85em; }
|
||||
.gal-a pre { background: var(--bg2); padding: .5rem; overflow-x: auto; font-size: .75rem; margin: .4rem 0; border-radius: 3px; }
|
||||
.gal-a pre code { background: none; padding: 0; }
|
||||
.gal-a a { color: var(--navy); text-decoration: underline; }
|
||||
.gal-a h1, .gal-a h2, .gal-a h3, .gal-a h4 { font-size: 1em; font-weight: 600; margin: .3rem 0; color: var(--t1); }
|
||||
.gal-a blockquote { border-left: 2px solid var(--border2); padding-left: .5rem; color: var(--t2); margin: .3rem 0; }
|
||||
.gal-a img { max-width: 100%; height: auto; }
|
||||
.gal-meta { display: flex; justify-content: space-between; align-items: center; font-family: var(--body); font-size: .65rem; font-weight: 400; letter-spacing: .1em; text-transform: uppercase; color: var(--t3); margin-top: auto; padding-top: .75rem; border-top: 1px solid var(--border); }
|
||||
.gal-date { font-size: .65rem; color: var(--t3); }
|
||||
.vote-controls { display: inline-flex; align-items: center; gap: .35rem; }
|
||||
.vote-btn { background: transparent; border: 1px solid var(--border2); color: var(--t3); cursor: pointer; padding: .2rem .45rem; font-size: .7rem; line-height: 1; transition: all .15s; -webkit-tap-highlight-color: transparent; }
|
||||
.vote-btn:hover:not(:disabled) { color: var(--navy); border-color: rgba(0,82,160,.35); background: rgba(0,82,160,.04); }
|
||||
.vote-btn:focus-visible { outline: 2px solid var(--navy); outline-offset: 1px; }
|
||||
.vote-btn:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.vote-btn.is-active.vote-up { background: rgba(46,125,50,.10); color: var(--up); border-color: rgba(46,125,50,.45); }
|
||||
.vote-btn.is-active.vote-down { background: rgba(198,40,40,.10); color: var(--down); border-color: rgba(198,40,40,.45); }
|
||||
.vote-btn.is-loading { opacity: .55; cursor: wait; }
|
||||
.vote-score { font-size: .8rem; font-weight: 500; color: var(--t1); min-width: 1.5rem; text-align: center; font-variant-numeric: tabular-nums; }
|
||||
.gallery-controls { display: flex; align-items: center; gap: 1rem; margin-bottom: 2rem; flex-wrap: wrap; }
|
||||
.gallery-search { flex: 1; min-width: 200px; font-family: var(--body); font-size: .875rem; font-weight: 300; color: var(--t1); background: #fff; border: 1px solid var(--border2); padding: .625rem 1rem; outline: none; transition: border-color .2s; }
|
||||
.gallery-search:focus { border-color: var(--navy); }
|
||||
.gallery-search::placeholder { color: var(--t3); }
|
||||
.sort-btn { font-family: var(--body); font-size: .7rem; font-weight: 400; letter-spacing: .1em; text-transform: uppercase; color: var(--t3); background: none; border: 1px solid var(--border2); padding: .5rem 1rem; cursor: pointer; transition: all .15s; }
|
||||
.sort-btn:hover, .sort-btn.active { color: var(--navy); border-color: rgba(0,82,160,.3); background: rgba(0,82,160,.03); }
|
||||
.gallery-empty { grid-column: 1/-1; padding: 4rem 0; text-align: center; }
|
||||
.gal-card.hidden { display: none; }"
|
||||
}
|
||||
|
||||
fn gallery_css_modal() -> String {
|
||||
".signin-modal { position: fixed; inset: 0; background: rgba(13,13,20,.55); display: none; align-items: center; justify-content: center; z-index: 1000; padding: 1rem; }
|
||||
.signin-modal.open { display: flex; }
|
||||
.signin-modal-card { background: #fff; border: 1px solid var(--border2); padding: 2rem; max-width: 22rem; width: 100%; box-shadow: 0 16px 48px rgba(0,0,0,.18); }
|
||||
.signin-modal h2 { font-family: var(--head); font-size: 1.4rem; font-weight: 600; color: var(--t1); margin-bottom: .5rem; }
|
||||
.signin-modal p { font-family: var(--body); font-size: .85rem; font-weight: 300; color: var(--t2); line-height: 1.6; margin-bottom: 1.25rem; }
|
||||
.signin-modal input { width: 100%; box-sizing: border-box; font-family: var(--body); font-size: .875rem; font-weight: 300; color: var(--t1); background: #fff; border: 1px solid var(--border2); padding: .75rem 1rem; outline: none; transition: border-color .2s; margin-bottom: .75rem; }
|
||||
.signin-modal input:focus { border-color: var(--navy); }
|
||||
.signin-modal-actions { display: flex; gap: .5rem; justify-content: space-between; align-items: center; }
|
||||
.signin-modal-msg { font-size: .8rem; color: var(--t2); margin-top: .75rem; min-height: 1rem; }
|
||||
.signin-modal .btn-primary { padding: .7rem 1.25rem; font-size: .7rem; }
|
||||
.signin-modal-cancel { background: none; border: none; color: var(--t3); cursor: pointer; font-family: var(--body); font-size: .75rem; letter-spacing: .14em; text-transform: uppercase; padding: .7rem .5rem; }
|
||||
.signin-modal-cancel:hover { color: var(--t1); }"
|
||||
}
|
||||
|
||||
fn gallery_nav_html() -> String {
|
||||
let logo_img: String = el_img(
|
||||
"/assets/brand/neuron-wordmark-on-light.png",
|
||||
"Neuron",
|
||||
"srcset=\"/assets/brand/neuron-wordmark-on-light@2x.png 2x\" height=\"28\""
|
||||
)
|
||||
let logo: String = el_a("/", "class=\"nav-logo\" aria-label=\"Neuron home\"", logo_img)
|
||||
|
||||
let dropdown_menu: String = el_div(
|
||||
"class=\"nav-dropdown-menu\"",
|
||||
el_a("/#mission", "class=\"nav-dropdown-item\"", "Our mission") +
|
||||
el_a("/#safety", "class=\"nav-dropdown-item\"", "Safety") +
|
||||
el_a("/#environmental", "class=\"nav-dropdown-item\"", "Environment")
|
||||
)
|
||||
let dropdown: String = el_div(
|
||||
"class=\"nav-dropdown\"",
|
||||
el_button("class=\"nav-link nav-dropdown-btn\" aria-haspopup=\"true\" aria-expanded=\"false\"", "Mission ▾") +
|
||||
dropdown_menu
|
||||
)
|
||||
|
||||
let nav_links: String = el_div(
|
||||
"class=\"nav-links\"",
|
||||
el_a("/#how-it-works", "class=\"nav-link\"", "How it works") +
|
||||
dropdown +
|
||||
el_a("/#pricing", "class=\"nav-link\"", "Pricing") +
|
||||
el_a("/#marketplace", "class=\"nav-link\"", "Marketplace") +
|
||||
el_a("/#enterprise", "class=\"nav-link\"", "Enterprise") +
|
||||
el_a("/about", "class=\"nav-link\"", "About") +
|
||||
el_a("/said", "class=\"nav-link\"", "Gallery") +
|
||||
el_a("/account", "class=\"nav-link\"", "Account") +
|
||||
el_a("/#pricing", "class=\"nav-cta\"", "Get Access")
|
||||
)
|
||||
|
||||
let hamburger: String = el_button(
|
||||
"class=\"nav-hamburger\" id=\"nav-hamburger\" aria-label=\"Open navigation\" aria-expanded=\"false\" aria-controls=\"nav-mobile\"",
|
||||
el_span("", "") + el_span("", "") + el_span("", "")
|
||||
)
|
||||
|
||||
let nav_mobile: String = el_div(
|
||||
"class=\"nav-mobile\" id=\"nav-mobile\" role=\"navigation\" aria-label=\"Mobile navigation\"",
|
||||
el_a("/#how-it-works", "class=\"nav-mobile-link\"", "How it works") +
|
||||
el_a("/#mission", "class=\"nav-mobile-link\"", "Mission") +
|
||||
el_a("/#safety", "class=\"nav-mobile-link\" style=\"padding-left:1.75rem;font-size:.8rem;color:var(--t3)\"", "- Safety") +
|
||||
el_a("/#environmental", "class=\"nav-mobile-link\" style=\"padding-left:1.75rem;font-size:.8rem;color:var(--t3)\"", "- Environment") +
|
||||
el_a("/#pricing", "class=\"nav-mobile-link\"", "Pricing") +
|
||||
el_a("/#enterprise", "class=\"nav-mobile-link\"", "Enterprise") +
|
||||
el_a("/about", "class=\"nav-mobile-link\"", "About") +
|
||||
el_a("/said", "class=\"nav-mobile-link\"", "Gallery") +
|
||||
el_a("/account", "class=\"nav-mobile-link\"", "Account") +
|
||||
el_a("/#pricing", "class=\"nav-mobile-cta\"", "Get Access")
|
||||
)
|
||||
|
||||
el_nav("id=\"nav\"", el_div("class=\"nav-inner\"", logo + nav_links + hamburger + nav_mobile))
|
||||
}
|
||||
|
||||
fn gallery_page(cards_json: String, supabase_url: String, supabase_anon_key: String) -> String {
|
||||
// Moved from module-level to avoid duplicate main() when linked with other modules.
|
||||
let gallery_share_allowlist: String = "{\"p\":[],\"br\":[],\"strong\":[],\"em\":[],\"u\":[],\"s\":[],\"code\":[],\"pre\":[],\"ul\":[],\"ol\":[],\"li\":[],\"h1\":[],\"h2\":[],\"h3\":[],\"h4\":[],\"blockquote\":[]}"
|
||||
@@ -44,20 +238,20 @@ fn gallery_page(cards_json: String, supabase_url: String, supabase_anon_key: Str
|
||||
}
|
||||
let ts_raw: String = json_get(card, "created_at")
|
||||
let ts_short: String = str_slice(ts_raw, 0, 10)
|
||||
let card_html: String = "<div class=\"gal-card\" data-share-id=\"" + cid + "\" data-score=\"" + score + "\" data-ts=\"" + cid + "\">
|
||||
<a href=\"/share/" + cid + "\" class=\"gal-link\">
|
||||
<div class=\"gal-q\">" + q_html + "</div>
|
||||
<div class=\"gal-a\">" + a_html + "</div>
|
||||
</a>
|
||||
<div class=\"gal-meta\">
|
||||
<div class=\"vote-controls\" data-share-id=\"" + cid + "\">
|
||||
<button type=\"button\" class=\"vote-btn vote-up\" aria-label=\"Upvote\" data-direction=\"up\" disabled>▲</button>
|
||||
<span class=\"vote-score\" data-score=\"" + score + "\">" + score + "</span>
|
||||
<button type=\"button\" class=\"vote-btn vote-down\" aria-label=\"Downvote\" data-direction=\"down\" disabled>▼</button>
|
||||
</div>
|
||||
<span class=\"gal-date\">" + ts_short + " · Read ↗</span>
|
||||
</div>
|
||||
</div>"
|
||||
let card_html: String = "<div class=\"gal-card\" data-share-id=\"" + cid + "\" data-score=\"" + score + "\" data-ts=\"" + cid + "\">" +
|
||||
"<a href=\"/share/" + cid + "\" class=\"gal-link\">" +
|
||||
"<div class=\"gal-q\">" + q_html + "</div>" +
|
||||
"<div class=\"gal-a\">" + a_html + "</div>" +
|
||||
"</a>" +
|
||||
"<div class=\"gal-meta\">" +
|
||||
"<div class=\"vote-controls\" data-share-id=\"" + cid + "\">" +
|
||||
"<button type=\"button\" class=\"vote-btn vote-up\" aria-label=\"Upvote\" data-direction=\"up\" disabled>▲</button>" +
|
||||
"<span class=\"vote-score\" data-score=\"" + score + "\">" + score + "</span>" +
|
||||
"<button type=\"button\" class=\"vote-btn vote-down\" aria-label=\"Downvote\" data-direction=\"down\" disabled>▼</button>" +
|
||||
"</div>" +
|
||||
"<span class=\"gal-date\">" + ts_short + " · Read ↗</span>" +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
let cards_html = cards_html + card_html
|
||||
let i = i + 1
|
||||
}
|
||||
@@ -65,217 +259,78 @@ fn gallery_page(cards_json: String, supabase_url: String, supabase_anon_key: Str
|
||||
"<div style=\"grid-column:1/-1;padding:4rem 0;text-align:center\"><p class=\"label\" style=\"margin-bottom:1rem\">Nothing yet</p><p style=\"font-family:var(--body);font-weight:300;color:var(--t2);line-height:1.7\">Nothing shared yet.</p></div>"
|
||||
} else { "" }
|
||||
|
||||
return <!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Things Neuron Said</title>
|
||||
<meta name="description" content="Real conversations with Neuron, voted up by users.">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&family=IBM+Plex+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #FAFAF8; --bg2: #F0F0EC; --card: #FFFFFF;
|
||||
--navy: #0052A0; --navy-65: rgba(0,82,160,.65); --navy-85: rgba(0,82,160,.85);
|
||||
--t1: #0D0D14; --t2: #3A3A4A; --t3: #6B6B7E;
|
||||
--border: rgba(0,0,0,.07); --border2: rgba(0,0,0,.13);
|
||||
--up: #2E7D32; --down: #C62828;
|
||||
--head: 'Playfair Display', Georgia, serif;
|
||||
--body: 'IBM Plex Sans', system-ui, sans-serif;
|
||||
}
|
||||
html { scroll-behavior: smooth; }
|
||||
html, body { background: var(--bg); color: var(--t1); font-family: var(--body); -webkit-font-smoothing: antialiased; }
|
||||
body::before {
|
||||
content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;
|
||||
background-image: linear-gradient(rgba(0,0,0,.022) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,0,0,.022) 1px, transparent 1px);
|
||||
background-size: 48px 48px;
|
||||
}
|
||||
.label { font-family: var(--body); font-size: .75rem; font-weight: 500; letter-spacing: .2em; text-transform: uppercase; color: var(--navy-65); }
|
||||
.display-lg { font-family: var(--head); font-size: clamp(2rem,4vw,3.25rem); font-weight: 600; line-height: 1.1; letter-spacing: -.02em; color: var(--t1); }
|
||||
.btn-primary { display: inline-flex; align-items: center; gap: .75rem; font-family: var(--body); font-size: .75rem; font-weight: 500; letter-spacing: .15em; text-transform: uppercase; text-decoration: none; color: #fff; background: var(--navy); padding: 1rem 2rem; border: none; cursor: pointer; transition: background 300ms; }
|
||||
.btn-primary:hover { background: #0078D4; }
|
||||
.btn-ghost { display: inline-flex; align-items: center; gap: .75rem; font-family: var(--body); font-size: .75rem; font-weight: 400; letter-spacing: .15em; text-transform: uppercase; text-decoration: none; color: var(--t2); background: transparent; padding: 1rem 2rem; border: 1px solid var(--border2); cursor: pointer; transition: border-color 300ms, color 300ms; }
|
||||
.btn-ghost:hover { border-color: rgba(0,82,160,.35); color: var(--t1); }
|
||||
/* Nav */
|
||||
#nav { position: fixed; top: 0; left: 0; right: 0; z-index: 100; transition: background 500ms, border-color 500ms; }
|
||||
#nav.scrolled { background: rgba(250,250,248,.95); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-bottom: 1px solid var(--border); }
|
||||
.nav-inner { max-width: 1280px; margin: 0 auto; padding: 0 2rem; height: 4rem; display: flex; align-items: center; justify-content: space-between; position: relative; }
|
||||
.nav-logo img { height: 2rem; width: auto; display: block; }
|
||||
.nav-links { display: flex; align-items: center; gap: 1.25rem; flex-wrap: nowrap; }
|
||||
.nav-link { font-family: var(--body); font-size: .675rem; font-weight: 400; letter-spacing: .16em; text-transform: uppercase; color: var(--t3); text-decoration: none; white-space: nowrap; transition: color 200ms; }
|
||||
.nav-link:hover { color: var(--t1); }
|
||||
.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: .675rem; font-weight: 400; letter-spacing: .16em; text-transform: uppercase; color: var(--t3); transition: color 200ms; white-space: nowrap; }
|
||||
.nav-dropdown-btn:hover { color: var(--t1); }
|
||||
.nav-dropdown-menu { display: none; position: absolute; top: calc(100% + .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: .375rem 0; }
|
||||
.nav-dropdown.open .nav-dropdown-menu { display: flex; }
|
||||
.nav-dropdown-item { font-family: var(--body); font-size: .8125rem; font-weight: 400; color: var(--t2); text-decoration: none; padding: .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: .675rem; font-weight: 500; letter-spacing: .15em; text-transform: uppercase; color: #fff; background: var(--navy); text-decoration: none; padding: .575rem 1.1rem; white-space: nowrap; box-shadow: 0 2px 12px rgba(0,82,160,.20); transition: background 300ms; }
|
||||
.nav-cta:hover { background: #0078D4; }
|
||||
.nav-hamburger { display: none; flex-direction: column; justify-content: center; gap: 5px; background: none; border: none; cursor: pointer; padding: 6px 4px; z-index: 101; -webkit-tap-highlight-color: transparent; }
|
||||
.nav-hamburger span { display: block; width: 22px; height: 2px; background: var(--t1); border-radius: 1px; transition: transform 280ms ease, opacity 200ms ease; transform-origin: center; }
|
||||
.nav-hamburger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
|
||||
.nav-hamburger[aria-expanded="true"] span:nth-child(2) { opacity: 0; transform: scaleX(0); }
|
||||
.nav-hamburger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
|
||||
.nav-mobile { display: none; position: absolute; top: 4rem; left: 0; right: 0; background: rgba(250,250,248,.98); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border-bottom: 1px solid var(--border); padding: .5rem 0 1.5rem; flex-direction: column; box-shadow: 0 8px 32px rgba(0,0,0,.08); }
|
||||
.nav-mobile.open { display: flex; }
|
||||
.nav-mobile-link { display: block; padding: .875rem 2rem; font-family: var(--body); font-size: .75rem; font-weight: 400; letter-spacing: .16em; text-transform: uppercase; color: var(--t2); text-decoration: none; border-bottom: 1px solid rgba(0,0,0,.05); transition: color 150ms, background 150ms; }
|
||||
.nav-mobile-link:last-child { border-bottom: none; }
|
||||
.nav-mobile-link:hover { color: var(--t1); background: rgba(0,82,160,.03); }
|
||||
.nav-mobile-cta { display: block; margin: 1rem 2rem 0; padding: .8rem 1.5rem; text-align: center; font-family: var(--body); font-size: .75rem; font-weight: 500; letter-spacing: .15em; text-transform: uppercase; color: #fff; background: var(--navy); text-decoration: none; }
|
||||
@media (max-width: 1060px) { .nav-links { display: none; } .nav-hamburger { display: flex; } }
|
||||
@media (min-width: 1061px) { .nav-mobile { display: none !important; } .nav-hamburger { display: none !important; } }
|
||||
/* Gallery */
|
||||
.gallery-wrap { max-width: 1100px; margin: 0 auto; padding: 7rem 2.5rem 5rem; position: relative; z-index: 1; }
|
||||
.gallery-header { margin-bottom: 3rem; }
|
||||
.gallery-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.25rem; }
|
||||
@media (max-width: 900px) { .gallery-grid { grid-template-columns: 1fr 1fr; } }
|
||||
@media (max-width: 600px) { .gallery-grid { grid-template-columns: 1fr; } }
|
||||
.gal-card { background: var(--card); border: 1px solid var(--border); padding: 1.5rem; display: flex; flex-direction: column; gap: .875rem; transition: border-color .15s, box-shadow .15s; }
|
||||
.gal-card:hover { border-color: rgba(0,82,160,.25); box-shadow: 0 4px 20px rgba(0,82,160,.07); }
|
||||
.gal-link { text-decoration: none; color: inherit; display: flex; flex-direction: column; gap: .875rem; flex: 1; }
|
||||
.gal-q { font-family: var(--body); font-weight: 500; font-size: .875rem; color: var(--navy); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.gal-a { font-family: var(--body); font-weight: 300; font-size: .8125rem; color: var(--t2); line-height: 1.7; display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; flex: 1; max-height: 11rem; }
|
||||
.gal-a p { margin: 0; }
|
||||
.gal-a p + p { margin-top: .35rem; }
|
||||
.gal-a ol, .gal-a ul { padding-left: 1.25rem; margin: .25rem 0; }
|
||||
.gal-a li + li { margin-top: .15rem; }
|
||||
.gal-a strong { font-weight: 600; color: var(--t1); }
|
||||
.gal-a em { font-style: italic; }
|
||||
.gal-a code { background: rgba(0,0,0,0.05); padding: .1em .3em; border-radius: 3px; font-family: ui-monospace, 'IBM Plex Mono', Menlo, monospace; font-size: .85em; }
|
||||
.gal-a pre { background: var(--bg2); padding: .5rem; overflow-x: auto; font-size: .75rem; margin: .4rem 0; border-radius: 3px; }
|
||||
.gal-a pre code { background: none; padding: 0; }
|
||||
.gal-a a { color: var(--navy); text-decoration: underline; }
|
||||
.gal-a h1, .gal-a h2, .gal-a h3, .gal-a h4 { font-size: 1em; font-weight: 600; margin: .3rem 0; color: var(--t1); }
|
||||
.gal-a blockquote { border-left: 2px solid var(--border2); padding-left: .5rem; color: var(--t2); margin: .3rem 0; }
|
||||
.gal-a img { max-width: 100%; height: auto; }
|
||||
.gal-meta { display: flex; justify-content: space-between; align-items: center; font-family: var(--body); font-size: .65rem; font-weight: 400; letter-spacing: .1em; text-transform: uppercase; color: var(--t3); margin-top: auto; padding-top: .75rem; border-top: 1px solid var(--border); }
|
||||
.gal-date { font-size: .65rem; color: var(--t3); }
|
||||
/* Vote controls */
|
||||
.vote-controls { display: inline-flex; align-items: center; gap: .35rem; }
|
||||
.vote-btn { background: transparent; border: 1px solid var(--border2); color: var(--t3); cursor: pointer; padding: .2rem .45rem; font-size: .7rem; line-height: 1; transition: all .15s; -webkit-tap-highlight-color: transparent; }
|
||||
.vote-btn:hover:not(:disabled) { color: var(--navy); border-color: rgba(0,82,160,.35); background: rgba(0,82,160,.04); }
|
||||
.vote-btn:focus-visible { outline: 2px solid var(--navy); outline-offset: 1px; }
|
||||
.vote-btn:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.vote-btn.is-active.vote-up { background: rgba(46,125,50,.10); color: var(--up); border-color: rgba(46,125,50,.45); }
|
||||
.vote-btn.is-active.vote-down { background: rgba(198,40,40,.10); color: var(--down); border-color: rgba(198,40,40,.45); }
|
||||
.vote-btn.is-loading { opacity: .55; cursor: wait; }
|
||||
.vote-score { font-size: .8rem; font-weight: 500; color: var(--t1); min-width: 1.5rem; text-align: center; font-variant-numeric: tabular-nums; }
|
||||
.gallery-controls { display: flex; align-items: center; gap: 1rem; margin-bottom: 2rem; flex-wrap: wrap; }
|
||||
.gallery-search { flex: 1; min-width: 200px; font-family: var(--body); font-size: .875rem; font-weight: 300; color: var(--t1); background: #fff; border: 1px solid var(--border2); padding: .625rem 1rem; outline: none; transition: border-color .2s; }
|
||||
.gallery-search:focus { border-color: var(--navy); }
|
||||
.gallery-search::placeholder { color: var(--t3); }
|
||||
.sort-btn { font-family: var(--body); font-size: .7rem; font-weight: 400; letter-spacing: .1em; text-transform: uppercase; color: var(--t3); background: none; border: 1px solid var(--border2); padding: .5rem 1rem; cursor: pointer; transition: all .15s; }
|
||||
.sort-btn:hover, .sort-btn.active { color: var(--navy); border-color: rgba(0,82,160,.3); background: rgba(0,82,160,.03); }
|
||||
.gallery-empty { grid-column: 1/-1; padding: 4rem 0; text-align: center; }
|
||||
.gal-card.hidden { display: none; }
|
||||
/* Sign-in modal */
|
||||
.signin-modal { position: fixed; inset: 0; background: rgba(13,13,20,.55); display: none; align-items: center; justify-content: center; z-index: 1000; padding: 1rem; }
|
||||
.signin-modal.open { display: flex; }
|
||||
.signin-modal-card { background: #fff; border: 1px solid var(--border2); padding: 2rem; max-width: 22rem; width: 100%; box-shadow: 0 16px 48px rgba(0,0,0,.18); }
|
||||
.signin-modal h2 { font-family: var(--head); font-size: 1.4rem; font-weight: 600; color: var(--t1); margin-bottom: .5rem; }
|
||||
.signin-modal p { font-family: var(--body); font-size: .85rem; font-weight: 300; color: var(--t2); line-height: 1.6; margin-bottom: 1.25rem; }
|
||||
.signin-modal input { width: 100%; box-sizing: border-box; font-family: var(--body); font-size: .875rem; font-weight: 300; color: var(--t1); background: #fff; border: 1px solid var(--border2); padding: .75rem 1rem; outline: none; transition: border-color .2s; margin-bottom: .75rem; }
|
||||
.signin-modal input:focus { border-color: var(--navy); }
|
||||
.signin-modal-actions { display: flex; gap: .5rem; justify-content: space-between; align-items: center; }
|
||||
.signin-modal-msg { font-size: .8rem; color: var(--t2); margin-top: .75rem; min-height: 1rem; }
|
||||
.signin-modal .btn-primary { padding: .7rem 1.25rem; font-size: .7rem; }
|
||||
.signin-modal-cancel { background: none; border: none; color: var(--t3); cursor: pointer; font-family: var(--body); font-size: .75rem; letter-spacing: .14em; text-transform: uppercase; padding: .7rem .5rem; }
|
||||
.signin-modal-cancel:hover { color: var(--t1); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav id="nav">
|
||||
<div class="nav-inner">
|
||||
<a href="/" class="nav-logo" aria-label="Neuron home"><img src="/assets/brand/neuron-wordmark-on-light.png" srcset="/assets/brand/neuron-wordmark-on-light@2x.png 2x" alt="Neuron" height="28"></a>
|
||||
<div class="nav-links">
|
||||
<a href="/#how-it-works" class="nav-link">How it works</a>
|
||||
<div class="nav-dropdown">
|
||||
<button class="nav-link nav-dropdown-btn" aria-haspopup="true" aria-expanded="false">Mission ▾</button>
|
||||
<div class="nav-dropdown-menu">
|
||||
<a href="/#mission" class="nav-dropdown-item">Our mission</a>
|
||||
<a href="/#safety" class="nav-dropdown-item">Safety</a>
|
||||
<a href="/#environmental" class="nav-dropdown-item">Environment</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/#pricing" class="nav-link">Pricing</a>
|
||||
<a href="/#marketplace" class="nav-link">Marketplace</a>
|
||||
<a href="/#enterprise" class="nav-link">Enterprise</a>
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
<a href="/said" class="nav-link">Gallery</a>
|
||||
<a href="/account" class="nav-link">Account</a>
|
||||
<a href="/#pricing" class="nav-cta">Get Access</a>
|
||||
</div>
|
||||
<button class="nav-hamburger" id="nav-hamburger" aria-label="Open navigation" aria-expanded="false" aria-controls="nav-mobile">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div class="nav-mobile" id="nav-mobile" role="navigation" aria-label="Mobile navigation">
|
||||
<a href="/#how-it-works" class="nav-mobile-link">How it works</a>
|
||||
<a href="/#mission" class="nav-mobile-link">Mission</a>
|
||||
<a href="/#safety" class="nav-mobile-link" style="padding-left:1.75rem;font-size:.8rem;color:var(--t3)">- Safety</a>
|
||||
<a href="/#environmental" class="nav-mobile-link" style="padding-left:1.75rem;font-size:.8rem;color:var(--t3)">- Environment</a>
|
||||
<a href="/#pricing" class="nav-mobile-link">Pricing</a>
|
||||
<a href="/#enterprise" class="nav-mobile-link">Enterprise</a>
|
||||
<a href="/about" class="nav-mobile-link">About</a>
|
||||
<a href="/said" class="nav-mobile-link">Gallery</a>
|
||||
<a href="/account" class="nav-mobile-link">Account</a>
|
||||
<a href="/#pricing" class="nav-mobile-cta">Get Access</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="gallery-wrap">
|
||||
<div class="gallery-header">
|
||||
<p class="label" style="margin-bottom:1rem">Things Neuron Said</p>
|
||||
<h1 class="display-lg" style="margin-bottom:1rem">Real conversations.<br>No curation.</h1>
|
||||
<p style="font-family:var(--body);font-weight:300;font-size:.9375rem;color:var(--t2);line-height:1.75;max-width:36rem">
|
||||
People chatted with Neuron and shared the exchanges that mattered. Voted up by readers. The best ones rise.
|
||||
</p>
|
||||
</div>
|
||||
<div class="gallery-controls">
|
||||
<input type="search" id="gal-search" class="gallery-search" placeholder="Search conversations..." autocomplete="off">
|
||||
<div style="display:flex;gap:.5rem;flex-shrink:0">
|
||||
<button class="sort-btn active" id="sort-top" onclick="setSort('top',this)">Top</button>
|
||||
<button class="sort-btn" id="sort-new" onclick="setSort('new',this)">Newest</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery-grid" id="gallery-grid">{raw(cards_html)}{raw(empty_html)}</div>
|
||||
<div id="no-results" style="display:none;padding:3rem 0;text-align:center">
|
||||
<p style="font-family:var(--body);font-weight:300;color:var(--t3)">No conversations match that search.</p>
|
||||
</div>
|
||||
<div style="margin-top:3rem">
|
||||
<a href="/" class="btn-ghost">← Back to site</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Magic-link sign-in modal -->
|
||||
<div class="signin-modal" id="signin-modal" role="dialog" aria-modal="true" aria-labelledby="signin-title">
|
||||
<div class="signin-modal-card">
|
||||
<h2 id="signin-title">Sign in to vote</h2>
|
||||
<p>We will email you a sign-in link. No password needed.</p>
|
||||
<input type="email" id="signin-email" placeholder="your@email.com" autocomplete="email">
|
||||
<div class="signin-modal-actions">
|
||||
<button type="button" class="signin-modal-cancel" id="signin-cancel">Cancel</button>
|
||||
<button type="button" class="btn-primary" id="signin-send">Send link</button>
|
||||
</div>
|
||||
<p class="signin-modal-msg" id="signin-msg"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.min.js"></script>
|
||||
<script>
|
||||
window.NEURON_CFG=window.NEURON_CFG||{};
|
||||
window.NEURON_CFG.supabase_url="{supabase_url}";
|
||||
window.NEURON_CFG.supabase_anon_key="{supabase_anon_key}";
|
||||
</script>
|
||||
<script src="/js/gallery.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
let all_css: String = gallery_css_base() + gallery_css_nav() + gallery_css_gallery() + gallery_css_modal()
|
||||
let style_block: String = "<style>" + all_css + "</style>"
|
||||
|
||||
let head_html: String =
|
||||
el_meta_charset("UTF-8") +
|
||||
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" +
|
||||
el_title("Things Neuron Said") +
|
||||
el_meta("description", "Real conversations with Neuron, voted up by users.") +
|
||||
"<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/assets/favicon-32.png\">" +
|
||||
"<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">" +
|
||||
"<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>" +
|
||||
"<link href=\"https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&family=IBM+Plex+Sans:wght@300;400;500;600&display=swap\" rel=\"stylesheet\">" +
|
||||
style_block
|
||||
|
||||
let sort_controls: String = el_div(
|
||||
"style=\"display:flex;gap:.5rem;flex-shrink:0\"",
|
||||
el_button("class=\"sort-btn active\" id=\"sort-top\" onclick=\"setSort('top',this)\"", "Top") +
|
||||
el_button("class=\"sort-btn\" id=\"sort-new\" onclick=\"setSort('new',this)\"", "Newest")
|
||||
)
|
||||
let gallery_controls: String = el_div(
|
||||
"class=\"gallery-controls\"",
|
||||
el_input("search", "id=\"gal-search\" class=\"gallery-search\" placeholder=\"Search conversations...\" autocomplete=\"off\"") +
|
||||
sort_controls
|
||||
)
|
||||
|
||||
let no_results: String = el_div(
|
||||
"id=\"no-results\" style=\"display:none;padding:3rem 0;text-align:center\"",
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;color:var(--t3)\"", "No conversations match that search.")
|
||||
)
|
||||
|
||||
let gallery_header: String = el_div(
|
||||
"class=\"gallery-header\"",
|
||||
el_p("class=\"label\" style=\"margin-bottom:1rem\"", "Things Neuron Said") +
|
||||
el_h1("class=\"display-lg\" style=\"margin-bottom:1rem\"", "Real conversations." + el_br() + "No curation.") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;font-size:.9375rem;color:var(--t2);line-height:1.75;max-width:36rem\"",
|
||||
"People chatted with Neuron and shared the exchanges that mattered. Voted up by readers. The best ones rise."
|
||||
)
|
||||
)
|
||||
|
||||
let gallery_grid: String = el_div("class=\"gallery-grid\" id=\"gallery-grid\"", cards_html + empty_html)
|
||||
|
||||
let gallery_wrap: String = el_div(
|
||||
"class=\"gallery-wrap\"",
|
||||
gallery_header + gallery_controls + gallery_grid + no_results +
|
||||
el_div("style=\"margin-top:3rem\"", el_a("/", "class=\"btn-ghost\"", "← Back to site"))
|
||||
)
|
||||
|
||||
let signin_modal: String = el_div(
|
||||
"class=\"signin-modal\" id=\"signin-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"signin-title\"",
|
||||
el_div(
|
||||
"class=\"signin-modal-card\"",
|
||||
el_h2("id=\"signin-title\"", "Sign in to vote") +
|
||||
el_p("", "We will email you a sign-in link. No password needed.") +
|
||||
el_input("email", "id=\"signin-email\" placeholder=\"your@email.com\" autocomplete=\"email\"") +
|
||||
el_div(
|
||||
"class=\"signin-modal-actions\"",
|
||||
el_button("type=\"button\" class=\"signin-modal-cancel\" id=\"signin-cancel\"", "Cancel") +
|
||||
el_button("type=\"button\" class=\"btn-primary\" id=\"signin-send\"", "Send link")
|
||||
) +
|
||||
el_p("class=\"signin-modal-msg\" id=\"signin-msg\"", "")
|
||||
)
|
||||
)
|
||||
|
||||
let cfg_js: String = "window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.supabase_url=\"" + supabase_url + "\";window.NEURON_CFG.supabase_anon_key=\"" + supabase_anon_key + "\";"
|
||||
|
||||
let body_html: String =
|
||||
gallery_nav_html() +
|
||||
gallery_wrap +
|
||||
signin_modal +
|
||||
"<script src=\"https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.min.js\"></script>" +
|
||||
el_script_inline(cfg_js) +
|
||||
el_script_src("/js/gallery.js", true)
|
||||
|
||||
el_html_doc("en", head_html, body_html)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user