openvibers/openvibe.deals Commits
Deals: a product home built with the showcase kit above the hot list. (#10)
Changes
@@@ -256,7 +256,7 @@ render time, so the cache stays short); signed-in views, forms, API responses, e- **Packages** (pinned release tarballs): `openvibe-publishing` v1.3.0 (seo gate, JSON-LD, feeds,sitemaps, index-hooks, discussion, ssr, and the shared ingest/publication chassis — the Sourcesclient, change cursor and pull loop, signed event consumer, common normalisers, and the publication-- glue Deals' indexing uses), `openvibe-contracts` v0.97.0, `openvibe-shared` v2.6.0++ glue Deals' indexing uses), `openvibe-contracts` v0.97.0, `openvibe-shared` v2.10.1(Frame, app icon, footer, legal, release, metrics, ready), `openvibe-sdk` v0.26.0 (sso, outbox,inbox, webhook signatures v2, service tokens, per-actor limits).- **OpenVibe.Network:** SSO (OAuth client `deals`, registered in production), JWKS,@@@ -11,7 +11,7 @@"packages": ["openvibe-publishing v1.3.0","openvibe-contracts v0.97.0",-- "openvibe-shared v2.6.0",++ "openvibe-shared v2.10.1","openvibe-sdk v0.26.0"],"capabilities": [@@@ -36,7 +36,7 @@"deployed": true,"deployment": "Deployed with `sudo ovhost deploy deals`: unit openvibe-deals, 127.0.0.1:4840 loopback only; Network OAuth client deals and its grants exist; on 2026-09-23 /api/ready answered 200 and the production database held 0 offers. Deployed internally, not launched: openvibe.deals still serves the OpenVibe.Sites placeholder until a person verifies a deals source's terms and enables it in Sources","placeholderInSites": true,-- "updated": "2026-10-04",++ "updated": "2026-10-07","features": ["offers with timestamped price observations shown \"as of\" and stale labels","unknown prices stay unknown in pages, JSON, JSON-LD, feeds and Search",@@@ -18,7 +18,7 @@"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.97.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1","pg": "^8.23.0"},"devDependencies": {@@@ -936,9 +936,9 @@}},"node_modules/openvibe-shared": {-- "version": "2.6.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",-- "integrity": "sha512-IKKvWTM/9UonQ4msrzM4yir0QcTUNbhyqeN1r5ywWavU4VTsL1sI5QdHQlxge7BAsMrQ4OvMEPlHjuAfUJM3rg==",++ "version": "2.10.1",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",++ "integrity": "sha512-6fUOw22bSvxg0K33BefAyAkNRkETe9euLNT0CHs2mLIvzYR1q38KiHRUQIWj8FESbVANF0pXMojhBz5TeqpMtg==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -29,7 +29,7 @@"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.97.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1","pg": "^8.23.0"},"devDependencies": {@@@ -1,4 +1,5 @@'use strict';++const showcase = require('openvibe-shared/showcase');/*** Public, server-rendered routes — useful without JavaScript (every action is a plain form):@@@ -119,7 +120,7 @@ function createPages(ctx) {title: kind === 'hot' ? 'Hot deals' : 'New deals',description: 'Deals submitted and voted on by the community, each with its source and the time its price was observed.',// The home page carries the site summary an AI can read (ai-summary meta + WebPage JSON-LD).-- ...(kind === 'hot' && pager.page === 1 ? { summary: SITE_SUMMARY } : {}),++ ...(kind === 'hot' && pager.page === 1 ? { summary: SITE_SUMMARY, styles: [showcase.STYLESHEET] } : {}),decision: pageDecision(canonical, { empty: total === 0, query: ['page'] }),canonical,feeds: [{ type: 'rss', href: '/feed.xml', title: 'New deals (RSS)' }, { type: 'atom', href: '/atom.xml', title: 'New deals (Atom)' }, { type: 'json', href: '/feed.json', title: 'New deals (JSON Feed)' }],@@@ -130,6 +131,7 @@ function createPages(ctx) {heading: kind === 'hot' ? 'Hot deals' : 'New deals',intro: kind === 'hot' ? 'Ranked by community votes over time (hotness formula hot@1). Every price shows when it was observed.' : 'The newest deals first. Every price shows when it was observed.',views: list, pager, urls, tabs: views.tabs(kind),++ lead: kind === 'hot' && pager.page === 1 ? views.dealsShowcase() : '',empty: 'No deals have been posted yet. Submit the first one.',}),}, { cacheable: true });@@@ -93,6 +93,7 @@ function renderPage(o) {navLinks: NAV_LINKS.map(({ label, href }) => ({ label, href })),home: '/',css: asset('css/deals.css'),++ styles: o.styles, // openvibe-shared stylesheet names (the home page's showcase.css)release: RELEASE,account,body: o.body,@@@ -9,6 +9,7 @@*/const { html, raw, renderMarkdown, paginationHtml, breadcrumbsHtml } = require('openvibe-publishing/ssr');const { AVAILABILITY_LABEL, CONDITIONS, AVAILABILITY } = require('../domain/values');++const showcase = require('openvibe-shared/showcase');const pad = (n) => String(n).padStart(2, '0');function when(ms) {@@@ -74,9 +75,28 @@ ${latestLine(v)}</div></li>`;}--function offerList({ heading, intro, views, pager, urls, tabs, empty }) {-- return html`${tabs ? raw(tabs) : ''}--<h1>${heading}</h1>++/** The front page opens with what OpenVibe.Deals is for (openvibe-shared/showcase); the hot list follows it. */++function dealsShowcase() {++ return showcase.hero({++ eyebrow: 'OpenVibe.Deals',++ title: 'Deals where every price', accent: 'says when',++ lede: 'People post deals and vote them up or down. Every price is an observation with its source and the time it was seen: an old one says it may no longer be available, and a price nobody stated stays "not stated".',++ actions: [{ label: 'Browse hot deals', href: '#deals', primary: true }, { label: 'Submit a deal', href: '/submit' }],++ }) + showcase.features({++ title: 'How it works',++ items: [++ { icon: 'ov:history', title: 'Prices as of a time', text: 'Each deal keeps every price it was seen at, newest first, each with its source. Nothing is called current without a fresh one.' },++ { icon: 'ov:check', title: 'Votes decide what is hot', text: 'One vote per person, counted on the server. A deal page shows its hotness with the formula and the inputs.' },++ { icon: 'ov:search', title: 'Compare across stores', text: 'A product page lists every offer for it, each with its own latest price and the time it was seen.' },++ { icon: 'ov:bell', title: 'Watches and saved searches', text: 'Watch words, a product or a price limit for new prices that match, or save a search to run again.' },++ ],++ });++}++++function offerList({ heading, intro, views, pager, urls, tabs, empty, lead }) {++ // Under the showcase the list's heading is an h2 above the sort tabs, so #deals lands on both.++ return html`${lead ? html`${raw(lead)}<h2 id="deals">${heading}</h2>` : ''}${tabs ? raw(tabs) : ''}++${lead ? '' : html`<h1>${heading}</h1>`}${intro ? html`<p class="lede">${intro}</p>` : ''}${views.length ? html`<ol class="deal-list">${views.map((v) => offerCard(v, urls))}</ol>` : html`<p class="empty">${empty || 'Nothing here yet.'}</p>`}${raw(pager ? paginationHtml(pager) : '')}`;@@@ -282,4 +302,4 @@ function message({ heading, text, action }) {return html`<h1>${heading}</h1><p>${text}</p>${action ? html`<p><a href="${action.href}">${action.label}</a></p>` : ''}`;}--module.exports = { offerList, tabs, offerPage, productPage, submitForm, watchesPage, searchPage, modPage, storePage, message, priceText, when };++module.exports = { dealsShowcase, offerList, tabs, offerPage, productPage, submitForm, watchesPage, searchPage, modPage, storePage, message, priceText, when };@@@ -12,14 +12,14 @@ const { spawn } = require('child_process');const { measure, check, format } = require('openvibe-shared/perf-budget');const BUDGETS = {-- htmlRawKB: 22, // measured 17.2 (fresh database, 2026-09-29)-- htmlBrotliKB: 5.5, // 4.5-- jsFiles: 5, // 4-- jsRawKB: 240, // 212.2-- jsBrotliKB: 58, // 49.9-- cssFiles: 2, // 1-- cssRawKB: 7, // 5.6-- cssBrotliKB: 2, // 1.5++ htmlRawKB: 25, // measured 23.5 (fresh database, 2026-10-07; 17.2 before the showcase hero and features)++ htmlBrotliKB: 6.5, // 6.0 (4.5 before the showcase)++ jsFiles: 5, // 5 (openvibe-shared 2.10's shell adds web-runtime.js)++ jsRawKB: 250, // 239.1 (212.2 before web-runtime.js)++ jsBrotliKB: 60, // 56.3 (49.9 before web-runtime.js)++ cssFiles: 2, // 2 (deals.css, openvibe-shared showcase.css)++ cssRawKB: 18.5, // 17.1 (5.6 before showcase.css)++ cssBrotliKB: 4.6, // 4.1 (1.5 before showcase.css)externalFiles: 1, // 0};
@@ -256,7 +256,7 @@ render time, so the cache stays short); signed-in views, forms, API responses, e
@@ -256,7 +256,7 @@ render time, so the cache stays short); signed-in views, forms, API responses, e
- **Packages** (pinned release tarballs): `openvibe-publishing` v1.3.0 (seo gate, JSON-LD, feeds,
- **Packages** (pinned release tarballs): `openvibe-publishing` v1.3.0 (seo gate, JSON-LD, feeds,
sitemaps, index-hooks, discussion, ssr, and the shared ingest/publication chassis — the Sources
sitemaps, index-hooks, discussion, ssr, and the shared ingest/publication chassis — the Sources
client, change cursor and pull loop, signed event consumer, common normalisers, and the publication
client, change cursor and pull loop, signed event consumer, common normalisers, and the publication
- glue Deals' indexing uses), `openvibe-contracts` v0.97.0, `openvibe-shared` v2.6.0
+ glue Deals' indexing uses), `openvibe-contracts` v0.97.0, `openvibe-shared` v2.10.1
(Frame, app icon, footer, legal, release, metrics, ready), `openvibe-sdk` v0.26.0 (sso, outbox,
(Frame, app icon, footer, legal, release, metrics, ready), `openvibe-sdk` v0.26.0 (sso, outbox,
inbox, webhook signatures v2, service tokens, per-actor limits).
inbox, webhook signatures v2, service tokens, per-actor limits).
- **OpenVibe.Network:** SSO (OAuth client `deals`, registered in production), JWKS,
- **OpenVibe.Network:** SSO (OAuth client `deals`, registered in production), JWKS,
@@ -11,7 +11,7 @@
@@ -11,7 +11,7 @@
"packages": [
"packages": [
"openvibe-publishing v1.3.0",
"openvibe-publishing v1.3.0",
"openvibe-contracts v0.97.0",
"openvibe-contracts v0.97.0",
- "openvibe-shared v2.6.0",
+ "openvibe-shared v2.10.1",
"openvibe-sdk v0.26.0"
"openvibe-sdk v0.26.0"
],
],
"capabilities": [
"capabilities": [
@@ -36,7 +36,7 @@
@@ -36,7 +36,7 @@
"deployed": true,
"deployed": true,
"deployment": "Deployed with `sudo ovhost deploy deals`: unit openvibe-deals, 127.0.0.1:4840 loopback only; Network OAuth client deals and its grants exist; on 2026-09-23 /api/ready answered 200 and the production database held 0 offers. Deployed internally, not launched: openvibe.deals still serves the OpenVibe.Sites placeholder until a person verifies a deals source's terms and enables it in Sources",
"deployment": "Deployed with `sudo ovhost deploy deals`: unit openvibe-deals, 127.0.0.1:4840 loopback only; Network OAuth client deals and its grants exist; on 2026-09-23 /api/ready answered 200 and the production database held 0 offers. Deployed internally, not launched: openvibe.deals still serves the OpenVibe.Sites placeholder until a person verifies a deals source's terms and enables it in Sources",
"placeholderInSites": true,
"placeholderInSites": true,
- "updated": "2026-10-04",
+ "updated": "2026-10-07",
"features": [
"features": [
"offers with timestamped price observations shown \"as of\" and stale labels",
"offers with timestamped price observations shown \"as of\" and stale labels",
"unknown prices stay unknown in pages, JSON, JSON-LD, feeds and Search",
"unknown prices stay unknown in pages, JSON, JSON-LD, feeds and Search",
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.97.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.97.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -936,9 +936,9 @@
@@ -936,9 +936,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "2.6.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
- "integrity": "sha512-IKKvWTM/9UonQ4msrzM4yir0QcTUNbhyqeN1r5ywWavU4VTsL1sI5QdHQlxge7BAsMrQ4OvMEPlHjuAfUJM3rg==",
+ "version": "2.10.1",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
+ "integrity": "sha512-6fUOw22bSvxg0K33BefAyAkNRkETe9euLNT0CHs2mLIvzYR1q38KiHRUQIWj8FESbVANF0pXMojhBz5TeqpMtg==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -29,7 +29,7 @@
@@ -29,7 +29,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.97.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.97.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -1,4 +1,5 @@
@@ -1,4 +1,5 @@
'use strict';
'use strict';
+const showcase = require('openvibe-shared/showcase');
/**
/**
* Public, server-rendered routes — useful without JavaScript (every action is a plain form):
* Public, server-rendered routes — useful without JavaScript (every action is a plain form):
@@ -119,7 +120,7 @@ function createPages(ctx) {
@@ -119,7 +120,7 @@ function createPages(ctx) {
title: kind === 'hot' ? 'Hot deals' : 'New deals',
title: kind === 'hot' ? 'Hot deals' : 'New deals',
description: 'Deals submitted and voted on by the community, each with its source and the time its price was observed.',
description: 'Deals submitted and voted on by the community, each with its source and the time its price was observed.',
// The home page carries the site summary an AI can read (ai-summary meta + WebPage JSON-LD).
// The home page carries the site summary an AI can read (ai-summary meta + WebPage JSON-LD).
- ...(kind === 'hot' && pager.page === 1 ? { summary: SITE_SUMMARY } : {}),
+ ...(kind === 'hot' && pager.page === 1 ? { summary: SITE_SUMMARY, styles: [showcase.STYLESHEET] } : {}),
decision: pageDecision(canonical, { empty: total === 0, query: ['page'] }),
decision: pageDecision(canonical, { empty: total === 0, query: ['page'] }),
canonical,
canonical,
feeds: [{ type: 'rss', href: '/feed.xml', title: 'New deals (RSS)' }, { type: 'atom', href: '/atom.xml', title: 'New deals (Atom)' }, { type: 'json', href: '/feed.json', title: 'New deals (JSON Feed)' }],
feeds: [{ type: 'rss', href: '/feed.xml', title: 'New deals (RSS)' }, { type: 'atom', href: '/atom.xml', title: 'New deals (Atom)' }, { type: 'json', href: '/feed.json', title: 'New deals (JSON Feed)' }],
@@ -130,6 +131,7 @@ function createPages(ctx) {
@@ -130,6 +131,7 @@ function createPages(ctx) {
heading: kind === 'hot' ? 'Hot deals' : 'New deals',
heading: kind === 'hot' ? 'Hot deals' : 'New deals',
intro: kind === 'hot' ? 'Ranked by community votes over time (hotness formula hot@1). Every price shows when it was observed.' : 'The newest deals first. Every price shows when it was observed.',
intro: kind === 'hot' ? 'Ranked by community votes over time (hotness formula hot@1). Every price shows when it was observed.' : 'The newest deals first. Every price shows when it was observed.',
views: list, pager, urls, tabs: views.tabs(kind),
views: list, pager, urls, tabs: views.tabs(kind),
+ lead: kind === 'hot' && pager.page === 1 ? views.dealsShowcase() : '',
empty: 'No deals have been posted yet. Submit the first one.',
empty: 'No deals have been posted yet. Submit the first one.',
}),
}),
}, { cacheable: true });
}, { cacheable: true });
@@ -93,6 +93,7 @@ function renderPage(o) {
@@ -93,6 +93,7 @@ function renderPage(o) {
navLinks: NAV_LINKS.map(({ label, href }) => ({ label, href })),
navLinks: NAV_LINKS.map(({ label, href }) => ({ label, href })),
home: '/',
home: '/',
css: asset('css/deals.css'),
css: asset('css/deals.css'),
+ styles: o.styles, // openvibe-shared stylesheet names (the home page's showcase.css)
release: RELEASE,
release: RELEASE,
account,
account,
body: o.body,
body: o.body,
@@ -9,6 +9,7 @@
@@ -9,6 +9,7 @@
*/
*/
const { html, raw, renderMarkdown, paginationHtml, breadcrumbsHtml } = require('openvibe-publishing/ssr');
const { html, raw, renderMarkdown, paginationHtml, breadcrumbsHtml } = require('openvibe-publishing/ssr');
const { AVAILABILITY_LABEL, CONDITIONS, AVAILABILITY } = require('../domain/values');
const { AVAILABILITY_LABEL, CONDITIONS, AVAILABILITY } = require('../domain/values');
+const showcase = require('openvibe-shared/showcase');
const pad = (n) => String(n).padStart(2, '0');
const pad = (n) => String(n).padStart(2, '0');
function when(ms) {
function when(ms) {
@@ -74,9 +75,28 @@ ${latestLine(v)}
@@ -74,9 +75,28 @@ ${latestLine(v)}
</div></li>`;
</div></li>`;
}
}
-function offerList({ heading, intro, views, pager, urls, tabs, empty }) {
- return html`${tabs ? raw(tabs) : ''}
-<h1>${heading}</h1>
+/** The front page opens with what OpenVibe.Deals is for (openvibe-shared/showcase); the hot list follows it. */
+function dealsShowcase() {
+ return showcase.hero({
+ eyebrow: 'OpenVibe.Deals',
+ title: 'Deals where every price', accent: 'says when',
+ lede: 'People post deals and vote them up or down. Every price is an observation with its source and the time it was seen: an old one says it may no longer be available, and a price nobody stated stays "not stated".',
+ actions: [{ label: 'Browse hot deals', href: '#deals', primary: true }, { label: 'Submit a deal', href: '/submit' }],
+ }) + showcase.features({
+ title: 'How it works',
+ items: [
+ { icon: 'ov:history', title: 'Prices as of a time', text: 'Each deal keeps every price it was seen at, newest first, each with its source. Nothing is called current without a fresh one.' },
+ { icon: 'ov:check', title: 'Votes decide what is hot', text: 'One vote per person, counted on the server. A deal page shows its hotness with the formula and the inputs.' },
+ { icon: 'ov:search', title: 'Compare across stores', text: 'A product page lists every offer for it, each with its own latest price and the time it was seen.' },
+ { icon: 'ov:bell', title: 'Watches and saved searches', text: 'Watch words, a product or a price limit for new prices that match, or save a search to run again.' },
+ ],
+ });
+}
+
+function offerList({ heading, intro, views, pager, urls, tabs, empty, lead }) {
+ // Under the showcase the list's heading is an h2 above the sort tabs, so #deals lands on both.
+ return html`${lead ? html`${raw(lead)}<h2 id="deals">${heading}</h2>` : ''}${tabs ? raw(tabs) : ''}
+${lead ? '' : html`<h1>${heading}</h1>`}
${intro ? html`<p class="lede">${intro}</p>` : ''}
${intro ? html`<p class="lede">${intro}</p>` : ''}
${views.length ? html`<ol class="deal-list">${views.map((v) => offerCard(v, urls))}</ol>` : html`<p class="empty">${empty || 'Nothing here yet.'}</p>`}
${views.length ? html`<ol class="deal-list">${views.map((v) => offerCard(v, urls))}</ol>` : html`<p class="empty">${empty || 'Nothing here yet.'}</p>`}
${raw(pager ? paginationHtml(pager) : '')}`;
${raw(pager ? paginationHtml(pager) : '')}`;
@@ -282,4 +302,4 @@ function message({ heading, text, action }) {
@@ -282,4 +302,4 @@ function message({ heading, text, action }) {
return html`<h1>${heading}</h1><p>${text}</p>${action ? html`<p><a href="${action.href}">${action.label}</a></p>` : ''}`;
return html`<h1>${heading}</h1><p>${text}</p>${action ? html`<p><a href="${action.href}">${action.label}</a></p>` : ''}`;
}
}
-module.exports = { offerList, tabs, offerPage, productPage, submitForm, watchesPage, searchPage, modPage, storePage, message, priceText, when };
+module.exports = { dealsShowcase, offerList, tabs, offerPage, productPage, submitForm, watchesPage, searchPage, modPage, storePage, message, priceText, when };
@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
const BUDGETS = {
- htmlRawKB: 22, // measured 17.2 (fresh database, 2026-09-29)
- htmlBrotliKB: 5.5, // 4.5
- jsFiles: 5, // 4
- jsRawKB: 240, // 212.2
- jsBrotliKB: 58, // 49.9
- cssFiles: 2, // 1
- cssRawKB: 7, // 5.6
- cssBrotliKB: 2, // 1.5
+ htmlRawKB: 25, // measured 23.5 (fresh database, 2026-10-07; 17.2 before the showcase hero and features)
+ htmlBrotliKB: 6.5, // 6.0 (4.5 before the showcase)
+ jsFiles: 5, // 5 (openvibe-shared 2.10's shell adds web-runtime.js)
+ jsRawKB: 250, // 239.1 (212.2 before web-runtime.js)
+ jsBrotliKB: 60, // 56.3 (49.9 before web-runtime.js)
+ cssFiles: 2, // 2 (deals.css, openvibe-shared showcase.css)
+ cssRawKB: 18.5, // 17.1 (5.6 before showcase.css)
+ cssBrotliKB: 4.6, // 4.1 (1.5 before showcase.css)
externalFiles: 1, // 0
externalFiles: 1, // 0
};
};