openvibers/openvibe.live Commits
Hero: chip alignment, shipped line moved up, and another pass at the blank buttons
Changes
@@@ -24,7 +24,7 @@padding: clamp(6px, 1.2vw, 8px) clamp(14px, 2.4vw, 20px) clamp(6px, 1.2vw, 8px) 6px;border-radius: 999px; min-height: clamp(44px, 7.2vw, 52px); width: 100%;font: inherit; font-weight: 700; font-size: clamp(0.88rem, 1.6vw, 0.98rem); letter-spacing: 0.01em;-- color: var(--text-primary); text-decoration: none; cursor: pointer; isolation: isolate; overflow: hidden;++ color: var(--text-primary); text-decoration: none; cursor: pointer; overflow: hidden;background:linear-gradient(180deg, rgba(255,255,255,0.07), rgba(255,255,255,0) 46%),color-mix(in srgb, var(--c) 10%, var(--bg-card));@@@ -51,14 +51,18 @@}/* home-fx.js adds .fx-live when the row scrolls into view and restarts it, so the wave alwaysplays when someone arrives at the buttons instead of running unseen on a page-load clock. */--#page-home .hero-cta.fx-live .hb::before { animation-play-state: running; will-change: transform; }++#page-home .hero-cta.fx-live .hb::before { animation-play-state: running; }#page-home .hero-cta.fx-live .hb-ico { animation-play-state: running; }.hb::after {content: ''; position: absolute; inset: -1px; border-radius: inherit; z-index: 0; padding: 1px;background: conic-gradient(from var(--a, 0deg), transparent 0 70%, var(--c) 85%, transparent 100%);-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);-webkit-mask-composite: xor; mask-composite: exclude; opacity: 0; transition: opacity 0.25s;++ display: none;}++/* Only built when a pointer is actually on the button — there are no hover states on a phone,++ so this never costs a touch device anything. */++.hb:hover::after, .hb:focus-visible::after { display: block; }.hb-ico {width: clamp(30px, 5vw, 34px); height: clamp(30px, 5vw, 34px); border-radius: 50%; flex: none;display: grid; place-items: center; font-size: clamp(0.82rem, 1.5vw, 0.92rem); color: var(--c);@@@ -15085,3 +15085,39 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srgmin-height: 0 !important;transition: height 0.42s cubic-bezier(.2, 1, .3, 1);}++++++/* ── Stat chip alignment ─────────────────────────────────────────────────────++ The delta used to sit inline beside the number, so a wide pairing ("70,177" next to "+1.7k 7d")++ wrapped to a second line, pushed the label down, and left that chip taller than its neighbours++ — three rows of chips, none of them lining up.++++ Three columns instead: icon, then the number over its label, then the delta pinned to the right++ edge and vertically centred. Every chip is now the same shape whether it has a delta or not,++ because an absent delta simply collapses its column to zero. Nothing wraps, so nothing can++ change a chip's height. */++.hero-stat {++ display: grid; grid-template-columns: auto minmax(0, 1fr) auto;++ align-items: center; gap: clamp(6px, 1vw, 10px);++}++.hero-stat-meta { min-width: 0; display: flex; flex-direction: column; gap: 1px; }++.hero-stat-num { white-space: nowrap; line-height: 1.15; }++.hero-stat-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2; }++.hero-stat-delta { justify-self: end; align-self: center; flex: none; }++/* The sparkline chip is a single full-width cell, not a three-column one. */++.hero-stat--spark { display: flex; }++++/* The shipped-commit line now sits under the philosophy row in the hero, so it reads as a++ footnote to "built in the open" rather than as something bolted to the bottom of the page. */++#page-home .hero-latest {++ display: inline-flex; align-items: center; gap: 8px; max-width: min(560px, 92vw);++ margin: 0 auto 12px; padding: 6px 14px; border-radius: 999px;++ font-size: 0.76rem; color: var(--text-muted); text-decoration: none;++ background: color-mix(in srgb, var(--accent) 7%, transparent);++ border: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);++ transition: color 0.2s, border-color 0.2s, background 0.2s;++}++#page-home .hero-latest:hover { color: var(--text-primary); border-color: color-mix(in srgb, var(--accent) 42%, transparent); background: color-mix(in srgb, var(--accent) 13%, transparent); }++#page-home .hero-latest b { font-weight: 700; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }++#page-home .hero-latest i { color: var(--accent); flex: none; }++@media (max-width: 480px) { #page-home .hero-latest { font-size: 0.7rem; padding: 5px 11px; max-width: 94vw; } }@@@ -88,8 +88,8 @@overlay) and is fetched without blocking the first paint: media="print" makes the browsertreat it as non-critical, and the onload flips it back to all once it has arrived. --><link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>-- <link rel="stylesheet" href="/css/style.css?v=181">-- <link rel="stylesheet" href="/css/home-fx.css?v=11">++ <link rel="stylesheet" href="/css/style.css?v=183">++ <link rel="stylesheet" href="/css/home-fx.css?v=12"><link rel="stylesheet" href="/css/home-tour.css?v=7"><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">@@@ -307,6 +307,9 @@<span aria-hidden="true">•</span><a href="/dmca" class="hero-philosophy-link" onclick="return handleLinkClick(event, '/dmca')">DMCA</a></div>++ <!-- Newest shipped commit — built in the open, receipts included. Sits under the++ philosophy row so the proof is next to the promise. -->++ <a class="hero-latest" id="hero-latest" href="/updates" onclick="return handleLinkClick(event, '/updates')" style="display:none"></a><div class="hero-stats" id="hero-stats" aria-label="Site stats"><div class="ovsk ovsk-panel" aria-hidden="true"><div class="ovsk-bar" style="height:14px;width:45%"></div><div class="ovsk-bar" style="height:10px;width:92%"></div><div class="ovsk-bar" style="height:10px;width:68%"></div></div></div></div></div>@@@ -740,10 +743,7 @@<div class="home-changelog-fade" id="home-changelog-fade"></div></div>-- <!-- Newest shipped commit — built in the open, receipts included. It belongs under the-- content, not in front of every stream on the site. -->-- <!-- Newest shipped commit — built in the open, receipts included. -->-- <a class="hero-latest" id="hero-latest" href="/updates" onclick="return handleLinkClick(event, '/updates')" style="display:none"></a>++</div>@@@ -5130,7 +5130,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig<script src="/live-notify.js?v=3" defer></script><script src="/js/ov-skeleton.js?v=1" defer></script><script src="/js/ov-odometer.js?v=3" defer></script>--<script src="/js/app.js?v=158" defer></script>++<script src="/js/app.js?v=159" defer></script><script src="/js/stream-player.js?v=22" defer></script><script src="/js/stream-pip.js?v=1" defer></script><script src="/js/chat-shared.js?v=3" defer></script>@@@ -1463,7 +1463,7 @@ function renderHeroStats(stats) {metric: r.metric || null,};const clickable = !!r.metric;-- return `<div class="hero-stat ${r.cls || ''} ${clickable ? 'hero-stat--clickable' : ''}" ${r.key ? `data-stat="${r.key}"` : ''} data-tip="${esc(JSON.stringify(tip))}" ${clickable ? `data-metric="${r.metric}" role="button" tabindex="0" aria-label="${esc(r.label)} — show over time"` : ''}><i class="fa-solid ${r.icon}"></i><div class="hero-stat-meta"><span class="hero-stat-numline"><span class="hero-stat-num" data-n="${r.num || 0}">0</span>${recDelta(r.recent, r.unit || '')}</span><span class="hero-stat-label">${r.label}${clickable ? ' <i class="fa-solid fa-chart-line hero-stat-chart-hint"></i>' : ''}</span>${sub && !r.recent ? `<span class="hero-stat-sub">${sub}</span>` : ''}</div></div>`;++ return `<div class="hero-stat ${r.cls || ''} ${clickable ? 'hero-stat--clickable' : ''}" ${r.key ? `data-stat="${r.key}"` : ''} data-tip="${esc(JSON.stringify(tip))}" ${clickable ? `data-metric="${r.metric}" role="button" tabindex="0" aria-label="${esc(r.label)} — show over time"` : ''}><i class="fa-solid ${r.icon}"></i><div class="hero-stat-meta"><span class="hero-stat-num" data-n="${r.num || 0}">0</span><span class="hero-stat-label">${r.label}${clickable ? ' <i class="fa-solid fa-chart-line hero-stat-chart-hint"></i>' : ''}</span>${sub && !r.recent ? `<span class="hero-stat-sub">${sub}</span>` : ''}</div>${recDelta(r.recent, r.unit || '')}</div>`;};// The full board is four groups and two dozen chips. Shown by default it pushed every live// stream on the site below the fold, and because it only arrives once the stats request lands
@@ -24,7 +24,7 @@
@@ -24,7 +24,7 @@
padding: clamp(6px, 1.2vw, 8px) clamp(14px, 2.4vw, 20px) clamp(6px, 1.2vw, 8px) 6px;
padding: clamp(6px, 1.2vw, 8px) clamp(14px, 2.4vw, 20px) clamp(6px, 1.2vw, 8px) 6px;
border-radius: 999px; min-height: clamp(44px, 7.2vw, 52px); width: 100%;
border-radius: 999px; min-height: clamp(44px, 7.2vw, 52px); width: 100%;
font: inherit; font-weight: 700; font-size: clamp(0.88rem, 1.6vw, 0.98rem); letter-spacing: 0.01em;
font: inherit; font-weight: 700; font-size: clamp(0.88rem, 1.6vw, 0.98rem); letter-spacing: 0.01em;
- color: var(--text-primary); text-decoration: none; cursor: pointer; isolation: isolate; overflow: hidden;
+ color: var(--text-primary); text-decoration: none; cursor: pointer; overflow: hidden;
background:
background:
linear-gradient(180deg, rgba(255,255,255,0.07), rgba(255,255,255,0) 46%),
linear-gradient(180deg, rgba(255,255,255,0.07), rgba(255,255,255,0) 46%),
color-mix(in srgb, var(--c) 10%, var(--bg-card));
color-mix(in srgb, var(--c) 10%, var(--bg-card));
@@ -51,14 +51,18 @@
@@ -51,14 +51,18 @@
}
}
/* home-fx.js adds .fx-live when the row scrolls into view and restarts it, so the wave always
/* home-fx.js adds .fx-live when the row scrolls into view and restarts it, so the wave always
plays when someone arrives at the buttons instead of running unseen on a page-load clock. */
plays when someone arrives at the buttons instead of running unseen on a page-load clock. */
-#page-home .hero-cta.fx-live .hb::before { animation-play-state: running; will-change: transform; }
+#page-home .hero-cta.fx-live .hb::before { animation-play-state: running; }
#page-home .hero-cta.fx-live .hb-ico { animation-play-state: running; }
#page-home .hero-cta.fx-live .hb-ico { animation-play-state: running; }
.hb::after {
.hb::after {
content: ''; position: absolute; inset: -1px; border-radius: inherit; z-index: 0; padding: 1px;
content: ''; position: absolute; inset: -1px; border-radius: inherit; z-index: 0; padding: 1px;
background: conic-gradient(from var(--a, 0deg), transparent 0 70%, var(--c) 85%, transparent 100%);
background: conic-gradient(from var(--a, 0deg), transparent 0 70%, var(--c) 85%, transparent 100%);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor; mask-composite: exclude; opacity: 0; transition: opacity 0.25s;
-webkit-mask-composite: xor; mask-composite: exclude; opacity: 0; transition: opacity 0.25s;
+ display: none;
}
}
+/* Only built when a pointer is actually on the button — there are no hover states on a phone,
+ so this never costs a touch device anything. */
+.hb:hover::after, .hb:focus-visible::after { display: block; }
.hb-ico {
.hb-ico {
width: clamp(30px, 5vw, 34px); height: clamp(30px, 5vw, 34px); border-radius: 50%; flex: none;
width: clamp(30px, 5vw, 34px); height: clamp(30px, 5vw, 34px); border-radius: 50%; flex: none;
display: grid; place-items: center; font-size: clamp(0.82rem, 1.5vw, 0.92rem); color: var(--c);
display: grid; place-items: center; font-size: clamp(0.82rem, 1.5vw, 0.92rem); color: var(--c);
@@ -15085,3 +15085,39 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
@@ -15085,3 +15085,39 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
min-height: 0 !important;
min-height: 0 !important;
transition: height 0.42s cubic-bezier(.2, 1, .3, 1);
transition: height 0.42s cubic-bezier(.2, 1, .3, 1);
}
}
+
+
+/* ── Stat chip alignment ─────────────────────────────────────────────────────
+ The delta used to sit inline beside the number, so a wide pairing ("70,177" next to "+1.7k 7d")
+ wrapped to a second line, pushed the label down, and left that chip taller than its neighbours
+ — three rows of chips, none of them lining up.
+
+ Three columns instead: icon, then the number over its label, then the delta pinned to the right
+ edge and vertically centred. Every chip is now the same shape whether it has a delta or not,
+ because an absent delta simply collapses its column to zero. Nothing wraps, so nothing can
+ change a chip's height. */
+.hero-stat {
+ display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
+ align-items: center; gap: clamp(6px, 1vw, 10px);
+}
+.hero-stat-meta { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
+.hero-stat-num { white-space: nowrap; line-height: 1.15; }
+.hero-stat-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2; }
+.hero-stat-delta { justify-self: end; align-self: center; flex: none; }
+/* The sparkline chip is a single full-width cell, not a three-column one. */
+.hero-stat--spark { display: flex; }
+
+/* The shipped-commit line now sits under the philosophy row in the hero, so it reads as a
+ footnote to "built in the open" rather than as something bolted to the bottom of the page. */
+#page-home .hero-latest {
+ display: inline-flex; align-items: center; gap: 8px; max-width: min(560px, 92vw);
+ margin: 0 auto 12px; padding: 6px 14px; border-radius: 999px;
+ font-size: 0.76rem; color: var(--text-muted); text-decoration: none;
+ background: color-mix(in srgb, var(--accent) 7%, transparent);
+ border: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
+ transition: color 0.2s, border-color 0.2s, background 0.2s;
+}
+#page-home .hero-latest:hover { color: var(--text-primary); border-color: color-mix(in srgb, var(--accent) 42%, transparent); background: color-mix(in srgb, var(--accent) 13%, transparent); }
+#page-home .hero-latest b { font-weight: 700; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
+#page-home .hero-latest i { color: var(--accent); flex: none; }
+@media (max-width: 480px) { #page-home .hero-latest { font-size: 0.7rem; padding: 5px 11px; max-width: 94vw; } }
@@ -88,8 +88,8 @@
@@ -88,8 +88,8 @@
overlay) and is fetched without blocking the first paint: media="print" makes the browser
overlay) and is fetched without blocking the first paint: media="print" makes the browser
treat it as non-critical, and the onload flips it back to all once it has arrived. -->
treat it as non-critical, and the onload flips it back to all once it has arrived. -->
<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=181">
- <link rel="stylesheet" href="/css/home-fx.css?v=11">
+ <link rel="stylesheet" href="/css/style.css?v=183">
+ <link rel="stylesheet" href="/css/home-fx.css?v=12">
<link rel="stylesheet" href="/css/home-tour.css?v=7">
<link rel="stylesheet" href="/css/home-tour.css?v=7">
<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">
@@ -307,6 +307,9 @@
@@ -307,6 +307,9 @@
<span aria-hidden="true">•</span>
<span aria-hidden="true">•</span>
<a href="/dmca" class="hero-philosophy-link" onclick="return handleLinkClick(event, '/dmca')">DMCA</a>
<a href="/dmca" class="hero-philosophy-link" onclick="return handleLinkClick(event, '/dmca')">DMCA</a>
</div>
</div>
+ <!-- Newest shipped commit — built in the open, receipts included. Sits under the
+ philosophy row so the proof is next to the promise. -->
+ <a class="hero-latest" id="hero-latest" href="/updates" onclick="return handleLinkClick(event, '/updates')" style="display:none"></a>
<div class="hero-stats" id="hero-stats" aria-label="Site stats"><div class="ovsk ovsk-panel" aria-hidden="true"><div class="ovsk-bar" style="height:14px;width:45%"></div><div class="ovsk-bar" style="height:10px;width:92%"></div><div class="ovsk-bar" style="height:10px;width:68%"></div></div></div>
<div class="hero-stats" id="hero-stats" aria-label="Site stats"><div class="ovsk ovsk-panel" aria-hidden="true"><div class="ovsk-bar" style="height:14px;width:45%"></div><div class="ovsk-bar" style="height:10px;width:92%"></div><div class="ovsk-bar" style="height:10px;width:68%"></div></div></div>
</div>
</div>
</div>
</div>
@@ -740,10 +743,7 @@
@@ -740,10 +743,7 @@
<div class="home-changelog-fade" id="home-changelog-fade"></div>
<div class="home-changelog-fade" id="home-changelog-fade"></div>
</div>
</div>
- <!-- Newest shipped commit — built in the open, receipts included. It belongs under the
- content, not in front of every stream on the site. -->
- <!-- Newest shipped commit — built in the open, receipts included. -->
- <a class="hero-latest" id="hero-latest" href="/updates" onclick="return handleLinkClick(event, '/updates')" style="display:none"></a>
+
</div>
</div>
@@ -5130,7 +5130,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
@@ -5130,7 +5130,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
<script src="/live-notify.js?v=3" defer></script>
<script src="/live-notify.js?v=3" defer></script>
<script src="/js/ov-skeleton.js?v=1" defer></script>
<script src="/js/ov-skeleton.js?v=1" defer></script>
<script src="/js/ov-odometer.js?v=3" defer></script>
<script src="/js/ov-odometer.js?v=3" defer></script>
-<script src="/js/app.js?v=158" defer></script>
+<script src="/js/app.js?v=159" defer></script>
<script src="/js/stream-player.js?v=22" defer></script>
<script src="/js/stream-player.js?v=22" defer></script>
<script src="/js/stream-pip.js?v=1" defer></script>
<script src="/js/stream-pip.js?v=1" defer></script>
<script src="/js/chat-shared.js?v=3" defer></script>
<script src="/js/chat-shared.js?v=3" defer></script>
@@ -1463,7 +1463,7 @@ function renderHeroStats(stats) {
@@ -1463,7 +1463,7 @@ function renderHeroStats(stats) {
metric: r.metric || null,
metric: r.metric || null,
};
};
const clickable = !!r.metric;
const clickable = !!r.metric;
- return `<div class="hero-stat ${r.cls || ''} ${clickable ? 'hero-stat--clickable' : ''}" ${r.key ? `data-stat="${r.key}"` : ''} data-tip="${esc(JSON.stringify(tip))}" ${clickable ? `data-metric="${r.metric}" role="button" tabindex="0" aria-label="${esc(r.label)} — show over time"` : ''}><i class="fa-solid ${r.icon}"></i><div class="hero-stat-meta"><span class="hero-stat-numline"><span class="hero-stat-num" data-n="${r.num || 0}">0</span>${recDelta(r.recent, r.unit || '')}</span><span class="hero-stat-label">${r.label}${clickable ? ' <i class="fa-solid fa-chart-line hero-stat-chart-hint"></i>' : ''}</span>${sub && !r.recent ? `<span class="hero-stat-sub">${sub}</span>` : ''}</div></div>`;
+ return `<div class="hero-stat ${r.cls || ''} ${clickable ? 'hero-stat--clickable' : ''}" ${r.key ? `data-stat="${r.key}"` : ''} data-tip="${esc(JSON.stringify(tip))}" ${clickable ? `data-metric="${r.metric}" role="button" tabindex="0" aria-label="${esc(r.label)} — show over time"` : ''}><i class="fa-solid ${r.icon}"></i><div class="hero-stat-meta"><span class="hero-stat-num" data-n="${r.num || 0}">0</span><span class="hero-stat-label">${r.label}${clickable ? ' <i class="fa-solid fa-chart-line hero-stat-chart-hint"></i>' : ''}</span>${sub && !r.recent ? `<span class="hero-stat-sub">${sub}</span>` : ''}</div>${recDelta(r.recent, r.unit || '')}</div>`;
};
};
// The full board is four groups and two dozen chips. Shown by default it pushed every live
// The full board is four groups and two dozen chips. Shown by default it pushed every live
// stream on the site below the fold, and because it only arrives once the stats request lands
// stream on the site below the fold, and because it only arrives once the stats request lands