openvibers/openvibe.tips Commits

Tips' home on the showcase kit, with what it really costs to send (plan T11) (#10)

OP OpenVibers committed 5 Oct 2026 · 5e06967588 · 7 files, +78 −502

The home now opens with openvibe-shared/showcase: a hero ("Support the
creators you watch": Find a creator, Start your tip page), what you can
send (tip, paid message, text-to-speech, media request), how it works,
and "What it costs to send": the minimums a new tip page starts with,
taken from migrations/0001's column defaults (1 / 100 / 100 / 25 Vibes),
saying each creator sets their own. The creators list follows (#creators).

- NEW_PAGE_MINIMUMS is checked against the migration's defaults by
  test/pages.test.js, so the page never quotes a number the service does
  not use.
- The hero is the page's one h1; showcase.css is linked on the home only
  (layout.page takes `styles`); the kit sits in the 880 px column.
- openvibe-shared v2.6.0 -> v2.9.0 (package.json + lockfile).

Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex 4312040..27945b0 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -16,7 +16,7 @@
"jsonwebtoken": "^9.0.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.92.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.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.9.0",
"pg": "^8.23.0"
},
"devDependencies": {
@@@ -91,65 +91,6 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
-- "node_modules/base64-js": {
-- "version": "1.5.1",
-- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
-- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
-- "funding": [
-- {
-- "type": "github",
-- "url": "https://github.com/sponsors/feross"
-- },
-- {
-- "type": "patreon",
-- "url": "https://www.patreon.com/feross"
-- },
-- {
-- "type": "consulting",
-- "url": "https://feross.org/support"
-- }
-- ],
-- "license": "MIT",
-- "optional": true,
-- "peer": true
-- },
-- "node_modules/better-sqlite3": {
-- "version": "11.10.0",
-- "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
-- "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
-- "hasInstallScript": true,
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "bindings": "^1.5.0",
-- "prebuild-install": "^7.1.1"
-- }
-- },
-- "node_modules/bindings": {
-- "version": "1.5.0",
-- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
-- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "file-uri-to-path": "1.0.0"
-- }
-- },
-- "node_modules/bl": {
-- "version": "4.1.0",
-- "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
-- "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "buffer": "^5.5.0",
-- "inherits": "^2.0.4",
-- "readable-stream": "^3.4.0"
-- }
-- },
"node_modules/body-parser": {
"version": "1.20.8",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz",
@@@ -174,32 +115,6 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
-- "node_modules/buffer": {
-- "version": "5.7.1",
-- "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
-- "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
-- "funding": [
-- {
-- "type": "github",
-- "url": "https://github.com/sponsors/feross"
-- },
-- {
-- "type": "patreon",
-- "url": "https://www.patreon.com/feross"
-- },
-- {
-- "type": "consulting",
-- "url": "https://feross.org/support"
-- }
-- ],
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "base64-js": "^1.3.1",
-- "ieee754": "^1.1.13"
-- }
-- },
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@@@ -244,14 +159,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
-- "node_modules/chownr": {
-- "version": "1.1.4",
-- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
-- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
-- "license": "ISC",
-- "optional": true,
-- "peer": true
-- },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@@@ -319,34 +226,6 @@
"ms": "2.0.0"
}
},
-- "node_modules/decompress-response": {
-- "version": "6.0.0",
-- "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
-- "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "mimic-response": "^3.1.0"
-- },
-- "engines": {
-- "node": ">=10"
-- },
-- "funding": {
-- "url": "https://github.com/sponsors/sindresorhus"
-- }
-- },
-- "node_modules/deep-extend": {
-- "version": "0.6.0",
-- "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
-- "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "engines": {
-- "node": ">=4.0.0"
-- }
-- },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
@@@ -375,17 +254,6 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
-- "node_modules/detect-libc": {
-- "version": "2.1.2",
-- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
-- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
-- "license": "Apache-2.0",
-- "optional": true,
-- "peer": true,
-- "engines": {
-- "node": ">=8"
-- }
-- },
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@@@ -436,17 +304,6 @@
"node": ">= 0.8"
}
},
-- "node_modules/end-of-stream": {
-- "version": "1.4.5",
-- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
-- "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "once": "^1.4.0"
-- }
-- },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@@ -492,17 +349,6 @@
"node": ">= 0.6"
}
},
-- "node_modules/expand-template": {
-- "version": "2.0.3",
-- "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
-- "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
-- "license": "(MIT OR WTFPL)",
-- "optional": true,
-- "peer": true,
-- "engines": {
-- "node": ">=6"
-- }
-- },
"node_modules/express": {
"version": "4.22.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz",
@@@ -571,14 +417,6 @@
],
"license": "BSD-3-Clause"
},
-- "node_modules/file-uri-to-path": {
-- "version": "1.0.0",
-- "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
-- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true
-- },
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
@@@ -615,14 +453,6 @@
"node": ">= 0.6"
}
},
-- "node_modules/fs-constants": {
-- "version": "1.0.0",
-- "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
-- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true
-- },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@@ -669,14 +499,6 @@
"node": ">= 0.4"
}
},
-- "node_modules/github-from-package": {
-- "version": "0.0.0",
-- "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
-- "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true
-- },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@@ -745,42 +567,12 @@
"node": ">=0.10.0"
}
},
-- "node_modules/ieee754": {
-- "version": "1.2.1",
-- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
-- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
-- "funding": [
-- {
-- "type": "github",
-- "url": "https://github.com/sponsors/feross"
-- },
-- {
-- "type": "patreon",
-- "url": "https://www.patreon.com/feross"
-- },
-- {
-- "type": "consulting",
-- "url": "https://feross.org/support"
-- }
-- ],
-- "license": "BSD-3-Clause",
-- "optional": true,
-- "peer": true
-- },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
-- "node_modules/ini": {
-- "version": "1.3.8",
-- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
-- "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
-- "license": "ISC",
-- "optional": true,
-- "peer": true
-- },
"node_modules/iovalkey": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/iovalkey/-/iovalkey-0.4.0.tgz",
@@@ -1011,53 +803,12 @@
"node": ">= 0.6"
}
},
-- "node_modules/mimic-response": {
-- "version": "3.1.0",
-- "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
-- "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "engines": {
-- "node": ">=10"
-- },
-- "funding": {
-- "url": "https://github.com/sponsors/sindresorhus"
-- }
-- },
-- "node_modules/minimist": {
-- "version": "1.2.8",
-- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
-- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "funding": {
-- "url": "https://github.com/sponsors/ljharb"
-- }
-- },
-- "node_modules/mkdirp-classic": {
-- "version": "0.5.3",
-- "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
-- "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true
-- },
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
-- "node_modules/napi-build-utils": {
-- "version": "2.0.0",
-- "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
-- "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true
-- },
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@@ -1067,20 +818,6 @@
"node": ">= 0.6"
}
},
-- "node_modules/node-abi": {
-- "version": "3.96.0",
-- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz",
-- "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "semver": "^7.3.5"
-- },
-- "engines": {
-- "node": ">=10"
-- }
-- },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@@ -1105,17 +842,6 @@
"node": ">= 0.8"
}
},
-- "node_modules/once": {
-- "version": "1.4.0",
-- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
-- "license": "ISC",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "wrappy": "1"
-- }
-- },
"node_modules/openvibe-contracts": {
"version": "0.92.0",
"resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.92.0",
@@@ -1162,9 +888,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.9.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.9.0",
++ "integrity": "sha512-ESXg23tqj73C14KvoT3kC/6VfZEkjpN1Q9fdipQ3bqIGfxiiSQIz0osGzthHHm3okruCq6aDQld1kFP9tx8+kA==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
@@@ -1316,35 +1042,6 @@
"node": ">=0.10.0"
}
},
-- "node_modules/prebuild-install": {
-- "version": "7.1.3",
-- "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
-- "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
-- "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "detect-libc": "^2.0.0",
-- "expand-template": "^2.0.3",
-- "github-from-package": "0.0.0",
-- "minimist": "^1.2.3",
-- "mkdirp-classic": "^0.5.3",
-- "napi-build-utils": "^2.0.0",
-- "node-abi": "^3.3.0",
-- "pump": "^3.0.0",
-- "rc": "^1.2.7",
-- "simple-get": "^4.0.0",
-- "tar-fs": "^2.0.0",
-- "tunnel-agent": "^0.6.0"
-- },
-- "bin": {
-- "prebuild-install": "bin.js"
-- },
-- "engines": {
-- "node": ">=10"
-- }
-- },
"node_modules/proxy-addr": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
@@@ -1362,18 +1059,6 @@
"url": "https://opencollective.com/express"
}
},
-- "node_modules/pump": {
-- "version": "3.0.4",
-- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
-- "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "end-of-stream": "^1.1.0",
-- "once": "^1.3.1"
-- }
-- },
"node_modules/qs": {
"version": "6.16.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
@@@ -1414,39 +1099,6 @@
"node": ">= 0.8"
}
},
-- "node_modules/rc": {
-- "version": "1.2.8",
-- "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
-- "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
-- "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "deep-extend": "^0.6.0",
-- "ini": "~1.3.0",
-- "minimist": "^1.2.0",
-- "strip-json-comments": "~2.0.1"
-- },
-- "bin": {
-- "rc": "cli.js"
-- }
-- },
-- "node_modules/readable-stream": {
-- "version": "3.6.2",
-- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
-- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "inherits": "^2.0.3",
-- "string_decoder": "^1.1.1",
-- "util-deprecate": "^1.0.1"
-- },
-- "engines": {
-- "node": ">= 6"
-- }
-- },
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
@@@ -1638,55 +1290,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
-- "node_modules/simple-concat": {
-- "version": "1.0.1",
-- "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
-- "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
-- "funding": [
-- {
-- "type": "github",
-- "url": "https://github.com/sponsors/feross"
-- },
-- {
-- "type": "patreon",
-- "url": "https://www.patreon.com/feross"
-- },
-- {
-- "type": "consulting",
-- "url": "https://feross.org/support"
-- }
-- ],
-- "license": "MIT",
-- "optional": true,
-- "peer": true
-- },
-- "node_modules/simple-get": {
-- "version": "4.0.1",
-- "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
-- "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
-- "funding": [
-- {
-- "type": "github",
-- "url": "https://github.com/sponsors/feross"
-- },
-- {
-- "type": "patreon",
-- "url": "https://www.patreon.com/feross"
-- },
-- {
-- "type": "consulting",
-- "url": "https://feross.org/support"
-- }
-- ],
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "decompress-response": "^6.0.0",
-- "once": "^1.3.1",
-- "simple-concat": "^1.0.0"
-- }
-- },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@@ -1711,60 +1314,6 @@
"node": ">= 0.8"
}
},
-- "node_modules/string_decoder": {
-- "version": "1.3.0",
-- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
-- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "safe-buffer": "~5.2.0"
-- }
-- },
-- "node_modules/strip-json-comments": {
-- "version": "2.0.1",
-- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-- "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "engines": {
-- "node": ">=0.10.0"
-- }
-- },
-- "node_modules/tar-fs": {
-- "version": "2.1.5",
-- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
-- "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "chownr": "^1.1.1",
-- "mkdirp-classic": "^0.5.2",
-- "pump": "^3.0.0",
-- "tar-stream": "^2.1.4"
-- }
-- },
-- "node_modules/tar-stream": {
-- "version": "2.2.0",
-- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
-- "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "bl": "^4.0.3",
-- "end-of-stream": "^1.4.1",
-- "fs-constants": "^1.0.0",
-- "inherits": "^2.0.3",
-- "readable-stream": "^3.1.1"
-- },
-- "engines": {
-- "node": ">=6"
-- }
-- },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@@ -1774,20 +1323,6 @@
"node": ">=0.6"
}
},
-- "node_modules/tunnel-agent": {
-- "version": "0.6.0",
-- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-- "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
-- "license": "Apache-2.0",
-- "optional": true,
-- "peer": true,
-- "dependencies": {
-- "safe-buffer": "^5.0.1"
-- },
-- "engines": {
-- "node": "*"
-- }
-- },
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@@ -1810,14 +1345,6 @@
"node": ">= 0.8"
}
},
-- "node_modules/util-deprecate": {
-- "version": "1.0.2",
-- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
-- "license": "MIT",
-- "optional": true,
-- "peer": true
-- },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@@ -1836,14 +1363,6 @@
"node": ">= 0.8"
}
},
-- "node_modules/wrappy": {
-- "version": "1.0.2",
-- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
-- "license": "ISC",
-- "optional": true,
-- "peer": true
-- },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
ddiff --git a/package.json b/package.json
iindex c22d859..76511fc 100644
---- a/package.json
++++ b/package.json
@@@ -28,7 +28,7 @@
"jsonwebtoken": "^9.0.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.92.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.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.9.0",
"pg": "^8.23.0"
},
"engines": {
ddiff --git a/public/css/tips.css b/public/css/tips.css
iindex c153a55..78ab59f 100644
---- a/public/css/tips.css
++++ b/public/css/tips.css
@@@ -77,3 +77,12 @@ ul.mod-list, ul.plain { list-style: none; padding: 0; }
.mod-item.hidden .msg, .mod-item.test { color: var(--tips-muted); }
.mod-item.held { border-left: 3px solid var(--tips-bad); padding-left: 10px; }
.mod-item .msg { margin: 6px 0; overflow-wrap: anywhere; }
++
++/* The home's kit sections (openvibe-shared/showcase) sit in this page's 880 px column, which already has its gutter. */
++.page .sc-hero, .page .sc-sec { padding-left: 0; padding-right: 0; }
++.page .sc-hero { padding-top: 8px; }
++.page .sc-sec { margin-top: 44px; }
++.page #creators .creators { margin-top: 12px; }
++.page .sc-tiers { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
++@media (min-width: 640px) { .page .sc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
++.page .sc-price { font-size: 24px; }
ddiff --git a/server/web/layout.js b/server/web/layout.js
iindex d0aa405..6c1df80 100644
---- a/server/web/layout.js
++++ b/server/web/layout.js
@@@ -81,6 +81,8 @@ function createLayout({ config, release }) {
appIcon.headTags({ site: 'tips' }),
release ? release.metaTag() : '',
`<link rel="stylesheet" href="${asset('css/tips.css')}">`,
++ // openvibe-shared stylesheets a page asks for by name (the home's showcase.css).
++ ...(o.styles || []).map((name) => `<link rel="stylesheet" href="${ovServe.url(name)}">`),
`<meta name="ov-boost" content="tips@${esc(RELEASE)}">`,
`<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>`,
`<script src="${asset('js/tips.js')}" defer></script>`,
ddiff --git a/server/web/pages.js b/server/web/pages.js
iindex 60daae6..fac99cd 100644
---- a/server/web/pages.js
++++ b/server/web/pages.js
@@@ -5,6 +5,7 @@
* run the dashboard, with JavaScript switched off. Copy rules: no pricing claims, no pleading copy —
* the creator's own headline is the only pitch on their page.
*/
++const showcase = require('openvibe-shared/showcase');
const { esc } = require('./layout');
const { VOICES } = require('../domain/profiles');
@@@ -35,21 +36,52 @@ function goalCard(g) {
function errorBox(msg) { return msg ? `<p class="notice error" role="alert">${esc(msg)}</p>` : ''; }
++/**
++ * What a new tip page asks for until its creator changes it: migrations/0001_initial.sql's column defaults
++ * (creator_tip_profiles.min_amount, paid_message_min, tts_min_amount, media_request_min). test/pages.test.js reads
++ * the migration and fails if these drift, so the home page never quotes a number the service does not use.
++ */
++const NEW_PAGE_MINIMUMS = Object.freeze({ tip: 1, paid_message: 100, tts: 100, media_request: 25 });
++const vibes = (n) => `${n} ${n === 1 ? 'Vibe' : 'Vibes'}`;
++
++/** The home: the product's own words (openvibe-shared/showcase), then the creators who switched their page on. */
function home({ creators }) {
-- return `<section class="hero">
-- <h1>Support the creators you watch</h1>
-- <p>Tip a creator with Vibes, send a highlighted paid message, have your message read out, or request a video for their stream. Creators set goals and show every tip on stream with an overlay.</p>
-- <p class="actions"><a class="btn" href="/dashboard">Open your creator dashboard</a> <a class="btn ghost" href="/receipts">Your receipts</a></p>
--</section>
--<section>
-- <h2>How it works</h2>
-- <ol class="steps">
-- <li><b>Pick a creator.</b> Every creator who switched their page on has one at openvibe.tips/<i>name</i>.</li>
-- <li><b>Choose what to send.</b> A tip, a paid message, text-to-speech or a media request, within the creator's own limits.</li>
-- <li><b>Pay with Vibes.</b> From your Vibes balance, or through checkout. Payments are handled by OpenVibe.Billing; your receipt shows when the payment settled and when your message was delivered.</li>
-- </ol>
--</section>
--${creators.length ? `<section><h2>Creators on OpenVibe.Tips</h2><ul class="creators">${creators.map((c) => `<li><a href="/${esc(c.handle)}">${c.avatar_url ? `<img src="${esc(c.avatar_url)}" alt="" width="40" height="40" loading="lazy">` : ''}<span>${esc(c.display_name)}</span></a></li>`).join('')}</ul></section>` : ''}`;
++ const m = NEW_PAGE_MINIMUMS;
++ return showcase.hero({
++ eyebrow: 'OpenVibe.Tips',
++ title: 'Support the creators', accent: 'you watch',
++ lede: 'Tip a creator with Vibes, send a highlighted paid message, have your message read out, or request a video for their stream. Creators set goals and show every tip on stream with an overlay.',
++ actions: creators.length
++ ? [{ label: 'Find a creator', href: '#creators', primary: true }, { label: 'Start your tip page', href: '/dashboard' }]
++ : [{ label: 'Start your tip page', href: '/dashboard', primary: true }],
++ note: 'Already sent something? Your receipts are at openvibe.tips/receipts.',
++ }) + showcase.features({
++ title: 'What you can send',
++ items: [
++ { icon: 'ov:tips', title: 'A tip', text: 'Vibes straight to the creator, with an optional message.' },
++ { icon: 'ov:chat', title: 'A paid message', text: 'Your message, highlighted in their chat.' },
++ { icon: 'ov:audio', title: 'Text-to-speech', text: 'Your message read out on stream, where the creator turned it on.' },
++ { icon: 'ov:video', title: 'A media request', text: 'A video for their stream, up to the length they allow, where they turned it on.' },
++ ],
++ }) + showcase.steps({
++ title: 'How it works',
++ items: [
++ { title: 'Pick a creator', text: 'Every creator who switched their page on has one at openvibe.tips/name.' },
++ { title: 'Choose what to send', text: "A tip, a paid message, text-to-speech or a media request, within the creator's own limits." },
++ { title: 'Pay with Vibes', text: 'From your Vibes balance, or through checkout. Payments are handled by OpenVibe.Billing; your receipt shows when the payment settled and when your message was delivered.' },
++ ],
++ }) + showcase.pricing({
++ title: 'What it costs to send',
++ lede: 'The least you can send of each. Every creator sets their own minimums; these are what a new page starts with.',
++ tiers: [
++ { name: 'Tip', price: vibes(m.tip), items: ['Any amount above the minimum', 'Counts toward their goals'] },
++ { name: 'Paid message', price: vibes(m.paid_message), items: ['Highlighted in chat', 'Shown by their stream overlay'] },
++ { name: 'Text-to-speech', price: vibes(m.tts), items: ['Read out on stream', 'Only where the creator turned it on'] },
++ { name: 'Media request', price: vibes(m.media_request), items: ['A video for their stream', 'Only where the creator turned it on'] },
++ ],
++ note: "The creator's page shows their own minimums before you pay.",
++ }) + `
++${creators.length ? `<section class="sc-sec" id="creators" aria-labelledby="creators-h"><h2 id="creators-h">Creators on OpenVibe.Tips</h2><ul class="creators">${creators.map((c) => `<li><a href="/${esc(c.handle)}">${c.avatar_url ? `<img src="${esc(c.avatar_url)}" alt="" width="40" height="40" loading="lazy">` : ''}<span>${esc(c.display_name)}</span></a></li>`).join('')}</ul></section>` : ''}`;
}
/** The tip form: every field works as a plain POST. */
@@@ -351,4 +383,4 @@ function errorPage({ status, title, message }) {
return `<section class="card"><h1>${esc(title)}</h1><p>${esc(message)}</p><p><a href="/">OpenVibe.Tips home</a></p></section><!-- ${status} -->`;
}
--module.exports = { home, creatorPage, goalsPage, supportersPage, receiptsPage, erasePage, receiptPage, dashboard, moderatePage, invitePage, inviteCreated, tokenCreated, errorPage, tipForm };
++module.exports = { NEW_PAGE_MINIMUMS, home, creatorPage, goalsPage, supportersPage, receiptsPage, erasePage, receiptPage, dashboard, moderatePage, invitePage, inviteCreated, tokenCreated, errorPage, tipForm };
ddiff --git a/server/web/routes.js b/server/web/routes.js
iindex dda673d..554c7ca 100644
---- a/server/web/routes.js
++++ b/server/web/routes.js
@@@ -79,7 +79,7 @@ function createWebRoutes({ domain, config, layout, userAuth }) {
// ── Home, robots, sitemap ────────────────────────────────
r.get('/', withViewer, async (req, res) => {
const creators = await db.many(sql`SELECT handle, display_name, avatar_url FROM creator_tip_profiles WHERE page_enabled ORDER BY updated_at DESC LIMIT 24`);
-- html(res, pageFor(req, { active: 'home', canonicalPath: '/', body: pages.home({ creators }) }));
++ html(res, pageFor(req, { active: 'home', canonicalPath: '/', styles: [require('openvibe-shared/showcase').STYLESHEET], body: pages.home({ creators }) }));
});
// What shipped on OpenVibe.Tips: the shared update log every OpenVibe site has.
r.get('/updates', withViewer, (req, res) => html(res, pageFor(req, { canonicalPath: '/updates', title: 'What shipped on OpenVibe.Tips', body: frame.updatesBody({ service: 'tips', siteName: 'OpenVibe.Tips' }) + `<script src="${ovServe.url('shipped.js')}" defer></script>` })));
ddiff --git a/test/pages.test.js b/test/pages.test.js
iindex 2a77a30..b026d78 100644
---- a/test/pages.test.js
++++ b/test/pages.test.js
@@@ -31,6 +31,20 @@ const { boot, check, done } = require('./helpers/app');
assert.match(r.text, /id="ov-footer"|class="ovf/);
assert.match(r.text, /href="\/alex"/);
assert.ok(!r.text.includes('href="/bob"'), 'switched-off pages are not listed');
++ // The home opens with the showcase kit (openvibe-shared/showcase): one h1 (the hero), the kit's sheet, the
++ // pricing from the real defaults, and the creators list still below it.
++ assert.ok(r.text.includes('class="sc-hero') && r.text.includes('class="sc-tiers"'), 'hero and pricing');
++ assert.match(r.text, /<link rel="stylesheet" href="\/shared\/showcase\.css\?v=[^"]+">/);
++ assert.strictEqual((r.text.match(/<h1[\s>]/g) || []).length, 1, 'one h1');
++ assert.ok(r.text.indexOf('class="sc-hero') < r.text.indexOf('id="creators"'), 'the creators follow the showcase');
++ assert.ok(!(await get('/alex')).text.includes('showcase.css'), 'only the home links the kit');
++ });
++
++ await check('the home quotes the minimums a new page really starts with (the migration defaults)', () => {
++ const sql = require('fs').readFileSync(require('path').join(__dirname, '..', 'migrations', '0001_initial.sql'), 'utf8');
++ const def = (col) => Number((sql.match(new RegExp(`\\b${col}\\s+bigint NOT NULL DEFAULT (\\d+)`)) || [])[1]);
++ const { NEW_PAGE_MINIMUMS: m } = require('../server/web/pages');
++ assert.deepStrictEqual(m, { tip: def('min_amount'), paid_message: def('paid_message_min'), tts: def('tts_min_amount'), media_request: def('media_request_min') });
});
await check('every page carries the boost marker and script, and the navbar signs in back to the current page', async () => {