openvibers/openvibe.today Commits
Today's home leads with what is real; one 'be the first' row instead of an empty box (#32)
Changes
@@@ -179,3 +179,9 @@ html[data-ov-motion=reduced] .td-demo-toast, html[data-ov-motion=reduced] .td-ria.td-more { position: relative; }a.td-more::after { content: ''; position: absolute; left: 0; right: 0; top: -14px; bottom: -14px; }}++++/* Be the first (10-31): an empty calendar is one short row after the scenes, never an empty box up top. */++.td-first-list { list-style: none; margin: 10px 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); gap: 10px; }++.td-first-list li { display: grid; gap: 4px; padding: 14px 16px; border-radius: 16px; border: 1px dashed color-mix(in srgb, var(--room-accent) 45%, var(--border, #2c3d5c)); }++.td-first-list span { color: var(--text-secondary, #a8b3c4); font-size: 14px; line-height: 1.45; }++.td-first-list a { justify-self: start; margin-top: 4px; font-weight: 700; color: var(--room-strong, var(--room-accent)); }@@@ -133,8 +133,12 @@ function home({ upcoming = [], now = Date.now() } = {}) {{ value: channels.size, label: 'channels' },].filter((p) => p.value),});-- const coming = `<section class="td-coming" id="td-upcoming" aria-labelledby="td-coming-h"><div class="td-head-row"><h2 id="td-coming-h">Coming up</h2><a class="td-more" href="/developers#feeds">Subscribe${kit.icon('chevron')}</a></div>--${days(upcoming, { now, empty: 'Nothing is scheduled yet. Schedule a stream and it shows here, on your channel and in your followers’ calendars.' })}</section>`;++ // Real first (the owner, 10-31: empty sections made the rooms look unfinished): Coming up shows only with something in++ // it; an empty calendar is one short "be the first" row after the scenes.++ const coming = upcoming.length ? `<section class="td-coming" id="td-upcoming" aria-labelledby="td-coming-h"><div class="td-head-row"><h2 id="td-coming-h">Coming up</h2><a class="td-more" href="/developers#feeds">Subscribe${kit.icon('chevron')}</a></div>++${days(upcoming, { now })}</section>` : '';++ const beFirst = upcoming.length ? '' : `<section class="td-coming td-first" aria-labelledby="td-first-h"><h2 id="td-first-h">Be the first</h2>++<ul class="td-first-list"><li><b>Schedule the first stream</b><span>It shows here, on your channel and in your followers’ calendars, and anyone can set a reminder for it.</span><a href="/schedule">Schedule a stream</a></li></ul></section>`;const scenesHTML = `<section class="sc-room-scenes" aria-label="What you can do">${scenes.map((x, i) => `<article class="sc-scene${i % 2 ? ' is-flip' : ''}" data-ov-reveal"><div class="sc-scene-text"><h2>${esc(x.title)}</h2>${x.text ? `<p>${esc(x.text)}</p>` : ''}${x.action ? `<a class="sc-scene-go" href="${esc(x.action.href)}">${esc(x.action.label)}${kit.icon('chevron')}</a>` : ''}</div><div class="sc-scene-stage">${x.html}</div></article>`).join('')}</section>`;@@@ -148,7 +152,7 @@ ${days(upcoming, { now, empty: 'Nothing is scheduled yet. Schedule a stream and['Can I see this in my own calendar app?', 'Yes. Every channel has a public calendar feed, and your agenda has a private one. Subscribe in any app that reads iCalendar (.ics); updates and cancellations follow.'],['Who sees what I schedule?', 'A public stream is on your channel, here and in feeds. A followers-only one is seen only by the people who follow you. Followers hear about at most three new schedules from you a day.'],] });-- const body = hero.replace(/<\/div>\s*$/, `${coming}${scenesHTML}${explore}${faq}</div>`);++ const body = hero.replace(/<\/div>\s*$/, `${coming}${scenesHTML}${beFirst}${explore}${faq}</div>`);return `<div class="td-wrap" id="td-home">${body}</div>`;}nnew file mode 100644@@@ -0,0 +1,18 @@++'use strict';++// Today's home leads with what is real (the owner, 10-31: empty sections made the rooms look unfinished). With nothing++// coming up there is no empty "Coming up" box under the hero: one short "be the first" row follows the scenes.++const assert = require('assert');++const fs = require('fs');++const path = require('path');++++const src = fs.readFileSync(path.join(__dirname, '..', 'server/views/index.js'), 'utf8');++assert.match(src, /const coming = upcoming\.length \? `<section class="td-coming" id="td-upcoming"/, 'Coming up only with something in it');++assert.match(src, /\$\{coming\}\$\{scenesHTML\}\$\{beFirst\}\$\{explore\}\$\{faq\}/, 'the missing comes after the scenes');++assert.ok(!src.includes('Nothing is scheduled yet'), 'no empty box under the hero');++++const views = require('../server/views');++const page = views.home({ upcoming: [], live: [], now: Date.parse('2026-10-31T12:00:00Z') });++assert.ok(!page.includes('id="td-upcoming"'), 'no empty Coming up');++assert.ok(page.indexOf('Be the first') > page.indexOf('class="sc-room-scenes"'), 'the row follows the scenes');++assert.ok(page.includes('href="/schedule">Schedule a stream</a>'), 'it says how the first one starts');++console.log('today home: real first, one "be the first" row while nothing is coming up');
@@ -179,3 +179,9 @@ html[data-ov-motion=reduced] .td-demo-toast, html[data-ov-motion=reduced] .td-ri
@@ -179,3 +179,9 @@ html[data-ov-motion=reduced] .td-demo-toast, html[data-ov-motion=reduced] .td-ri
a.td-more { position: relative; }
a.td-more { position: relative; }
a.td-more::after { content: ''; position: absolute; left: 0; right: 0; top: -14px; bottom: -14px; }
a.td-more::after { content: ''; position: absolute; left: 0; right: 0; top: -14px; bottom: -14px; }
}
}
+
+/* Be the first (10-31): an empty calendar is one short row after the scenes, never an empty box up top. */
+.td-first-list { list-style: none; margin: 10px 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); gap: 10px; }
+.td-first-list li { display: grid; gap: 4px; padding: 14px 16px; border-radius: 16px; border: 1px dashed color-mix(in srgb, var(--room-accent) 45%, var(--border, #2c3d5c)); }
+.td-first-list span { color: var(--text-secondary, #a8b3c4); font-size: 14px; line-height: 1.45; }
+.td-first-list a { justify-self: start; margin-top: 4px; font-weight: 700; color: var(--room-strong, var(--room-accent)); }
@@ -133,8 +133,12 @@ function home({ upcoming = [], now = Date.now() } = {}) {
@@ -133,8 +133,12 @@ function home({ upcoming = [], now = Date.now() } = {}) {
{ value: channels.size, label: 'channels' },
{ value: channels.size, label: 'channels' },
].filter((p) => p.value),
].filter((p) => p.value),
});
});
- const coming = `<section class="td-coming" id="td-upcoming" aria-labelledby="td-coming-h"><div class="td-head-row"><h2 id="td-coming-h">Coming up</h2><a class="td-more" href="/developers#feeds">Subscribe${kit.icon('chevron')}</a></div>
-${days(upcoming, { now, empty: 'Nothing is scheduled yet. Schedule a stream and it shows here, on your channel and in your followers’ calendars.' })}</section>`;
+ // Real first (the owner, 10-31: empty sections made the rooms look unfinished): Coming up shows only with something in
+ // it; an empty calendar is one short "be the first" row after the scenes.
+ const coming = upcoming.length ? `<section class="td-coming" id="td-upcoming" aria-labelledby="td-coming-h"><div class="td-head-row"><h2 id="td-coming-h">Coming up</h2><a class="td-more" href="/developers#feeds">Subscribe${kit.icon('chevron')}</a></div>
+${days(upcoming, { now })}</section>` : '';
+ const beFirst = upcoming.length ? '' : `<section class="td-coming td-first" aria-labelledby="td-first-h"><h2 id="td-first-h">Be the first</h2>
+<ul class="td-first-list"><li><b>Schedule the first stream</b><span>It shows here, on your channel and in your followers’ calendars, and anyone can set a reminder for it.</span><a href="/schedule">Schedule a stream</a></li></ul></section>`;
const scenesHTML = `<section class="sc-room-scenes" aria-label="What you can do">${scenes.map((x, i) => `<article class="sc-scene${i % 2 ? ' is-flip' : ''}" data-ov-reveal">
const scenesHTML = `<section class="sc-room-scenes" aria-label="What you can do">${scenes.map((x, i) => `<article class="sc-scene${i % 2 ? ' is-flip' : ''}" data-ov-reveal">
<div class="sc-scene-text"><h2>${esc(x.title)}</h2>${x.text ? `<p>${esc(x.text)}</p>` : ''}${x.action ? `<a class="sc-scene-go" href="${esc(x.action.href)}">${esc(x.action.label)}${kit.icon('chevron')}</a>` : ''}</div>
<div class="sc-scene-text"><h2>${esc(x.title)}</h2>${x.text ? `<p>${esc(x.text)}</p>` : ''}${x.action ? `<a class="sc-scene-go" href="${esc(x.action.href)}">${esc(x.action.label)}${kit.icon('chevron')}</a>` : ''}</div>
<div class="sc-scene-stage">${x.html}</div></article>`).join('')}</section>`;
<div class="sc-scene-stage">${x.html}</div></article>`).join('')}</section>`;
@@ -148,7 +152,7 @@ ${days(upcoming, { now, empty: 'Nothing is scheduled yet. Schedule a stream and
@@ -148,7 +152,7 @@ ${days(upcoming, { now, empty: 'Nothing is scheduled yet. Schedule a stream and
['Can I see this in my own calendar app?', 'Yes. Every channel has a public calendar feed, and your agenda has a private one. Subscribe in any app that reads iCalendar (.ics); updates and cancellations follow.'],
['Can I see this in my own calendar app?', 'Yes. Every channel has a public calendar feed, and your agenda has a private one. Subscribe in any app that reads iCalendar (.ics); updates and cancellations follow.'],
['Who sees what I schedule?', 'A public stream is on your channel, here and in feeds. A followers-only one is seen only by the people who follow you. Followers hear about at most three new schedules from you a day.'],
['Who sees what I schedule?', 'A public stream is on your channel, here and in feeds. A followers-only one is seen only by the people who follow you. Followers hear about at most three new schedules from you a day.'],
] });
] });
- const body = hero.replace(/<\/div>\s*$/, `${coming}${scenesHTML}${explore}${faq}</div>`);
+ const body = hero.replace(/<\/div>\s*$/, `${coming}${scenesHTML}${beFirst}${explore}${faq}</div>`);
return `<div class="td-wrap" id="td-home">${body}</div>`;
return `<div class="td-wrap" id="td-home">${body}</div>`;
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+'use strict';
+// Today's home leads with what is real (the owner, 10-31: empty sections made the rooms look unfinished). With nothing
+// coming up there is no empty "Coming up" box under the hero: one short "be the first" row follows the scenes.
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+
+const src = fs.readFileSync(path.join(__dirname, '..', 'server/views/index.js'), 'utf8');
+assert.match(src, /const coming = upcoming\.length \? `<section class="td-coming" id="td-upcoming"/, 'Coming up only with something in it');
+assert.match(src, /\$\{coming\}\$\{scenesHTML\}\$\{beFirst\}\$\{explore\}\$\{faq\}/, 'the missing comes after the scenes');
+assert.ok(!src.includes('Nothing is scheduled yet'), 'no empty box under the hero');
+
+const views = require('../server/views');
+const page = views.home({ upcoming: [], live: [], now: Date.parse('2026-10-31T12:00:00Z') });
+assert.ok(!page.includes('id="td-upcoming"'), 'no empty Coming up');
+assert.ok(page.indexOf('Be the first') > page.indexOf('class="sc-room-scenes"'), 'the row follows the scenes');
+assert.ok(page.includes('href="/schedule">Schedule a stream</a>'), 'it says how the first one starts');
+console.log('today home: real first, one "be the first" row while nothing is coming up');