openvibers/openvibe.vision Commits

Vision v2: See it. Use it everywhere. Task-led home, network row, consent scene, trimmed nav, real switches (#3)

OP OpenVibers committed 15 days ago · c15a12894c · 3 files, +52 −17

* Vision v2: See it. Use it everywhere. Task-led home, network row, consent scene, trimmed nav, real switches

* Vision v2: See it. Use it everywhere. Task-led home, network row, consent scene, trimmed nav, real switches

Changes

ddiff --git a/public/css/vision.css b/public/css/vision.css
iindex 7bed8f2..42fa416 100644
---- a/public/css/vision.css
++++ b/public/css/vision.css
@@@ -102,3 +102,34 @@ html.ov-motion .vi-race-box { animation: vi-pop .6s cubic-bezier(.2,.9,.3,1.2) b
@media (max-width: 759px) { .sc-room-pic .vi-race-box, .sc-room-pic .vi-race-tag { display: none; } }
@media (prefers-reduced-motion: reduce) { .vi-race-img path, .vi-race-box { animation: none !important; } }
html[data-ov-motion=reduced] .vi-race-img path, html[data-ov-motion=reduced] .vi-race-box { animation: none !important; }
++
++/* Vision v2: the task grid, the network row and the consent demo */
++.vi-tasks { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 230px), 1fr)); }
++.vi-task { display: grid; gap: 6px; height: 100%; padding: 16px 18px; border-radius: 18px; border: 1px solid var(--border, #1f2d47); background: var(--bg-card, #0f1729); color: inherit; text-decoration: none; transition: border-color .15s, transform .15s; }
++.vi-task b { font-size: 1.05rem; color: var(--room-strong); }
++.vi-task span { color: var(--text-secondary, #96a7c2); font-size: .9rem; line-height: 1.5; }
++.vi-task:hover { border-color: var(--room-accent); transform: translateY(-1px); }
++.vi-task:focus-visible { outline: 2px solid var(--room-accent); outline-offset: 2px; }
++.vi-net { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 200px), 1fr)); }
++.vi-net li { display: grid; gap: 2px; padding: 11px 14px; border-radius: 14px; background: color-mix(in srgb, var(--room-accent) 8%, transparent); border: 1px solid color-mix(in srgb, var(--room-accent) 22%, transparent); }
++.vi-net b { font-size: .9rem; }
++.vi-net span { font-size: .82rem; color: var(--text-secondary, #96a7c2); }
++.vi-demo-consent { list-style: none; margin: 0; padding: 14px; display: grid; gap: 10px; border-radius: 18px; background: var(--bg-card, #0f1729); border: 1px solid var(--border, #1f2d47); }
++.vi-demo-consent li { display: grid; grid-template-columns: auto 1fr; column-gap: 12px; align-items: center; }
++.vi-demo-consent b { font-size: .95rem; }
++.vi-demo-consent small { grid-column: 2; color: var(--text-muted, #7386a3); }
++.vi-demo-switch { grid-row: span 2; position: relative; width: 38px; height: 22px; border-radius: 999px; background: var(--border, #1f2d47); }
++.vi-demo-switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: var(--text-muted, #7386a3); transition: transform .2s; }
++.vi-demo-switch.is-on { background: color-mix(in srgb, var(--room-accent) 55%, transparent); }
++.vi-demo-switch.is-on::after { transform: translateX(16px); background: var(--room-strong); }
++@media (prefers-reduced-motion: reduce) { .vi-task, .vi-demo-switch::after { transition: none; } .vi-task:hover { transform: none; } }
++
++/* The consent switch: a real toggle (the checkbox keeps role="switch" for assistive tech) */
++.vi-check:has(> input[role=switch]) { display: inline-flex; align-items: center; gap: 10px; padding: 0; border: 0; background: none; font-weight: 700; cursor: pointer; }
++.vi-check > input[role=switch] { appearance: none; -webkit-appearance: none; position: relative; flex: none; width: 44px; height: 26px; margin: 0; border-radius: 999px; background: var(--border, #1f2d47); cursor: pointer; transition: background .2s; }
++.vi-check > input[role=switch]::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: var(--text-muted, #7386a3); transition: transform .2s, background .2s; }
++.vi-check > input[role=switch]:checked { background: color-mix(in srgb, var(--room-accent) 55%, transparent); }
++.vi-check > input[role=switch]:checked::after { transform: translateX(18px); background: var(--room-strong); }
++.vi-check > input[role=switch]:focus-visible { outline: 2px solid var(--room-accent); outline-offset: 2px; }
++.vi-check > input[role=switch]:disabled { opacity: .5; cursor: not-allowed; }
++@media (prefers-reduced-motion: reduce) { .vi-check > input[role=switch], .vi-check > input[role=switch]::after { transition: none; } }
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 1a4dab2..4cd0198 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -20,15 +20,11 @@ const TAGLINE = 'See with the best model for the job.';
const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
const NAV = [
{ label: 'Home', href: '/' },
-- { label: 'Ask a picture', href: '/ask' },
-- { label: 'OCR', href: '/ocr' },
-- { label: 'Find', href: '/find' },
++ { label: 'Look', href: '/ask' },
{ label: 'Read', href: '/read' },
++ { label: 'Find', href: '/find' },
{ label: 'Watch', href: '/watch' },
-- { label: 'Compare', href: '/compare' },
-- { label: 'UI check', href: '/ui-check' },
{ label: 'People', href: '/people' },
-- { label: 'Privacy', href: '/people/privacy' },
{ label: 'Developers', href: '/developers' },
];
ddiff --git a/server/views/index.js b/server/views/index.js
iindex 6765d6e..8d4e054 100644
---- a/server/views/index.js
++++ b/server/views/index.js
@@@ -20,20 +20,22 @@ function motif() {
}
function home({ signedIn = false, ai = false } = {}) {
-- const tryIt = `<section class="vi-section vi-try" id="vi-try" aria-labelledby="vi-try-h">
--<div class="vi-head-row"><h2 id="vi-try-h">Ask a picture</h2><span class="vi-count">${ai ? 'local OCR first, then OpenVibe.AI' : 'local engines only on this host right now'}</span></div>
--<p class="vi-lede">Describe, read, translate, identify or count. Upload a file or paste a public image URL; the local reader answers a read question before any model is called.</p>
--<div class="vi-actions"><a class="vi-btn" href="/ask">Ask a picture</a><a class="vi-btn vi-btn-quiet" href="/ocr">Read text only</a><a class="vi-btn vi-btn-quiet" href="/compare">Compare models</a><a class="vi-btn vi-btn-quiet" href="/ui-check">Check a page</a></div>
--${signedIn ? '' : '<p class="vi-fine">Ask and compare save an answer to your account, so they need <a href="/auth/login?next=%2Fask">an OpenVibe sign-in</a>. OCR and the UI check work without one.</p>'}
--</section>`;
++ const tryIt = `<section class="vi-section vi-tasks-wrap" id="vi-try" aria-labelledby="vi-try-h">
++<div class="vi-head-row"><h2 id="vi-try-h">What do you want to do?</h2><span class="vi-count">${ai ? 'local engines first, then OpenVibe.AI' : 'local engines on this host'}</span></div>
++<ul class="vi-tasks"><li><a class="vi-task" href="/ask"><b>Look</b><span>Ask anything about a picture: what it shows, how many, what it says.</span></a></li><li><a class="vi-task" href="/read"><b>Read</b><span>Receipts, signs, screenshots and documents, read on this host first, for nothing.</span></a></li><li><a class="vi-task" href="/ask"><b>Translate</b><span>A sign, a menu or a stream overlay, read and put into your language.</span></a></li><li><a class="vi-task" href="/find"><b>Find</b><span>Search your own pictures by words or by another picture. Only you see the results.</span></a></li><li><a class="vi-task" href="/watch"><b>Watch</b><span>Point it at a still from your camera and get told when a delivery, an animal or a change shows up.</span></a></li><li><a class="vi-task" href="/people"><b>People</b><span>Faces with your consent: verify yourself, find yourself, tag co-streamers who opted in, or just blur.</span></a></li><li><a class="vi-task" href="/compare"><b>Compare</b><span>One picture, several models, side by side, with what each answer cost and how long it took.</span></a></li><li><a class="vi-task" href="/ui-check"><b>Check a page</b><span>Overflow, clipped text, low contrast and overlap, found in HTML, a URL or a screenshot.</span></a></li></ul>
++${signedIn ? '' : '<p class="vi-fine">Reading, blurring and the page check work without an account. Saving answers, your picture search and faces need <a href="/auth/login?next=%2F">an OpenVibe sign-in</a>.</p>'}
++<p class="vi-fine"><a href="/people">Your face choices</a> · <a href="/people/privacy">How faces are kept</a></p>
++</section>
++<section class="vi-section vi-net-wrap" aria-labelledby="vi-net-h"><div class="vi-head-row"><h2 id="vi-net-h">Works across OpenVibe</h2><a class="vi-more" href="/developers">The API</a></div>
++<ul class="vi-net"><li><b>Pics and Media</b><span>alt text for every image</span></li><li><b>Food</b><span>a receipt becomes your pantry</span></li><li><b>Deals</b><span>photograph it, compare offers</span></li><li><b>Cam</b><span>alerts on your own cameras</span></li><li><b>Live</b><span>scene changes and moments</span></li><li><b>Design</b><span>the page check</span></li><li><b>Search</b><span>search by picture</span></li><li><b>Bench</b><span>which model sees best, per dollar</span></li></ul></section>`;
const room = sc.room({
room: 'vision',
hero: {
-- title: 'See with the best model for the job',
-- lede: 'Ask a picture. Read its text with a local OCR engine first. Compare the local reader with a routed vision model on the same image, with the cost and the time beside each answer. Check a page for overflow and layering faults. Local face tools use separate consent for each identifying purpose.',
-- start: { href: '/ask', label: 'Ask a picture' },
-- chips: [{ label: 'Read text', href: '/ocr' }, { label: 'Compare models', href: '/compare' }, { label: 'UI check', href: '/ui-check' }, { label: 'The API', href: '/developers' }],
-- proof: [{ value: '0.0000', label: 'USD for the local OCR first pass' }],
++ title: 'See it. Use it everywhere.',
++ lede: 'Point Vision at a picture and do something with it: read a receipt into your pantry, find your own photos by words, get told when your camera sees a delivery, or check a page before it ships. Every answer says which model saw it, what it cost and what was kept.',
++ start: { href: '/ask', label: 'Look at a picture' },
++ chips: [{ label: 'Read', href: '/read' }, { label: 'Find', href: '/find' }, { label: 'Watch', href: '/watch' }, { label: 'People', href: '/people' }, { label: 'The API', href: '/developers' }],
++ proof: [{ value: '0.0000', label: 'USD for a local read' }, { value: '8', label: 'tools, one API' }],
motif: motif(),
},
scenes: [
@@@ -55,6 +57,12 @@ ${signedIn ? '' : '<p class="vi-fine">Ask and compare save an answer to your acc
html: `<div class="vi-demo-check"><div class="vi-demo-page"><span class="vi-demo-overflow">this card is 1400px wide</span><span class="vi-demo-clip">a headline that gets cut off at the edge</span></div><ul><li class="is-bad"><b>overflow</b><span>1400px in a 390px view</span></li><li class="is-bad"><b>cut off</b><span>nowrap inside 120px</span></li><li class="is-bad"><b>contrast</b><span>2.4:1, needs 4.5:1</span></li></ul></div>`,
action: { href: '/ui-check', label: 'Check a page' },
},
++ {
++ title: 'Faces, only with consent',
++ text: 'Each face feature is its own switch with its own consent: verify yourself, find yourself in your own pictures, tag co-streamers who enrolled, or group the people in your own library. Matching runs on this host; templates are encrypted and erased the moment you switch a feature off. Detecting, counting and blurring identify nobody. Vision never identifies a stranger.',
++ html: `<ul class="vi-demo-consent"><li><span class="vi-demo-switch is-on" aria-hidden="true"></span><b>Verify yourself</b><small>your face, your account</small></li><li><span class="vi-demo-switch is-on" aria-hidden="true"></span><b>Find yourself</b><small>in your own pictures</small></li><li><span class="vi-demo-switch" aria-hidden="true"></span><b>Cast tags</b><small>co-streamers who enrolled</small></li><li><span class="vi-demo-switch" aria-hidden="true"></span><b>Library people</b><small>off unless you turn it on</small></li></ul>`,
++ action: { href: '/people', label: 'Your choices' },
++ },
],
faq: { items: [
['Does Vision recognise faces?', 'Only for a person who separately opts into a specific purpose: self verification, self search, cast tagging or private library grouping. Detect and blur never identify anyone. Public AI text never describes a person\'s looks.'],