openvibers/openvibe.live Commits
Join panel: perks collapse to a chip row, sentences on request
Changes
@@@ -207,18 +207,46 @@@keyframes joinMark { 0%, 100% { transform: translateY(0) rotate(0); } 50% { transform: translateY(-3px) rotate(-7deg); } }.ovg-join-head h3 { margin: 0; font-size: 1.02rem; font-weight: 900; letter-spacing: -0.015em; }.ovg-join-head p { margin: 3px 0 0; font-size: 0.82rem; color: var(--text-muted); }--.ovg-join-perks { position: relative; list-style: none; margin: 14px 0 0; padding: 0;-- display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px 18px; }--/* Visible by default — the entrance is a bonus, never the reason anything is readable. */--.ovg-join-perks li { display: flex; gap: 10px; align-items: flex-start; }++/* Collapsed: a wrapped row of labelled chips — the whole pitch at a glance, one line tall per++ two or three perks. Expanded: the same list with its sentences, in columns. The collapse is++ purely presentational, so the text is always in the document for crawlers and screen readers. */++.ovg-join-perks { position: relative; list-style: none; margin: 13px 0 0; padding: 0;++ display: flex; flex-wrap: wrap; gap: 7px; }++.ovg-join-perks li { display: flex; gap: 8px; align-items: center;++ padding: 6px 11px 6px 7px; border-radius: 999px;++ background: color-mix(in srgb, var(--accent) 9%, transparent);++ border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);++ transition: background 0.2s, border-color 0.2s; }++.ovg-join-perks li:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); border-color: color-mix(in srgb, var(--accent) 38%, transparent); }++.ovg-join-perks small { display: none; }++.ovg-join .ovg-join-ico { width: 22px; height: 22px; border-radius: 7px; font-size: 0.66rem; }++.ovg-join-perks b { font-size: 0.8rem; white-space: nowrap; }++++/* Expanded */++.ovg-join.is-open .ovg-join-perks { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px 18px; }++.ovg-join.is-open .ovg-join-perks li { display: flex; align-items: flex-start; gap: 10px;++ padding: 0; border: 0; border-radius: 0; background: none; }++.ovg-join.is-open .ovg-join-perks li:hover { background: none; }++.ovg-join.is-open .ovg-join-perks small { display: block; }++.ovg-join.is-open .ovg-join-ico { width: 28px; height: 28px; border-radius: 9px; font-size: 0.76rem; }++.ovg-join.is-open .ovg-join-perks b { white-space: normal; font-size: 0.86rem; }++++.ovg-join-expand { display: inline-flex; align-items: center; gap: 7px; margin-top: 11px;++ padding: 5px 2px; background: none; border: 0; cursor: pointer; font: inherit;++ font-size: 0.76rem; font-weight: 700; color: color-mix(in srgb, var(--accent) 78%, var(--text-muted));++ transition: color 0.2s; }++.ovg-join-expand:hover { color: var(--accent); }++.ovg-join-expand i { font-size: 0.66rem; transition: transform 0.28s cubic-bezier(.2, 1, .3, 1); }++.ovg-join.is-open .ovg-join-expand i { transform: rotate(180deg); }.tour-quest-slot.fx-live .ovg-join-perks li { animation: joinPerk 0.6s cubic-bezier(.2, 1, .3, 1) both;animation-delay: calc(var(--i) * 85ms + 100ms); }@keyframes joinPerk { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }.ovg-join-ico { width: 28px; height: 28px; flex: none; border-radius: 9px; display: grid; place-items: center; font-size: 0.76rem;color: var(--accent); background: color-mix(in srgb, var(--accent) 15%, transparent);border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent); }--.ovg-join-perks b { display: block; font-size: 0.86rem; font-weight: 800; }--.ovg-join-perks small { display: block; margin-top: 1px; font-size: 0.76rem; line-height: 1.45; color: var(--text-muted); }++.ovg-join-perks b { font-weight: 800; }++.ovg-join.is-open .ovg-join-perks b { display: block; }++.ovg-join.is-open .ovg-join-perks small { margin-top: 1px; font-size: 0.76rem; line-height: 1.45; color: var(--text-muted); }.ovg-join-cta { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 16px; }.ovg-join-go { position: relative; overflow: hidden; }.ovg-join-go::after { content: ''; position: absolute; top: 0; bottom: 0; left: -45%; width: 35%;@@@ -90,7 +90,7 @@<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin><link rel="stylesheet" href="/css/style.css?v=169"><link rel="stylesheet" href="/css/home-fx.css?v=10">-- <link rel="stylesheet" href="/css/home-tour.css?v=4">++ <link rel="stylesheet" href="/css/home-tour.css?v=6"><link rel="stylesheet" href="/css/i18n-star.css?v=5"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/chat-shared.css?v=1" media="print" onload="this.media='all';this.onload=null">@@@ -5215,7 +5215,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig<script src="/js/home-fx.js?v=8" defer></script><script src="/js/home-tour.js?v=6" defer></script><script src="/js/ov-guide.js?v=1" defer></script>--<script src="/js/guides-live.js?v=4" defer></script>++<script src="/js/guides-live.js?v=5" defer></script><!-- ── OpenVibe shared libs (notifications, account sync, themes) ── --><script src="/shared/theme-loader.js?v=1" defer></script>@@@ -349,8 +349,14 @@}function renderJoin(el) {++ // Six perks with a sentence each is a tall block to put in the middle of a front page, and++ // most of that height is explanation nobody needs before they are interested. Collapsed,++ // the perks are a single wrapped row of labels — still the whole pitch, at a glance — and++ // the sentences are one tap away for anyone who wants them. The call to action never++ // moves or hides.++ const open = (() => { try { return localStorage.getItem('ov_join_open') === '1'; } catch { return false; } })();el.innerHTML = `-- <div class="ovg-join">++ <div class="ovg-join${open ? ' is-open' : ''}"><span class="ovg-join-glow" aria-hidden="true"></span><div class="ovg-join-head"><span class="ovg-join-mark" aria-hidden="true"><i class="fa-solid fa-circle-nodes"></i></span>@@@ -359,9 +365,12 @@<p>Free to make, works everywhere on the network, and takes about ten seconds.</p></div></div>-- <ul class="ovg-join-perks">++ <ul class="ovg-join-perks" id="ovg-join-perks">${PERKS.map(([ico, title, why], i) => `<li style="--i:${i}"><span class="ovg-join-ico"><i class="fa-solid ${ico}"></i></span><span><b>${title}</b><small>${why}</small></span></li>`).join('')}</ul>++ <button type="button" class="ovg-join-expand" aria-expanded="${open}" aria-controls="ovg-join-perks">++ <i class="fa-solid fa-chevron-down"></i><span></span>++ </button><div class="ovg-join-cta"><a class="btn btn-primary btn-lg ovg-join-go" href="/api/auth/sso/login"><i class="fa-solid fa-user-plus"></i> Create your free account</a><a class="btn btn-outline ovg-join-in" href="/api/auth/sso/login"><i class="fa-solid fa-right-to-bracket"></i> I already have one</a>@@@ -369,6 +378,16 @@</div></div>`;el.querySelector('.ovg-join-tour').addEventListener('click', () => openSiteTour());++ const panel = el.querySelector('.ovg-join');++ const exp = el.querySelector('.ovg-join-expand');++ const label = () => { exp.querySelector('span').textContent = panel.classList.contains('is-open') ? 'Less detail' : 'What each of these means'; };++ label();++ exp.addEventListener('click', () => {++ panel.classList.toggle('is-open');++ exp.setAttribute('aria-expanded', String(panel.classList.contains('is-open')));++ label();++ try { localStorage.setItem('ov_join_open', panel.classList.contains('is-open') ? '1' : '0'); } catch { /* */ }++ });}window.setupNextUp = async function (el) {
@@ -207,18 +207,46 @@
@@ -207,18 +207,46 @@
@keyframes joinMark { 0%, 100% { transform: translateY(0) rotate(0); } 50% { transform: translateY(-3px) rotate(-7deg); } }
@keyframes joinMark { 0%, 100% { transform: translateY(0) rotate(0); } 50% { transform: translateY(-3px) rotate(-7deg); } }
.ovg-join-head h3 { margin: 0; font-size: 1.02rem; font-weight: 900; letter-spacing: -0.015em; }
.ovg-join-head h3 { margin: 0; font-size: 1.02rem; font-weight: 900; letter-spacing: -0.015em; }
.ovg-join-head p { margin: 3px 0 0; font-size: 0.82rem; color: var(--text-muted); }
.ovg-join-head p { margin: 3px 0 0; font-size: 0.82rem; color: var(--text-muted); }
-.ovg-join-perks { position: relative; list-style: none; margin: 14px 0 0; padding: 0;
- display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px 18px; }
-/* Visible by default — the entrance is a bonus, never the reason anything is readable. */
-.ovg-join-perks li { display: flex; gap: 10px; align-items: flex-start; }
+/* Collapsed: a wrapped row of labelled chips — the whole pitch at a glance, one line tall per
+ two or three perks. Expanded: the same list with its sentences, in columns. The collapse is
+ purely presentational, so the text is always in the document for crawlers and screen readers. */
+.ovg-join-perks { position: relative; list-style: none; margin: 13px 0 0; padding: 0;
+ display: flex; flex-wrap: wrap; gap: 7px; }
+.ovg-join-perks li { display: flex; gap: 8px; align-items: center;
+ padding: 6px 11px 6px 7px; border-radius: 999px;
+ background: color-mix(in srgb, var(--accent) 9%, transparent);
+ border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
+ transition: background 0.2s, border-color 0.2s; }
+.ovg-join-perks li:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); border-color: color-mix(in srgb, var(--accent) 38%, transparent); }
+.ovg-join-perks small { display: none; }
+.ovg-join .ovg-join-ico { width: 22px; height: 22px; border-radius: 7px; font-size: 0.66rem; }
+.ovg-join-perks b { font-size: 0.8rem; white-space: nowrap; }
+
+/* Expanded */
+.ovg-join.is-open .ovg-join-perks { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px 18px; }
+.ovg-join.is-open .ovg-join-perks li { display: flex; align-items: flex-start; gap: 10px;
+ padding: 0; border: 0; border-radius: 0; background: none; }
+.ovg-join.is-open .ovg-join-perks li:hover { background: none; }
+.ovg-join.is-open .ovg-join-perks small { display: block; }
+.ovg-join.is-open .ovg-join-ico { width: 28px; height: 28px; border-radius: 9px; font-size: 0.76rem; }
+.ovg-join.is-open .ovg-join-perks b { white-space: normal; font-size: 0.86rem; }
+
+.ovg-join-expand { display: inline-flex; align-items: center; gap: 7px; margin-top: 11px;
+ padding: 5px 2px; background: none; border: 0; cursor: pointer; font: inherit;
+ font-size: 0.76rem; font-weight: 700; color: color-mix(in srgb, var(--accent) 78%, var(--text-muted));
+ transition: color 0.2s; }
+.ovg-join-expand:hover { color: var(--accent); }
+.ovg-join-expand i { font-size: 0.66rem; transition: transform 0.28s cubic-bezier(.2, 1, .3, 1); }
+.ovg-join.is-open .ovg-join-expand i { transform: rotate(180deg); }
.tour-quest-slot.fx-live .ovg-join-perks li { animation: joinPerk 0.6s cubic-bezier(.2, 1, .3, 1) both;
.tour-quest-slot.fx-live .ovg-join-perks li { animation: joinPerk 0.6s cubic-bezier(.2, 1, .3, 1) both;
animation-delay: calc(var(--i) * 85ms + 100ms); }
animation-delay: calc(var(--i) * 85ms + 100ms); }
@keyframes joinPerk { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes joinPerk { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.ovg-join-ico { width: 28px; height: 28px; flex: none; border-radius: 9px; display: grid; place-items: center; font-size: 0.76rem;
.ovg-join-ico { width: 28px; height: 28px; flex: none; border-radius: 9px; display: grid; place-items: center; font-size: 0.76rem;
color: var(--accent); background: color-mix(in srgb, var(--accent) 15%, transparent);
color: var(--accent); background: color-mix(in srgb, var(--accent) 15%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent); }
border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent); }
-.ovg-join-perks b { display: block; font-size: 0.86rem; font-weight: 800; }
-.ovg-join-perks small { display: block; margin-top: 1px; font-size: 0.76rem; line-height: 1.45; color: var(--text-muted); }
+.ovg-join-perks b { font-weight: 800; }
+.ovg-join.is-open .ovg-join-perks b { display: block; }
+.ovg-join.is-open .ovg-join-perks small { margin-top: 1px; font-size: 0.76rem; line-height: 1.45; color: var(--text-muted); }
.ovg-join-cta { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 16px; }
.ovg-join-cta { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 16px; }
.ovg-join-go { position: relative; overflow: hidden; }
.ovg-join-go { position: relative; overflow: hidden; }
.ovg-join-go::after { content: ''; position: absolute; top: 0; bottom: 0; left: -45%; width: 35%;
.ovg-join-go::after { content: ''; position: absolute; top: 0; bottom: 0; left: -45%; width: 35%;
@@ -90,7 +90,7 @@
@@ -90,7 +90,7 @@
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<link rel="stylesheet" href="/css/style.css?v=169">
<link rel="stylesheet" href="/css/style.css?v=169">
<link rel="stylesheet" href="/css/home-fx.css?v=10">
<link rel="stylesheet" href="/css/home-fx.css?v=10">
- <link rel="stylesheet" href="/css/home-tour.css?v=4">
+ <link rel="stylesheet" href="/css/home-tour.css?v=6">
<link rel="stylesheet" href="/css/i18n-star.css?v=5">
<link rel="stylesheet" href="/css/i18n-star.css?v=5">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/chat-shared.css?v=1" media="print" onload="this.media='all';this.onload=null">
<link rel="stylesheet" href="/css/chat-shared.css?v=1" media="print" onload="this.media='all';this.onload=null">
@@ -5215,7 +5215,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
@@ -5215,7 +5215,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
<script src="/js/home-fx.js?v=8" defer></script>
<script src="/js/home-fx.js?v=8" defer></script>
<script src="/js/home-tour.js?v=6" defer></script>
<script src="/js/home-tour.js?v=6" defer></script>
<script src="/js/ov-guide.js?v=1" defer></script>
<script src="/js/ov-guide.js?v=1" defer></script>
-<script src="/js/guides-live.js?v=4" defer></script>
+<script src="/js/guides-live.js?v=5" defer></script>
<!-- ── OpenVibe shared libs (notifications, account sync, themes) ── -->
<!-- ── OpenVibe shared libs (notifications, account sync, themes) ── -->
<script src="/shared/theme-loader.js?v=1" defer></script>
<script src="/shared/theme-loader.js?v=1" defer></script>
@@ -349,8 +349,14 @@
@@ -349,8 +349,14 @@
}
}
function renderJoin(el) {
function renderJoin(el) {
+ // Six perks with a sentence each is a tall block to put in the middle of a front page, and
+ // most of that height is explanation nobody needs before they are interested. Collapsed,
+ // the perks are a single wrapped row of labels — still the whole pitch, at a glance — and
+ // the sentences are one tap away for anyone who wants them. The call to action never
+ // moves or hides.
+ const open = (() => { try { return localStorage.getItem('ov_join_open') === '1'; } catch { return false; } })();
el.innerHTML = `
el.innerHTML = `
- <div class="ovg-join">
+ <div class="ovg-join${open ? ' is-open' : ''}">
<span class="ovg-join-glow" aria-hidden="true"></span>
<span class="ovg-join-glow" aria-hidden="true"></span>
<div class="ovg-join-head">
<div class="ovg-join-head">
<span class="ovg-join-mark" aria-hidden="true"><i class="fa-solid fa-circle-nodes"></i></span>
<span class="ovg-join-mark" aria-hidden="true"><i class="fa-solid fa-circle-nodes"></i></span>
@@ -359,9 +365,12 @@
@@ -359,9 +365,12 @@
<p>Free to make, works everywhere on the network, and takes about ten seconds.</p>
<p>Free to make, works everywhere on the network, and takes about ten seconds.</p>
</div>
</div>
</div>
</div>
- <ul class="ovg-join-perks">
+ <ul class="ovg-join-perks" id="ovg-join-perks">
${PERKS.map(([ico, title, why], i) => `<li style="--i:${i}"><span class="ovg-join-ico"><i class="fa-solid ${ico}"></i></span><span><b>${title}</b><small>${why}</small></span></li>`).join('')}
${PERKS.map(([ico, title, why], i) => `<li style="--i:${i}"><span class="ovg-join-ico"><i class="fa-solid ${ico}"></i></span><span><b>${title}</b><small>${why}</small></span></li>`).join('')}
</ul>
</ul>
+ <button type="button" class="ovg-join-expand" aria-expanded="${open}" aria-controls="ovg-join-perks">
+ <i class="fa-solid fa-chevron-down"></i><span></span>
+ </button>
<div class="ovg-join-cta">
<div class="ovg-join-cta">
<a class="btn btn-primary btn-lg ovg-join-go" href="/api/auth/sso/login"><i class="fa-solid fa-user-plus"></i> Create your free account</a>
<a class="btn btn-primary btn-lg ovg-join-go" href="/api/auth/sso/login"><i class="fa-solid fa-user-plus"></i> Create your free account</a>
<a class="btn btn-outline ovg-join-in" href="/api/auth/sso/login"><i class="fa-solid fa-right-to-bracket"></i> I already have one</a>
<a class="btn btn-outline ovg-join-in" href="/api/auth/sso/login"><i class="fa-solid fa-right-to-bracket"></i> I already have one</a>
@@ -369,6 +378,16 @@
@@ -369,6 +378,16 @@
</div>
</div>
</div>`;
</div>`;
el.querySelector('.ovg-join-tour').addEventListener('click', () => openSiteTour());
el.querySelector('.ovg-join-tour').addEventListener('click', () => openSiteTour());
+ const panel = el.querySelector('.ovg-join');
+ const exp = el.querySelector('.ovg-join-expand');
+ const label = () => { exp.querySelector('span').textContent = panel.classList.contains('is-open') ? 'Less detail' : 'What each of these means'; };
+ label();
+ exp.addEventListener('click', () => {
+ panel.classList.toggle('is-open');
+ exp.setAttribute('aria-expanded', String(panel.classList.contains('is-open')));
+ label();
+ try { localStorage.setItem('ov_join_open', panel.classList.contains('is-open') ? '1' : '0'); } catch { /* */ }
+ });
}
}
window.setupNextUp = async function (el) {
window.setupNextUp = async function (el) {