openvibers/openvibe.space Commits

The home's picture is what Space does: a sentence in, an app out (#267)

OP OpenVibers committed 3 days ago · 982d55ee1a · 3 files, +76 −18

The hero drew the shared orbit. It now shows the request a person types and the small app that comes back at its own
address. An example, labelled; still under reduced motion.

Changes

ddiff --git a/public/css/space.css b/public/css/space.css
iindex 43930ee..b4a5a0c 100644
---- a/public/css/space.css
++++ b/public/css/space.css
@@@ -120,3 +120,28 @@ body {
.sp-fold-block .sc-sec { margin: 0; max-width: none; padding: 0; }
}
@media (max-width: 640px) { .sp-fold-block { margin-top: 24px; } .sp-fold-block > summary h2 { font-size: 1.05rem; } }
++
++/* ── The home's picture (server/render/pages.js spaceScene): a sentence in, an app out ── */
++.sp-scene { width: min(100%, 400px); margin: 0 auto; display: grid; gap: 12px; }
++.sp-scene-ask { display: grid; gap: 2px; margin: 0; padding: 10px 14px; border-radius: 14px 14px 14px 4px; border: 1px solid color-mix(in srgb, var(--accent, #3b82f6) 45%, transparent); background: color-mix(in srgb, var(--accent, #3b82f6) 12%, transparent); justify-self: start; max-width: 92%; }
++.sp-scene-ask span { font-size: .72rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--accent-light, #60a5fa); }
++.sp-scene-ask b { color: var(--text-primary, #e6edf7); line-height: 1.3; }
++.sp-scene-win { border: 1px solid var(--border, #1f2d47); border-radius: 16px; overflow: hidden; background: linear-gradient(var(--bg-card, #131c2e), var(--bg-card, #131c2e)), var(--bg-primary, #0a0f1c); box-shadow: 0 14px 34px rgba(0, 0, 0, .3);
++ animation: spSceneIn var(--ov-dur-4, .52s) var(--ov-ease-spring, cubic-bezier(.34, 1.56, .64, 1)) .5s both; }
++.sp-scene-bar { display: flex; align-items: center; gap: 6px; margin: 0; padding: 9px 12px; border-bottom: 1px solid var(--border, #1f2d47); background: color-mix(in srgb, var(--bg-primary, #0a0f1c) 60%, transparent); }
++.sp-scene-bar i { width: 9px; height: 9px; border-radius: 50%; background: color-mix(in srgb, var(--text-muted, #7386a3) 45%, transparent); }
++.sp-scene-bar span { margin-left: 6px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 500 .8rem/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: var(--text-secondary, #96a7c2); }
++.sp-scene-app { display: grid; gap: 10px; padding: 14px; }
++.sp-scene-app > b { font-size: 1.05rem; color: var(--text-primary, #e6edf7); }
++.sp-scene-app ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
++.sp-scene-app li { display: flex; align-items: center; gap: 9px; min-height: 38px; padding: 4px 10px; border-radius: 10px; border: 1px solid var(--border, #1f2d47); color: var(--text-primary, #e6edf7); font-size: .92rem;
++ animation: spSceneRow var(--ov-dur-3, .32s) var(--ov-ease-out, ease-out) both; }
++.sp-scene-app li:nth-child(1) { animation-delay: .9s; } .sp-scene-app li:nth-child(2) { animation-delay: 1.05s; } .sp-scene-app li:nth-child(3) { animation-delay: 1.2s; }
++.sp-scene-app em { margin-left: auto; font-style: normal; color: var(--text-muted, #7386a3); font-size: .82rem; }
++.sp-scene-av { flex: none; display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: hsl(var(--h) 55% 42%); color: #fff; font-size: .72rem; font-weight: 800; }
++.sp-scene-btn { justify-self: start; padding: 8px 14px; border-radius: 10px; background: var(--accent, #3b82f6); color: #fff; font-weight: 700; font-size: .9rem; }
++.sp-scene figcaption { text-align: center; color: var(--text-muted, #7386a3); font-size: .8rem; }
++@keyframes spSceneIn { from { opacity: 0; transform: translateY(14px) scale(.97); } to { opacity: 1; transform: none; } }
++@keyframes spSceneRow { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: none; } }
++@media (prefers-reduced-motion: reduce) { .sp-scene-win, .sp-scene-app li { animation: none; } }
++html[data-ov-motion=reduced] .sp-scene-win, html[data-ov-motion=reduced] .sp-scene-app li { animation: none; }
ddiff --git a/server/render/pages.js b/server/render/pages.js
iindex e48d32e..1df92d6 100644
---- a/server/render/pages.js
++++ b/server/render/pages.js
@@@ -39,6 +39,24 @@ function defaultQuotas() {
return quotas;
}
++/**
++ * The home's picture: a sentence in, an app out. The request a person types ("A sign-up sheet for our game night"),
++ * and under it the space that comes back, as a small window: its address, a title, the list people add themselves
++ * to, and its one button. Plain markup and CSS (public/css/space.css, .sp-scene): the window rises in and its rows
++ * arrive once, and everything is simply in place under reduced motion. The words in it are an example, and the caption
++ * says so.
++ */
++function spaceScene() {
++ return `<figure class="sp-scene" aria-label="An example: a request, and the space it becomes">
++<p class="sp-scene-ask"><span>You ask</span><b>A sign-up sheet for our game night</b></p>
++<div class="sp-scene-win">
++<p class="sp-scene-bar"><i></i><i></i><i></i><span>game-night.openvibe.space</span></p>
++<div class="sp-scene-app"><b>Game night, Friday</b>
++<ul><li><span class="sp-scene-av" style="--h:200">M</span>maru<em>bringing snacks</em></li><li><span class="sp-scene-av" style="--h:120">K</span>kiwi<em>two guests</em></li><li><span class="sp-scene-av" style="--h:320">T</span>teddy</li></ul>
++<span class="sp-scene-btn">Add my name</span></div></div>
++<figcaption>An example: one sentence in, a space people can sign in to and use.</figcaption></figure>`;
++}
++
/** Hosting is working for staff; dynamic pages and spaces are the next stage. */
function homePage() {
const q = defaultQuotas();
@@@ -107,24 +125,9 @@ curl -X POST "https://openvibe.space/api/v1/sites/$SITE_ID/deploys?activate=1" \
const body = sc.hero({ pageHead: { icon: 'home' },
eyebrow: 'OpenVibe.Space · spaces',
title: 'Make a space.',
-- // The hero's picture is this site's own (2026-11-01): it drew the generic aura, the same on six homes.
-- // What a space is made of, around the space: sign-in, AI, data, hosting and the editor.
-- orbit: {
-- label: 'What a space has',
-- center: { title: 'Your space', sub: 'an app on your account' },
-- inner: [
-- { icon: 'ov:account', label: 'Sign-in', href: '/spaces/new' },
-- { icon: 'ov:ai', label: 'AI', href: '/spaces/new' },
-- { icon: 'ov:drive', label: 'Your data', href: '/spaces/new' },
-- { icon: 'ov:website', label: 'Hosting', href: '/dashboard' },
-- { icon: 'ov:code', label: 'Editor', href: '/spaces/new' },
-- ],
-- outer: [
-- { icon: 'ov:network', label: 'Network', href: 'https://openvibe.network/' },
-- { icon: 'ov:media', label: 'Media', href: 'https://openvibe.media/' },
-- { icon: 'ov:community', label: 'Community', href: 'https://openvibe.community/' },
-- ],
-- },
++ // The hero's picture is what Space does, drawn (2026-11-10; the owner: no more "generic circled" homes): what
++ // a person asks for, and the small app that comes back at its own address. An example, and it says so.
++ aside: { html: spaceScene() },
accent: 'An app on your OpenVibe account.',
lede: 'Describe what you want and OpenVibe AI builds it, or start from a template and change anything in the editor. A space signs visitors in with OpenVibe, keeps shared lists and private data, and talks to OpenVibe AI.',
actions: [
ddiff --git a/test/home-picture.test.js b/test/home-picture.test.js
nnew file mode 100644
iindex 0000000..9138224
---- /dev/null
++++ b/test/home-picture.test.js
@@@ -0,0 +1,30 @@
++'use strict';
++// The home's picture is what Space does: a sentence in, an app out (the owner, 2026-11-09: no more "generic circled"
++// homes). It is an example and says so, and it is still under reduced motion.
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const { boot } = require('./helpers/app');
++
++(async () => {
++ const t = await boot();
++ try {
++ const res = await fetch(`${t.base || t.url}/`);
++ assert.strictEqual(res.status, 200);
++ const home = await res.text();
++ const scene = (home.match(/<figure class="sp-scene"[\s\S]*?<\/figure>/) || [''])[0];
++ assert.ok(scene, 'the picture is in the hero');
++ assert.match(scene, /<p class="sp-scene-ask"><span>You ask<\/span><b>A sign-up sheet for our game night<\/b><\/p>/);
++ assert.match(scene, /<span>game-night\.openvibe\.space<\/span>/, 'the space has its own address');
++ assert.strictEqual((scene.match(/<li>/g) || []).length, 3, 'three people on the sheet');
++ assert.match(scene, /<figcaption>An example: /, 'it says it is an example');
++ assert.ok(!/class="sc-orbit|sc-orbit-/.test(home), 'the generic orbit is gone from this home');
++ const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'space.css'), 'utf8');
++ assert.match(css, /@media \(prefers-reduced-motion: reduce\) \{ \.sp-scene-win, \.sp-scene-app li \{ animation: none; \} \}/);
++ assert.match(css, /html\[data-ov-motion=reduced\] \.sp-scene-win/);
++ console.log('home-picture: a request and the space it becomes, labelled an example, still under reduced motion');
++ } finally {
++ if (t.close) await t.close();
++ }
++ process.exit(0);
++})().catch((e) => { console.error(e); process.exit(1); });