Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93ffe552a5 | ||
|
|
071d78dd6c | ||
|
|
000127f88c | ||
|
|
7cf334de8b | ||
|
|
42edebcf6d | ||
|
|
281a6511f1 | ||
|
|
b85a101749 | ||
|
|
b33f7f5f23 | ||
|
|
5328168e99 | ||
|
|
73a8d42174 | ||
|
|
a6b889fc0d | ||
|
|
44f450a90c | ||
|
|
fbe6f4a649 | ||
|
|
edc47631bb | ||
|
|
1353254445 | ||
|
|
97b6e9c11d | ||
|
|
3d1486ef79 | ||
|
|
147e57845b | ||
|
|
494b00b926 | ||
|
|
8f316f915b | ||
|
|
002790a01d | ||
|
|
143abc8fd1 | ||
|
|
6376e73951 | ||
|
|
e2935c23fd | ||
|
|
96bbeef040 | ||
|
|
b78337af6d | ||
|
|
d58d411180 | ||
|
|
5463677237 | ||
|
|
ca959cb33e | ||
|
|
4d367109d5 | ||
|
|
b5ee063260 | ||
|
|
77fa149cc3 | ||
|
|
35e84971b8 | ||
|
|
12b1c1e7f8 | ||
|
|
30895e4444 | ||
|
|
0a3057d081 | ||
|
|
f61563d2e2 | ||
|
|
614ff3b1f1 | ||
|
|
3792b218a9 | ||
|
|
492c591f06 | ||
|
|
5d7df0eb61 | ||
|
|
89986759e0 | ||
|
|
cc02228548 | ||
|
|
61b893e94d | ||
|
|
3b0465067f | ||
|
|
e2540c729f | ||
|
|
18981f546c | ||
|
|
a6e67e2284 | ||
|
|
b02d275bdb | ||
|
|
13430ccea0 | ||
|
|
599c40c3cc | ||
|
|
c092e10188 | ||
|
|
e36e2c0aee | ||
|
|
661d6965e4 | ||
|
|
4ed5c97678 | ||
|
|
32686c5ef0 | ||
|
|
fd6919b65f | ||
|
|
c081d950b2 | ||
|
|
e9e21f733d | ||
|
|
c870d35f7c | ||
|
|
507f09dff5 | ||
|
|
4e42db2667 | ||
|
|
0b0d3628ee | ||
|
|
0cf50ee7e9 | ||
|
|
b54a0209d9 | ||
|
|
0d7eb42643 | ||
|
|
419bb02610 | ||
|
|
e05006f0b7 | ||
|
|
da0d17c8f9 | ||
|
|
0bf0c1be5c | ||
|
|
70dec62d8f | ||
|
|
87d0caed5b | ||
|
|
d6fd0fd8e9 | ||
|
|
55e3be4624 | ||
|
|
a5f9a33465 | ||
|
|
5b57fc644d | ||
|
|
86be0284d9 | ||
|
|
79e592b4a6 | ||
|
|
de358dd643 | ||
|
|
cb58c73b17 | ||
|
|
98dd51509d | ||
|
|
ebbddf77bc | ||
|
|
f9e2b975d1 | ||
|
|
0434401596 | ||
|
|
c99f6834cd | ||
|
|
7d389bb33d | ||
|
|
0addd59226 | ||
|
|
e9ec0b790e | ||
|
|
0ec2d48142 | ||
|
|
55b2dc77e7 | ||
|
|
ebc306c928 | ||
|
|
eec5b58175 | ||
|
|
4f266e25db | ||
|
|
9361752b54 | ||
|
|
073b62459a | ||
|
|
55429a705c | ||
|
|
ff83438dee | ||
|
|
879330b2a2 | ||
|
|
04865ffdc5 | ||
|
|
c3f08a8178 | ||
|
|
eaae8066da | ||
|
|
cb18615c1b | ||
|
|
3573b7a287 | ||
|
|
0a929e7b6d | ||
|
|
ccf65f7ccd | ||
|
|
2f0d8ad07b | ||
|
|
df20aaa114 | ||
|
|
9d6f17fedb | ||
|
|
fb07d706b1 | ||
|
|
31cad16403 | ||
|
|
23c3681b21 | ||
|
|
d53ce6742a | ||
|
|
45749f1977 | ||
|
|
004c6426a8 | ||
|
|
484343b027 | ||
|
|
46ef2b8123 | ||
|
|
10751a8fb9 | ||
|
|
9d7efce44e | ||
|
|
151c547490 | ||
|
|
8855fac640 | ||
|
|
a43f8e634d | ||
|
|
934d7c6cce | ||
|
|
9122a14493 | ||
|
|
86464f9798 | ||
|
|
b84f8c6031 | ||
|
|
17dda8f2c1 | ||
|
|
ad0713b4cb | ||
|
|
6ecbef207f | ||
|
|
ae6ba0d9e0 | ||
|
|
0843c70123 | ||
|
|
a97ce36c70 | ||
|
|
7995f92a38 | ||
|
|
ea81e3bf32 | ||
|
|
9531fb21be | ||
|
|
daf39ce361 | ||
|
|
7070eea35e | ||
|
|
814bb65ffe | ||
|
|
749818fa13 | ||
|
|
f8c227e1ed | ||
|
|
dbceaa98df | ||
|
|
bf552d3816 | ||
|
|
c1ae2c8c9f | ||
|
|
efe4ff99eb | ||
|
|
1a6b304daa | ||
|
|
fe0e023bd7 | ||
|
|
1db10f7f81 | ||
|
|
90bbcf0ad4 | ||
|
|
35898a95a5 | ||
|
|
3bf380dfc6 | ||
|
|
2e516ca989 | ||
|
|
3fe9f75700 | ||
|
|
cc10bc33f0 | ||
|
|
30e66fe969 | ||
|
|
b5cde1b977 | ||
|
|
384892eea2 | ||
|
|
c3b16baf0d | ||
|
|
3eb63f28fb | ||
|
|
d2c4758d6d | ||
|
|
c76f780264 | ||
|
|
58d28019e9 | ||
|
|
bc392b7088 | ||
|
|
bece17afda | ||
|
|
c699d3f438 | ||
|
|
6fb26fede5 | ||
|
|
73be11403b | ||
|
|
56cf4aa8e6 | ||
|
|
38e010c828 | ||
|
|
19bcc8ca6b | ||
|
|
cfa2e2f8bf | ||
|
|
ee2175a7ad | ||
|
|
47a78a083c | ||
|
|
723c8f8bf8 | ||
|
|
4529a59499 | ||
|
|
41b2b8fba6 | ||
|
|
dba7763000 | ||
|
|
ac29eea29c | ||
|
|
98fb9b437d | ||
|
|
38f70b666d | ||
|
|
e7939f52c1 | ||
|
|
f4925a2726 | ||
|
|
715087992e | ||
|
|
ddf1a560a8 | ||
|
|
c0290b33a7 | ||
|
|
79aa7f7669 | ||
|
|
9834fc7d9f | ||
|
|
25e53c08e3 | ||
|
|
8cf1ff0037 | ||
|
|
4fd16c5a8e | ||
|
|
d1d481555d | ||
|
|
8ad39b5cf6 | ||
|
|
b3b467755a | ||
|
|
957a3ae6fa | ||
|
|
58fd6ef7d5 | ||
|
|
2bafecfb50 | ||
|
|
f04c21004a | ||
|
|
63a178263a | ||
|
|
4d97493003 | ||
|
|
bffd98247e | ||
|
|
a453ead95e | ||
|
|
9a9dcb239f | ||
|
|
8981f1cb6a | ||
|
|
ff3d9fa05c | ||
|
|
251290ab2e | ||
|
|
3b2874a5c9 | ||
|
|
38ec169b22 | ||
|
|
8158eb2089 | ||
|
|
48cf7ed4f8 | ||
|
|
8e8ab147fb | ||
|
|
19f62d545d | ||
|
|
7286295a3a | ||
|
|
24b12d9108 | ||
|
|
1bb935628e | ||
|
|
7a923ae77c | ||
|
|
26c4420293 | ||
|
|
4f75f2dff4 | ||
|
|
4926cb5242 | ||
|
|
d9d07d8ff9 | ||
|
|
82f779099a | ||
|
|
332fc156bf | ||
|
|
d35f396c2f | ||
|
|
c4cc89af29 | ||
|
|
f80ebef5c3 | ||
|
|
e3b2220269 | ||
|
|
b5f218b221 | ||
|
|
84948101f3 | ||
|
|
c39208c629 | ||
|
|
62ccc468f9 | ||
|
|
33f96b762a | ||
|
|
7c9443e712 | ||
|
|
beb8b54d14 | ||
|
|
e6b465ccca | ||
|
|
287d954823 | ||
|
|
1c805b053e | ||
|
|
908435f476 | ||
|
|
b773e1ec21 | ||
|
|
c0ffde6462 | ||
|
|
d84dbe5fae | ||
|
|
793375a4fb | ||
|
|
c93075bb22 | ||
|
|
aa4e86758a | ||
|
|
9d57a43c98 | ||
|
|
e76107c8b2 | ||
|
|
8dd4b40b5a | ||
|
|
1e42b2af56 | ||
|
|
24823ebed9 | ||
|
|
1f0fb6ac87 | ||
|
|
9e296d4910 | ||
|
|
f2c7520a40 | ||
|
|
05f3f88795 | ||
|
|
8fd5ce3c88 | ||
|
|
0fd54abde8 | ||
|
|
5d04571b6b | ||
|
|
a39446f1e0 | ||
|
|
01a9b2f0bd | ||
|
|
8925eee72e | ||
|
|
16562cbb39 | ||
|
|
c9ea82a6dc | ||
|
|
76e063e8f6 | ||
|
|
e07d4c4cab | ||
|
|
6dd970d55b | ||
|
|
ad4f56a1d4 | ||
|
|
2529ab1314 | ||
|
|
80438555f5 | ||
|
|
5d3c2a5f35 | ||
|
|
22e93d83dd | ||
|
|
991da0f2d5 | ||
|
|
0824bbca98 | ||
|
|
69ea5b0919 | ||
|
|
7550007fe6 | ||
|
|
2d20ecb6ca | ||
|
|
fe89e54a2a | ||
|
|
c2620a7564 | ||
|
|
d843a6aa51 | ||
|
|
2132a913d5 | ||
|
|
99e8bd0c78 | ||
|
|
90e3649506 | ||
|
|
ce94e2c411 | ||
|
|
dbc8a5278a | ||
|
|
bbad8f7d48 | ||
|
|
b990578027 | ||
|
|
a37e82fe03 | ||
|
|
c8600f824e | ||
|
|
73a7a48c5c | ||
|
|
f6e1bf2424 | ||
|
|
7672726088 | ||
|
|
57cde4460a | ||
|
|
62fe113bf4 | ||
|
|
fd400f6fb2 | ||
|
|
9041f9c0b7 | ||
|
|
1d8fa2d0c0 | ||
|
|
84002d439d | ||
|
|
109a0a7aec | ||
|
|
3d56cc3835 | ||
|
|
7d88cb1dbc | ||
|
|
94a203f594 | ||
|
|
57cf16a0f4 | ||
|
|
94eb750854 | ||
|
|
7c3af36003 | ||
|
|
b1b0b0f625 | ||
|
|
24309c1356 | ||
|
|
a940d0cdfd | ||
|
|
4a588ed5d5 | ||
|
|
6bb089f4c1 | ||
|
|
f81c97e5a8 | ||
|
|
58d2611df7 | ||
|
|
edc03bf152 | ||
|
|
be0e16efec | ||
|
|
205a9fdbda | ||
|
|
88ad16326b | ||
|
|
8a73d6d1fb | ||
|
|
7b324593c5 | ||
|
|
1421a0c198 | ||
|
|
21b095e45f | ||
|
|
cf34e28a47 | ||
|
|
ef6b8bf1cf | ||
|
|
ad58ff04a3 | ||
|
|
f579c965a7 | ||
|
|
b46135cd36 | ||
|
|
8baeab2b0f | ||
|
|
f33cd66642 | ||
|
|
e0a9fae4c8 | ||
|
|
2fb2a83272 | ||
|
|
2e77cdbe04 | ||
|
|
361185ec02 | ||
|
|
0abd4249d1 | ||
|
|
1e1600984c | ||
|
|
05a3254372 | ||
|
|
2cfcbc8366 | ||
|
|
7c295b57be | ||
|
|
cbf5203dcf | ||
|
|
97e74875ae | ||
|
|
61815ba289 | ||
|
|
9fa359f9b5 | ||
|
|
3d04c90ddc | ||
|
|
1dfb9f2877 | ||
|
|
2b42d0ada6 | ||
|
|
8a4ab536da | ||
|
|
06c979569f | ||
|
|
9498ba3465 | ||
|
|
2e26242153 | ||
|
|
d950fd9470 | ||
|
|
2e6e7012a6 |
No files matched your search
@@ -0,0 +1,81 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Cache npm directory
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ matrix.node-version }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-${{ matrix.node-version }}-npm-
|
||||
|
||||
- name: Cache node_modules
|
||||
id: node-modules-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-${{ matrix.node-version }}-modules-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-${{ matrix.node-version }}-modules-
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Install ripgrep
|
||||
run: sudo apt-get update && sudo apt-get install -y ripgrep
|
||||
|
||||
- name: Run lint suite
|
||||
run: npm run lint
|
||||
|
||||
- name: Build CSS
|
||||
run: npm run build:css
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run visual regression tests
|
||||
id: visual
|
||||
run: npm run test:visual
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload Playwright artifacts
|
||||
if: steps.visual.outcome == 'failure'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-visual-diffs
|
||||
path: |
|
||||
playwright-report
|
||||
test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Run unit tests
|
||||
run: npm run test:unit
|
||||
|
||||
- name: Check CSS artifacts are clean
|
||||
run: npm run check:css
|
||||
|
||||
- name: Fail if visual regressions detected
|
||||
if: steps.visual.outcome == 'failure'
|
||||
run: exit 1
|
||||
@@ -2,3 +2,6 @@ saved_config.yaml
|
||||
repo-context.txt
|
||||
node_modules/
|
||||
src/webtorrent-docs/
|
||||
playwright-report/
|
||||
test-results/
|
||||
tests/visual/__snapshots__/
|
||||
@@ -1,6 +1,10 @@
|
||||
# Bitvid — AI Agent Guide
|
||||
# bitvid — AI Agent Guide
|
||||
|
||||
This guide tells AI agents how to keep Bitvid aligned with the current product direction. Follow it whenever you touch code, content, or documentation inside this repository.
|
||||
This guide tells AI agents how to keep bitvid aligned with the current product direction. Follow it whenever you touch code, content, or documentation inside this repository.
|
||||
|
||||
For any nostr related work, please review the nip documentation located in /docs/nips before begining work.
|
||||
|
||||
**Comment publishing guardrail:** Session actors are limited to passive telemetry (e.g., view counters). Always require the logged-in Nostr signer when sending video comments through [`js/nostr/commentEvents.js`](js/nostr/commentEvents.js). See the reminder in [`docs/nostr-event-schemas.md`](docs/nostr-event-schemas.md#event-catalogue) for context.
|
||||
|
||||
---
|
||||
|
||||
@@ -8,15 +12,33 @@ This guide tells AI agents how to keep Bitvid aligned with the current product d
|
||||
|
||||
* **Main** is the production track. Anything merged here must preserve today’s UX and magnet safety guarantees. Rollbacks should be painless: keep commits atomic, avoid destructive migrations, and leave feature flags in a known-good default (`false` unless product explicitly flips them).
|
||||
* **Unstable** is our experimentation lane. Gate risky behavior behind feature flags defined in `js/constants.js` and document the toggle/rollback plan in PR descriptions.
|
||||
* **Emergency response:** If a change regresses URL-first playback or breaks magnet parsing, revert immediately and annotate the AGENTS.md changelog with remediation tips so future agents do not repeat the mistake.
|
||||
* **Emergency response:** If a change regresses URL-first playback or breaks magnet parsing, revert immediately, call this out in the PR, and annotate the AGENTS.md changelog with remediation tips. **Note:** These regressions usually surface in dev logs and tests around playback/magnet helpers—reference them when triaging so future agents don’t repeat the mistake.
|
||||
|
||||
### Browser logging policy
|
||||
|
||||
* Route every browser log through `js/utils/logger.js`. Do **not** call `console.*` directly.
|
||||
* Use `logger.dev.*` for diagnostics that should disappear in production (flagged by `IS_DEV_MODE`/`isDevMode`). Use `logger.user.*` for operator-facing warnings and errors that must reach production consoles.
|
||||
* Before promoting a build, flip `IS_DEV_MODE` in `config/instance-config.js` to match the target environment so `window.__BITVID_DEV_MODE__` and the logger channels behave correctly.
|
||||
* Review and follow [`docs/logging.md`](docs/logging.md) whenever you add new logging or touch inline scripts.
|
||||
|
||||
## 2. Mission: URL‑First Playback with WebTorrent Fallback
|
||||
|
||||
* **Goal:** Always deliver smooth playback while keeping hosting costs low.
|
||||
* **Primary transport:** A hosted video URL (MP4/WebM/HLS/DASH) that the `<video>` element can stream directly.
|
||||
* **Fallback transport:** A WebTorrent magnet that includes browser‑safe trackers plus optional HTTP hints.
|
||||
* **Runtime behavior:** Call `playVideoWithFallback({ url, magnet })` (see `js/playbackUtils.js`). It probes the URL first; only when that fails should WebTorrent start.
|
||||
* **Content contract:** Bitvid posts (Nostr kind `30078`) must include a `title` and at least one of `url` or `magnet`. Prefer to publish both along with optional `thumbnail`, `description`, and `mode` fields. When mirroring to NIP‑94 (`kind 1063`), copy the hosted URL and (optionally) the magnet so other clients can discover the same asset.
|
||||
* **Runtime behavior:** Call `bitvidApp.playVideoWithFallback({ url, magnet })` in `js/app.js`. This function orchestrates URL probing and magnet fallback, delegating low-level stream handling to `js/services/playbackService.js`. `js/playbackUtils.js` now provides magnet/session helper utilities rather than the playback entry point itself.
|
||||
* **Content contract:** bitvid posts (Nostr kind `30078`) must include a `title` and at least one of `url` or `magnet`. Prefer to publish both along with optional `thumbnail`, `description`, and `mode` fields. When mirroring to NIP‑94 (`kind 1063`), copy the hosted URL and (optionally) the magnet so other clients can discover the same asset.
|
||||
|
||||
## Styling & Theming Rules (token-first)
|
||||
|
||||
* **Single source of truth:** All colors, spacing, radii, and typography decisions must flow through our design tokens; never reach for ad-hoc HEX/RGB values when a token exists.
|
||||
* **Theme scopes:** Tokens live at three levels—`core` (global primitives), `theme` (light/dark surface/background mappings), and `component` (contextual overrides). Additions must document their scope and consumers.
|
||||
* **Semantic tokens:** Maintain this canonical list: `bg`, `bg-muted`, `bg-raised`, `text`, `text-muted`, `border`, `border-strong`, `accent`, `accent-hover`, `accent-active`, `accent-muted`, `accent-contrast`, `danger`, `warning`, `success`, `info`, `overlay`, `overlay-strong`.
|
||||
* **Palette guidance:** Map semantic tokens to palette primitives defined in the CSS build pipeline; when a new primitive is needed, extend the palette once and remap consumers rather than hard-coding colors downstream.
|
||||
* **Icon contrast rule:** Icons and glyph-only buttons must meet a minimum 3:1 contrast ratio against their background; prefer `accent-contrast` or `text` tokens when in doubt.
|
||||
* **Accessibility expectations:** Default themes must satisfy WCAG 2.1 AA contrast ratios for text and controls, and preserve focus outlines sourced from the token set.
|
||||
* **Prohibitions:** Do not commit inline styles, `<font>` tags, or CSS variables whose values are literal colors; always reference the appropriate token variables.
|
||||
* **Theme toggling:** Implement new themes through token swaps only (e.g., toggling the `data-theme` attribute) and keep component logic agnostic to specific palette values. Document toggles alongside the [README CSS build pipeline](README.md#css-build-pipeline).
|
||||
|
||||
---
|
||||
|
||||
@@ -24,9 +46,10 @@ This guide tells AI agents how to keep Bitvid aligned with the current product d
|
||||
|
||||
* **Use the helpers every time:** Always run inbound values through `safeDecodeMagnet()` before playback or normalization, and call `normalizeAndAugmentMagnet()` (see `js/magnetUtils.js`) to append `ws=` / `xs=` hints without mutating hashes.
|
||||
* **Keep magnets raw:** Persist the literal `magnet:?xt=urn:btih:...` string. Decode once, then feed that exact value to WebTorrent or storage. Never rely on re-encoding helpers that might alter casing.
|
||||
* **Never call `new URL()` on magnets:** URL constructors/`URLSearchParams` percent-encode the `xt` payload and corrupt legacy hashes. Manipulate magnets with the helper utilities or string functions that respect the raw hash.
|
||||
* **Never call `new URL()` on magnets:** URL constructors/`URLSearchParams` percent-encode the `xt` payload and can corrupt legacy hashes. Manipulate magnets with the helper utilities or string functions that respect the raw hash.
|
||||
* **HTTP hints:** Encourage HTTPS `ws=` web seeds (file roots only) and HTTPS `xs=` pointers to `.torrent` files so fallback peers warm quickly.
|
||||
* **Tracker policy:** Browser code must ship WSS trackers only. Pull the canonical list from `js/constants.js`; do not add UDP or plaintext HTTP trackers.
|
||||
* **Tracker policy:** Browser code must ship **WSS trackers only**. Pull the canonical list from `js/constants.js`; do not add UDP or plaintext HTTP trackers. (If you introduce a new tracker set or feature flag, update `js/constants.js` and mention it here.)
|
||||
* **Validation reference:** When in doubt about legacy infohash handling or normalization, consult the existing tests that cover legacy/bare infohash inputs and magnet normalization. If you extend helper behavior, extend those tests alongside the code.
|
||||
|
||||
---
|
||||
|
||||
@@ -46,29 +69,41 @@ This guide tells AI agents how to keep Bitvid aligned with the current product d
|
||||
Run this script before shipping notable UI changes, especially around upload/playback flows. Automate when possible, but manual verification is required for releases.
|
||||
|
||||
1. **Smoke test the modal**
|
||||
|
||||
* Launch the site, open the Upload modal, and confirm required-field validation (title + one of URL/magnet).
|
||||
* Submit with URL only, magnet only, and both; ensure success states are clear.
|
||||
2. **URL-first playback**
|
||||
|
||||
* Publish a post with both URL and magnet. Load the card and verify the player fetches the hosted URL first (check network tab or logs).
|
||||
* Temporarily block the URL (e.g., devtools throttling or offline). Confirm playback seamlessly falls back to the magnet.
|
||||
3. **Magnet hygiene**
|
||||
|
||||
* Paste an encoded magnet; ensure `safeDecodeMagnet()` returns the raw value and the final string still includes the original `xt` hash.
|
||||
* Confirm `normalizeAndAugmentMagnet()` appends `ws=`/`xs=` hints without corrupting parameters.
|
||||
4. **P2P hints**
|
||||
|
||||
* Verify magnets authored through the modal include HTTPS `ws=` and optional `xs=` values when provided.
|
||||
* Ensure tracker lists come from `js/constants.js` and remain WSS-only.
|
||||
5. **Cross-browser sanity**
|
||||
|
||||
* Spot-check playback in Chromium and Firefox (desktop) to ensure no console errors related to CORS, Range requests, or tracker connections.
|
||||
|
||||
Document the run in PR descriptions so QA can cross-reference results.
|
||||
|
||||
---
|
||||
|
||||
## 6. Additional Notes for Agents
|
||||
## 6. Moderation Quickstart (bitvid)
|
||||
|
||||
* **Nostr interoperability:** Keep kind `30078` events as the source of truth and optionally mirror to kind `1063` so external clients see the hosted URL.
|
||||
* **Probing:** Lightweight `HEAD`/`GET` requests should back `probeUrl()` so dead URLs can be hidden or flagged without blocking the UI.
|
||||
* **Extensibility:** Future work (live streams, NIP-96 uploads, analytics) should preserve the URL-first strategy and magnet safety rules above.
|
||||
The moderation and admin list tooling remain active in bitvid and must follow Nostr moderation policies and QA steps. Review `docs/moderation/README.md` and these key expectations:
|
||||
|
||||
1. **Report thresholds:** Parse NIP-56 reports and compute `trustedReportCount` using **only F1 followers**.
|
||||
2. **Default behaviors:** Blur thumbnails at ≥3 F1 `nudity` reports and block autoplay at ≥2.
|
||||
3. **Admin lists:** Use NIP‑51 lists — `10000` for mute/downrank and `30000` for admin hard-hide/whitelist. Ensure these list IDs are correctly handled in the profile modal.
|
||||
4. **Profile Modal Integration:** Admin list toggles and moderation controls in `ProfileModalController` must trigger local state changes and reflect mutations visually without breaking cached profiles.
|
||||
5. **User overrides:** Add a “show anyway” option to any blurred or blocked content.
|
||||
6. **Testing:** Run all cases from `docs/moderation/testing.md` before shipping changes. Confirm blur, hide, and override flows function correctly across sessions.
|
||||
7. **Telemetry and privacy:** Log moderation decisions locally for debugging without leaking PII.
|
||||
8. **QA demo:** Record a ≤90 s screen demo showing blur/hide/override flows when submitting major moderation work.
|
||||
|
||||
---
|
||||
|
||||
@@ -78,16 +113,70 @@ Document the run in PR descriptions so QA can cross-reference results.
|
||||
`{ "version": 3, "title": string, "url"?: string, "magnet"?: string, "thumbnail"?: string, "description"?: string, "mode": "live"|"dev", "isPrivate": boolean, "deleted": boolean, "videoRootId": string }`.
|
||||
* **Validation:** Every note must include a non-empty `title` plus at least one playable source (`url` or `magnet`). URL-only and magnet-only posts are both valid. Legacy v2 magnet notes stay readable.
|
||||
* **Upload UX:** The modal collects a hosted HTTPS URL and/or magnet. Enforce HTTPS for direct playback while keeping magnets optional when a URL is supplied.
|
||||
* **Playback orchestration:** `playVideoWithFallback` probes and plays the HTTPS URL first, watches for stalls/errors, and then falls back to WebTorrent. When both sources exist, pass the hosted URL through to WebTorrent as a webseed hint.
|
||||
* **Playback orchestration:** `bitvidApp.playVideoWithFallback` probes and plays the HTTPS URL first, watches for stalls/errors, and then falls back to WebTorrent. When both sources exist, pass the hosted URL through to WebTorrent as a webseed hint.
|
||||
* **Status messaging:** Update modal copy to reflect whether playback is direct or via P2P so regressions surface quickly during QA.
|
||||
|
||||
---
|
||||
|
||||
## 8. Architectural Guide: `bitvidApp` vs. UI Controllers
|
||||
|
||||
To keep the codebase maintainable, we are moving complex UI logic from `js/app.js` into dedicated controller classes (e.g., `ProfileModalController`, `UploadModal`). **This is an architectural direction for `unstable`**—some controllers may not exist yet. When working on these components, follow these core principles:
|
||||
|
||||
#### **Principle: Separation of Concerns**
|
||||
|
||||
* **`bitvidApp` (js/app.js): The Orchestrator.** Its main job is to manage application-wide state, wire up services, and orchestrate high-level actions. It should **not** directly manipulate the DOM elements of complex components like modals. Instead, it should instantiate their controllers and communicate with them through a clean API.
|
||||
|
||||
* **UI Controllers (e.g., `js/ui/profileModalController.js`): The Specialists.** A controller is responsible for a specific piece of the UI. It manages its own DOM elements, handles user interactions within its scope, and uses services and callbacks provided by `bitvidApp` to perform actions.
|
||||
|
||||
#### **State Management: The Source of Truth**
|
||||
|
||||
The single source of truth for shared application state (like saved profiles, relay lists, and block lists) lives in modules like `js/state/cache.js` and services like `relayManager`.
|
||||
|
||||
1. **`bitvidApp` Writes State:** `bitvidApp` is responsible for calling the functions that mutate this central state (e.g., `setSavedProfiles`, `userBlocks.addBlock`).
|
||||
2. **`bitvidApp` Notifies the Controller:** After updating the state, `bitvidApp` must call a method on the controller to tell it that its data is stale and it needs to re-render.
|
||||
|
||||
* **Example:** When a user logs in, `bitvidApp` updates the active user and saved profiles, then calls `this.profileModalController.handleAuthLogin(detail)` so the modal can update its own display.
|
||||
3. **Controller Reads State:** The controller receives functions to *read* this state in its constructor (via the `state` and `services` objects). It should **always** use these functions (e.g., `this.state.getSavedProfiles()`, `this.services.userBlocks.getBlocked()`) to get the data it needs to render.
|
||||
|
||||
#### **Execution Flow: Callbacks**
|
||||
|
||||
When a user performs an action inside a modal that needs to affect the whole application (like switching a profile or updating relays), the flow is:
|
||||
|
||||
1. **User Action:** A user clicks a "Switch Profile" button inside the modal.
|
||||
2. **Controller Event Handler:** The controller's internal event listener fires.
|
||||
3. **Controller Invokes Callback:** The controller calls a callback function passed to it by `bitvidApp` (e.g., `this.callbacks.onRequestSwitchProfile({ pubkey })`). It does *not* contain the logic for switching profiles itself.
|
||||
4. **`bitvidApp` Executes the Logic:** `bitvidApp` provides the implementation for that callback (e.g., `handleProfileSwitchRequest`). This method contains the actual logic to call the auth service, reload videos, and update the application state.
|
||||
|
||||
#### **Checklist for Refactoring from `bitvidApp` to a Controller:**
|
||||
|
||||
When moving a feature to a controller, follow these steps:
|
||||
|
||||
1. **Identify Logic:** Find the methods in `js/app.js` that directly read from or write to the modal's DOM (e.g., old `renderSavedProfiles` or `populateBlockedList` logic).
|
||||
2. **Create Controller Method:** Implement the new logic inside the controller. Ensure it gets its data from `this.services.*` or `this.state.*`.
|
||||
3. **Create `bitvidApp` Wrapper:** In `js/app.js`, replace the old logic with a new, thin wrapper method. This wrapper should:
|
||||
|
||||
* a. Update any central application state if needed.
|
||||
* b. Call the new method on the controller instance (e.g., `this.profileModalController.populateBlockedList()`).
|
||||
4. **Wire Up Callbacks:** If the controller needs to trigger an app-wide action, ensure a callback is defined in the controller's constructor and provided by `bitvidApp` during instantiation.
|
||||
5. **Remove Old Code:** Once fully replaced, delete the old DOM manipulation logic and direct event listeners from `js/app.js`. All interactions should be routed through the controller.
|
||||
|
||||
#### **File Map (quick reference)**
|
||||
|
||||
* `js/app.js` — **`bitvidApp`** (orchestrator; owns high-level flows like `playVideoWithFallback` wiring)
|
||||
* `js/services/playbackService.js` — Lower-level playback stream handling
|
||||
* `js/playbackUtils.js` — Magnet/session helper utilities
|
||||
* `js/magnetUtils.js` — `safeDecodeMagnet`, `normalizeAndAugmentMagnet` (WSS trackers, `ws=`/`xs=` hints)
|
||||
* `js/constants.js` — Feature flags, canonical WSS tracker list
|
||||
|
||||
---
|
||||
|
||||
## Next
|
||||
|
||||
Please read these documents next.
|
||||
Please read these documents next.
|
||||
|
||||
docs/nostr-event-schemas.md
|
||||
js/nostrEventSchemas.js
|
||||
* `docs/nostr-event-schemas.md`
|
||||
* `js/nostrEventSchemas.js`
|
||||
|
||||
And if you need to create new nostr kinds please keep the logic centralized there in nostrEventSchemas.js and the nostr-event-schemas.md up to date.
|
||||
And if you need to create new nostr kinds please keep the logic centralized there in `nostrEventSchemas.js` and the `nostr-event-schemas.md` up to date.
|
||||
|
||||
**End of AGENTS.md**
|
||||
@@ -0,0 +1,40 @@
|
||||
# Contributing to bitvid
|
||||
|
||||
Thanks for helping build an open video ecosystem on nostr!
|
||||
|
||||
## License Model
|
||||
|
||||
- **App** (this repo): GPL-3.0-or-later
|
||||
- **Potential SDKs/embeds** (if split later): Apache-2.0 (to keep integration friction low)
|
||||
|
||||
All contributions to this repository are made under the repository’s license (GPL-3.0-or-later).
|
||||
|
||||
## Developer Certificate of Origin (DCO)
|
||||
|
||||
We use the DCO to keep contributions simple and legally sound. Sign your commits with `-s`:
|
||||
|
||||
```bash
|
||||
git commit -s -m "feat: add cool thing"
|
||||
```
|
||||
|
||||
This adds a `Signed-off-by: Your Name <[email protected]>` line and certifies you have the right to contribute under the project license.
|
||||
See [https://developercertificate.org/](https://developercertificate.org/) for the full text.
|
||||
|
||||
## (Optional) Contributor License Agreement (CLA)
|
||||
|
||||
If we later introduce **dual-licensing** options (e.g., commercial licenses for enterprises), we may add an *optional* CLA for contributors who want to grant relicense rights. You will be asked to agree explicitly in any PR that requires it. We will keep community use under GPL-3.0-or-later.
|
||||
|
||||
## Code Guidelines
|
||||
|
||||
* Prefer small, focused PRs.
|
||||
* Add tests where reasonable; keep UI accessible.
|
||||
* Follow existing formatting/lint rules.
|
||||
* Update docs and user-facing text when behavior changes.
|
||||
|
||||
## Security
|
||||
|
||||
If you find a vulnerability, please email **[[email protected]](mailto:[email protected])**. We’ll coordinate a responsible disclosure window before public release.
|
||||
|
||||
## Trademark
|
||||
|
||||
The **bitvid** name and logos are trademarks (see `TRADEMARKS.md`). Don’t use them in ways that imply official sponsorship or confuse users. “Powered by bitvid” attribution is encouraged.
|
||||
@@ -0,0 +1,693 @@
|
||||
bitvid — decentralized, client-side video app for the nostr ecosystem
|
||||
Copyright (c) 2025 Adam Malin
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
PASTE THE FULL OFFICIAL GPLv3 LICENSE TEXT BELOW THIS LINE
|
||||
(Use the canonical text from https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of
|
||||
a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same
|
||||
work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey,
|
||||
without conditions so long as your license otherwise remains in force.
|
||||
You may convey covered works to others for the sole purpose of having
|
||||
them make modifications exclusively for you, or provide you with
|
||||
facilities for running those works, provided that you comply with the
|
||||
terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for
|
||||
you must do so exclusively on your behalf, under your direction and
|
||||
control, on terms that prohibit them from making any copies of your
|
||||
copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes
|
||||
it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article 11
|
||||
of the WIPO copyright treaty adopted on 20 December 1996, or similar
|
||||
laws prohibiting or restricting circumvention of such measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
Corresponding Source under the terms of this License, in one of these
|
||||
ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms of
|
||||
that license document, provided that the further restriction does not
|
||||
survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the additional
|
||||
terms that apply to those files, or a notice indicating where to find
|
||||
the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions; the above
|
||||
requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run a
|
||||
copy of the Program. Ancillary propagation of a covered work occurring
|
||||
solely as a consequence of using peer-to-peer transmission to receive a
|
||||
copy likewise does not require acceptance. However, nothing other than
|
||||
this License grants you permission to propagate or modify any covered
|
||||
work. These actions infringe copyright if you do not accept this
|
||||
License. Therefore, by modifying or propagating a covered work, you
|
||||
indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone to
|
||||
copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is in
|
||||
the business of distributing software, under which you make payment to
|
||||
the third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties who
|
||||
would receive the covered work from you, a discriminatory patent
|
||||
license (a) in connection with copies of the covered work conveyed by
|
||||
you (or copies made from those copies), or (b) primarily for and in
|
||||
connection with specific products or compilations that contain the
|
||||
covered work, unless you entered into that arrangement, or that patent
|
||||
license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions
|
||||
of the GNU General Public License can be used, that proxy's public
|
||||
statement of acceptance of a version permanently authorizes you to choose
|
||||
that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
bitvid Copyright (C) 2025 Adam Malin
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w`.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c` for details.
|
||||
|
||||
The hypothetical commands `show w` and `show c` should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -10,8 +10,15 @@
|
||||
|
||||
## Features
|
||||
|
||||
- **Decentralized Sharing**: Video sharing without central servers.
|
||||
- **Private Video Listings**: Share encrypted videos for added privacy.
|
||||
- **Decentralized Sharing**: Publish and browse videos without a centralized server.
|
||||
- **Channel profile pages**: The [channel view](views/channel-profile.html) and `js/channelProfile.js` render banners, playlists, links, and follow stats so every creator has a branded landing page.
|
||||
- **Audience flags**: The [Upload](components/upload-modal.html) and [Edit Video](components/edit-video-modal.html) modals expose **NSFW** and **For Kids** toggles that map straight to note metadata for safer discovery.
|
||||
- **Richer metadata repeaters**: Configure variants, captions, segments, participants, references, and hashtags directly in the Upload modal so posts ship with structured context.
|
||||
- **Cloudflare R2 Uploads**: Publish through the modal’s Cloudflare flow with progress tracking and credential helpers.
|
||||
- **Encrypted Watch History**: Sync viewing activity privately through the NIP-04 encrypted pipeline with local fallbacks.
|
||||
- **Live View Counters**: Subscribe to view events and see totals update in real time on video cards and the video modal.
|
||||
- **Lightning Zaps**: Tip creators with Lightning payments via the Zap controls in the video modal.
|
||||
- **Private Video Listings**: Hide cards from shared grids by flipping the visibility switch in the Edit Video modal after publishing.
|
||||
- **Nostr Integration**: Use Nostr keys for login and interaction.
|
||||
- **WebTorrent Streaming**: Stream videos directly through torrent technology.
|
||||
- **Developer-Friendly**: Open source and customizable for your needs.
|
||||
@@ -27,25 +34,59 @@
|
||||
2. **Login with Nostr**:
|
||||
- Use a compatible Nostr browser extension or manually input your public key.
|
||||
|
||||
### Post a video
|
||||
### Upload a video
|
||||
|
||||
The upload modal enforces **Title + (URL or Magnet)**. Hosted URLs are strongly recommended so the player can start instantly, and you may also add a magnet for resilience.
|
||||
Open the **Upload** modal from the header toolbar and start by selecting the NIP-71 event type that matches your post. The chooser sets the correct Nostr kind and schema defaults so the published note follows [`docs/nostr-event-schemas.md`](docs/nostr-event-schemas.md).
|
||||
|
||||
- **Title**: Required. Matches the in-app validation copy.
|
||||
- **Hosted URL (recommended)**: Supply an HTTPS MP4/WebM/HLS/DASH asset. This is the primary playback path.
|
||||
- **Magnet (optional but encouraged)**: Paste the literal `magnet:?xt=urn:btih:...` string. The form decodes it with `safeDecodeMagnet()` to prevent hash corruption. Never wrap magnets in `new URL()`—store them raw or decode then pass directly to the helpers.
|
||||
- **Web seeds (`ws=`)**: HTTPS only. Point to a file root (e.g., `https://cdn.example.com/video/`). Mixed-content (`http://`) hints are rejected just like the modal message explains.
|
||||
- **Additional sources (`xs=`)**: Recommend adding an HTTPS `.torrent` link so WebTorrent peers can bootstrap faster.
|
||||
- **Trackers**: Bitvid’s browser client only connects to WSS trackers shipped in `js/constants.js`. Do not add UDP or plaintext HTTP trackers to published magnets.
|
||||
- **Private toggle**: Encrypts the listing for invited viewers.
|
||||
Both upload modes expose metadata repeaters for **variants**, **captions/text tracks**, **segments**, **hashtags**, **participants**, and **references**. Use these when you have multiple playback qualities (variants), accessibility tracks (captions), multipart drops (segments), discoverability tags (hashtags), credited collaborators (participants), or cross-posts/threads (references). Skip any repeater you don’t need—the base schema stays valid without them.
|
||||
|
||||
Pick the flow that matches your source material:
|
||||
|
||||
- **Custom (hosted URL or magnet)**: Provide a title plus an HTTPS video URL and/or a WebTorrent magnet. The form requires at least one transport, validates `ws=`/`xs=` hints, keeps magnets raw by decoding them with `safeDecodeMagnet()` before publish, and applies whatever metadata repeaters you configured.
|
||||
- **Cloudflare (R2 direct upload)**: Enter your Cloudflare credentials in the guided form, optionally expand the **Advanced settings** accordion to override pathing or access controls, then drop media files for bitvid to upload through the R2 API. The modal tracks progress, applies your metadata selections, auto-fills the primary `imeta` variant once the upload completes, and publishes the resulting R2 URL back into the note automatically.
|
||||
|
||||
Hosted URLs remain the preferred playback path, and you can still add a magnet or supplemental web seeds when using either mode. Use the **Private** toggle to keep the resulting card visible only to you, and lean on the repeaters whenever you want to surface richer context or alternate assets as outlined in the event schema reference.
|
||||
|
||||
### How playback works
|
||||
|
||||
1. **URL-first**: `playVideoWithFallback({ url, magnet })` attempts the hosted URL immediately. Healthy URLs deliver the full experience without touching P2P resources.
|
||||
2. **WebTorrent fallback**: If the URL probe fails or returns an error status, Bitvid falls back to WebTorrent using the raw magnet. The helpers append HTTPS `ws=`/`xs=` hints so peers seed quickly.
|
||||
2. **WebTorrent fallback**: If the URL probe fails or returns an error status, bitvid falls back to WebTorrent using the raw magnet. The helpers append HTTPS `ws=`/`xs=` hints so peers seed quickly.
|
||||
3. **Safety checks**: Magnets are decoded with `safeDecodeMagnet()` and normalized via `normalizeAndAugmentMagnet()` before reaching WebTorrent. Trackers remain WSS-only to satisfy browser constraints.
|
||||
4. **Operator playbook**: If a deployment causes playback regressions, flip the relevant feature flags back to their default values in `js/constants.js` and redeploy. Capture the rollback steps in AGENTS.md and the PR description so the Main channel stays stable.
|
||||
|
||||
### Watch history & view counts
|
||||
|
||||
- **Encrypted watch history**: When you opt into watch history, bitvid stores entries locally and syncs them as NIP-04 encrypted events so only your keys can decrypt them. The History view and the Profile modal’s History tab hydrate from relays when available and gracefully fall back to the local cache when offline.
|
||||
- **Live view counters**: The `viewCounter` module hydrates totals from relays, subscribes to live updates, and dedupes local plays. Video cards and the video modal update in real time as new view events arrive.
|
||||
|
||||
### Support creators with Lightning
|
||||
|
||||
Click a card to open the video modal and use the **Zap** button (lightning bolt icon) to send Lightning payments. The Zap dialog walks you through selecting an amount, splitting sats, and pushing the payment through your active wallet connection or Nostr Wallet Connect session.
|
||||
|
||||
### Moderation & safety controls
|
||||
|
||||
Operators can tune thresholds and lists from the Profile modal:
|
||||
|
||||
- The **Moderation** tab manages blur and autoplay limits plus the relay-synced mute/ban lists.
|
||||
- Each video card’s **More** menu surfaces per-video actions powered by `videoMenuRenderers.js`, including:
|
||||
- **Repost** to boost the note as a kind 6 event.
|
||||
- **Mirror** to republish the hosted URL and magnet as a kind 1063 event.
|
||||
- **Rebroadcast** to push the existing event to additional relays (see cooldown note below).
|
||||
- **Remove from history** to clear the item from your encrypted watch log.
|
||||
- **Mute/Unmute creator** to control local visibility without severing follows.
|
||||
- **Blacklist creator** for operators who maintain shared deny lists.
|
||||
- **Block creator** to drop their events entirely.
|
||||
- **Report** to issue a NIP-56 moderation report.
|
||||
- When a rebroadcast hits rate limits, the app surfaces cooldown guidance (e.g., “Rebroadcast is cooling down. Try again in 30s.”) so operators know when to retry without spamming relays.
|
||||
|
||||
### Hashtag interests & disinterests
|
||||
|
||||
- The Profile modal’s **Hashtags** tab lets you maintain interests (tags you want surfaced) and disinterests (tags to downrank) with dedicated input fields and per-tag remove actions. Preferences are cached locally so list edits render instantly.
|
||||
- bitvid persists the lists by publishing a replaceable `kind 30015` event (legacy `30005` remains readable) with the identifier `bitvid:tag-preferences`. The `HashtagPreferencesService` normalizes tags, keeps interests and disinterests mutually exclusive, and emits change events so UI stays in sync.【F:js/services/hashtagPreferencesService.js†L206-L276】【F:js/services/hashtagPreferencesService.js†L304-L324】【F:docs/nostr-event-schemas.md†L154-L201】
|
||||
- Legacy `30005` preference events are still fetched and decrypted; saving updates reissues them as `30015`, so convergence happens automatically as viewers adjust their lists. Operators who need a faster rollout can ask affected accounts to re-save (e.g., toggle a tag) to trigger the new publish cycle.【F:js/services/hashtagPreferencesService.js†L360-L470】【F:docs/nostr-event-schemas.md†L196-L201】
|
||||
- Preference payloads are encrypted before leaving the browser. The service attempts NIP-44 v2 first, falls back to NIP-44 or NIP-04 based on the signer/browser capabilities, records the active scheme in the `['encrypted', <scheme>]` tag, and decrypts in the same priority order when loading from relays.【F:js/services/hashtagPreferencesService.js†L520-L657】【F:js/services/hashtagPreferencesService.js†L356-L511】
|
||||
- Published ciphertext is stored on every configured write relay and accepted ones are tracked so operators can diagnose relay failures without exposing the plaintext list.【F:js/services/hashtagPreferencesService.js†L658-L711】
|
||||
|
||||
---
|
||||
|
||||
## For Developers
|
||||
@@ -62,7 +103,6 @@ To run **bitvid** locally:
|
||||
```
|
||||
|
||||
2. Start a local server:
|
||||
|
||||
- Using Python:
|
||||
```bash
|
||||
python -m http.server 8000
|
||||
@@ -73,26 +113,131 @@ To run **bitvid** locally:
|
||||
```
|
||||
|
||||
3. Open the site in your browser:
|
||||
```
|
||||
http://localhost:8000
|
||||
```
|
||||
|
||||
```
|
||||
http://localhost:8000
|
||||
```
|
||||
|
||||
### CSS build pipeline
|
||||
|
||||
Tailwind utilities are generated from `css/tailwind.source.css` using the build
|
||||
script below. Run this whenever you add or remove classes in HTML or JavaScript
|
||||
so the purged output stays in sync:
|
||||
Tailwind utilities are generated from `css/tailwind.source.css` and themed via
|
||||
the shared tokens in `css/tokens.css`. Install dependencies once and lean on the
|
||||
package scripts to keep formatting, linting, and generated output consistent:
|
||||
|
||||
- **Token imports:** `css/tailwind.source.css` imports `css/tokens.css`, and
|
||||
Tailwind consumes that source file directly so utilities inherit the same
|
||||
palette, spacing, and typography primitives across the stack—no
|
||||
`css/style.css` intermediary.
|
||||
- **Core scripts:**
|
||||
|
||||
```bash
|
||||
npm run format # normalize CSS/HTML/JS/MD sources with Prettier + tailwindcss plugin
|
||||
npm run lint:css # enforce design token usage and guard against raw hex colors
|
||||
npm run build # run the Tailwind build locally (output remains gitignored)
|
||||
```
|
||||
|
||||
- **No hard-coded colors:** Follow the token-first rules in `AGENTS.md`—reach
|
||||
for semantic tokens instead of literal HEX/RGB values.
|
||||
- **Theme scopes:** Apply tokens by toggling the `data-theme` attribute on
|
||||
scope wrappers so components stay palette-agnostic.
|
||||
|
||||
```bash
|
||||
npm install # first run only
|
||||
npm run build:css
|
||||
npm install # install Prettier, Stylelint, and Tailwind toolchain
|
||||
npm run format # format CSS/HTML/JS/MD with Prettier + tailwindcss plugin
|
||||
npm run lint # run CSS, hex color, inline-style, design-token, and Tailwind color/bracket guards in one pass
|
||||
npm run lint:css # enforce token usage and forbid raw hex colors
|
||||
npm run lint:inline-styles # fail CI if inline style attributes or element.style usage slip in
|
||||
npm run build # run the Tailwind build (delegates to npm run build:css)
|
||||
npm run build:beacon # bundle torrent/dist assets and re-run the inline-style guard
|
||||
npm run build:beacon:bundle # bundle beacon assets without running the guard (rarely needed)
|
||||
```
|
||||
|
||||
The command compiles Tailwind with `tailwind.config.cjs`, runs it through the
|
||||
PostCSS pipeline defined in `postcss.config.cjs` (for autoprefixing), and emits
|
||||
the purged, minified stylesheet at `css/tailwind.generated.css`. Commit the
|
||||
regenerated file alongside any template changes so deployments pick up the
|
||||
latest styles.
|
||||
#### Admin runbook: regenerate Tailwind styles
|
||||
|
||||
Need to confirm Tailwind picks up token or template changes? Run these
|
||||
commands from the repo root:
|
||||
|
||||
1. Install dependencies (only required after cloning or when packages change):
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Rebuild the Tailwind bundle for local verification (the output stays
|
||||
gitignored and CI/Netlify handle deploy-time generation):
|
||||
|
||||
```bash
|
||||
npm run build:css
|
||||
```
|
||||
|
||||
The generated `css/tailwind.generated.css` artifact is ignored in git. CI
|
||||
workflows and Netlify deploys run `npm run build` to produce the compiled
|
||||
stylesheet during deployment, so source changes are all you need to commit.
|
||||
|
||||
Inline styles are intentionally blocked. `npm run lint:inline-styles` scans HTML
|
||||
and scripts for `style=` attributes, `element.style`, or `style.cssText` usage
|
||||
and will fail CI until offending markup is moved into the shared CSS/token
|
||||
system.
|
||||
|
||||
#### Design token guard
|
||||
|
||||
`npm run lint:tokens` inspects JavaScript **and** `css/tailwind.source.css`
|
||||
for raw `px`/`rem` measurements. Prefer design tokens, Tailwind `theme()` calls,
|
||||
or existing utilities over hard-coded lengths. When unavoidable (for example
|
||||
hairline borders or browser reset quirks) add an explicit allowlist entry in
|
||||
`scripts/check-design-tokens.mjs` so future contributors understand why the
|
||||
constant exists.
|
||||
|
||||
The script now crawls every markup-emitting module under `js/ui`,
|
||||
`js/channelProfile.js`, and `torrent/ui`, so UI tweaks anywhere in the repo
|
||||
benefit from the guard without manual configuration.
|
||||
|
||||
Beacon builds inherit the same rule. `npm run build:beacon` now bundles
|
||||
`torrent/dist` and immediately re-runs the inline-style checker against the
|
||||
fresh output so third-party dependencies cannot sneak inline style mutations
|
||||
into production. Use `npm run build:beacon:bundle` only if you need the raw
|
||||
esbuild output for debugging.
|
||||
|
||||
### Nostr facade migration
|
||||
|
||||
Nostr helpers now ship behind dedicated facades so downstream plugins can pick
|
||||
the entry point that matches their feature set:
|
||||
|
||||
1. **Default client ([NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md)):** `import { nostrClient, requestDefaultExtensionPermissions } from './nostrClientFacade.js';`
|
||||
2. **View counters ([NIP-71](https://github.com/nostr-protocol/nips/blob/master/71.md)):** `import { recordVideoView, subscribeVideoViewEvents } from './nostrViewEventsFacade.js';`
|
||||
3. **Watch-history lists ([NIP-51](https://github.com/nostr-protocol/nips/blob/master/51.md) + [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) encryption):** `import { updateWatchHistoryListWithDefaultClient } from './nostrWatchHistoryFacade.js';`
|
||||
|
||||
`js/nostr.js` remains as a compatibility shim while existing plugins migrate, but
|
||||
it will be removed in a future release. Move any legacy `import { ... } from './nostr.js';`
|
||||
calls over to the facades above so upgrades stay painless.
|
||||
|
||||
The build command compiles Tailwind with `tailwind.config.cjs`, runs it through
|
||||
the PostCSS pipeline defined in `postcss.config.cjs` (for autoprefixing), and
|
||||
emits the purged, minified stylesheet at `css/tailwind.generated.css`. That
|
||||
bundle is generated automatically during CI/Netlify deploys and remains
|
||||
gitignored locally; reference `css/tailwind.generated.css` in templates, but do
|
||||
not commit the compiled file. Avoid vendoring legacy `tailwind.min.css`
|
||||
artifacts now that the deploy pipeline owns the build step.
|
||||
|
||||
### Logo usage
|
||||
|
||||
- Wrap inline logo `<svg>` elements with the `.bv-logo` component class. It
|
||||
seeds `--logo-wordmark-color`, `--logo-accent-color`, and
|
||||
`--logo-background-color` with the palette tokens exported in
|
||||
`css/tokens.css`.
|
||||
- Adjust variants through data attributes instead of inline styles. For
|
||||
example, `data-wordmark="current"` ties the wordmark to the wrapper’s
|
||||
`currentColor`, `[data-variant="inverse"]` swaps in the inverse palette, and
|
||||
`data-accent="current"` makes the accent follow the surrounding text when
|
||||
needed.
|
||||
- Inside the SVG, target elements with `.bv-logo__wordmark`,
|
||||
`.bv-logo__accent`, and `.bv-logo__background` so fills inherit the custom
|
||||
properties seeded by `.bv-logo`. Group paths with `<g>` wrappers where
|
||||
possible to avoid repeating classes on every primitive.
|
||||
- Use Tailwind text utilities (such as `text-text-strong` or `text-text`) on the
|
||||
wrapper to resolve to token-backed colors. Avoid hard-coded hex colors in the
|
||||
SVG; customize the logo via the provided classes when deployments need a
|
||||
different accent or background.
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -100,8 +245,31 @@ latest styles.
|
||||
- Central place for instance-specific values like the Super Admin npub and the
|
||||
default whitelist-only mode setting. Update the documented exports here when
|
||||
preparing a new deployment.
|
||||
- **`config.js`**:
|
||||
- Toggle `isDevMode` for development (`true`) or production (`false`).
|
||||
- Flip `IS_DEV_MODE` to `false` before shipping production builds. The flag
|
||||
flows into `js/config.js` as `isDevMode`, seeds the
|
||||
`window.__BITVID_DEV_MODE__` global for inline scripts, and gates whether the
|
||||
dev logging channel emits to the console. See
|
||||
[`docs/logging.md`](docs/logging.md) for rollout guidance.
|
||||
- Tune `PLATFORM_FEE_PERCENT` (0–100) to keep a percentage of Lightning tips.
|
||||
When the fee is positive, bitvid routes the platform’s split to
|
||||
`PLATFORM_LUD16_OVERRIDE`, so set it to the Lightning address that should
|
||||
receive the sats (or publish a `lud16` on the Super Admin profile). Leave
|
||||
the fee at `0` to pass through every satoshi.
|
||||
- Populate `DEFAULT_RELAY_URLS_OVERRIDE` with WSS URLs to replace the bundled
|
||||
relay bootstrap list. Keep it empty to stick with the upstream defaults.
|
||||
- Customize `THEME_ACCENT_OVERRIDES` with `#RRGGBB` hex strings when you want
|
||||
light or dark mode to use different accent, accent-strong, or
|
||||
accent-pressed colors. Leave the values `null` to inherit the defaults from
|
||||
`css/tokens.css`.
|
||||
- **`js/config.js`**:
|
||||
- Re-exports `isDevMode` (derived from `IS_DEV_MODE`) for modules, publishes
|
||||
`window.__BITVID_DEV_MODE__`, and centralizes the global configuration
|
||||
surface.
|
||||
- **`js/utils/logger.js`**:
|
||||
- Provides the shared `logger` utility. Route user-facing errors through
|
||||
`logger.user` and keep experimental diagnostics on `logger.dev` so operators
|
||||
can quiet development noise in production. Details live in
|
||||
[`docs/logging.md`](docs/logging.md).
|
||||
- **`js/constants.js`**:
|
||||
- Source for browser-safe tracker lists and feature flags that govern WebTorrent behavior.
|
||||
- **Magnet helpers**:
|
||||
@@ -109,7 +277,7 @@ latest styles.
|
||||
|
||||
### Relay compatibility
|
||||
|
||||
Bitvid now requests per-video discussion counts using the NIP-45 `COUNT` frame. The bundled client opens each relay via
|
||||
bitvid now requests per-video discussion counts using the NIP-45 `COUNT` frame. The bundled client opens each relay via
|
||||
`this.pool.ensureRelay(url)` and streams a raw `COUNT` message, so your relay stack must understand that verb (nostr-tools ≥ 1.8
|
||||
or any relay advertising NIP-45 support). Relays that do not implement `COUNT` are skipped gracefully—the UI keeps the count
|
||||
placeholder at “—” and development builds log a warning—so mixed deployments remain usable while you phase in compatible relays.
|
||||
@@ -144,7 +312,7 @@ placeholder at “—” and development builds log a warning—so mixed deploym
|
||||
|
||||
### Contribution Guidelines
|
||||
|
||||
- Follow the [MIT License](https://opensource.org/licenses/MIT).
|
||||
- Follow the [GPL-3.0-or-later License](LICENSE).
|
||||
- Use clear, concise commit messages.
|
||||
- Respect the existing coding style and architecture.
|
||||
- Run the manual QA script (see below) and note results in PR descriptions for changes that affect upload or playback.
|
||||
@@ -153,7 +321,14 @@ placeholder at “—” and development builds log a warning—so mixed deploym
|
||||
|
||||
## Testing
|
||||
|
||||
Use the manual QA checklist before releases or when altering upload/playback flows:
|
||||
Continuous integration runs CSS linting/builds, the Playwright kitchen-sink snapshots, and the Node-based unit tests on every push.
|
||||
Before pushing, run `npm run build` locally so the Tailwind bundle regenerates.
|
||||
Pair that with `npm run test:unit` for application logic changes and
|
||||
`npm run test:visual` for presentation updates to mirror the CI surface area.
|
||||
|
||||
### Manual QA checklist
|
||||
|
||||
Use this checklist before releases or when altering upload/playback flows:
|
||||
|
||||
1. Open the Upload modal, confirm validation (title plus URL or magnet), and test submissions for URL-only, magnet-only, and combined entries.
|
||||
2. Publish a post with both URL and magnet, verify the player streams the hosted URL, then simulate a URL failure and confirm WebTorrent playback.
|
||||
@@ -180,3 +355,9 @@ See [`docs/qa.md`](docs/qa.md) for the copy/paste-friendly checklist we share wi
|
||||
- **Website**: [bitvid.network](https://bitvid.network)
|
||||
- **GitHub**: [PR0M3TH3AN](https://github.com/PR0M3TH3AN)
|
||||
- **Nostr**: [npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe](https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0-or-later. See [LICENSE](./LICENSE).
|
||||
@@ -0,0 +1,21 @@
|
||||
# bitvid Trademarks
|
||||
|
||||
“bitvid”, the word mark, and any associated logos are trademarks of **Adam Malin**.
|
||||
|
||||
## You May
|
||||
|
||||
- Accurately describe unmodified builds as “bitvid”.
|
||||
- Use phrases like “Powered by bitvid”.
|
||||
- Link to https://bitvid.network.
|
||||
|
||||
## You May Not
|
||||
|
||||
- Use “bitvid” or confusingly similar names/logos in a way that implies official affiliation, endorsement, or ownership by the bitvid project.
|
||||
- Launch a service named “bitvid” without permission.
|
||||
- Use our logos in your product branding without permission.
|
||||
|
||||
## Fair Use & Community
|
||||
|
||||
We aim to foster a healthy open ecosystem. Nominative and descriptive uses that do not mislead users are generally fine. When in doubt, ask.
|
||||
|
||||
**Contact:** trademarks@bitvid.network
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M12,0C5.373,0 0,5.373 0,12C0,18.627 5.373,24 12,24C18.627,24 24,18.627 24,12C24,5.373 18.627,0 12,0ZM9.967,16.01C10.531,14.221 11.599,12.078 11.788,11.536C12.061,10.749 11.577,10.4 10.048,11.745L9.708,11.105C11.452,9.208 15.043,8.779 13.821,11.718C13.058,13.553 12.512,14.792 12.2,15.748C11.745,17.141 12.894,16.576 14.019,15.537C14.172,15.787 14.222,15.868 14.375,16.156C11.877,18.534 9.104,18.744 9.967,16.01ZM14.709,7.841C14.177,8.294 13.389,8.284 12.948,7.819C12.507,7.354 12.581,6.611 13.112,6.158C13.644,5.705 14.432,5.716 14.873,6.18C15.312,6.646 15.24,7.389 14.709,7.841Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M12,0C5.373,0 0,5.373 0,12C0,18.627 5.373,24 12,24C18.627,24 24,18.627 24,12C24,5.373 18.627,0 12,0ZM9.967,16.01C10.531,14.221 11.599,12.078 11.788,11.536C12.061,10.749 11.577,10.4 10.048,11.745L9.708,11.105C11.452,9.208 15.043,8.779 13.821,11.718C13.058,13.553 12.512,14.792 12.2,15.748C11.745,17.141 12.894,16.576 14.019,15.537C14.172,15.787 14.222,15.868 14.375,16.156C11.877,18.534 9.104,18.744 9.967,16.01ZM14.709,7.841C14.177,8.294 13.389,8.284 12.948,7.819C12.507,7.354 12.581,6.611 13.112,6.158C13.644,5.705 14.432,5.716 14.873,6.18C15.312,6.646 15.24,7.389 14.709,7.841Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<g transform="matrix(0.0349343,0,0,0.0349343,-55.5414,-28.6416)">
|
||||
<path d="M1878.62,1496.58C1719.5,1470.41 1597.95,1332.09 1597.95,1165.6C1597.95,980.47 1748.25,830.166 1933.38,830.166C2118.51,830.166 2268.82,980.47 2268.82,1165.6C2268.82,1331.86 2147.6,1470.03 1988.8,1496.47L1988.8,1466.79C2131.38,1440.68 2239.59,1315.68 2239.59,1165.6C2239.59,996.604 2102.38,859.399 1933.38,859.399C1764.39,859.399 1627.18,996.604 1627.18,1165.6C1627.18,1315.91 1735.72,1441.07 1878.62,1466.91L1878.62,1496.58ZM1878.62,1400.68C1771.77,1375.85 1692.06,1279.96 1692.06,1165.6C1692.06,1032.41 1800.19,924.274 1933.38,924.274C2066.57,924.274 2174.71,1032.41 2174.71,1165.6C2174.71,1279.73 2095.32,1375.46 1988.8,1400.53L1988.8,1370.37C2079.02,1345.98 2145.48,1263.49 2145.48,1165.6C2145.48,1048.54 2050.44,953.507 1933.38,953.507C1816.33,953.507 1721.29,1048.54 1721.29,1165.6C1721.29,1263.73 1788.07,1346.38 1878.62,1370.55L1878.62,1400.68ZM1878.62,1304.01C1823.55,1282.18 1784.56,1228.41 1784.56,1165.6C1784.56,1083.46 1851.24,1016.77 1933.38,1016.77C2015.52,1016.77 2082.21,1083.46 2082.21,1165.6C2082.21,1228.16 2043.53,1281.76 1988.8,1303.75L1988.8,1271.59C2026.93,1251.59 2052.98,1211.62 2052.98,1165.6C2052.98,1099.6 1999.39,1046.01 1933.38,1046.01C1867.38,1046.01 1813.79,1099.6 1813.79,1165.6C1813.79,1211.88 1840.13,1252.05 1878.62,1271.93L1878.62,1304.01ZM1933.38,1109.82C1964.17,1109.82 1989.16,1134.82 1989.16,1165.6C1989.16,1196.39 1964.17,1221.38 1933.38,1221.38C1902.6,1221.38 1877.6,1196.39 1877.6,1165.6C1877.6,1134.82 1902.6,1109.82 1933.38,1109.82Z" style="fill:rgb(92,111,138);"/>
|
||||
<path d="M1878.62,1496.58C1719.5,1470.41 1597.95,1332.09 1597.95,1165.6C1597.95,980.47 1748.25,830.166 1933.38,830.166C2118.51,830.166 2268.82,980.47 2268.82,1165.6C2268.82,1331.86 2147.6,1470.03 1988.8,1496.47L1988.8,1466.79C2131.38,1440.68 2239.59,1315.68 2239.59,1165.6C2239.59,996.604 2102.38,859.399 1933.38,859.399C1764.39,859.399 1627.18,996.604 1627.18,1165.6C1627.18,1315.91 1735.72,1441.07 1878.62,1466.91L1878.62,1496.58ZM1878.62,1400.68C1771.77,1375.85 1692.06,1279.96 1692.06,1165.6C1692.06,1032.41 1800.19,924.274 1933.38,924.274C2066.57,924.274 2174.71,1032.41 2174.71,1165.6C2174.71,1279.73 2095.32,1375.46 1988.8,1400.53L1988.8,1370.37C2079.02,1345.98 2145.48,1263.49 2145.48,1165.6C2145.48,1048.54 2050.44,953.507 1933.38,953.507C1816.33,953.507 1721.29,1048.54 1721.29,1165.6C1721.29,1263.73 1788.07,1346.38 1878.62,1370.55L1878.62,1400.68ZM1878.62,1304.01C1823.55,1282.18 1784.56,1228.41 1784.56,1165.6C1784.56,1083.46 1851.24,1016.77 1933.38,1016.77C2015.52,1016.77 2082.21,1083.46 2082.21,1165.6C2082.21,1228.16 2043.53,1281.76 1988.8,1303.75L1988.8,1271.59C2026.93,1251.59 2052.98,1211.62 2052.98,1165.6C2052.98,1099.6 1999.39,1046.01 1933.38,1046.01C1867.38,1046.01 1813.79,1099.6 1813.79,1165.6C1813.79,1211.88 1840.13,1252.05 1878.62,1271.93L1878.62,1304.01ZM1933.38,1109.82C1964.17,1109.82 1989.16,1134.82 1989.16,1165.6C1989.16,1196.39 1964.17,1221.38 1933.38,1221.38C1902.6,1221.38 1877.6,1196.39 1877.6,1165.6C1877.6,1134.82 1902.6,1109.82 1933.38,1109.82Z" fill="#5C6F8A"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<g transform="matrix(1.09255,0,0,1.09255,-0.61419,-0.557138)">
|
||||
<path d="M11.546,1.337C17.151,1.337 21.702,5.888 21.702,11.493C21.702,17.099 17.151,21.65 11.546,21.65C5.94,21.65 1.389,17.099 1.389,11.493C1.389,5.888 5.94,1.337 11.546,1.337ZM10.288,13.666C10.735,14.248 11.352,14.54 12.138,14.54C12.878,14.54 13.491,14.29 13.975,13.792C14.46,13.293 14.702,12.697 14.702,12.003C14.702,11.141 14.3,10.526 13.495,10.158C13.924,9.994 14.274,9.743 14.544,9.407C14.814,9.071 14.949,8.687 14.949,8.254C14.949,7.757 14.757,7.325 14.375,6.959C13.992,6.593 13.445,6.409 12.734,6.409C11.929,6.409 11.274,6.652 10.768,7.136C10.262,7.62 9.882,8.454 9.628,9.638L8.143,16.578L9.666,16.578L10.288,13.666ZM11.8,9.445L11.532,10.619C11.611,10.616 11.672,10.614 11.715,10.614C12.741,10.614 13.254,11.043 13.254,11.901C13.254,12.398 13.108,12.773 12.817,13.025C12.525,13.277 12.221,13.403 11.902,13.403C11.566,13.403 11.283,13.287 11.052,13.057C10.822,12.826 10.706,12.491 10.706,12.051C10.706,11.819 10.744,11.524 10.819,11.166L11.162,9.595C11.266,9.112 11.373,8.744 11.484,8.49C11.595,8.236 11.76,8.03 11.98,7.871C12.2,7.712 12.449,7.632 12.728,7.632C12.953,7.632 13.137,7.698 13.278,7.831C13.419,7.963 13.49,8.124 13.49,8.313C13.49,8.592 13.372,8.851 13.136,9.091C12.9,9.33 12.53,9.45 12.026,9.45C11.983,9.45 11.908,9.448 11.8,9.445Z" style="fill:rgb(92,111,138);"/>
|
||||
<path d="M11.546,1.337C17.151,1.337 21.702,5.888 21.702,11.493C21.702,17.099 17.151,21.65 11.546,21.65C5.94,21.65 1.389,17.099 1.389,11.493C1.389,5.888 5.94,1.337 11.546,1.337ZM10.288,13.666C10.735,14.248 11.352,14.54 12.138,14.54C12.878,14.54 13.491,14.29 13.975,13.792C14.46,13.293 14.702,12.697 14.702,12.003C14.702,11.141 14.3,10.526 13.495,10.158C13.924,9.994 14.274,9.743 14.544,9.407C14.814,9.071 14.949,8.687 14.949,8.254C14.949,7.757 14.757,7.325 14.375,6.959C13.992,6.593 13.445,6.409 12.734,6.409C11.929,6.409 11.274,6.652 10.768,7.136C10.262,7.62 9.882,8.454 9.628,9.638L8.143,16.578L9.666,16.578L10.288,13.666ZM11.8,9.445L11.532,10.619C11.611,10.616 11.672,10.614 11.715,10.614C12.741,10.614 13.254,11.043 13.254,11.901C13.254,12.398 13.108,12.773 12.817,13.025C12.525,13.277 12.221,13.403 11.902,13.403C11.566,13.403 11.283,13.287 11.052,13.057C10.822,12.826 10.706,12.491 10.706,12.051C10.706,11.819 10.744,11.524 10.819,11.166L11.162,9.595C11.266,9.112 11.373,8.744 11.484,8.49C11.595,8.236 11.76,8.03 11.98,7.871C12.2,7.712 12.449,7.632 12.728,7.632C12.953,7.632 13.137,7.698 13.278,7.831C13.419,7.963 13.49,8.124 13.49,8.313C13.49,8.592 13.372,8.851 13.136,9.091C12.9,9.33 12.53,9.45 12.026,9.45C11.983,9.45 11.908,9.448 11.8,9.445Z" fill="#5C6F8A"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,33 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<rect class="cls-1" x="7.1" y="7.8" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="18.78" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="29.77" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="40.76" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="51.75" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="62.74" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="73.73" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="84.72" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="95.71" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="7.1" y="7.8" width="5.84" height="5.48" />
|
||||
<rect x="7.1" y="18.78" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="29.77" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="40.76" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="51.75" width="80.88" height="5.48" />
|
||||
<rect x="7.1" y="62.74" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="73.73" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="84.72" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="95.71" width="5.84" height="5.48" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M121.28,25.08v44.54c0,5.67,2.79,8.54,8.22,8.54h11.57c5.51,0,8.3-2.87,8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7,0,13.01,4.47,13.01,13.33v15.01c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33V25.08h5.83Z"/>
|
||||
<path class="cls-2" d="M168.53,25.08v6.26h-5.83v-6.26h5.83ZM168.53,42.64v41.27h-5.83v-41.27h5.83Z"/>
|
||||
<path class="cls-2" d="M191.36,25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82v-17.16h5.83Z"/>
|
||||
<path class="cls-2" d="M212.88,42.24l16.84,35.68,16.6-35.68h6.07l-19.56,41.67h-6.31l-20.04-41.67h6.39Z"/>
|
||||
<path class="cls-2" d="M264.05,25.08v6.26h-5.83v-6.26h5.83ZM264.05,42.24v41.67h-5.83v-41.67h5.83Z"/>
|
||||
<path class="cls-2" d="M311.21,25.08v45.5c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33v-15.01c0-8.86,4.31-13.33,13.01-13.33h17.72v5.83h-16.6c-5.51,0-8.3,2.79-8.3,8.46v13.09c0,5.67,2.79,8.54,8.3,8.54h11.49c5.51,0,8.3-2.87,8.3-8.54V25.08h5.83Z"/>
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M121.28 25.08v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V25.08h5.83Z" />
|
||||
<path d="M168.53 25.08v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M191.36 25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V25.08h5.83Z" />
|
||||
<path d="M212.88 42.24l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M264.05 25.08v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83v-41.67h5.83Z" />
|
||||
<path d="M311.21 25.08v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V25.08h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,33 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #383838;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<rect class="cls-1" x="7.1" y="7.8" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="18.78" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="29.77" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="40.76" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="51.75" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="62.74" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="73.73" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="84.72" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="95.71" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="7.1" y="7.8" width="5.84" height="5.48" />
|
||||
<rect x="7.1" y="18.78" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="29.77" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="40.76" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="51.75" width="80.88" height="5.48" />
|
||||
<rect x="7.1" y="62.74" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="73.73" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="84.72" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="95.71" width="5.84" height="5.48" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M121.28,25.08v44.54c0,5.67,2.79,8.54,8.22,8.54h11.57c5.51,0,8.3-2.87,8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7,0,13.01,4.47,13.01,13.33v15.01c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33V25.08h5.83Z"/>
|
||||
<path class="cls-2" d="M168.53,25.08v6.26h-5.83v-6.26h5.83ZM168.53,42.64v41.27h-5.83v-41.27h5.83Z"/>
|
||||
<path class="cls-2" d="M191.36,25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82v-17.16h5.83Z"/>
|
||||
<path class="cls-2" d="M212.88,42.24l16.84,35.68,16.6-35.68h6.07l-19.56,41.67h-6.31l-20.04-41.67h6.39Z"/>
|
||||
<path class="cls-2" d="M264.05,25.08v6.26h-5.83v-6.26h5.83ZM264.05,42.24v41.67h-5.83v-41.67h5.83Z"/>
|
||||
<path class="cls-2" d="M311.21,25.08v45.5c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33v-15.01c0-8.86,4.31-13.33,13.01-13.33h17.72v5.83h-16.6c-5.51,0-8.3,2.79-8.3,8.46v13.09c0,5.67,2.79,8.54,8.3,8.54h11.49c5.51,0,8.3-2.87,8.3-8.54V25.08h5.83Z"/>
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M121.28 25.08v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V25.08h5.83Z" />
|
||||
<path d="M168.53 25.08v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M191.36 25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V25.08h5.83Z" />
|
||||
<path d="M212.88 42.24l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M264.05 25.08v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83v-41.67h5.83Z" />
|
||||
<path d="M311.21 25.08v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V25.08h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M13.744,8C13.744,8 15.266,0 10.409,0L2,0L2,24L22,24L22,11C22,7.581 16.753,7.255 13.744,8ZM18,19L6,19L6,18L18,18L18,19ZM18,16L6,16L6,15L18,15L18,16ZM18,13L6,13L6,12L18,12L18,13ZM14.568,0.075C16.77,1.249 20.506,4.958 22,6.956C20.714,6.056 17.956,5.299 15.909,5.777C16.131,4.309 15.724,1.243 14.568,0.075Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M13.744,8C13.744,8 15.266,0 10.409,0L2,0L2,24L22,24L22,11C22,7.581 16.753,7.255 13.744,8ZM18,19L6,19L6,18L18,18L18,19ZM18,16L6,16L6,15L18,15L18,16ZM18,13L6,13L6,12L18,12L18,13ZM14.568,0.075C16.77,1.249 20.506,4.958 22,6.956C20.714,6.056 17.956,5.299 15.909,5.777C16.131,4.309 15.724,1.243 14.568,0.075Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 817 B After Width: | Height: | Size: 803 B |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M12,0C5.373,0 0,5.373 0,12C0,18.627 5.373,24 12,24C18.627,24 24,18.627 24,12C24,5.373 18.627,0 12,0ZM12,22C8.877,22 6.086,20.559 4.251,18.31C4.51,17.722 5.034,17.315 6.118,17.064C8.362,16.546 10.577,16.083 9.511,14.119C6.356,8.299 8.612,5 12,5C15.322,5 17.634,8.177 14.489,14.119C13.454,16.071 15.589,16.535 17.882,17.064C18.964,17.314 19.492,17.719 19.753,18.305C17.917,20.558 15.125,22 12,22Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M12,0C5.373,0 0,5.373 0,12C0,18.627 5.373,24 12,24C18.627,24 24,18.627 24,12C24,5.373 18.627,0 12,0ZM12,22C8.877,22 6.086,20.559 4.251,18.31C4.51,17.722 5.034,17.315 6.118,17.064C8.362,16.546 10.577,16.083 9.511,14.119C6.356,8.299 8.612,5 12,5C15.322,5 17.634,8.177 14.489,14.119C13.454,16.071 15.589,16.535 17.882,17.064C18.964,17.314 19.492,17.719 19.753,18.305C17.917,20.558 15.125,22 12,22Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 909 B After Width: | Height: | Size: 895 B |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M12,0C18.623,0 24,5.377 24,12C24,18.623 18.623,24 12,24C5.377,24 0,18.623 0,12C0,5.377 5.377,0 12,0M14.557,16L9.442,16C9.988,18.46 10.883,20.114 12,21.744C13.194,20.003 14.041,18.334 14.557,16M7.4,16L2.833,16C4.069,18.825 6.537,20.972 9.588,21.716C8.54,19.983 7.805,18.058 7.4,16M21.167,16L16.6,16C16.209,17.988 15.505,19.887 14.425,21.694C17.437,20.931 19.942,18.799 21.167,16M7.162,10L2.2,10C1.933,11.313 1.933,12.685 2.2,14L7.115,14C6.996,12.671 7.014,11.328 7.162,10M14.823,10L9.176,10C9.011,11.326 8.991,12.672 9.123,14L14.876,14C15.009,12.672 14.987,11.327 14.823,10M21.8,10L16.837,10C16.985,11.328 17.003,12.671 16.885,14L21.8,14C22.06,12.715 22.073,11.352 21.8,10M9.644,2.271C6.567,3.003 4.077,5.157 2.833,8L7.486,8C7.921,5.958 8.664,4.015 9.644,2.271M11.999,2.223C10.91,3.993 10.089,5.676 9.536,8L14.463,8C13.929,5.754 13.126,4.052 11.999,2.223M14.367,2.292C15.38,4.104 16.1,6.052 16.513,8L21.167,8C19.935,5.184 17.405,3.042 14.367,2.292" style="fill:rgb(92,111,138);"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M12,0C18.623,0 24,5.377 24,12C24,18.623 18.623,24 12,24C5.377,24 0,18.623 0,12C0,5.377 5.377,0 12,0M14.557,16L9.442,16C9.988,18.46 10.883,20.114 12,21.744C13.194,20.003 14.041,18.334 14.557,16M7.4,16L2.833,16C4.069,18.825 6.537,20.972 9.588,21.716C8.54,19.983 7.805,18.058 7.4,16M21.167,16L16.6,16C16.209,17.988 15.505,19.887 14.425,21.694C17.437,20.931 19.942,18.799 21.167,16M7.162,10L2.2,10C1.933,11.313 1.933,12.685 2.2,14L7.115,14C6.996,12.671 7.014,11.328 7.162,10M14.823,10L9.176,10C9.011,11.326 8.991,12.672 9.123,14L14.876,14C15.009,12.672 14.987,11.327 14.823,10M21.8,10L16.837,10C16.985,11.328 17.003,12.671 16.885,14L21.8,14C22.06,12.715 22.073,11.352 21.8,10M9.644,2.271C6.567,3.003 4.077,5.157 2.833,8L7.486,8C7.921,5.958 8.664,4.015 9.644,2.271M11.999,2.223C10.91,3.993 10.089,5.676 9.536,8L14.463,8C13.929,5.754 13.126,4.052 11.999,2.223M14.367,2.292C15.38,4.104 16.1,6.052 16.513,8L21.167,8C19.935,5.184 17.405,3.042 14.367,2.292" fill="#5C6F8A"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M12,0C18.623,0 24,5.377 24,12C24,18.623 18.623,24 12,24C5.377,24 0,18.623 0,12C0,5.377 5.377,0 12,0ZM12,2C17.519,2 22,6.481 22,12C22,17.519 17.519,22 12,22C6.481,22 2,17.519 2,12C2,6.481 6.481,2 12,2ZM13.476,14.955C14.464,14.55 15.233,13.744 15.592,12.739L18,6L11.328,8.387C10.322,8.747 9.517,9.518 9.112,10.506L6.047,18L13.476,14.955ZM13.354,10.669C13.905,11.22 13.905,12.115 13.354,12.665C12.803,13.216 11.909,13.216 11.358,12.665C10.807,12.115 10.807,11.22 11.358,10.669C11.909,10.118 12.803,10.118 13.354,10.669Z" style="fill:rgb(92,111,138);"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M12,0C18.623,0 24,5.377 24,12C24,18.623 18.623,24 12,24C5.377,24 0,18.623 0,12C0,5.377 5.377,0 12,0ZM12,2C17.519,2 22,6.481 22,12C22,17.519 17.519,22 12,22C6.481,22 2,17.519 2,12C2,6.481 6.481,2 12,2ZM13.476,14.955C14.464,14.55 15.233,13.744 15.592,12.739L18,6L11.328,8.387C10.322,8.747 9.517,9.518 9.112,10.506L6.047,18L13.476,14.955ZM13.354,10.669C13.905,11.22 13.905,12.115 13.354,12.665C12.803,13.216 11.909,13.216 11.358,12.665C10.807,12.115 10.807,11.22 11.358,10.669C11.909,10.118 12.803,10.118 13.354,10.669Z" fill="#5C6F8A"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1013 B After Width: | Height: | Size: 997 B |
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<g id="iconmonstr-weather-64.svg" transform="matrix(1,0,0,1,12,12)">
|
||||
<g id="iconmonstr-filter-down-lined.svg" transform="matrix(1,0,0,1,-12,-12)">
|
||||
<path d="M15.344,17.778C15.344,17.364 15.008,17.028 14.594,17.028L9.434,17.028C9.02,17.028 8.684,17.364 8.684,17.778C8.684,18.192 9.02,18.528 9.434,18.528L14.594,18.528C15.008,18.528 15.344,18.192 15.344,17.778ZM17.55,13.778C17.55,13.364 17.214,13.028 16.8,13.028L7.204,13.028C6.79,13.028 6.454,13.364 6.454,13.778C6.454,14.192 6.79,14.528 7.204,14.528L16.8,14.528C17.214,14.528 17.55,14.192 17.55,13.778ZM20,9.778C20,9.364 19.664,9.028 19.25,9.028L4.75,9.028C4.336,9.028 4,9.364 4,9.778C4,10.192 4.336,10.528 4.75,10.528L19.25,10.528C19.664,10.528 20,10.192 20,9.778ZM22,5.778C22,5.364 21.664,5.028 21.25,5.028L2.75,5.028C2.336,5.028 2,5.364 2,5.778C2,6.192 2.336,6.528 2.75,6.528L21.25,6.528C21.664,6.528 22,6.192 22,5.778Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<path d="M15.344,17.778C15.344,17.364 15.008,17.028 14.594,17.028L9.434,17.028C9.02,17.028 8.684,17.364 8.684,17.778C8.684,18.192 9.02,18.528 9.434,18.528L14.594,18.528C15.008,18.528 15.344,18.192 15.344,17.778ZM17.55,13.778C17.55,13.364 17.214,13.028 16.8,13.028L7.204,13.028C6.79,13.028 6.454,13.364 6.454,13.778C6.454,14.192 6.79,14.528 7.204,14.528L16.8,14.528C17.214,14.528 17.55,14.192 17.55,13.778ZM20,9.778C20,9.364 19.664,9.028 19.25,9.028L4.75,9.028C4.336,9.028 4,9.364 4,9.778C4,10.192 4.336,10.528 4.75,10.528L19.25,10.528C19.664,10.528 20,10.192 20,9.778ZM22,5.778C22,5.364 21.664,5.028 21.25,5.028L2.75,5.028C2.336,5.028 2,5.364 2,5.778C2,6.192 2.336,6.528 2.75,6.528L21.25,6.528C21.664,6.528 22,6.192 22,5.778Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</g>
|
||||
<g id="iconmonstr-filter-down-lined.svg1" serif:id="iconmonstr-filter-down-lined.svg">
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M5 5a2 2 0 1 1 4 0h9a1 1 0 1 1 0 2H9a2 2 0 1 1-4 0H4a1 1 0 1 1 0-2h1Zm10 6a2 2 0 1 1 4 0h1a1 1 0 1 1 0 2h-1a2 2 0 1 1-4 0H4a1 1 0 1 1 0-2h11Zm-6 7a2 2 0 1 1 4 0h6a1 1 0 1 1 0 2h-6a2 2 0 1 1-4 0H4a1 1 0 1 1 0-2h5Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 316 B |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M6.676,9.18C5.25,9.171 3.459,9.944 2.093,11.31C1.572,11.831 1.114,12.439 0.76,13.122C1.992,12.189 3.307,11.897 4.846,12.761C5.299,11.562 5.902,10.343 6.676,9.18ZM14.83,17.323C13.566,18.149 12.324,18.745 11.249,19.165C12.112,20.705 11.82,22.018 10.888,23.25C11.572,22.897 12.179,22.438 12.7,21.916C14.07,20.547 14.844,18.751 14.83,17.323ZM19.957,4.035C19.613,4.011 19.276,4 18.946,4C11.777,4 7.697,9.465 6.213,13.86L10.152,17.8C14.677,16.18 20,12.251 20,5.158C20,4.792 19.986,4.418 19.957,4.035ZM11.717,12.293C11.391,11.968 11.391,11.44 11.717,11.115C12.042,10.789 12.57,10.789 12.895,11.115C13.221,11.441 13.221,11.968 12.895,12.293C12.569,12.619 12.042,12.619 11.717,12.293ZM14.073,9.937C13.422,9.287 13.422,8.231 14.073,7.58C14.724,6.929 15.779,6.929 16.429,7.58C17.08,8.231 17.08,9.286 16.429,9.937C15.779,10.587 14.725,10.587 14.073,9.937ZM1.641,20.315L0.886,19.56L5.226,15.237L5.981,15.992L1.641,20.315ZM5.79,21.862L5.035,21.107L8.064,18.053L8.819,18.808L5.79,21.862ZM0.755,24L-0,23.245L5.373,17.881L6.129,18.636L0.755,24ZM21.838,9.709C21.65,10.327 21.165,10.811 20.547,11C21.165,11.188 21.65,11.672 21.838,12.291C22.027,11.672 22.511,11.188 23.129,11C22.511,10.812 22.027,10.328 21.838,9.709ZM7.183,3.205C6.936,4.015 6.302,4.648 5.493,4.895C6.303,5.142 6.936,5.776 7.183,6.585C7.431,5.776 8.064,5.142 8.873,4.895C8.063,4.648 7.431,4.015 7.183,3.205ZM5.356,0C5.157,0.649 4.65,1.157 4,1.355C4.65,1.554 5.157,2.062 5.356,2.71C5.554,2.061 6.062,1.553 6.71,1.355C6.062,1.157 5.555,0.649 5.356,0ZM10.743,0C10.427,1.035 9.616,1.846 8.58,2.163C9.616,2.479 10.427,3.289 10.743,4.326C11.059,3.29 11.87,2.48 12.905,2.163C11.87,1.846 11.06,1.035 10.743,0ZM21.838,13.64C21.522,14.676 20.711,15.486 19.675,15.803C20.711,16.119 21.522,16.965 21.838,18C22.154,16.964 22.965,16.119 24,15.803C22.965,15.486 22.154,14.676 21.838,13.64Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M6.676,9.18C5.25,9.171 3.459,9.944 2.093,11.31C1.572,11.831 1.114,12.439 0.76,13.122C1.992,12.189 3.307,11.897 4.846,12.761C5.299,11.562 5.902,10.343 6.676,9.18ZM14.83,17.323C13.566,18.149 12.324,18.745 11.249,19.165C12.112,20.705 11.82,22.018 10.888,23.25C11.572,22.897 12.179,22.438 12.7,21.916C14.07,20.547 14.844,18.751 14.83,17.323ZM19.957,4.035C19.613,4.011 19.276,4 18.946,4C11.777,4 7.697,9.465 6.213,13.86L10.152,17.8C14.677,16.18 20,12.251 20,5.158C20,4.792 19.986,4.418 19.957,4.035ZM11.717,12.293C11.391,11.968 11.391,11.44 11.717,11.115C12.042,10.789 12.57,10.789 12.895,11.115C13.221,11.441 13.221,11.968 12.895,12.293C12.569,12.619 12.042,12.619 11.717,12.293ZM14.073,9.937C13.422,9.287 13.422,8.231 14.073,7.58C14.724,6.929 15.779,6.929 16.429,7.58C17.08,8.231 17.08,9.286 16.429,9.937C15.779,10.587 14.725,10.587 14.073,9.937ZM1.641,20.315L0.886,19.56L5.226,15.237L5.981,15.992L1.641,20.315ZM5.79,21.862L5.035,21.107L8.064,18.053L8.819,18.808L5.79,21.862ZM0.755,24L-0,23.245L5.373,17.881L6.129,18.636L0.755,24ZM21.838,9.709C21.65,10.327 21.165,10.811 20.547,11C21.165,11.188 21.65,11.672 21.838,12.291C22.027,11.672 22.511,11.188 23.129,11C22.511,10.812 22.027,10.328 21.838,9.709ZM7.183,3.205C6.936,4.015 6.302,4.648 5.493,4.895C6.303,5.142 6.936,5.776 7.183,6.585C7.431,5.776 8.064,5.142 8.873,4.895C8.063,4.648 7.431,4.015 7.183,3.205ZM5.356,0C5.157,0.649 4.65,1.157 4,1.355C4.65,1.554 5.157,2.062 5.356,2.71C5.554,2.061 6.062,1.553 6.71,1.355C6.062,1.157 5.555,0.649 5.356,0ZM10.743,0C10.427,1.035 9.616,1.846 8.58,2.163C9.616,2.479 10.427,3.289 10.743,4.326C11.059,3.29 11.87,2.48 12.905,2.163C11.87,1.846 11.06,1.035 10.743,0ZM21.838,13.64C21.522,14.676 20.711,15.486 19.675,15.803C20.711,16.119 21.522,16.965 21.838,18C22.154,16.964 22.965,16.119 24,15.803C22.965,15.486 22.154,14.676 21.838,13.64Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M12,0C5.374,0 0,5.373 0,12C0,17.302 3.438,21.8 8.207,23.387C8.806,23.498 9,23.126 9,22.81L9,20.576C5.662,21.302 4.967,19.16 4.967,19.16C4.421,17.773 3.634,17.404 3.634,17.404C2.545,16.659 3.717,16.675 3.717,16.675C4.922,16.759 5.556,17.912 5.556,17.912C6.626,19.746 8.363,19.216 9.048,18.909C9.155,18.134 9.466,17.604 9.81,17.305C7.145,17 4.343,15.971 4.343,11.374C4.343,10.063 4.812,8.993 5.579,8.153C5.455,7.85 5.044,6.629 5.696,4.977C5.696,4.977 6.704,4.655 8.997,6.207C9.954,5.941 10.98,5.808 12,5.803C13.02,5.808 14.047,5.941 15.006,6.207C17.297,4.655 18.303,4.977 18.303,4.977C18.956,6.63 18.545,7.851 18.421,8.153C19.191,8.993 19.656,10.064 19.656,11.374C19.656,15.983 16.849,16.998 14.177,17.295C14.607,17.667 15,18.397 15,19.517L15,22.81C15,23.129 15.192,23.504 15.801,23.386C20.566,21.797 24,17.3 24,12C24,5.373 18.627,0 12,0Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M12,0C5.374,0 0,5.373 0,12C0,17.302 3.438,21.8 8.207,23.387C8.806,23.498 9,23.126 9,22.81L9,20.576C5.662,21.302 4.967,19.16 4.967,19.16C4.421,17.773 3.634,17.404 3.634,17.404C2.545,16.659 3.717,16.675 3.717,16.675C4.922,16.759 5.556,17.912 5.556,17.912C6.626,19.746 8.363,19.216 9.048,18.909C9.155,18.134 9.466,17.604 9.81,17.305C7.145,17 4.343,15.971 4.343,11.374C4.343,10.063 4.812,8.993 5.579,8.153C5.455,7.85 5.044,6.629 5.696,4.977C5.696,4.977 6.704,4.655 8.997,6.207C9.954,5.941 10.98,5.808 12,5.803C13.02,5.808 14.047,5.941 15.006,6.207C17.297,4.655 18.303,4.977 18.303,4.977C18.956,6.63 18.545,7.851 18.421,8.153C19.191,8.993 19.656,10.064 19.656,11.374C19.656,15.983 16.849,16.998 14.177,17.295C14.607,17.667 15,18.397 15,19.517L15,22.81C15,23.129 15.192,23.504 15.801,23.386C20.566,21.797 24,17.3 24,12C24,5.373 18.627,0 12,0Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M23.334,11.96C22.621,11.234 22.462,10.131 22.941,9.233C23.283,8.593 23.307,7.832 23.005,7.171C22.704,6.511 22.112,6.029 21.404,5.869C20.413,5.644 19.682,4.802 19.601,3.788C19.542,3.065 19.15,2.41 18.539,2.018C17.93,1.625 17.172,1.54 16.489,1.789C15.533,2.136 14.463,1.821 13.847,1.013C13.407,0.437 12.723,0.098 11.997,0.098C11.272,0.098 10.588,0.437 10.148,1.013C9.535,1.822 8.465,2.137 7.509,1.79C6.827,1.542 6.069,1.627 5.459,2.019C4.849,2.411 4.456,3.066 4.398,3.789C4.316,4.803 3.586,5.646 2.595,5.87C1.887,6.03 1.295,6.512 0.994,7.172C0.693,7.832 0.717,8.594 1.059,9.233C1.538,10.13 1.379,11.234 0.667,11.96C0.158,12.477 -0.08,13.202 0.023,13.92C0.126,14.638 0.559,15.267 1.193,15.62C2.081,16.115 2.545,17.13 2.337,18.125C2.19,18.835 2.381,19.573 2.856,20.121C3.332,20.67 4.036,20.965 4.758,20.919C5.774,20.856 6.711,21.459 7.075,22.408C7.334,23.086 7.895,23.603 8.592,23.807C9.287,24.011 10.039,23.879 10.623,23.45C11.442,22.847 12.559,22.847 13.377,23.45C13.961,23.88 14.713,24.012 15.408,23.807C16.105,23.603 16.666,23.085 16.926,22.408C17.289,21.459 18.227,20.855 19.242,20.919C19.966,20.965 20.669,20.67 21.144,20.121C21.619,19.573 21.811,18.835 21.663,18.125C21.456,17.13 21.919,16.115 22.808,15.62C23.441,15.266 23.873,14.638 23.977,13.92C24.081,13.202 23.842,12.477 23.334,11.96ZM10.75,17.39L6.25,13.026L8.107,11.169L10.75,13.675L16.393,7.891L18.25,9.748L10.75,17.39Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M23.334,11.96C22.621,11.234 22.462,10.131 22.941,9.233C23.283,8.593 23.307,7.832 23.005,7.171C22.704,6.511 22.112,6.029 21.404,5.869C20.413,5.644 19.682,4.802 19.601,3.788C19.542,3.065 19.15,2.41 18.539,2.018C17.93,1.625 17.172,1.54 16.489,1.789C15.533,2.136 14.463,1.821 13.847,1.013C13.407,0.437 12.723,0.098 11.997,0.098C11.272,0.098 10.588,0.437 10.148,1.013C9.535,1.822 8.465,2.137 7.509,1.79C6.827,1.542 6.069,1.627 5.459,2.019C4.849,2.411 4.456,3.066 4.398,3.789C4.316,4.803 3.586,5.646 2.595,5.87C1.887,6.03 1.295,6.512 0.994,7.172C0.693,7.832 0.717,8.594 1.059,9.233C1.538,10.13 1.379,11.234 0.667,11.96C0.158,12.477 -0.08,13.202 0.023,13.92C0.126,14.638 0.559,15.267 1.193,15.62C2.081,16.115 2.545,17.13 2.337,18.125C2.19,18.835 2.381,19.573 2.856,20.121C3.332,20.67 4.036,20.965 4.758,20.919C5.774,20.856 6.711,21.459 7.075,22.408C7.334,23.086 7.895,23.603 8.592,23.807C9.287,24.011 10.039,23.879 10.623,23.45C11.442,22.847 12.559,22.847 13.377,23.45C13.961,23.88 14.713,24.012 15.408,23.807C16.105,23.603 16.666,23.085 16.926,22.408C17.289,21.459 18.227,20.855 19.242,20.919C19.966,20.965 20.669,20.67 21.144,20.121C21.619,19.573 21.811,18.835 21.663,18.125C21.456,17.13 21.919,16.115 22.808,15.62C23.441,15.266 23.873,14.638 23.977,13.92C24.081,13.202 23.842,12.477 23.334,11.96ZM10.75,17.39L6.25,13.026L8.107,11.169L10.75,13.675L16.393,7.891L18.25,9.748L10.75,17.39Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -9,13 +9,13 @@
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xml:space="preserve"
|
||||
xmlns:serif="http://www.serif.com/"
|
||||
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
|
||||
fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,12,12)">
|
||||
<g transform="matrix(1,0,0,1,-12,-12)">
|
||||
<path
|
||||
d="M12,2C6.486,2 2,6.486 2,12C2,17.514 6.486,22 12,22C17.514,22 22,17.514 22,12C22,6.486 17.514,2 12,2ZM12,4C16.411,4 20,7.589 20,12C20,16.411 16.411,20 12,20C7.589,20 4,16.411 4,12C4,7.589 7.589,4 12,4ZM11,7C10.448,7 10,7.448 10,8L10,13C10,13.266 10.105,13.52 10.293,13.707L13.293,16.707C13.683,17.098 14.317,17.098 14.707,16.707C15.098,16.317 15.098,15.683 14.707,15.293L12,12.586L12,8C12,7.448 11.552,7 11,7Z"
|
||||
style="fill:rgb(92,111,138);fill-rule:nonzero;"
|
||||
fill="#5C6F8A" fill-rule="nonzero"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M12,6.453L21,14.828L21,24L15,24L15,18L9,18L9,24L3,24L3,14.828L12,6.453ZM24,12.148L12,1L0,12.133L1.361,13.598L12,3.73L22.639,13.613L24,12.148Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M12,6.453L21,14.828L21,24L15,24L15,18L9,18L9,24L3,24L3,14.828L12,6.453ZM24,12.148L12,1L0,12.133L1.361,13.598L12,3.73L22.639,13.613L24,12.148Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 656 B After Width: | Height: | Size: 642 B |
@@ -1,13 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<g id="iconmonstr-weather-64.svg" transform="matrix(1,0,0,1,12,12)">
|
||||
<g id="iconmonstr-whats-hot-1.svg" transform="matrix(1,0,0,1,-12,-12)">
|
||||
<clipPath id="_clip1">
|
||||
<rect x="0" y="0" width="24" height="24"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip1)">
|
||||
<path d="M8.625,0C9.235,7.189 3,9.664 3,15.996C3,20.297 6.069,23.968 12,24C17.931,24.032 21,19.586 21,15.044C21,10.903 18.938,6.998 15.048,4.57C15.972,7.177 14.742,9.558 13.547,10.378C13.617,7.041 12.422,2.089 8.625,0ZM13.336,13C17.091,16.989 14.785,22 11.769,22C9.934,22 8.99,20.735 9,19.423C9.019,16.99 11.737,16.988 13.336,13Z" style="fill:rgb(92,111,138);"/>
|
||||
<path d="M8.625,0C9.235,7.189 3,9.664 3,15.996C3,20.297 6.069,23.968 12,24C17.931,24.032 21,19.586 21,15.044C21,10.903 18.938,6.998 15.048,4.57C15.972,7.177 14.742,9.558 13.547,10.378C13.617,7.041 12.422,2.089 8.625,0ZM13.336,13C17.091,16.989 14.785,22 11.769,22C9.934,22 8.99,20.735 9,19.423C9.019,16.99 11.737,16.988 13.336,13Z" fill="#5C6F8A"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="iconmonstr-whats-hot-1.svg1" serif:id="iconmonstr-whats-hot-1.svg">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M21,7.702L12.5,12.322L12.5,22C14.067,21.135 18.879,18.483 20.477,17.601C20.8,17.424 21,17.082 21,16.71L21,7.702ZM11.5,12.321L3,7.599L3,16.605C3,16.975 3.197,17.313 3.514,17.492C5.104,18.39 9.93,21.115 11.5,22L11.5,12.321ZM3.421,6.692L12,11.455L20.672,6.742C20.672,6.742 14.041,3.004 12.486,2.128C12.335,2.043 12.167,2 12,2C11.832,2 11.665,2.043 11.514,2.128C9.959,3.004 3.421,6.692 3.421,6.692Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M21,7.702L12.5,12.322L12.5,22C14.067,21.135 18.879,18.483 20.477,17.601C20.8,17.424 21,17.082 21,16.71L21,7.702ZM11.5,12.321L3,7.599L3,16.605C3,16.975 3.197,17.313 3.514,17.492C5.104,18.39 9.93,21.115 11.5,22L11.5,12.321ZM3.421,6.692L12,11.455L20.672,6.742C20.672,6.742 14.041,3.004 12.486,2.128C12.335,2.043 12.167,2 12,2C11.832,2 11.665,2.043 11.514,2.128C9.959,3.004 3.421,6.692 3.421,6.692Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 909 B After Width: | Height: | Size: 895 B |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M21.337,0.458L17.4,4.394C14.951,2.928 11.73,3.25 9.62,5.36L5.359,9.622C3.249,11.732 2.926,14.953 4.391,17.402L0.456,21.337C-0.153,21.946 -0.153,22.934 0.456,23.544C1.065,24.154 2.054,24.153 2.663,23.544L6.598,19.609C9.047,21.074 12.268,20.753 14.378,18.642L18.639,14.381C20.749,12.271 21.072,9.05 19.607,6.6L23.543,2.664C24.152,2.055 24.152,1.067 23.543,0.457C22.934,-0.153 21.946,-0.152 21.337,0.458ZM16.475,12.215L12.214,16.476C11.319,17.37 10.038,17.645 8.904,17.303L10.536,15.671C11.145,15.062 11.145,14.074 10.536,13.464C9.927,12.854 8.938,12.855 8.329,13.464L6.698,15.096C6.355,13.963 6.631,12.681 7.524,11.787L11.785,7.526C12.68,6.632 13.961,6.357 15.095,6.699L13.464,8.329C12.855,8.938 12.855,9.926 13.464,10.536C14.073,11.146 15.062,11.145 15.671,10.536L17.301,8.906C17.644,10.039 17.368,11.321 16.475,12.215Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M21.337,0.458L17.4,4.394C14.951,2.928 11.73,3.25 9.62,5.36L5.359,9.622C3.249,11.732 2.926,14.953 4.391,17.402L0.456,21.337C-0.153,21.946 -0.153,22.934 0.456,23.544C1.065,24.154 2.054,24.153 2.663,23.544L6.598,19.609C9.047,21.074 12.268,20.753 14.378,18.642L18.639,14.381C20.749,12.271 21.072,9.05 19.607,6.6L23.543,2.664C24.152,2.055 24.152,1.067 23.543,0.457C22.934,-0.153 21.946,-0.152 21.337,0.458ZM16.475,12.215L12.214,16.476C11.319,17.37 10.038,17.645 8.904,17.303L10.536,15.671C11.145,15.062 11.145,14.074 10.536,13.464C9.927,12.854 8.938,12.855 8.329,13.464L6.698,15.096C6.355,13.963 6.631,12.681 7.524,11.787L11.785,7.526C12.68,6.632 13.961,6.357 15.095,6.699L13.464,8.329C12.855,8.938 12.855,9.926 13.464,10.536C14.073,11.146 15.062,11.145 15.671,10.536L17.301,8.906C17.644,10.039 17.368,11.321 16.475,12.215Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M22,16.75C22,16.336 21.664,16 21.25,16L2.75,16C2.336,16 2,16.336 2,16.75C2,17.164 2.336,17.5 2.75,17.5L21.25,17.5C21.664,17.5 22,17.164 22,16.75ZM22,11.75C22,11.336 21.664,11 21.25,11L2.75,11C2.336,11 2,11.336 2,11.75C2,12.164 2.336,12.5 2.75,12.5L21.25,12.5C21.664,12.5 22,12.164 22,11.75ZM22,6.75C22,6.336 21.664,6 21.25,6L2.75,6C2.336,6 2,6.336 2,6.75C2,7.164 2.336,7.5 2.75,7.5L21.25,7.5C21.664,7.5 22,7.164 22,6.75Z" style="fill:rgb(17,24,39);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M22,16.75C22,16.336 21.664,16 21.25,16L2.75,16C2.336,16 2,16.336 2,16.75C2,17.164 2.336,17.5 2.75,17.5L21.25,17.5C21.664,17.5 22,17.164 22,16.75ZM22,11.75C22,11.336 21.664,11 21.25,11L2.75,11C2.336,11 2,11.336 2,11.75C2,12.164 2.336,12.5 2.75,12.5L21.25,12.5C21.664,12.5 22,12.164 22,11.75ZM22,6.75C22,6.336 21.664,6 21.25,6L2.75,6C2.336,6 2,6.336 2,6.75C2,7.164 2.336,7.5 2.75,7.5L21.25,7.5C21.664,7.5 22,7.164 22,6.75Z" fill="#111827" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 921 B |
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<g id="Artboard1" transform="matrix(0.217336,0,0,0.428004,4.79827,-2.20936)">
|
||||
<rect x="-22.078" y="5.162" width="110.428" height="56.074" style="fill:none;"/>
|
||||
<rect x="-22.078" y="5.162" width="110.428" height="56.074" fill="none"/>
|
||||
<g id="nostr-head.svg" transform="matrix(0.143477,0,0,0.0728563,33.1364,33.1991)">
|
||||
<g transform="matrix(1,0,0,1,-310,-310)">
|
||||
<path d="M620,270.227L620,597.655C620,609.968 610.081,619.961 597.859,619.961L332.161,619.961C319.938,619.961 310.02,609.968 310.02,597.655L310.02,536.678C311.23,461.931 319.079,390.332 335.558,357.759C345.438,338.168 361.722,327.506 380.427,321.802C415.768,311.102 477.779,318.419 504.099,317.16C504.099,317.16 583.605,320.346 583.605,274.987C583.605,238.48 548.07,241.352 548.07,241.352C508.902,242.374 479.068,239.699 459.738,232.028C427.365,219.203 426.272,195.678 426.155,187.81C424.554,96.934 291.549,86.037 174.359,108.579C46.235,133.127 175.765,318.143 175.765,565.121L175.765,598.088C175.531,610.204 165.807,620 153.702,620L22.142,620C9.919,620 0,610.008 0,597.694L0,31.393C0,19.08 9.919,9.088 22.142,9.088L145.813,9.088C158.036,9.088 167.955,19.08 167.955,31.393C167.955,49.687 188.378,59.876 203.139,49.215C247.617,17.113 304.709,0 368.595,0C511.714,0 619.922,84.031 619.922,270.227L620,270.227ZM382.419,203.782C382.419,177.424 361.214,156.062 335.051,156.062C308.887,156.062 287.683,177.424 287.683,203.782C287.683,230.14 308.887,251.501 335.051,251.501C361.214,251.501 382.419,230.14 382.419,203.782Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<path d="M620,270.227L620,597.655C620,609.968 610.081,619.961 597.859,619.961L332.161,619.961C319.938,619.961 310.02,609.968 310.02,597.655L310.02,536.678C311.23,461.931 319.079,390.332 335.558,357.759C345.438,338.168 361.722,327.506 380.427,321.802C415.768,311.102 477.779,318.419 504.099,317.16C504.099,317.16 583.605,320.346 583.605,274.987C583.605,238.48 548.07,241.352 548.07,241.352C508.902,242.374 479.068,239.699 459.738,232.028C427.365,219.203 426.272,195.678 426.155,187.81C424.554,96.934 291.549,86.037 174.359,108.579C46.235,133.127 175.765,318.143 175.765,565.121L175.765,598.088C175.531,610.204 165.807,620 153.702,620L22.142,620C9.919,620 0,610.008 0,597.694L0,31.393C0,19.08 9.919,9.088 22.142,9.088L145.813,9.088C158.036,9.088 167.955,19.08 167.955,31.393C167.955,49.687 188.378,59.876 203.139,49.215C247.617,17.113 304.709,0 368.595,0C511.714,0 619.922,84.031 619.922,270.227L620,270.227ZM382.419,203.782C382.419,177.424 361.214,156.062 335.051,156.062C308.887,156.062 287.683,177.424 287.683,203.782C287.683,230.14 308.887,251.501 335.051,251.501C361.214,251.501 382.419,230.14 382.419,203.782Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<g id="iconmonstr-weather-64.svg" transform="matrix(1,0,0,1,12,12)">
|
||||
<g transform="matrix(1,0,0,1,-12,-12)">
|
||||
<path d="M11,10L0,10L0,8L11,8C11.552,8 12,7.552 12,7C12,6.448 11.552,6 11,6C10.597,6 10.253,6.242 10.095,6.587L8.346,5.631C8.845,4.666 9.84,4 11,4C14.971,4 14.969,10 11,10ZM18,17C18,15.344 16.656,14 15,14L0,14L0,16L15,16C15.552,16 16,16.448 16,17C16,17.552 15.552,18 15,18C14.597,18 14.253,17.758 14.095,17.413L12.346,18.369C12.845,19.334 13.84,20 15,20C16.656,20 18,18.656 18,17ZM19.014,9.345C19.096,8.592 19.726,8 20.5,8C21.327,8 22,8.673 22,9.5C22,10.327 21.327,11 20.5,11L0,11L0,13L20.5,13C22.432,13 24,11.432 24,9.5C24,7.568 22.432,6 20.5,6C18.876,6 17.523,7.116 17.128,8.617L19.014,9.345Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<path d="M11,10L0,10L0,8L11,8C11.552,8 12,7.552 12,7C12,6.448 11.552,6 11,6C10.597,6 10.253,6.242 10.095,6.587L8.346,5.631C8.845,4.666 9.84,4 11,4C14.971,4 14.969,10 11,10ZM18,17C18,15.344 16.656,14 15,14L0,14L0,16L15,16C15.552,16 16,16.448 16,17C16,17.552 15.552,18 15,18C14.597,18 14.253,17.758 14.095,17.413L12.346,18.369C12.845,19.334 13.84,20 15,20C16.656,20 18,18.656 18,17ZM19.014,9.345C19.096,8.592 19.726,8 20.5,8C21.327,8 22,8.673 22,9.5C22,10.327 21.327,11 20.5,11L0,11L0,13L20.5,13C22.432,13 24,11.432 24,9.5C24,7.568 22.432,6 20.5,6C18.876,6 17.523,7.116 17.128,8.617L19.014,9.345Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M23.961,8.429C23.13,9.411 22.347,10.347 22,12.204L22,18.887L18,21.366L18,12.205C17.653,10.348 16.87,9.412 16.039,8.43C15.131,7.355 14,6.019 14,3.801L14.019,3.456L12,2L6.455,6L0,2L0,20L6.455,24L12,20L17.545,24L24,20L24,8.382L23.961,8.429ZM11,18.255L7,21.14L7,8.073L11,5.187L11,18.255ZM20,0C17.9,0 16,1.702 16,3.801C16,6.922 19.188,7.252 20,12C20.812,7.252 24,6.922 24,3.801C24,1.702 22.1,0 20,0ZM20,5.5C19.172,5.5 18.5,4.829 18.5,4C18.5,3.171 19.172,2.5 20,2.5C20.828,2.5 21.5,3.171 21.5,4C21.5,4.829 20.828,5.5 20,5.5Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M23.961,8.429C23.13,9.411 22.347,10.347 22,12.204L22,18.887L18,21.366L18,12.205C17.653,10.348 16.87,9.412 16.039,8.43C15.131,7.355 14,6.019 14,3.801L14.019,3.456L12,2L6.455,6L0,2L0,20L6.455,24L12,20L17.545,24L24,20L24,8.382L23.961,8.429ZM11,18.255L7,21.14L7,8.073L11,5.187L11,18.255ZM20,0C17.9,0 16,1.702 16,3.801C16,6.922 19.188,7.252 20,12C20.812,7.252 24,6.922 24,3.801C24,1.702 22.1,0 20,0ZM20,5.5C19.172,5.5 18.5,4.829 18.5,4C18.5,3.171 19.172,2.5 20,2.5C20.828,2.5 21.5,3.171 21.5,4C21.5,4.829 20.828,5.5 20,5.5Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1019 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M23.822 20.88l-6.353-6.354c.93-1.465 1.467-3.2 1.467-5.059.001-5.219-4.247-9.467-9.468-9.467s-9.468 4.248-9.468 9.468c0 5.221 4.247 9.469 9.468 9.469 1.768 0 3.421-.487 4.839-1.333l6.396 6.396 3.119-3.12zm-20.294-11.412c0-3.273 2.665-5.938 5.939-5.938 3.275 0 5.94 2.664 5.94 5.938 0 3.275-2.665 5.939-5.94 5.939-3.274 0-5.939-2.664-5.939-5.939z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 472 B |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M21.698,10.658L24,12L11.998,19L0,12L2.301,10.658L11.998,16.316L21.698,10.658ZM11.998,21.315L2.301,15.657L0,17L11.998,24L24,17L21.698,15.658L11.998,21.315ZM24,7L11.998,-0L0,7L11.998,14L24,7Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M21.698,10.658L24,12L11.998,19L0,12L2.301,10.658L11.998,16.316L21.698,10.658ZM11.998,21.315L2.301,15.657L0,17L11.998,24L24,17L21.698,15.658L11.998,21.315ZM24,7L11.998,-0L0,7L11.998,14L24,7Z" fill="white" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 694 B After Width: | Height: | Size: 688 B |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M21.698,10.658L24,12L11.998,19L0,12L2.301,10.658L11.998,16.316L21.698,10.658ZM11.998,21.315L2.301,15.657L0,17L11.998,24L24,17L21.698,15.658L11.998,21.315ZM24,7L11.998,-0L0,7L11.998,14L24,7Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M21.698,10.658L24,12L11.998,19L0,12L2.301,10.658L11.998,16.316L21.698,10.658ZM11.998,21.315L2.301,15.657L0,17L11.998,24L24,17L21.698,15.658L11.998,21.315ZM24,7L11.998,-0L0,7L11.998,14L24,7Z" fill="#5C6F8A" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 690 B |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M24,13.616L24,10.384C22.349,9.797 21.306,9.632 20.781,8.365L20.781,8.364C20.254,7.093 20.881,6.23 21.628,4.657L19.343,2.372C17.782,3.114 16.91,3.747 15.636,3.219L15.635,3.219C14.366,2.693 14.2,1.643 13.616,0L10.384,0C9.802,1.635 9.635,2.692 8.365,3.219L8.364,3.219C7.093,3.747 6.232,3.121 4.657,2.372L2.372,4.657C3.117,6.225 3.747,7.091 3.219,8.364C2.692,9.635 1.635,9.802 0,10.384L0,13.616C1.632,14.196 2.692,14.365 3.219,15.635C3.749,16.917 3.105,17.801 2.372,19.342L4.657,21.628C6.219,20.885 7.091,20.253 8.364,20.781L8.365,20.781C9.635,21.307 9.801,22.36 10.384,24L13.616,24C14.198,22.364 14.366,21.31 15.643,20.778L15.644,20.778C16.906,20.254 17.764,20.879 19.342,21.629L21.627,19.343C20.883,17.78 20.252,16.91 20.779,15.637C21.306,14.366 22.367,14.197 24,13.616ZM12,16C9.791,16 8,14.209 8,12C8,9.791 9.791,8 12,8C14.209,8 16,9.791 16,12C16,14.209 14.209,16 12,16Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||
<path d="M24,13.616L24,10.384C22.349,9.797 21.306,9.632 20.781,8.365L20.781,8.364C20.254,7.093 20.881,6.23 21.628,4.657L19.343,2.372C17.782,3.114 16.91,3.747 15.636,3.219L15.635,3.219C14.366,2.693 14.2,1.643 13.616,0L10.384,0C9.802,1.635 9.635,2.692 8.365,3.219L8.364,3.219C7.093,3.747 6.232,3.121 4.657,2.372L2.372,4.657C3.117,6.225 3.747,7.091 3.219,8.364C2.692,9.635 1.635,9.802 0,10.384L0,13.616C1.632,14.196 2.692,14.365 3.219,15.635C3.749,16.917 3.105,17.801 2.372,19.342L4.657,21.628C6.219,20.885 7.091,20.253 8.364,20.781L8.365,20.781C9.635,21.307 9.801,22.36 10.384,24L13.616,24C14.198,22.364 14.366,21.31 15.643,20.778L15.644,20.778C16.906,20.254 17.764,20.879 19.342,21.629L21.627,19.343C20.883,17.78 20.252,16.91 20.779,15.637C21.306,14.366 22.367,14.197 24,13.616ZM12,16C9.791,16 8,14.209 8,12C8,9.791 9.791,8 12,8C14.209,8 16,9.791 16,12C16,14.209 14.209,16 12,16Z" fill="white" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,7 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.0349343,0,0,0.0349343,-55.5414,-28.6416)">
|
||||
<path d="M1878.62,1496.58C1719.5,1470.41 1597.95,1332.09 1597.95,1165.6C1597.95,980.47 1748.25,830.166 1933.38,830.166C2118.51,830.166 2268.82,980.47 2268.82,1165.6C2268.82,1331.86 2147.6,1470.03 1988.8,1496.47L1988.8,1466.79C2131.38,1440.68 2239.59,1315.68 2239.59,1165.6C2239.59,996.604 2102.38,859.399 1933.38,859.399C1764.39,859.399 1627.18,996.604 1627.18,1165.6C1627.18,1315.91 1735.72,1441.07 1878.62,1466.91L1878.62,1496.58ZM1878.62,1400.68C1771.77,1375.85 1692.06,1279.96 1692.06,1165.6C1692.06,1032.41 1800.19,924.274 1933.38,924.274C2066.57,924.274 2174.71,1032.41 2174.71,1165.6C2174.71,1279.73 2095.32,1375.46 1988.8,1400.53L1988.8,1370.37C2079.02,1345.98 2145.48,1263.49 2145.48,1165.6C2145.48,1048.54 2050.44,953.507 1933.38,953.507C1816.33,953.507 1721.29,1048.54 1721.29,1165.6C1721.29,1263.73 1788.07,1346.38 1878.62,1370.55L1878.62,1400.68ZM1878.62,1304.01C1823.55,1282.18 1784.56,1228.41 1784.56,1165.6C1784.56,1083.46 1851.24,1016.77 1933.38,1016.77C2015.52,1016.77 2082.21,1083.46 2082.21,1165.6C2082.21,1228.16 2043.53,1281.76 1988.8,1303.75L1988.8,1271.59C2026.93,1251.59 2052.98,1211.62 2052.98,1165.6C2052.98,1099.6 1999.39,1046.01 1933.38,1046.01C1867.38,1046.01 1813.79,1099.6 1813.79,1165.6C1813.79,1211.88 1840.13,1252.05 1878.62,1271.93L1878.62,1304.01ZM1933.38,1109.82C1964.17,1109.82 1989.16,1134.82 1989.16,1165.6C1989.16,1196.39 1964.17,1221.38 1933.38,1221.38C1902.6,1221.38 1877.6,1196.39 1877.6,1165.6C1877.6,1134.82 1902.6,1109.82 1933.38,1109.82Z" style="fill:rgb(92,111,138);"/>
|
||||
</g>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<g transform="matrix(0.0349343 0 0 0.0349343 -55.5414 -28.6416)">
|
||||
<path d="M1878.62 1496.58c-159.12-26.17-280.67-164.49-280.67-330.98 0-185.13 150.3-335.43 335.43-335.43s335.44 150.3 335.44 335.43c0 166.26-121.22 304.43-280.02 330.87v-29.68c142.58-26.11 250.79-151.11 250.8-301.19 0-169-137.21-306.21-306.21-306.21s-306.2 137.21-306.2 306.21c0 150.31 108.54 275.47 251.44 301.33v29.73Zm0-95.9c-106.85-24.83-186.56-120.72-186.56-235.08 0-133.19 108.13-241.33 241.32-241.33s241.33 108.14 241.33 241.33c0 113.13-79.39 208.86-185.91 233.93v-30.16c90.22-24.39 156.68-106.88 156.68-204.77 0-117.06-97.12-214.22-214.18-214.22s-214.18 97.12-214.18 214.22c0 97.13 66.78 179.78 157.33 203.95v32.08Zm0-96.67c-55.07-21.83-94.06-75.6-94.06-138.41 0-82.14 66.68-148.83 148.82-148.83s148.83 66.69 148.83 148.83c0 62.56-38.68 116.16-93.41 138.15v-32.16c38.13-20 64.18-59.97 64.18-106 0-66 53.59-119.59 119.6-119.59s119.59 53.59 119.59 119.59c0 46.28-26.34 86.45-64.83 106.33v32.08c-55.07 21.83-94.06 75.6-94.06 138.41 0 82.14 66.68 148.83 148.82 148.83s148.83-66.69 148.83-148.83c0-62.6-38.68-116.2-93.41-138.19v-32.08c38.13-20 64.18-59.97 64.18-106 0-66-53.59-119.59-119.6-119.59s-119.59 53.59-119.59 119.59c0 46.28 26.34 86.45 64.83 106.33v32.08c-55.07 21.83-94.06 75.6-94.06 138.41 0 82.14 66.68 148.83 148.82 148.83s148.83-66.69 148.83-148.83c0-62.56-38.68-116.16-93.41-138.15v-32.16c38.13-20 64.18-59.97 64.18-106 0-66-53.59-119.59-119.6-119.59s-119.59 53.59-119.59 119.59c0 46.28 26.34 86.45 64.83 106.33v32.08Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,29 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #383838;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<rect class="cls-1" x="7.1" y="7.8" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="18.78" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="29.77" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="40.76" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="51.75" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="62.74" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="73.73" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="84.72" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="95.71" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="7.1" y="7.8" width="5.84" height="5.48" />
|
||||
<rect x="7.1" y="18.78" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="29.77" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="40.76" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="51.75" width="80.88" height="5.48" />
|
||||
<rect x="7.1" y="62.74" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="73.73" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="84.72" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="95.71" width="5.84" height="5.48" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-1" d="M121.28,25.08v44.54c0,5.67,2.79,8.54,8.22,8.54h11.57c5.51,0,8.3-2.87,8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7,0,13.01,4.47,13.01,13.33v15.01c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33V25.08h5.83Z"/>
|
||||
<path class="cls-1" d="M168.53,25.08v6.26h-5.83v-6.26h5.83ZM168.53,42.64v41.27h-5.83v-41.27h5.83Z"/>
|
||||
<path class="cls-1" d="M191.36,25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82v-17.16h5.83Z"/>
|
||||
<path class="cls-1" d="M212.88,42.24l16.84,35.68,16.6-35.68h6.07l-19.56,41.67h-6.31l-20.04-41.67h6.39Z"/>
|
||||
<path class="cls-1" d="M264.05,25.08v6.26h-5.83v-6.26h5.83ZM264.05,42.24v41.67h-5.83v-41.67h5.83Z"/>
|
||||
<path class="cls-1" d="M311.21,25.08v45.5c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33v-15.01c0-8.86,4.31-13.33,13.01-13.33h17.72v5.83h-16.6c-5.51,0-8.3,2.79-8.3,8.46v13.09c0,5.67,2.79,8.54,8.3,8.54h11.49c5.51,0,8.3-2.87,8.3-8.54V25.08h5.83Z"/>
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M121.28 25.08v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V25.08h5.83Z" />
|
||||
<path d="M168.53 25.08v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M191.36 25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V25.08h5.83Z" />
|
||||
<path d="M212.88 42.24l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M264.05 25.08v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83v-41.67h5.83Z" />
|
||||
<path d="M311.21 25.08v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V25.08h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,29 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<rect class="cls-1" x="7.1" y="7.8" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="18.78" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="29.77" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="40.76" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="51.75" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="62.74" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="73.73" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="84.72" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="95.71" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="7.1" y="7.8" width="5.84" height="5.48" />
|
||||
<rect x="7.1" y="18.78" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="29.77" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="40.76" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="51.75" width="80.88" height="5.48" />
|
||||
<rect x="7.1" y="62.74" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="73.73" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="84.72" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="95.71" width="5.84" height="5.48" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-1" d="M121.28,25.08v44.54c0,5.67,2.79,8.54,8.22,8.54h11.57c5.51,0,8.3-2.87,8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7,0,13.01,4.47,13.01,13.33v15.01c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33V25.08h5.83Z"/>
|
||||
<path class="cls-1" d="M168.53,25.08v6.26h-5.83v-6.26h5.83ZM168.53,42.64v41.27h-5.83v-41.27h5.83Z"/>
|
||||
<path class="cls-1" d="M191.36,25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82v-17.16h5.83Z"/>
|
||||
<path class="cls-1" d="M212.88,42.24l16.84,35.68,16.6-35.68h6.07l-19.56,41.67h-6.31l-20.04-41.67h6.39Z"/>
|
||||
<path class="cls-1" d="M264.05,25.08v6.26h-5.83v-6.26h5.83ZM264.05,42.24v41.67h-5.83v-41.67h5.83Z"/>
|
||||
<path class="cls-1" d="M311.21,25.08v45.5c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33v-15.01c0-8.86,4.31-13.33,13.01-13.33h17.72v5.83h-16.6c-5.51,0-8.3,2.79-8.3,8.46v13.09c0,5.67,2.79,8.54,8.3,8.54h11.49c5.51,0,8.3-2.87,8.3-8.54V25.08h5.83Z"/>
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M121.28 25.08v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V25.08h5.83Z" />
|
||||
<path d="M168.53 25.08v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M191.36 25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V25.08h5.83Z" />
|
||||
<path d="M212.88 42.24l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M264.05 25.08v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83v-41.67h5.83Z" />
|
||||
<path d="M311.21 25.08v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V25.08h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,33 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #383838;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<rect class="cls-1" x="7.1" y="7.8" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="18.78" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="29.77" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="40.76" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="51.75" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="62.74" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="73.73" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="84.72" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="95.71" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="7.1" y="7.8" width="5.84" height="5.48" />
|
||||
<rect x="7.1" y="18.78" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="29.77" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="40.76" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="51.75" width="80.88" height="5.48" />
|
||||
<rect x="7.1" y="62.74" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="73.73" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="84.72" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="95.71" width="5.84" height="5.48" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M121.28,25.08v44.54c0,5.67,2.79,8.54,8.22,8.54h11.57c5.51,0,8.3-2.87,8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7,0,13.01,4.47,13.01,13.33v15.01c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33V25.08h5.83Z"/>
|
||||
<path class="cls-2" d="M168.53,25.08v6.26h-5.83v-6.26h5.83ZM168.53,42.64v41.27h-5.83v-41.27h5.83Z"/>
|
||||
<path class="cls-2" d="M191.36,25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82v-17.16h5.83Z"/>
|
||||
<path class="cls-2" d="M212.88,42.24l16.84,35.68,16.6-35.68h6.07l-19.56,41.67h-6.31l-20.04-41.67h6.39Z"/>
|
||||
<path class="cls-2" d="M264.05,25.08v6.26h-5.83v-6.26h5.83ZM264.05,42.24v41.67h-5.83v-41.67h5.83Z"/>
|
||||
<path class="cls-2" d="M311.21,25.08v45.5c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33v-15.01c0-8.86,4.31-13.33,13.01-13.33h17.72v5.83h-16.6c-5.51,0-8.3,2.79-8.3,8.46v13.09c0,5.67,2.79,8.54,8.3,8.54h11.49c5.51,0,8.3-2.87,8.3-8.54V25.08h5.83Z"/>
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M121.28 25.08v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V25.08h5.83Z" />
|
||||
<path d="M168.53 25.08v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M191.36 25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V25.08h5.83Z" />
|
||||
<path d="M212.88 42.24l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M264.05 25.08v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83v-41.67h5.83Z" />
|
||||
<path d="M311.21 25.08v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V25.08h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,33 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<rect class="cls-1" x="7.1" y="7.8" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="18.78" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="29.77" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="40.76" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="51.75" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="62.74" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="73.73" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="84.72" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="7.1" y="95.71" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.32 108.98">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="7.1" y="7.8" width="5.84" height="5.48" />
|
||||
<rect x="7.1" y="18.78" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="29.77" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="40.76" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="51.75" width="80.88" height="5.48" />
|
||||
<rect x="7.1" y="62.74" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="73.73" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="84.72" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="95.71" width="5.84" height="5.48" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M121.28,25.08v44.54c0,5.67,2.79,8.54,8.22,8.54h11.57c5.51,0,8.3-2.87,8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7,0,13.01,4.47,13.01,13.33v15.01c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33V25.08h5.83Z"/>
|
||||
<path class="cls-2" d="M168.53,25.08v6.26h-5.83v-6.26h5.83ZM168.53,42.64v41.27h-5.83v-41.27h5.83Z"/>
|
||||
<path class="cls-2" d="M191.36,25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82v-17.16h5.83Z"/>
|
||||
<path class="cls-2" d="M212.88,42.24l16.84,35.68,16.6-35.68h6.07l-19.56,41.67h-6.31l-20.04-41.67h6.39Z"/>
|
||||
<path class="cls-2" d="M264.05,25.08v6.26h-5.83v-6.26h5.83ZM264.05,42.24v41.67h-5.83v-41.67h5.83Z"/>
|
||||
<path class="cls-2" d="M311.21,25.08v45.5c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33v-15.01c0-8.86,4.31-13.33,13.01-13.33h17.72v5.83h-16.6c-5.51,0-8.3,2.79-8.3,8.46v13.09c0,5.67,2.79,8.54,8.3,8.54h11.49c5.51,0,8.3-2.87,8.3-8.54V25.08h5.83Z"/>
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M121.28 25.08v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V25.08h5.83Z" />
|
||||
<path d="M168.53 25.08v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M191.36 25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V25.08h5.83Z" />
|
||||
<path d="M212.88 42.24l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M264.05 25.08v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83v-41.67h5.83Z" />
|
||||
<path d="M311.21 25.08v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V25.08h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,19 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112.46 112.46">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="15.79" y="9.53" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="20.52" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="31.51" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="42.5" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="53.49" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="64.48" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="75.47" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="86.45" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="97.44" width="5.84" height="5.48"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112.46 112.46">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="15.79" y="9.53" width="5.84" height="5.48" />
|
||||
<rect x="15.79" y="20.52" width="24.02" height="5.48" />
|
||||
<rect x="15.79" y="31.51" width="42.85" height="5.48" />
|
||||
<rect x="15.79" y="42.5" width="61.68" height="5.48" />
|
||||
<rect x="15.79" y="53.49" width="80.88" height="5.48" />
|
||||
<rect x="15.79" y="64.48" width="61.68" height="5.48" />
|
||||
<rect x="15.79" y="75.47" width="42.85" height="5.48" />
|
||||
<rect x="15.79" y="86.45" width="24.02" height="5.48" />
|
||||
<rect x="15.79" y="97.44" width="5.84" height="5.48" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 878 B After Width: | Height: | Size: 658 B |
@@ -1,19 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112.46 112.46">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #383838;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="15.79" y="9.53" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="20.52" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="31.51" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="42.5" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="53.49" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="64.48" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="75.47" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="86.45" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="97.44" width="5.84" height="5.48"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112.46 112.46">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="15.79" y="9.53" width="5.84" height="5.48" />
|
||||
<rect x="15.79" y="20.52" width="24.02" height="5.48" />
|
||||
<rect x="15.79" y="31.51" width="42.85" height="5.48" />
|
||||
<rect x="15.79" y="42.5" width="61.68" height="5.48" />
|
||||
<rect x="15.79" y="53.49" width="80.88" height="5.48" />
|
||||
<rect x="15.79" y="64.48" width="61.68" height="5.48" />
|
||||
<rect x="15.79" y="75.47" width="42.85" height="5.48" />
|
||||
<rect x="15.79" y="86.45" width="24.02" height="5.48" />
|
||||
<rect x="15.79" y="97.44" width="5.84" height="5.48" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 881 B After Width: | Height: | Size: 658 B |
@@ -1,19 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112.46 112.46">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="15.79" y="9.53" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="20.52" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="31.51" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="42.5" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="53.49" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="64.48" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="75.47" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="86.45" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="15.79" y="97.44" width="5.84" height="5.48"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112.46 112.46">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="15.79" y="9.53" width="5.84" height="5.48" />
|
||||
<rect x="15.79" y="20.52" width="24.02" height="5.48" />
|
||||
<rect x="15.79" y="31.51" width="42.85" height="5.48" />
|
||||
<rect x="15.79" y="42.5" width="61.68" height="5.48" />
|
||||
<rect x="15.79" y="53.49" width="80.88" height="5.48" />
|
||||
<rect x="15.79" y="64.48" width="61.68" height="5.48" />
|
||||
<rect x="15.79" y="75.47" width="42.85" height="5.48" />
|
||||
<rect x="15.79" y="86.45" width="24.02" height="5.48" />
|
||||
<rect x="15.79" y="97.44" width="5.84" height="5.48" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 881 B After Width: | Height: | Size: 658 B |
@@ -1,16 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229.82 88">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #383838;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M22.86,14.59v44.54c0,5.67,2.79,8.54,8.22,8.54h11.57c5.51,0,8.3-2.87,8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7,0,13.01,4.47,13.01,13.33v15.01c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33V14.59h5.83Z"/>
|
||||
<path class="cls-1" d="M70.11,14.59v6.26h-5.83v-6.26h5.83ZM70.11,32.15v41.27h-5.83v-41.27h5.83Z"/>
|
||||
<path class="cls-1" d="M92.94,14.59v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V14.59h5.83Z"/>
|
||||
<path class="cls-1" d="M114.46,31.75l16.84,35.68,16.6-35.68h6.07l-19.56,41.67h-6.31l-20.04-41.67h6.39Z"/>
|
||||
<path class="cls-1" d="M165.63,14.59v6.26h-5.83v-6.26h5.83ZM165.63,31.75v41.67h-5.83V31.75h5.83Z"/>
|
||||
<path class="cls-1" d="M212.79,14.59v45.5c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33v-15.01c0-8.86,4.31-13.33,13.01-13.33h17.72v5.83h-16.6c-5.51,0-8.3,2.79-8.3,8.46v13.09c0,5.67,2.79,8.54,8.3,8.54h11.49c5.51,0,8.3-2.87,8.3-8.54V14.59h5.83Z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229.82 88">
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M22.86 14.59v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V14.59h5.83Z" />
|
||||
<path d="M70.11 14.59v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M92.94 14.59v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V14.59h5.83Z" />
|
||||
<path d="M114.46 31.75l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M165.63 14.59v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83V31.75h5.83Z" />
|
||||
<path d="M212.79 14.59v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V14.59h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 997 B |
@@ -1,16 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229.82 88">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M22.86,14.59v44.54c0,5.67,2.79,8.54,8.22,8.54h11.57c5.51,0,8.3-2.87,8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7,0,13.01,4.47,13.01,13.33v15.01c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33V14.59h5.83Z"/>
|
||||
<path class="cls-1" d="M70.11,14.59v6.26h-5.83v-6.26h5.83ZM70.11,32.15v41.27h-5.83v-41.27h5.83Z"/>
|
||||
<path class="cls-1" d="M92.94,14.59v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V14.59h5.83Z"/>
|
||||
<path class="cls-1" d="M114.46,31.75l16.84,35.68,16.6-35.68h6.07l-19.56,41.67h-6.31l-20.04-41.67h6.39Z"/>
|
||||
<path class="cls-1" d="M165.63,14.59v6.26h-5.83v-6.26h5.83ZM165.63,31.75v41.67h-5.83V31.75h5.83Z"/>
|
||||
<path class="cls-1" d="M212.79,14.59v45.5c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33v-15.01c0-8.86,4.31-13.33,13.01-13.33h17.72v5.83h-16.6c-5.51,0-8.3,2.79-8.3,8.46v13.09c0,5.67,2.79,8.54,8.3,8.54h11.49c5.51,0,8.3-2.87,8.3-8.54V14.59h5.83Z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229.82 88">
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M22.86 14.59v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V14.59h5.83Z" />
|
||||
<path d="M70.11 14.59v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M92.94 14.59v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V14.59h5.83Z" />
|
||||
<path d="M114.46 31.75l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M165.63 14.59v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83V31.75h5.83Z" />
|
||||
<path d="M212.79 14.59v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V14.59h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 997 B |
@@ -1,16 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229.82 88">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M22.86,14.59v44.54c0,5.67,2.79,8.54,8.22,8.54h11.57c5.51,0,8.3-2.87,8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7,0,13.01,4.47,13.01,13.33v15.01c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33V14.59h5.83Z"/>
|
||||
<path class="cls-1" d="M70.11,14.59v6.26h-5.83v-6.26h5.83ZM70.11,32.15v41.27h-5.83v-41.27h5.83Z"/>
|
||||
<path class="cls-1" d="M92.94,14.59v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V14.59h5.83Z"/>
|
||||
<path class="cls-1" d="M114.46,31.75l16.84,35.68,16.6-35.68h6.07l-19.56,41.67h-6.31l-20.04-41.67h6.39Z"/>
|
||||
<path class="cls-1" d="M165.63,14.59v6.26h-5.83v-6.26h5.83ZM165.63,31.75v41.67h-5.83V31.75h5.83Z"/>
|
||||
<path class="cls-1" d="M212.79,14.59v45.5c0,8.86-4.31,13.33-13.01,13.33h-13.73c-8.7,0-13.01-4.47-13.01-13.33v-15.01c0-8.86,4.31-13.33,13.01-13.33h17.72v5.83h-16.6c-5.51,0-8.3,2.79-8.3,8.46v13.09c0,5.67,2.79,8.54,8.3,8.54h11.49c5.51,0,8.3-2.87,8.3-8.54V14.59h5.83Z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229.82 88">
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M22.86 14.59v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V14.59h5.83Z" />
|
||||
<path d="M70.11 14.59v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M92.94 14.59v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V14.59h5.83Z" />
|
||||
<path d="M114.46 31.75l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M165.63 14.59v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83V31.75h5.83Z" />
|
||||
<path d="M212.79 14.59v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V14.59h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 997 B |
@@ -1,26 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #383838;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-2" width="196.27" height="196.27"/>
|
||||
<g>
|
||||
<rect class="cls-1" x="67.7" y="51.44" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="62.43" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="73.41" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="84.4" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="95.39" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="106.38" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="117.37" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="128.36" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="139.35" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<rect class="bv-logo__background" width="196.27" height="196.27" />
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="67.7" y="51.44" width="5.84" height="5.48" />
|
||||
<rect x="67.7" y="62.43" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="73.41" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="84.4" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="95.39" width="80.88" height="5.48" />
|
||||
<rect x="67.7" y="106.38" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="117.36" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="128.35" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="139.34" width="5.84" height="5.48" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1010 B After Width: | Height: | Size: 724 B |
@@ -1,26 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #383838;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-2" width="196.27" height="196.27"/>
|
||||
<g>
|
||||
<rect class="cls-1" x="67.7" y="51.44" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="62.43" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="73.41" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="84.4" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="95.39" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="106.38" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="117.37" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="128.36" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="139.35" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<rect class="bv-logo__background" width="196.27" height="196.27" />
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="67.7" y="51.44" width="5.84" height="5.48" />
|
||||
<rect x="67.7" y="62.43" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="73.41" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="84.4" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="95.39" width="80.88" height="5.48" />
|
||||
<rect x="67.7" y="106.38" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="117.36" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="128.35" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="139.34" width="5.84" height="5.48" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1007 B After Width: | Height: | Size: 724 B |
@@ -1,26 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #383838;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" width="196.27" height="196.27"/>
|
||||
<g>
|
||||
<rect class="cls-2" x="67.7" y="51.44" width="5.84" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="62.43" width="24.02" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="73.41" width="42.85" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="84.4" width="61.68" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="95.39" width="80.88" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="106.38" width="61.68" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="117.37" width="42.85" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="128.36" width="24.02" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="139.35" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<rect class="bv-logo__background" width="196.27" height="196.27" />
|
||||
<g class="bv-logo__wordmark">
|
||||
<rect x="67.7" y="51.44" width="5.84" height="5.48" />
|
||||
<rect x="67.7" y="62.43" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="73.41" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="84.4" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="95.39" width="80.88" height="5.48" />
|
||||
<rect x="67.7" y="106.38" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="117.36" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="128.35" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="139.34" width="5.84" height="5.48" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1010 B After Width: | Height: | Size: 726 B |
@@ -1,26 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" width="196.27" height="196.27"/>
|
||||
<g>
|
||||
<rect class="cls-2" x="67.7" y="51.44" width="5.84" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="62.43" width="24.02" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="73.41" width="42.85" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="84.4" width="61.68" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="95.39" width="80.88" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="106.38" width="61.68" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="117.37" width="42.85" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="128.36" width="24.02" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="139.35" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<rect class="bv-logo__background" width="196.27" height="196.27" />
|
||||
<g class="bv-logo__wordmark">
|
||||
<rect x="67.7" y="51.44" width="5.84" height="5.48" />
|
||||
<rect x="67.7" y="62.43" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="73.41" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="84.4" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="95.39" width="80.88" height="5.48" />
|
||||
<rect x="67.7" y="106.38" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="117.36" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="128.35" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="139.34" width="5.84" height="5.48" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1007 B After Width: | Height: | Size: 726 B |
@@ -1,26 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #383838;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" width="196.27" height="196.27"/>
|
||||
<g>
|
||||
<rect class="cls-2" x="67.7" y="51.44" width="5.84" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="62.43" width="24.02" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="73.41" width="42.85" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="84.4" width="61.68" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="95.39" width="80.88" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="106.38" width="61.68" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="117.37" width="42.85" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="128.36" width="24.02" height="5.48"/>
|
||||
<rect class="cls-2" x="67.7" y="139.35" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<rect class="bv-logo__background" width="196.27" height="196.27" />
|
||||
<g class="bv-logo__wordmark">
|
||||
<rect x="67.7" y="51.44" width="5.84" height="5.48" />
|
||||
<rect x="67.7" y="62.43" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="73.41" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="84.4" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="95.39" width="80.88" height="5.48" />
|
||||
<rect x="67.7" y="106.38" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="117.36" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="128.35" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="139.34" width="5.84" height="5.48" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1007 B After Width: | Height: | Size: 726 B |
@@ -1,26 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fe0032;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-2" width="196.27" height="196.27"/>
|
||||
<g>
|
||||
<rect class="cls-1" x="67.7" y="51.44" width="5.84" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="62.43" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="73.41" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="84.4" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="95.39" width="80.88" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="106.38" width="61.68" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="117.37" width="42.85" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="128.36" width="24.02" height="5.48"/>
|
||||
<rect class="cls-1" x="67.7" y="139.35" width="5.84" height="5.48"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.27 196.27">
|
||||
<rect class="bv-logo__background" width="196.27" height="196.27" />
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="67.7" y="51.44" width="5.84" height="5.48" />
|
||||
<rect x="67.7" y="62.43" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="73.41" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="84.4" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="95.39" width="80.88" height="5.48" />
|
||||
<rect x="67.7" y="106.38" width="61.68" height="5.48" />
|
||||
<rect x="67.7" y="117.36" width="42.85" height="5.48" />
|
||||
<rect x="67.7" y="128.35" width="24.02" height="5.48" />
|
||||
<rect x="67.7" y="139.34" width="5.84" height="5.48" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1007 B After Width: | Height: | Size: 724 B |
@@ -1,5 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M12,0C18.623,0 24,5.377 24,12C24,18.623 18.623,24 12,24C5.377,24 0,18.623 0,12C0,5.377 5.377,0 12,0M14.557,16L9.442,16C9.988,18.46 10.883,20.114 12,21.744C13.194,20.003 14.041,18.334 14.557,16M7.4,16L2.833,16C4.069,18.825 6.537,20.972 9.588,21.716C8.54,19.983 7.805,18.058 7.4,16M21.167,16L16.6,16C16.209,17.988 15.505,19.887 14.425,21.694C17.437,20.931 19.942,18.799 21.167,16M7.162,10L2.2,10C1.933,11.313 1.933,12.685 2.2,14L7.115,14C6.996,12.671 7.014,11.328 7.162,10M14.823,10L9.176,10C9.011,11.326 8.991,12.672 9.123,14L14.876,14C15.009,12.672 14.987,11.327 14.823,10M21.8,10L16.837,10C16.985,11.328 17.003,12.671 16.885,14L21.8,14C22.06,12.715 22.073,11.352 21.8,10M9.644,2.271C6.567,3.003 4.077,5.157 2.833,8L7.486,8C7.921,5.958 8.664,4.015 9.644,2.271M11.999,2.223C10.91,3.993 10.089,5.676 9.536,8L14.463,8C13.929,5.754 13.126,4.052 11.999,2.223M14.367,2.292C15.38,4.104 16.1,6.052 16.513,8L21.167,8C19.935,5.184 17.405,3.042 14.367,2.292" style="fill:rgb(92,111,138);"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0C18.623 0 24 5.377 24 12c0 6.623-5.377 12-12 12S0 18.623 0 12 5.377 0 12 0Zm2.557 16H9.442c.546 2.46 1.441 4.114 2.558 5.744 1.194-1.741 2.041-3.41 2.557-5.744ZM7.4 16H2.833c1.236 2.825 3.704 4.972 6.755 5.716-.048-1.733-.783-3.658-1.188-5.716Zm13.767 0H16.6c-.391 1.988-1.095 3.887-2.175 5.694 3.012-.763 5.517-2.895 6.742-5.694ZM7.162 10H2.2a7.996 7.996 0 0 0 0 4h4.915a22.266 22.266 0 0 1 .047-4Zm7.661 0H9.176a22.238 22.238 0 0 0-.053 4h5.753a22.238 22.238 0 0 0-.053-4Zm6.638 0h-4.963a22.266 22.266 0 0 1 .048 4H21.8a7.99 7.99 0 0 0-.342-4ZM9.644 2.271A11.996 11.996 0 0 0 2.833 8h4.653c.435-2.042 1.178-3.985 2.158-5.729ZM12 2.223c-1.089 1.77-1.91 3.453-2.463 5.777h4.927C13.93 5.754 13.127 4.052 12 2.223Zm2.368.069C15.38 4.104 16.1 6.052 16.513 8h4.654a11.995 11.995 0 0 0-6.799-5.708Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 901 B |
@@ -1,5 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M23.334,11.96C22.621,11.234 22.462,10.131 22.941,9.233C23.283,8.593 23.307,7.832 23.005,7.171C22.704,6.511 22.112,6.029 21.404,5.869C20.413,5.644 19.682,4.802 19.601,3.788C19.542,3.065 19.15,2.41 18.539,2.018C17.93,1.625 17.172,1.54 16.489,1.789C15.533,2.136 14.463,1.821 13.847,1.013C13.407,0.437 12.723,0.098 11.997,0.098C11.272,0.098 10.588,0.437 10.148,1.013C9.535,1.822 8.465,2.137 7.509,1.79C6.827,1.542 6.069,1.627 5.459,2.019C4.849,2.411 4.456,3.066 4.398,3.789C4.316,4.803 3.586,5.646 2.595,5.87C1.887,6.03 1.295,6.512 0.994,7.172C0.693,7.832 0.717,8.594 1.059,9.233C1.538,10.13 1.379,11.234 0.667,11.96C0.158,12.477 -0.08,13.202 0.023,13.92C0.126,14.638 0.559,15.267 1.193,15.62C2.081,16.115 2.545,17.13 2.337,18.125C2.19,18.835 2.381,19.573 2.856,20.121C3.332,20.67 4.036,20.965 4.758,20.919C5.774,20.856 6.711,21.459 7.075,22.408C7.334,23.086 7.895,23.603 8.592,23.807C9.287,24.011 10.039,23.879 10.623,23.45C11.442,22.847 12.559,22.847 13.377,23.45C13.961,23.88 14.713,24.012 15.408,23.807C16.105,23.603 16.666,23.085 16.926,22.408C17.289,21.459 18.227,20.855 19.242,20.919C19.966,20.965 20.669,20.67 21.144,20.121C21.619,19.573 21.811,18.835 21.663,18.125C21.456,17.13 21.919,16.115 22.808,15.62C23.441,15.266 23.873,14.638 23.977,13.92C24.081,13.202 23.842,12.477 23.334,11.96ZM10.75,17.39L6.25,13.026L8.107,11.169L10.75,13.675L16.393,7.891L18.25,9.748L10.75,17.39Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" fill-rule="nonzero">
|
||||
<path d="M23.334 11.96C22.621 11.234 22.462 10.131 22.941 9.233C23.283 8.593 23.307 7.832 23.005 7.171C22.704 6.511 22.112 6.029 21.404 5.869C20.413 5.644 19.682 4.802 19.601 3.788C19.542 3.065 19.15 2.41 18.539 2.018C17.93 1.625 17.172 1.54 16.489 1.789C15.533 2.136 14.463 1.821 13.847 1.013C13.407 0.437 12.723 0.098 11.997 0.098C11.272 0.098 10.588 0.437 10.148 1.013C9.535 1.822 8.465 2.137 7.509 1.79C6.827 1.542 6.069 1.627 5.459 2.019C4.849 2.411 4.456 3.066 4.398 3.789C4.316 4.803 3.586 5.646 2.595 5.87C1.887 6.03 1.295 6.512 0.994 7.172C0.693 7.832 0.717 8.594 1.059 9.233C1.538 10.13 1.379 11.234 0.667 11.96C0.158 12.477 -0.08 13.202 0.023 13.92C0.126 14.638 0.559 15.267 1.193 15.62C2.081 16.115 2.545 17.13 2.337 18.125C2.19 18.835 2.381 19.573 2.856 20.121C3.332 20.67 4.036 20.965 4.758 20.919C5.774 20.856 6.711 21.459 7.075 22.408C7.334 23.086 7.895 23.603 8.592 23.807C9.287 24.011 10.039 23.879 10.623 23.45C11.442 22.847 12.559 22.847 13.377 23.45C13.961 23.88 14.713 24.012 15.408 23.807C16.105 23.603 16.666 23.085 16.926 22.408C17.289 21.459 18.227 20.855 19.242 20.919C19.966 20.965 20.669 20.67 21.144 20.121C21.619 19.573 21.811 18.835 21.663 18.125C21.456 17.13 21.919 16.115 22.808 15.62C23.441 15.266 23.873 14.638 23.977 13.92C24.081 13.202 23.842 12.477 23.334 11.96ZM10.75 17.39L6.25 13.026L8.107 11.169L10.75 13.675L16.393 7.891L18.25 9.748L10.75 17.39Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,5 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M12,6.453L21,14.828L21,24L15,24L15,18L9,18L9,24L3,24L3,14.828L12,6.453ZM24,12.148L12,1L0,12.133L1.361,13.598L12,3.73L22.639,13.613L24,12.148Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" fill-rule="nonzero">
|
||||
<path d="M12 6.453 21 14.828V24h-6v-6H9v6H3v-9.172zM24 12.148 12 1 0 12.133l1.361 1.465L12 3.73l10.639 9.883z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 656 B After Width: | Height: | Size: 223 B |
@@ -1,5 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M21,7.702L12.5,12.322L12.5,22C14.067,21.135 18.879,18.483 20.477,17.601C20.8,17.424 21,17.082 21,16.71L21,7.702ZM11.5,12.321L3,7.599L3,16.605C3,16.975 3.197,17.313 3.514,17.492C5.104,18.39 9.93,21.115 11.5,22L11.5,12.321ZM3.421,6.692L12,11.455L20.672,6.742C20.672,6.742 14.041,3.004 12.486,2.128C12.335,2.043 12.167,2 12,2C11.832,2 11.665,2.043 11.514,2.128C9.959,3.004 3.421,6.692 3.421,6.692Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" fill-rule="nonzero">
|
||||
<path d="M21 7.702 12.5 12.322V22c1.567-.865 6.379-3.517 7.977-4.399.323-.177.523-.519.523-.891V7.702zm-9.5 4.619L3 7.599v9.006c0 .37.197.708.514.887 1.59.898 6.416 3.623 7.986 4.508v-9.789zm-8.079-5.629L12 11.455l8.672-4.713s-6.631-3.738-8.186-4.614c-.151-.085-.319-.128-.486-.128s-.335.043-.486.128c-1.555.876-8.093 4.564-8.093 4.564Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 909 B After Width: | Height: | Size: 450 B |
@@ -1,102 +1,56 @@
|
||||
<!-- components/application-form.html
|
||||
<!-- components/application-form.html
|
||||
https://bitvid.network?modal=application => open nostrFormModal
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Application Form</title>
|
||||
<!-- Include Tailwind CSS -->
|
||||
<link href="css/tailwind.generated.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="bg-gray-900 text-gray-100">
|
||||
<!-- Application Form Modal -->
|
||||
<div
|
||||
id="nostrFormModal"
|
||||
class="fixed inset-0 z-50 hidden"
|
||||
style="background: transparent"
|
||||
>
|
||||
<!-- Dark/blur overlay -->
|
||||
<div
|
||||
class="absolute inset-0 z-10"
|
||||
style="
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
"
|
||||
></div>
|
||||
<div
|
||||
id="nostrFormModal"
|
||||
class="bv-modal hidden items-start justify-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="nostrFormModalTitle"
|
||||
aria-describedby="nostrFormModalDescription"
|
||||
data-ds="new"
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
<!-- Outer container with full width and centered content -->
|
||||
<div
|
||||
class="modal-container relative h-full w-full flex items-start justify-center overflow-y-auto z-20"
|
||||
<div class="bv-modal__panel modal-sheet max-h-modal-shell overflow-y-auto" tabindex="-1">
|
||||
<!-- Header bar with updated styling -->
|
||||
<div class="modal-header">
|
||||
<h2
|
||||
id="nostrFormModalTitle"
|
||||
class="pb-2 text-3xl font-extrabold tracking-wide text-text"
|
||||
>
|
||||
<!-- Inner content container -->
|
||||
<div
|
||||
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-6xl my-0 rounded-lg overflow-hidden relative"
|
||||
Application Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeNostrFormModal"
|
||||
class="modal-close"
|
||||
aria-label="Close application form modal"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<!-- Header bar with updated styling -->
|
||||
<div
|
||||
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent p-4 flex items-center justify-between border-b border-gray-700"
|
||||
>
|
||||
<h2 class="text-3xl font-extrabold text-white tracking-wide pb-2">
|
||||
Application Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeNostrFormModal"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-gray-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="p-6">
|
||||
<div class="w-full" style="height: 80vh">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-application-form.html"
|
||||
class="w-full h-full"
|
||||
frameborder="0"
|
||||
style="border: none; box-shadow: 0 0 2px rgba(0, 0, 0, 0.2)"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Optional Script for Opening/Closing the Modal -->
|
||||
<script>
|
||||
const openBtn = document.getElementById("openNostrForm");
|
||||
const appModal = document.getElementById("nostrFormModal");
|
||||
const closeBtn = document.getElementById("closeNostrFormModal");
|
||||
|
||||
// Open the modal when the designated button is clicked.
|
||||
if (openBtn) {
|
||||
openBtn.addEventListener("click", () => {
|
||||
appModal.classList.remove("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
// Close the modal when the close button is clicked.
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener("click", () => {
|
||||
appModal.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<div id="nostrFormModalDescription" class="modal-body">
|
||||
<div class="modal-embed-frame">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-application-form.html"
|
||||
class="h-full w-full rounded-lg border border-border/30 shadow-sm"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,67 +1,57 @@
|
||||
<!-- components/bug-fix-form.html
|
||||
<!-- components/bug-fix-form.html
|
||||
// https://bitvid.network?modal=bug => open bugFixModal
|
||||
// Admin: npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe
|
||||
// Form:
|
||||
// Responses:
|
||||
// Form:
|
||||
// Responses:
|
||||
-->
|
||||
<div
|
||||
id="bugFixModal"
|
||||
class="fixed inset-0 z-50 hidden"
|
||||
style="background: transparent"
|
||||
class="bv-modal hidden items-start justify-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="bugFixModalTitle"
|
||||
aria-describedby="bugFixModalDescription"
|
||||
data-ds="new"
|
||||
>
|
||||
<!-- Dark/blur overlay -->
|
||||
<div
|
||||
class="absolute inset-0 z-10"
|
||||
style="
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
"
|
||||
></div>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
<div
|
||||
class="modal-container relative h-full w-full flex items-start justify-center overflow-y-auto z-20"
|
||||
>
|
||||
<div
|
||||
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-6xl my-0 rounded-lg overflow-hidden relative"
|
||||
>
|
||||
<!-- Header bar (sticky) with updated title styling -->
|
||||
<div
|
||||
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent p-4 flex items-center justify-between border-b border-gray-700"
|
||||
<div class="bv-modal__panel modal-sheet max-h-modal-shell overflow-y-auto" tabindex="-1">
|
||||
<!-- Header bar (sticky) with updated title styling -->
|
||||
<div class="modal-header">
|
||||
<h2
|
||||
id="bugFixModalTitle"
|
||||
class="pb-2 text-3xl font-extrabold tracking-wide text-text"
|
||||
>
|
||||
<h2 class="text-3xl font-extrabold text-white tracking-wide pb-2">
|
||||
Bug Fix Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeBugFixModal"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
|
||||
Bug Fix Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeBugFixModal"
|
||||
class="modal-close"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-gray-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="w-full" style="height: 80vh">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-bug-fix-form.html"
|
||||
class="w-full h-full"
|
||||
frameborder="0"
|
||||
style="border: none; box-shadow: 0 0 2px rgba(0, 0, 0, 0.2)"
|
||||
></iframe>
|
||||
</div>
|
||||
<div id="bugFixModalDescription" class="modal-body">
|
||||
<div class="modal-embed-frame">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-bug-fix-form.html"
|
||||
class="h-full w-full rounded-lg border border-border/30 shadow-sm"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,64 +1,54 @@
|
||||
<!-- components/content-appeals-form.html
|
||||
<!-- components/content-appeals-form.html
|
||||
https://bitvid.network?modal=appeals => open contentAppealsModal
|
||||
-->
|
||||
<div
|
||||
id="contentAppealsModal"
|
||||
class="fixed inset-0 z-50 hidden"
|
||||
style="background: transparent"
|
||||
class="bv-modal hidden items-start justify-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="contentAppealsModalTitle"
|
||||
aria-describedby="contentAppealsModalDescription"
|
||||
data-ds="new"
|
||||
>
|
||||
<!-- Dark/blur overlay -->
|
||||
<div
|
||||
class="absolute inset-0 z-10"
|
||||
style="
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
"
|
||||
></div>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
<div
|
||||
class="modal-container relative h-full w-full flex items-start justify-center overflow-y-auto z-20"
|
||||
>
|
||||
<div
|
||||
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-6xl my-0 rounded-lg overflow-hidden relative"
|
||||
>
|
||||
<!-- Header bar (sticky) with improved title styling -->
|
||||
<div
|
||||
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent p-4 flex items-center justify-between border-b border-gray-700"
|
||||
<div class="bv-modal__panel modal-sheet max-h-modal-shell overflow-y-auto" tabindex="-1">
|
||||
<!-- Header bar (sticky) with improved title styling -->
|
||||
<div class="modal-header">
|
||||
<h2
|
||||
id="contentAppealsModalTitle"
|
||||
class="pb-2 text-3xl font-extrabold tracking-wide text-text"
|
||||
>
|
||||
<h2 class="text-3xl font-extrabold text-white tracking-wide pb-2">
|
||||
Content Appeals Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeContentAppealsModal"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
|
||||
Content Appeals Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeContentAppealsModal"
|
||||
class="modal-close"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-gray-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="w-full" style="height: 80vh">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-content-appeals-form.html"
|
||||
class="w-full h-full"
|
||||
frameborder="0"
|
||||
style="border: none; box-shadow: 0 0 2px rgba(0, 0, 0, 0.2)"
|
||||
></iframe>
|
||||
</div>
|
||||
<div id="contentAppealsModalDescription" class="modal-body">
|
||||
<div class="modal-embed-frame">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-content-appeals-form.html"
|
||||
class="h-full w-full rounded-lg border border-border/30 shadow-sm"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<!-- components/delete-video-modal.html -->
|
||||
<div
|
||||
id="deleteVideoModal"
|
||||
class="bv-modal hidden items-start justify-center md:items-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="deleteModalTitle"
|
||||
aria-describedby="deleteModalDescription"
|
||||
data-ds="new"
|
||||
>
|
||||
<div
|
||||
id="deleteVideoModalOverlay"
|
||||
class="bv-modal-backdrop"
|
||||
data-dismiss
|
||||
></div>
|
||||
|
||||
<div class="modal-sheet max-h-modal-shell w-full max-w-lg overflow-y-auto" tabindex="-1">
|
||||
<div class="modal-header">
|
||||
<div class="flex flex-col">
|
||||
<h2 id="deleteModalTitle" class="text-xl font-bold text-text">Delete video</h2>
|
||||
<p id="deleteModalSubtitle" class="text-xs text-muted">
|
||||
This action removes the note from every feed.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
id="closeDeleteVideoModal"
|
||||
class="modal-close"
|
||||
type="button"
|
||||
aria-label="Close delete modal"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body space-y-6">
|
||||
<div class="rounded-md border border-critical/40 bg-critical/10 p-4 text-sm text-critical">
|
||||
<p class="font-semibold">Deleting publishes tombstones for every revision.</p>
|
||||
<p class="mt-2 text-xs">
|
||||
Relays and clients will treat the note as deleted. This cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 text-sm text-text">
|
||||
<p id="deleteModalDescription">
|
||||
Are you sure you want to delete this video? Viewers will no longer see it in any grid.
|
||||
</p>
|
||||
<dl id="deleteModalMetadata" class="grid gap-3 text-xs text-muted sm:grid-cols-2"></dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer flex flex-col gap-3 sm:flex-row sm:justify-end">
|
||||
<button type="button" id="cancelDeleteVideo" class="btn-ghost w-full sm:w-auto">Cancel</button>
|
||||
<button
|
||||
type="button"
|
||||
id="confirmDeleteVideo"
|
||||
class="btn w-full sm:w-auto"
|
||||
data-variant="critical"
|
||||
>
|
||||
Delete all versions
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,127 +1,138 @@
|
||||
<!-- Disclaimer Modal (wide, matching other modals) -->
|
||||
<div
|
||||
id="disclaimerModal"
|
||||
class="fixed inset-0 z-50 hidden"
|
||||
style="background: transparent"
|
||||
class="bv-modal hidden items-start justify-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="disclaimerModalTitle"
|
||||
aria-describedby="disclaimerModalDescription"
|
||||
data-ds="new"
|
||||
>
|
||||
<!-- Dark/blur overlay -->
|
||||
<div
|
||||
class="absolute inset-0 z-10"
|
||||
style="
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
"
|
||||
></div>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
<!-- Outer container with wide layout, just like content-appeals-form -->
|
||||
<div
|
||||
class="modal-container relative h-full w-full flex items-start justify-center overflow-y-auto z-20"
|
||||
>
|
||||
<!-- The .modal-content, same classes: bg-gray-900, w-full max-w-[90%], etc. -->
|
||||
<div
|
||||
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-6xl my-0 rounded-lg overflow-hidden relative flex flex-col"
|
||||
>
|
||||
<!-- Sticky top bar, if you want a top heading or 'X' button up here -->
|
||||
<div
|
||||
class="modal-header sticky top-0 bg-gradient-to-b from-black/80 to-transparent p-4 flex items-center justify-between"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="modal-body flex-1 overflow-y-auto">
|
||||
<!-- Scrollable disclaimers area -->
|
||||
<div class="space-y-6 text-gray-300">
|
||||
<!-- Example: Insert your disclaimers here -->
|
||||
|
||||
<div class="flex justify-center mb-8">
|
||||
<img
|
||||
src="assets/svg/bitvid-logo-dark-mode.svg"
|
||||
alt="BitVid Logo"
|
||||
class="h-16"
|
||||
/>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-0">Welcome to bitvid</h2>
|
||||
<!-- Warning Alert -->
|
||||
<div
|
||||
class="bg-yellow-900/20 border border-yellow-700/50 rounded-lg p-4 mb-6 flex items-start"
|
||||
<div class="bv-modal__panel modal-sheet max-h-modal-shell flex flex-col overflow-hidden" tabindex="-1">
|
||||
<!-- Main Content -->
|
||||
<div class="modal-body flex-1 overflow-y-auto">
|
||||
<div class="mx-auto max-w-3xl space-y-8 text-base text-text">
|
||||
<div class="flex justify-center pt-1">
|
||||
<span
|
||||
class="inline-flex h-2xl items-center text-text bv-logo"
|
||||
role="img"
|
||||
aria-label="bitvid logo"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 text-yellow-500 mt-0.5 mr-3 flex-shrink-0"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
class="h-full w-auto"
|
||||
viewBox="0 0 318.32 108.98"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="7.1" y="7.8" width="5.84" height="5.48" />
|
||||
<rect x="7.1" y="18.78" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="29.77" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="40.76" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="51.75" width="80.88" height="5.48" />
|
||||
<rect x="7.1" y="62.74" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="73.73" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="84.72" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="95.71" width="5.84" height="5.48" />
|
||||
</g>
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M121.28 25.08v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V25.08h5.83Z" />
|
||||
<path d="M168.53 25.08v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M191.36 25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V25.08h5.83Z" />
|
||||
<path d="M212.88 42.24l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M264.05 25.08v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83v-41.67h5.83Z" />
|
||||
<path d="M311.21 25.08v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V25.08h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
<p class="text-yellow-200">
|
||||
This platform is currently in development and only supports Chrome
|
||||
and Firefox-based browsers. Other browsers are not supported at
|
||||
this time. You may encounter bugs or missing features. Give it a
|
||||
sec. Videos might take 10 to 60 seconds to load initially.
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
</span>
|
||||
</div>
|
||||
<header class="space-y-4 text-center">
|
||||
<h2
|
||||
id="disclaimerModalTitle"
|
||||
class="text-4xl font-semibold tracking-tight text-text"
|
||||
>
|
||||
Welcome to bitvid
|
||||
</h2>
|
||||
<p class="mx-auto max-w-2xl text-base leading-relaxed text-text-frosted">
|
||||
bitvid is a decentralized video platform where content is shared
|
||||
directly between users. We want you to understand a few important
|
||||
points before you continue:
|
||||
points before you continue.
|
||||
</p>
|
||||
</header>
|
||||
<div class="modal-alert" role="status">
|
||||
<svg
|
||||
class="modal-alert__icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-sm">
|
||||
This platform is currently in development and only supports Chrome
|
||||
and Firefox-based browsers. Other browsers are not supported at
|
||||
this time. You may encounter bugs or missing features. Give it a
|
||||
sec—videos might take 10 to 60 seconds to load initially.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="bg-gray-800 rounded-lg p-4">
|
||||
<h3 class="text-white font-semibold mb-2">Early Access Status</h3>
|
||||
<p class="text-gray-400">
|
||||
Currently, video posting is invite-only as we carefully scale
|
||||
our platform. While anyone can watch videos, content creation is
|
||||
limited to approved creators. This helps us maintain quality
|
||||
content during our early stages.
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-gray-800 rounded-lg p-4">
|
||||
<h3 class="text-white font-semibold mb-2">
|
||||
Content Responsibility & Moderation
|
||||
</h3>
|
||||
<p class="text-gray-400">
|
||||
While we don't host videos directly, we maintain community
|
||||
standards through access control. Users who violate our
|
||||
guidelines may be blocked from accessing the platform. All
|
||||
content must follow local laws and platform guidelines.
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-gray-800 rounded-lg p-4">
|
||||
<h3 class="text-white font-semibold mb-2">Platform Status</h3>
|
||||
<p class="text-gray-400">
|
||||
bitvid is a work in progress. Features may change or break, and
|
||||
security improvements are ongoing. Your feedback and patience
|
||||
help us build a better platform.
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-gray-800 rounded-lg p-4">
|
||||
<h3 class="text-white font-semibold mb-2">Get Involved</h3>
|
||||
<p class="text-gray-400">
|
||||
Are you a developer? We'd love your help! Visit our GitHub
|
||||
repository to contribute to building the future of decentralized
|
||||
video sharing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="disclaimerModalDescription" class="space-y-4">
|
||||
<article class="modal-tile space-y-2">
|
||||
<h3>Early Access Status</h3>
|
||||
<p>
|
||||
Currently, video posting is invite-only as we carefully scale
|
||||
our platform. While anyone can watch videos, content creation is
|
||||
limited to approved creators. This helps us maintain quality
|
||||
content during our early stages.
|
||||
</p>
|
||||
</article>
|
||||
<article class="modal-tile space-y-2">
|
||||
<h3>Content Responsibility & Moderation</h3>
|
||||
<p>
|
||||
While we don't host videos directly, we maintain community
|
||||
standards through access control. Users who violate our
|
||||
guidelines may be blocked from accessing the platform. All
|
||||
content must follow local laws and platform guidelines.
|
||||
</p>
|
||||
</article>
|
||||
<article class="modal-tile space-y-2">
|
||||
<h3>Platform Status</h3>
|
||||
<p>
|
||||
bitvid is a work in progress. Features may change or break, and
|
||||
security improvements are ongoing. Your feedback and patience
|
||||
help us build a better platform.
|
||||
</p>
|
||||
</article>
|
||||
<article class="modal-tile space-y-2">
|
||||
<h3>Get Involved</h3>
|
||||
<p>
|
||||
Are you a developer? We'd love your help! Visit our GitHub
|
||||
repository to contribute to building the future of decentralized
|
||||
video sharing.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Button at bottom -->
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
id="acceptDisclaimer"
|
||||
class="w-full bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-black transition-colors duration-200"
|
||||
>
|
||||
I Understand
|
||||
</button>
|
||||
</div>
|
||||
<!-- Button at bottom -->
|
||||
<div class="modal-footer pt-6 pb-16 md:pb-12">
|
||||
<button
|
||||
id="acceptDisclaimer"
|
||||
class="modal-primary"
|
||||
type="button"
|
||||
>
|
||||
I Understand
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,37 +1,21 @@
|
||||
<!-- components/edit-video-modal.html -->
|
||||
<div
|
||||
id="editVideoModal"
|
||||
class="fixed inset-0 z-50 hidden"
|
||||
style="background: transparent"
|
||||
class="bv-modal hidden items-start md:items-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="-1"
|
||||
data-ds="new"
|
||||
>
|
||||
<div
|
||||
id="editVideoModalOverlay"
|
||||
class="absolute inset-0 z-10"
|
||||
style="
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
"
|
||||
></div>
|
||||
<div id="editVideoModalOverlay" class="bv-modal-backdrop"></div>
|
||||
|
||||
<div
|
||||
class="relative modal-container h-full w-full flex items-start md:items-center justify-center overflow-y-auto z-20"
|
||||
>
|
||||
<div
|
||||
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-4xl my-0 rounded-lg overflow-y-auto relative"
|
||||
style="max-height: 90vh"
|
||||
>
|
||||
<div
|
||||
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent transition-transform duration-150 p-4 flex items-center justify-between"
|
||||
>
|
||||
<h2 class="text-xl font-bold text-white">Edit Video Note</h2>
|
||||
<button
|
||||
id="closeEditVideoModal"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
|
||||
>
|
||||
<div class="bv-modal__panel modal-sheet max-h-modal-shell overflow-y-auto" tabindex="-1">
|
||||
<div class="modal-header">
|
||||
<h2 class="text-xl font-bold text-text">Edit Video Note</h2>
|
||||
<button id="closeEditVideoModal" class="modal-close">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-gray-300"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -46,26 +30,26 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6 space-y-6">
|
||||
<p class="text-sm text-gray-400">
|
||||
<div class="modal-body space-y-6">
|
||||
<p class="text-sm text-muted">
|
||||
Fields that already have values are locked. Click <strong>Edit field</strong> to make changes.
|
||||
</p>
|
||||
|
||||
<form id="editVideoForm" class="space-y-5">
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoTitle" class="block text-sm font-medium text-gray-200">
|
||||
<label for="editVideoTitle" class="block text-sm font-medium text-text">
|
||||
Title
|
||||
</label>
|
||||
<div class="flex flex-col gap-2 sm:flex-row">
|
||||
<input
|
||||
type="text"
|
||||
id="editVideoTitle"
|
||||
class="flex-1 rounded-md border border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
class="flex-1 input"
|
||||
placeholder="Video title"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoTitle"
|
||||
>
|
||||
Edit field
|
||||
@@ -73,20 +57,616 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NOTE: Keep this NIP-71 markup in sync with the upload and revert modals. -->
|
||||
<div
|
||||
class="space-y-4 rounded-lg border border-border/70 bg-overlay-black/30 p-4"
|
||||
id="editNip71EventDetails"
|
||||
data-nip71-section="event-details"
|
||||
>
|
||||
<fieldset
|
||||
id="editNip71KindFieldset"
|
||||
class="space-y-2"
|
||||
data-nip71-fieldset="kind"
|
||||
>
|
||||
<legend class="text-sm font-semibold text-text">
|
||||
NIP-71 event type
|
||||
</legend>
|
||||
<p class="text-xs text-muted">
|
||||
Choose the video format. <span class="font-medium text-muted-strong">Kind 21</span> is a full video, while
|
||||
<span class="font-medium text-muted-strong">Kind 22</span> is a short.
|
||||
</p>
|
||||
<div
|
||||
class="form-control"
|
||||
role="radiogroup"
|
||||
aria-labelledby="editNip71KindFieldset"
|
||||
>
|
||||
<div class="menu sm:flex-row sm:flex-wrap sm:py-0" role="presentation">
|
||||
<label class="menu__item gap-3 px-0 py-0" for="editNip71KindLong">
|
||||
<input
|
||||
type="radio"
|
||||
name="editNip71Kind"
|
||||
id="editNip71KindLong"
|
||||
value="21"
|
||||
checked
|
||||
data-nip71-input="kind"
|
||||
class="h-4 w-4 border-border bg-panel text-info focus:ring-info"
|
||||
/>
|
||||
<span class="text-sm text-text">Normal video (kind 21)</span>
|
||||
</label>
|
||||
<label class="menu__item gap-3 px-0 py-0" for="editNip71KindShort">
|
||||
<input
|
||||
type="radio"
|
||||
name="editNip71Kind"
|
||||
id="editNip71KindShort"
|
||||
value="22"
|
||||
data-nip71-input="kind"
|
||||
class="h-4 w-4 border-border bg-panel text-info focus:ring-info"
|
||||
/>
|
||||
<span class="text-sm text-text">Short video (kind 22)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2" data-nip71-grid="metadata">
|
||||
<div class="space-y-2">
|
||||
<label for="editNip71PublishedAt" class="block text-sm font-medium text-text">
|
||||
Published at
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="editNip71PublishedAt"
|
||||
data-nip71-input="published_at"
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-xs text-muted">
|
||||
Set the canonical publish time for cross-client ordering.
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label for="editNip71AltText" class="block text-sm font-medium text-text">
|
||||
Alt text
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editNip71AltText"
|
||||
data-nip71-input="alt"
|
||||
placeholder="Describe the visuals for screen readers"
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-xs text-muted">
|
||||
Plain-language fallback shown when the media cannot load.
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label for="editNip71Duration" class="block text-sm font-medium text-text">
|
||||
Duration (seconds)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="editNip71Duration"
|
||||
min="0"
|
||||
step="0.001"
|
||||
data-nip71-input="duration"
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-xs text-muted">
|
||||
Optional legacy fallback in seconds (fractions allowed) for clients that expect a top-level duration.
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label for="editNip71ContentWarning" class="block text-sm font-medium text-text">
|
||||
Content warning
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editNip71ContentWarning"
|
||||
data-nip71-input="content-warning"
|
||||
placeholder="e.g., Flashing lights"
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-xs text-muted">
|
||||
Leave blank if the video is safe for all audiences.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="editNip71Summary" class="block text-sm font-medium text-text">
|
||||
Summary
|
||||
</label>
|
||||
<textarea
|
||||
id="editNip71Summary"
|
||||
rows="3"
|
||||
data-nip71-input="summary"
|
||||
class="textarea"
|
||||
placeholder="Short synopsis used as the Nostr content field"
|
||||
></textarea>
|
||||
<p class="text-xs text-muted">
|
||||
This becomes the event content. Keep it concise for Nostr readers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset
|
||||
id="editNip71TagsFieldset"
|
||||
class="space-y-6 rounded-lg border border-border/70 bg-overlay-black/30 p-4"
|
||||
data-nip71-section="tags"
|
||||
>
|
||||
<legend class="relative text-sm font-semibold text-text">
|
||||
<span class="inline-block -ml-2 rounded px-2 bg-overlay-black/30">NIP-71 tags</span>
|
||||
</legend>
|
||||
<p class="text-xs text-muted">
|
||||
Use the controls below to describe media variants, captions, chapters, participants, and references.
|
||||
</p>
|
||||
|
||||
<section class="space-y-4" aria-labelledby="editNip71ImetaLabel" data-nip71-repeater="imeta">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 id="editNip71ImetaLabel" class="text-sm font-medium text-text">Image metadata (imeta)</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-xs font-semibold"
|
||||
data-nip71-add="imeta"
|
||||
aria-describedby="editNip71ImetaHelp"
|
||||
>
|
||||
Add variant
|
||||
</button>
|
||||
</div>
|
||||
<p id="editNip71ImetaHelp" class="text-xs text-muted">
|
||||
Provide dimensions, MIME type, and resource links for each variant. Nested repeaters let you attach multiple
|
||||
<code>image</code>, <code>fallback</code>, or <code>service</code> values.
|
||||
</p>
|
||||
<div id="editNip71ImetaList" class="space-y-4" data-nip71-list="imeta"></div>
|
||||
<template id="editNip71ImetaTemplate" data-nip71-template="imeta">
|
||||
<article class="card space-y-3 bg-surface/60 p-4" data-nip71-entry="imeta">
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">
|
||||
MIME type (<code>m</code>)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
data-nip71-field="m"
|
||||
placeholder="video/mp4"
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-3xs text-muted">RFC 2046 media type for this encoding.</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">
|
||||
Pixel dimensions (<code>dim</code>)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
data-nip71-field="dim"
|
||||
placeholder="1920x1080"
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-3xs text-muted">Formatted as widthxheight.</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">
|
||||
Streaming URL (<code>url</code>)
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
data-nip71-field="url"
|
||||
placeholder="https://cdn.example.com/variant.m3u8"
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-3xs text-muted">HTTPS variant URL shared across clients.</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">
|
||||
Magnet (<code>x</code>)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
data-nip71-field="x"
|
||||
placeholder="magnet:?xt=urn:btih:..."
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-3xs text-muted">Optional magnet pointer if the variant is seeded via P2P.</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">
|
||||
Duration seconds (<code>duration</code>)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
data-nip71-field="duration"
|
||||
min="0"
|
||||
step="0.001"
|
||||
placeholder="29.97"
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-3xs text-muted">Fractional seconds supported; clients use this for precise timelines.</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">
|
||||
Bitrate bps (<code>bitrate</code>)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
data-nip71-field="bitrate"
|
||||
min="0"
|
||||
step="1"
|
||||
placeholder="3500000"
|
||||
class="input"
|
||||
/>
|
||||
<p class="text-3xs text-muted">Average bits per second for this variant.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3" data-nip71-nested="image">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-wide text-muted">Image values</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-nested-add="image"
|
||||
>
|
||||
Add image URL
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-2" data-nip71-nested-list="image"></div>
|
||||
<template data-nip71-nested-template="image">
|
||||
<div class="flex items-center gap-2" data-nip71-nested-entry="image">
|
||||
<input
|
||||
type="url"
|
||||
placeholder="https://cdn.example.com/poster.jpg"
|
||||
data-nip71-nested-field="image"
|
||||
class="flex-1 input"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-remove="nested"
|
||||
aria-label="Remove image URL"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3" data-nip71-nested="fallback">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-wide text-muted">Fallback URLs</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-nested-add="fallback"
|
||||
>
|
||||
Add fallback
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-2" data-nip71-nested-list="fallback"></div>
|
||||
<template data-nip71-nested-template="fallback">
|
||||
<div class="flex items-center gap-2" data-nip71-nested-entry="fallback">
|
||||
<input
|
||||
type="url"
|
||||
placeholder="https://cdn.backup.net/video.mp4"
|
||||
data-nip71-nested-field="fallback"
|
||||
class="flex-1 input"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-remove="nested"
|
||||
aria-label="Remove fallback URL"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3" data-nip71-nested="service">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-wide text-muted">Services</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-nested-add="service"
|
||||
>
|
||||
Add service
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-2" data-nip71-nested-list="service"></div>
|
||||
<template data-nip71-nested-template="service">
|
||||
<div class="flex items-center gap-2" data-nip71-nested-entry="service">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://api.example.com"
|
||||
data-nip71-nested-field="service"
|
||||
class="flex-1 input"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-remove="nested"
|
||||
aria-label="Remove service entry"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-remove="imeta"
|
||||
>
|
||||
Remove variant
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4" aria-labelledby="editNip71TextTracksLabel" data-nip71-repeater="text-track">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 id="editNip71TextTracksLabel" class="text-sm font-medium text-text">
|
||||
Caption & subtitle tracks (text-track)
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-xs font-semibold"
|
||||
data-nip71-add="text-track"
|
||||
aria-describedby="editNip71TextTracksHelp"
|
||||
>
|
||||
Add track
|
||||
</button>
|
||||
</div>
|
||||
<p id="editNip71TextTracksHelp" class="text-xs text-muted">
|
||||
Supply caption or subtitle URLs along with file type and language so players can surface accessibility features.
|
||||
</p>
|
||||
<div id="editNip71TextTrackList" class="space-y-4" data-nip71-list="text-track"></div>
|
||||
<template id="editNip71TextTrackTemplate" data-nip71-template="text-track">
|
||||
<article class="card space-y-3 bg-surface/60 p-4" data-nip71-entry="text-track">
|
||||
<div class="grid gap-3 md:grid-cols-3">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Track URL</label>
|
||||
<input
|
||||
type="url"
|
||||
data-nip71-field="url"
|
||||
placeholder="https://cdn.example.com/subs.vtt"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Type</label>
|
||||
<input
|
||||
type="text"
|
||||
data-nip71-field="type"
|
||||
placeholder="text/vtt"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Language</label>
|
||||
<input
|
||||
type="text"
|
||||
data-nip71-field="language"
|
||||
placeholder="en"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-remove="text-track"
|
||||
>
|
||||
Remove track
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4" aria-labelledby="editNip71SegmentsLabel" data-nip71-repeater="segment">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 id="editNip71SegmentsLabel" class="text-sm font-medium text-text">Chapters & segments (segment)</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-xs font-semibold"
|
||||
data-nip71-add="segment"
|
||||
aria-describedby="editNip71SegmentsHelp"
|
||||
>
|
||||
Add segment
|
||||
</button>
|
||||
</div>
|
||||
<p id="editNip71SegmentsHelp" class="text-xs text-muted">
|
||||
Outline chapters with start and end times, titles, and optional thumbnails.
|
||||
</p>
|
||||
<div id="editNip71SegmentList" class="space-y-4" data-nip71-list="segment"></div>
|
||||
<template id="editNip71SegmentTemplate" data-nip71-template="segment">
|
||||
<article class="card space-y-3 bg-surface/60 p-4" data-nip71-entry="segment">
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Start (seconds)</label>
|
||||
<input
|
||||
type="number"
|
||||
data-nip71-field="start"
|
||||
min="0"
|
||||
step="1"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">End (seconds)</label>
|
||||
<input
|
||||
type="number"
|
||||
data-nip71-field="end"
|
||||
min="0"
|
||||
step="1"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
data-nip71-field="title"
|
||||
placeholder="Chapter title"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Thumbnail URL</label>
|
||||
<input
|
||||
type="url"
|
||||
data-nip71-field="thumbnail"
|
||||
placeholder="https://cdn.example.com/frame.jpg"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-remove="segment"
|
||||
>
|
||||
Remove segment
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4" aria-labelledby="editNip71HashtagsLabel" data-nip71-repeater="t">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 id="editNip71HashtagsLabel" class="text-sm font-medium text-text">Hashtags (<code>t</code>)</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-xs font-semibold"
|
||||
data-nip71-add="t"
|
||||
>
|
||||
Add hashtag
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-muted">
|
||||
Enter keywords; leading <code>#</code> characters are stripped automatically when saving.
|
||||
</p>
|
||||
<div id="editNip71HashtagList" class="space-y-2" data-nip71-list="t"></div>
|
||||
<template id="editNip71HashtagTemplate" data-nip71-template="t">
|
||||
<div class="flex items-center gap-2" data-nip71-entry="t">
|
||||
<input
|
||||
type="text"
|
||||
data-nip71-field="value"
|
||||
placeholder="#nostr"
|
||||
class="flex-1 input"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-remove="t"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4" aria-labelledby="editNip71ParticipantsLabel" data-nip71-repeater="p">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 id="editNip71ParticipantsLabel" class="text-sm font-medium text-text">Participants (<code>p</code>)</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-xs font-semibold"
|
||||
data-nip71-add="p"
|
||||
>
|
||||
Add participant
|
||||
</button>
|
||||
</div>
|
||||
<div id="editNip71ParticipantList" class="space-y-3" data-nip71-list="p"></div>
|
||||
<template id="editNip71ParticipantTemplate" data-nip71-template="p">
|
||||
<article class="card space-y-3 bg-surface/60 p-4" data-nip71-entry="p">
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Participant pubkey</label>
|
||||
<input
|
||||
type="text"
|
||||
data-nip71-field="pubkey"
|
||||
placeholder="npub1... or hex"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Relay hint</label>
|
||||
<input
|
||||
type="text"
|
||||
data-nip71-field="relay"
|
||||
placeholder="wss://relay.example.com"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-remove="p"
|
||||
>
|
||||
Remove participant
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4" aria-labelledby="editNip71ReferencesLabel" data-nip71-repeater="r">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 id="editNip71ReferencesLabel" class="text-sm font-medium text-text">Reference links (<code>r</code>)</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-xs font-semibold"
|
||||
data-nip71-add="r"
|
||||
>
|
||||
Add reference
|
||||
</button>
|
||||
</div>
|
||||
<div id="editNip71ReferenceList" class="space-y-2" data-nip71-list="r"></div>
|
||||
<template id="editNip71ReferenceTemplate" data-nip71-template="r">
|
||||
<div class="flex items-center gap-2" data-nip71-entry="r">
|
||||
<input
|
||||
type="url"
|
||||
data-nip71-field="url"
|
||||
placeholder="https://example.com/context"
|
||||
class="flex-1 input"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring text-3xs font-medium"
|
||||
data-nip71-remove="r"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</fieldset>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoUrl" class="block text-sm font-medium text-gray-200">
|
||||
<label for="editVideoUrl" class="block text-sm font-medium text-text">
|
||||
Hosted video URL (https)
|
||||
</label>
|
||||
<div class="flex flex-col gap-2 sm:flex-row">
|
||||
<input
|
||||
type="url"
|
||||
id="editVideoUrl"
|
||||
class="flex-1 rounded-md border border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
class="flex-1 input"
|
||||
placeholder="https://cdn.example.com/video.mp4"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoUrl"
|
||||
>
|
||||
Edit field
|
||||
@@ -95,19 +675,19 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoWs" class="block text-sm font-medium text-gray-200">
|
||||
<label for="editVideoWs" class="block text-sm font-medium text-text">
|
||||
Web seed (ws=)
|
||||
</label>
|
||||
<div class="flex flex-col gap-2 sm:flex-row">
|
||||
<input
|
||||
type="url"
|
||||
id="editVideoWs"
|
||||
class="flex-1 rounded-md border border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
class="flex-1 input"
|
||||
placeholder="https://cdn.example.com/video.mp4"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoWs"
|
||||
>
|
||||
Edit field
|
||||
@@ -116,19 +696,19 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoXs" class="block text-sm font-medium text-gray-200">
|
||||
<label for="editVideoXs" class="block text-sm font-medium text-text">
|
||||
.torrent URL (xs=)
|
||||
</label>
|
||||
<div class="flex flex-col gap-2 sm:flex-row">
|
||||
<input
|
||||
type="url"
|
||||
id="editVideoXs"
|
||||
class="flex-1 rounded-md border border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
class="flex-1 input"
|
||||
placeholder="https://cdn.example.com/video.torrent"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoXs"
|
||||
>
|
||||
Edit field
|
||||
@@ -137,19 +717,19 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoMagnet" class="block text-sm font-medium text-gray-200">
|
||||
<label for="editVideoMagnet" class="block text-sm font-medium text-text">
|
||||
Magnet link
|
||||
</label>
|
||||
<div class="flex flex-col gap-2 sm:flex-row">
|
||||
<input
|
||||
type="text"
|
||||
id="editVideoMagnet"
|
||||
class="flex-1 rounded-md border border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
class="flex-1 input"
|
||||
placeholder="magnet:?xt=urn:btih:..."
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoMagnet"
|
||||
>
|
||||
Edit field
|
||||
@@ -158,19 +738,19 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoThumbnail" class="block text-sm font-medium text-gray-200">
|
||||
<label for="editVideoThumbnail" class="block text-sm font-medium text-text">
|
||||
Thumbnail URL
|
||||
</label>
|
||||
<div class="flex flex-col gap-2 sm:flex-row">
|
||||
<input
|
||||
type="url"
|
||||
id="editVideoThumbnail"
|
||||
class="flex-1 rounded-md border border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
class="flex-1 input"
|
||||
placeholder="https://cdn.example.com/preview.jpg"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoThumbnail"
|
||||
>
|
||||
Edit field
|
||||
@@ -179,19 +759,19 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoDescription" class="block text-sm font-medium text-gray-200">
|
||||
<label for="editVideoDescription" class="block text-sm font-medium text-text">
|
||||
Description
|
||||
</label>
|
||||
<div class="flex flex-col gap-2">
|
||||
<textarea
|
||||
id="editVideoDescription"
|
||||
rows="4"
|
||||
class="w-full rounded-md border border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
class="textarea"
|
||||
placeholder="What is this video about?"
|
||||
></textarea>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button self-start hidden"
|
||||
class="btn-ghost focus-ring hidden self-start text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoDescription"
|
||||
>
|
||||
Edit field
|
||||
@@ -200,25 +780,160 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editEnableComments" class="block text-sm font-medium text-gray-200">
|
||||
<span id="editVideoVisibilityLabel" class="block text-sm font-medium text-text">
|
||||
Visibility
|
||||
</span>
|
||||
<div
|
||||
class="space-y-3 rounded-md border border-border/70 bg-overlay-black/30 p-3"
|
||||
role="group"
|
||||
aria-labelledby="editVideoVisibilityLabel"
|
||||
data-visibility-toggle
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-2 sm:justify-between">
|
||||
<div
|
||||
class="video-visibility-toggle"
|
||||
data-visibility-options
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="video-visibility-toggle__option focus-ring"
|
||||
data-visibility-option="public"
|
||||
aria-pressed="false"
|
||||
>
|
||||
Public
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="video-visibility-toggle__option focus-ring"
|
||||
data-visibility-option="private"
|
||||
aria-pressed="false"
|
||||
>
|
||||
Private
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="editVideoIsPrivate"
|
||||
class="hidden"
|
||||
data-default-checked="false"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-xs text-muted" data-visibility-helper>
|
||||
Private notes stay visible only to you. Switch back to public when you're ready to share.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoIsPrivate"
|
||||
>
|
||||
Edit field
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
for="editVideoIsNsfw"
|
||||
id="editVideoIsNsfwLabel"
|
||||
class="block text-sm font-medium text-text"
|
||||
>
|
||||
NSFW content
|
||||
</label>
|
||||
<div
|
||||
class="form-control"
|
||||
role="group"
|
||||
aria-labelledby="editVideoIsNsfwLabel"
|
||||
>
|
||||
<label class="menu__item gap-3 px-0 py-0 text-left" for="editVideoIsNsfw">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="editVideoIsNsfw"
|
||||
class="checkbox"
|
||||
data-default-checked="false"
|
||||
/>
|
||||
<span class="text-sm text-muted-strong">
|
||||
Mark as sensitive to blur previews and gate playback.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoIsNsfw"
|
||||
>
|
||||
Edit field
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
for="editVideoIsForKids"
|
||||
id="editVideoIsForKidsLabel"
|
||||
class="block text-sm font-medium text-text"
|
||||
>
|
||||
Made for kids
|
||||
</label>
|
||||
<div
|
||||
class="form-control"
|
||||
role="group"
|
||||
aria-labelledby="editVideoIsForKidsLabel"
|
||||
>
|
||||
<label class="menu__item gap-3 px-0 py-0 text-left" for="editVideoIsForKids">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="editVideoIsForKids"
|
||||
class="checkbox"
|
||||
data-default-checked="false"
|
||||
/>
|
||||
<span class="text-sm text-muted-strong">
|
||||
Flag kid-friendly videos to adjust recommendations and controls.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editVideoIsForKids"
|
||||
>
|
||||
Edit field
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
for="editEnableComments"
|
||||
id="editEnableCommentsLabel"
|
||||
class="block text-sm font-medium text-text"
|
||||
>
|
||||
Enable comments
|
||||
</label>
|
||||
<div
|
||||
class="flex flex-col gap-2 rounded-md border border-gray-800 bg-black/30 p-3 sm:flex-row sm:items-center sm:justify-between"
|
||||
class="form-control"
|
||||
role="group"
|
||||
aria-labelledby="editEnableCommentsLabel"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="menu__item gap-3 px-0 py-0 text-left" for="editEnableComments">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="editEnableComments"
|
||||
class="h-5 w-5 rounded border-gray-600 bg-gray-800 text-blue-500 focus:ring-blue-500"
|
||||
class="checkbox"
|
||||
data-default-checked="true"
|
||||
checked
|
||||
/>
|
||||
<span class="text-sm text-gray-300">
|
||||
<span class="text-sm text-muted-strong">
|
||||
Allow replies once comment threads are available.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
class="btn-ghost focus-ring hidden text-xs font-semibold uppercase tracking-wide"
|
||||
data-edit-target="editEnableComments"
|
||||
>
|
||||
Edit field
|
||||
@@ -230,14 +945,14 @@
|
||||
<button
|
||||
type="button"
|
||||
id="cancelEditVideo"
|
||||
class="w-full sm:w-auto px-4 py-2 rounded-md bg-gray-800 text-gray-200 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-gray-500"
|
||||
class="btn-ghost focus-ring w-full sm:w-auto"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
id="submitEditVideo"
|
||||
class="w-full sm:w-auto px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-blue-500"
|
||||
class="btn focus-ring w-full sm:w-auto"
|
||||
>
|
||||
Save changes
|
||||
</button>
|
||||
@@ -245,5 +960,4 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,64 +1,54 @@
|
||||
<!-- components/feature-request-form.html
|
||||
// https://bitvid.network?modal=feature => open featureRequestModal
|
||||
<!-- components/feature-request-form.html
|
||||
// https://bitvid.network?modal=feature => open featureRequestModal
|
||||
-->
|
||||
<div
|
||||
id="featureRequestModal"
|
||||
class="fixed inset-0 z-50 hidden"
|
||||
style="background: transparent"
|
||||
class="bv-modal hidden items-start justify-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="featureRequestModalTitle"
|
||||
aria-describedby="featureRequestModalDescription"
|
||||
data-ds="new"
|
||||
>
|
||||
<!-- Dark/blur overlay -->
|
||||
<div
|
||||
class="absolute inset-0 z-10"
|
||||
style="
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
"
|
||||
></div>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
<div
|
||||
class="modal-container relative h-full w-full flex items-start justify-center overflow-y-auto z-20"
|
||||
>
|
||||
<div
|
||||
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-6xl my-0 rounded-lg overflow-hidden relative"
|
||||
>
|
||||
<!-- Header bar (sticky) with updated title styling -->
|
||||
<div
|
||||
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent p-4 flex items-center justify-between border-b border-gray-700"
|
||||
<div class="bv-modal__panel modal-sheet max-h-modal-shell overflow-y-auto" tabindex="-1">
|
||||
<!-- Header bar (sticky) with updated title styling -->
|
||||
<div class="modal-header">
|
||||
<h2
|
||||
id="featureRequestModalTitle"
|
||||
class="pb-2 text-3xl font-extrabold tracking-wide text-text"
|
||||
>
|
||||
<h2 class="text-3xl font-extrabold text-white tracking-wide pb-2">
|
||||
Feature Request Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeFeatureRequestModal"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
|
||||
Feature Request Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeFeatureRequestModal"
|
||||
class="modal-close"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-gray-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="w-full" style="height: 80vh">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-request-form.html"
|
||||
class="w-full h-full"
|
||||
frameborder="0"
|
||||
style="border: none; box-shadow: 0 0 2px rgba(0, 0, 0, 0.2)"
|
||||
></iframe>
|
||||
</div>
|
||||
<div id="featureRequestModalDescription" class="modal-body">
|
||||
<div class="modal-embed-frame">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-request-form.html"
|
||||
class="h-full w-full rounded-lg border border-border/30 shadow-sm"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,68 +1,58 @@
|
||||
<!-- components/content-appeals-form.html
|
||||
<!-- components/content-appeals-form.html
|
||||
// https://bitvid.network?modal=feedback => open generalFeedbackModal
|
||||
//
|
||||
//
|
||||
// Admin: npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe
|
||||
// Form:
|
||||
// Responses:
|
||||
// Form:
|
||||
// Responses:
|
||||
-->
|
||||
<div
|
||||
id="generalFeedbackModal"
|
||||
class="fixed inset-0 z-50 hidden"
|
||||
style="background: transparent"
|
||||
class="bv-modal hidden items-start justify-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="generalFeedbackModalTitle"
|
||||
aria-describedby="generalFeedbackModalDescription"
|
||||
data-ds="new"
|
||||
>
|
||||
<!-- Dark/blur overlay -->
|
||||
<div
|
||||
class="absolute inset-0 z-10"
|
||||
style="
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
"
|
||||
></div>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
<div
|
||||
class="modal-container relative h-full w-full flex items-start justify-center overflow-y-auto z-20"
|
||||
>
|
||||
<div
|
||||
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-6xl my-0 rounded-lg overflow-hidden relative"
|
||||
>
|
||||
<!-- Header bar (sticky) with updated title styling -->
|
||||
<div
|
||||
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent p-4 flex items-center justify-between border-b border-gray-700"
|
||||
<div class="bv-modal__panel modal-sheet max-h-modal-shell overflow-y-auto" tabindex="-1">
|
||||
<!-- Header bar (sticky) with updated title styling -->
|
||||
<div class="modal-header">
|
||||
<h2
|
||||
id="generalFeedbackModalTitle"
|
||||
class="pb-2 text-3xl font-extrabold tracking-wide text-text"
|
||||
>
|
||||
<h2 class="text-3xl font-extrabold text-white tracking-wide pb-2">
|
||||
General Feedback Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeGeneralFeedbackModal"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
|
||||
General Feedback Form
|
||||
</h2>
|
||||
<button
|
||||
id="closeGeneralFeedbackModal"
|
||||
class="modal-close"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-gray-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="w-full" style="height: 80vh">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-feedback-form.html"
|
||||
class="w-full h-full"
|
||||
frameborder="0"
|
||||
style="border: none; box-shadow: 0 0 2px rgba(0, 0, 0, 0.2)"
|
||||
></iframe>
|
||||
</div>
|
||||
<div id="generalFeedbackModalDescription" class="modal-body">
|
||||
<div class="modal-embed-frame">
|
||||
<iframe
|
||||
src="./components/iframe_forms/iframe-feedback-form.html"
|
||||
class="h-full w-full rounded-lg border border-border/30 shadow-sm"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,134 +3,14 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>bitvid Whitelist Application Form</title>
|
||||
<script type="module" src="./theme-sync.js"></script>
|
||||
<!-- Link to your main stylesheet -->
|
||||
<link rel="stylesheet" href="../../css/style.css" />
|
||||
<style>
|
||||
/* Override for form page to match modal field styling */
|
||||
|
||||
/* Remove width constraints from body so our container can be full width */
|
||||
body {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
/* Override the .container to use the full available width */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Card-like container for the form */
|
||||
.form-container {
|
||||
background-color: #111827; /* Tailwind's bg-gray-900 */
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Labels in a light gray */
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
font-weight: bold;
|
||||
color: #e5e7eb; /* Tailwind's text-gray-200 */
|
||||
}
|
||||
|
||||
/* Input, textarea, select mimic modal field styles */
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
margin-bottom: 0.75em;
|
||||
background-color: #1f2937; /* Tailwind's bg-gray-800 */
|
||||
color: #f3f4f6; /* Tailwind's text-gray-100 */
|
||||
border: 1px solid #374151; /* Tailwind's border-gray-700 */
|
||||
padding: 0.5em;
|
||||
border-radius: 0.375rem; /* rounded-md */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: #3b82f6; /* blue-500 */
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px #3b82f6;
|
||||
}
|
||||
|
||||
/* Style for checkboxes – display inline with a label */
|
||||
.checkbox-group label {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Button styled similarly to modal publish button */
|
||||
button {
|
||||
padding: 0.5em 1em;
|
||||
background: #3b82f6; /* blue-500 */
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Status log area */
|
||||
#status {
|
||||
margin-top: 1em;
|
||||
padding: 0.5em;
|
||||
background: #111827;
|
||||
white-space: pre-wrap;
|
||||
min-height: 80px;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.status-line {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
.error {
|
||||
color: #f87171; /* a red tint */
|
||||
}
|
||||
.success {
|
||||
color: #3b82f6; /* blue-500 */
|
||||
}
|
||||
.warn {
|
||||
color: #facc15; /* a yellow tone */
|
||||
}
|
||||
|
||||
/* Custom Scrollbar styling for WebKit browsers */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
/* Custom Scrollbar styling for Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
||||
}
|
||||
</style>
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="../../css/tailwind.generated.css" />
|
||||
<!-- Load nostr‑tools v2.17.0 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="iframe-form" data-ds="new">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
@@ -144,6 +24,7 @@
|
||||
<h2>1. Applicant Information</h2>
|
||||
<label for="applicantNpub">Nostr Public Key (npub):</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="applicantNpub"
|
||||
placeholder="Enter your npub"
|
||||
@@ -154,6 +35,7 @@
|
||||
>Preferred Contact Method (if applicable):</label
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="contactMethod"
|
||||
placeholder="Nostr DM, email, or other"
|
||||
@@ -161,6 +43,7 @@
|
||||
|
||||
<label for="username">Username or Alias (if applicable):</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="username"
|
||||
placeholder="Enter your preferred name"
|
||||
@@ -173,11 +56,17 @@
|
||||
</p>
|
||||
<div class="checkbox-group">
|
||||
<label
|
||||
><input type="checkbox" name="contentType" value="Educational" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="contentType"
|
||||
value="Educational"
|
||||
/>
|
||||
Educational</label
|
||||
>
|
||||
<label
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="contentType"
|
||||
value="Entertainment"
|
||||
@@ -186,6 +75,7 @@
|
||||
>
|
||||
<label
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="contentType"
|
||||
value="News & Journalism"
|
||||
@@ -194,6 +84,7 @@
|
||||
>
|
||||
<label
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="contentType"
|
||||
value="Creative Works"
|
||||
@@ -202,6 +93,7 @@
|
||||
>
|
||||
<label
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="contentType"
|
||||
value="Discussions & Opinions"
|
||||
@@ -209,12 +101,18 @@
|
||||
Discussions & Opinions</label
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" name="contentType" value="Other" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="contentType"
|
||||
value="Other"
|
||||
/>
|
||||
Other</label
|
||||
>
|
||||
</div>
|
||||
<label for="otherContent">Other (please specify):</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="otherContent"
|
||||
placeholder="Describe if Other was selected"
|
||||
@@ -222,6 +120,7 @@
|
||||
|
||||
<label for="whyJoin">Why do you want to join bitvid?</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="whyJoin"
|
||||
rows="3"
|
||||
placeholder="Explain your motivation"
|
||||
@@ -230,7 +129,7 @@
|
||||
<label for="priorExperience"
|
||||
>Have you created content on other platforms before?</label
|
||||
>
|
||||
<select id="priorExperience">
|
||||
<select class="select" id="priorExperience">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -240,6 +139,7 @@
|
||||
>If yes, provide links or references to your previous work:</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="experienceLinks"
|
||||
rows="2"
|
||||
placeholder="Paste links or describe your work"
|
||||
@@ -250,7 +150,7 @@
|
||||
<label for="familiarGuidelines"
|
||||
>Are you familiar with bitvid’s Community Guidelines?</label
|
||||
>
|
||||
<select id="familiarGuidelines">
|
||||
<select class="select" id="familiarGuidelines">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -260,7 +160,7 @@
|
||||
>Do you agree to follow the guidelines and respect decentralized
|
||||
moderation?</label
|
||||
>
|
||||
<select id="agreeGuidelines">
|
||||
<select class="select" id="agreeGuidelines">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -271,6 +171,7 @@
|
||||
community?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="communityContribution"
|
||||
rows="3"
|
||||
placeholder="Explain your approach"
|
||||
@@ -283,6 +184,7 @@
|
||||
bitvid?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="specialSkills"
|
||||
rows="3"
|
||||
placeholder="e.g., software development, moderation, design, advocacy"
|
||||
@@ -291,7 +193,7 @@
|
||||
<label for="testFeatures"
|
||||
>Would you be interested in helping test new features?</label
|
||||
>
|
||||
<select id="testFeatures">
|
||||
<select class="select" id="testFeatures">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -307,17 +209,32 @@
|
||||
revoked if guidelines are violated.
|
||||
</p>
|
||||
<label for="signature">Signature (Digital or Written):</label>
|
||||
<input type="text" id="signature" placeholder="Your signature" />
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="signature"
|
||||
placeholder="Your signature"
|
||||
/>
|
||||
|
||||
<label for="declarationDate">Date:</label>
|
||||
<input type="date" id="declarationDate" />
|
||||
<input class="form-control" type="date" id="declarationDate" />
|
||||
|
||||
<button type="submit">Submit Application</button>
|
||||
<button class="btn" type="submit">Submit Application</button>
|
||||
</form>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Provide no-op fallback loggers so embeds stay functional if bitvidLogger is unavailable.
|
||||
const noopLogger = {
|
||||
log: () => {},
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
};
|
||||
const dev = window.bitvidLogger?.dev || noopLogger;
|
||||
const user = window.bitvidLogger?.user || noopLogger;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Logging functions for both on-page and console output.
|
||||
function log(msg, type = "info") {
|
||||
@@ -328,7 +245,18 @@
|
||||
if (type === "warn") div.classList.add("warn");
|
||||
div.textContent = msg;
|
||||
document.getElementById("status").appendChild(div);
|
||||
console.log(`[${type.toUpperCase()}] ${msg}`);
|
||||
const formatted = `[${type.toUpperCase()}] ${msg}`;
|
||||
if (type === "error") {
|
||||
user.error(formatted);
|
||||
} else if (type === "warn") {
|
||||
user.warn(formatted);
|
||||
} else if (type === "success") {
|
||||
user.log(formatted);
|
||||
} else if (type === "info") {
|
||||
dev.info(formatted);
|
||||
} else {
|
||||
dev.log(formatted);
|
||||
}
|
||||
}
|
||||
function clear() {
|
||||
document.getElementById("status").innerHTML = "";
|
||||
@@ -551,10 +479,11 @@ For further questions, contact us through bitvid’s Nostr support channels.
|
||||
);
|
||||
} catch (err) {
|
||||
log("Error: " + err.message, "error");
|
||||
console.error(err);
|
||||
user.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,134 +3,13 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>bitvid Bug Report Form</title>
|
||||
<script type="module" src="./theme-sync.js"></script>
|
||||
<!-- Link to your main stylesheet -->
|
||||
<link rel="stylesheet" href="../../css/style.css" />
|
||||
<style>
|
||||
/* Override for form page to match modal field styling */
|
||||
|
||||
/* Remove width constraints from body so our container can be full width */
|
||||
body {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
/* Override the .container to use the full available width */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Card-like container for the form, similar to modal-content */
|
||||
.form-container {
|
||||
background-color: #111827; /* Tailwind's bg-gray-900 */
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Labels in a light gray */
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
font-weight: bold;
|
||||
color: #e5e7eb; /* Tailwind's text-gray-200 */
|
||||
}
|
||||
|
||||
/* Input, textarea, and select mimic modal field styles */
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
margin-bottom: 0.75em;
|
||||
background-color: #1f2937; /* Tailwind's bg-gray-800 */
|
||||
color: #f3f4f6; /* Tailwind's text-gray-100 */
|
||||
border: 1px solid #374151; /* Tailwind's border-gray-700 */
|
||||
padding: 0.5em;
|
||||
border-radius: 0.375rem; /* rounded-md */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: #3b82f6; /* blue-500 */
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px #3b82f6;
|
||||
}
|
||||
|
||||
/* Checkbox group styling */
|
||||
.checkbox-group label {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Button styled similarly to modal publish button */
|
||||
button {
|
||||
padding: 0.5em 1em;
|
||||
background: #3b82f6; /* blue-500 */
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Status log area */
|
||||
#status {
|
||||
margin-top: 1em;
|
||||
padding: 0.5em;
|
||||
background: #111827;
|
||||
white-space: pre-wrap;
|
||||
min-height: 80px;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.status-line {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
.error {
|
||||
color: #f87171; /* a red tint */
|
||||
}
|
||||
.success {
|
||||
color: #3b82f6; /* blue-500 */
|
||||
}
|
||||
.warn {
|
||||
color: #facc15; /* a yellow tone */
|
||||
}
|
||||
|
||||
/* Custom Scrollbar styling for WebKit browsers */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
/* Custom Scrollbar styling for Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
||||
}
|
||||
</style>
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="../../css/tailwind.generated.css" />
|
||||
<!-- Load nostr‑tools v2.17.0 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="iframe-form" data-ds="new">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
@@ -144,6 +23,7 @@
|
||||
<h2>1. User Information</h2>
|
||||
<label for="userNpub">Nostr Public Key (npub) (optional):</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="userNpub"
|
||||
placeholder="Enter your npub (optional)"
|
||||
@@ -151,15 +31,26 @@
|
||||
<p>Are you a (check all that apply):</p>
|
||||
<div class="checkbox-group">
|
||||
<label
|
||||
><input type="checkbox" name="userRole" value="Viewer" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="userRole"
|
||||
value="Viewer"
|
||||
/>
|
||||
Viewer</label
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" name="userRole" value="Content Creator" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="userRole"
|
||||
value="Content Creator"
|
||||
/>
|
||||
Content Creator</label
|
||||
>
|
||||
<label
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="userRole"
|
||||
value="Developer/Contributor"
|
||||
@@ -172,24 +63,28 @@
|
||||
<h2>2. Bug Details</h2>
|
||||
<label for="issueDescription">Describe the issue:</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="issueDescription"
|
||||
rows="3"
|
||||
placeholder="Provide a clear and concise description of the problem"
|
||||
></textarea>
|
||||
<label for="stepsToReproduce">Steps to reproduce the bug:</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="stepsToReproduce"
|
||||
rows="3"
|
||||
placeholder="List the actions taken before encountering the issue"
|
||||
></textarea>
|
||||
<label for="expectedBehavior">Expected behavior:</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="expectedBehavior"
|
||||
rows="2"
|
||||
placeholder="What should have happened instead?"
|
||||
></textarea>
|
||||
<label for="actualBehavior">Actual behavior:</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="actualBehavior"
|
||||
rows="2"
|
||||
placeholder="What actually happened?"
|
||||
@@ -200,26 +95,43 @@
|
||||
<p>What device were you using? (check all that apply):</p>
|
||||
<div class="checkbox-group">
|
||||
<label
|
||||
><input type="checkbox" name="deviceUsed" value="Desktop" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="deviceUsed"
|
||||
value="Desktop"
|
||||
/>
|
||||
Desktop</label
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" name="deviceUsed" value="Mobile" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="deviceUsed"
|
||||
value="Mobile"
|
||||
/>
|
||||
Mobile</label
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" name="deviceUsed" value="Tablet" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="deviceUsed"
|
||||
value="Tablet"
|
||||
/>
|
||||
Tablet</label
|
||||
>
|
||||
</div>
|
||||
<label for="operatingSystem">Operating System:</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="operatingSystem"
|
||||
placeholder="e.g., Windows, macOS, Linux, iOS, Android"
|
||||
/>
|
||||
<label for="browserInfo">Browser (if applicable):</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="browserInfo"
|
||||
placeholder="e.g., Chrome, Firefox, Safari"
|
||||
@@ -227,7 +139,7 @@
|
||||
<label for="usingVPN"
|
||||
>Are you using a VPN or privacy-focused browser settings?</label
|
||||
>
|
||||
<select id="usingVPN">
|
||||
<select class="select" id="usingVPN">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -239,6 +151,7 @@
|
||||
>Can you provide a screenshot or screen recording?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="screenshotInfo"
|
||||
rows="2"
|
||||
placeholder="Attach if possible (e.g., URL to screenshot)"
|
||||
@@ -247,12 +160,14 @@
|
||||
>Did the issue generate any error messages?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="errorMessages"
|
||||
rows="2"
|
||||
placeholder="Include the exact error messages, if any"
|
||||
></textarea>
|
||||
<label for="consoleLogs">Any relevant console logs?</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="consoleLogs"
|
||||
rows="2"
|
||||
placeholder="Paste any browser console logs if available"
|
||||
@@ -261,7 +176,7 @@
|
||||
<!-- Section 5: Additional Information -->
|
||||
<h2>5. Additional Information</h2>
|
||||
<label for="bugFrequency">How frequently does this bug occur?</label>
|
||||
<select id="bugFrequency">
|
||||
<select class="select" id="bugFrequency">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Every time">Every time</option>
|
||||
<option value="Occasionally">Occasionally</option>
|
||||
@@ -271,7 +186,7 @@
|
||||
>Does this bug impact core functionality (e.g., video playback,
|
||||
uploads, etc.)?</label
|
||||
>
|
||||
<select id="impactCore">
|
||||
<select class="select" id="impactCore">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -280,17 +195,28 @@
|
||||
>Any additional notes or suggestions?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="additionalNotes"
|
||||
rows="3"
|
||||
placeholder="Describe any other relevant details"
|
||||
></textarea>
|
||||
|
||||
<button type="submit">Submit Bug Report</button>
|
||||
<button class="btn" type="submit">Submit Bug Report</button>
|
||||
</form>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Provide no-op fallback loggers so embeds stay functional if bitvidLogger is unavailable.
|
||||
const noopLogger = {
|
||||
log: () => {},
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
};
|
||||
const dev = window.bitvidLogger?.dev || noopLogger;
|
||||
const user = window.bitvidLogger?.user || noopLogger;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Logging functions for both on-page and console output.
|
||||
function log(msg, type = "info") {
|
||||
@@ -301,7 +227,18 @@
|
||||
if (type === "warn") div.classList.add("warn");
|
||||
div.textContent = msg;
|
||||
document.getElementById("status").appendChild(div);
|
||||
console.log(`[${type.toUpperCase()}] ${msg}`);
|
||||
const formatted = `[${type.toUpperCase()}] ${msg}`;
|
||||
if (type === "error") {
|
||||
user.error(formatted);
|
||||
} else if (type === "warn") {
|
||||
user.warn(formatted);
|
||||
} else if (type === "success") {
|
||||
user.log(formatted);
|
||||
} else if (type === "info") {
|
||||
dev.info(formatted);
|
||||
} else {
|
||||
dev.log(formatted);
|
||||
}
|
||||
}
|
||||
function clear() {
|
||||
document.getElementById("status").innerHTML = "";
|
||||
@@ -522,10 +459,11 @@ For urgent issues, contact us through bitvid’s Nostr support channels.
|
||||
);
|
||||
} catch (err) {
|
||||
log("Error: " + err.message, "error");
|
||||
console.error(err);
|
||||
user.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,127 +3,13 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>bitvid Content Appeals Form</title>
|
||||
<script type="module" src="./theme-sync.js"></script>
|
||||
<!-- Link to your main stylesheet -->
|
||||
<link rel="stylesheet" href="../../css/style.css" />
|
||||
<style>
|
||||
/* Override for form page to match modal field styling */
|
||||
|
||||
/* Remove width constraints from body so our container can be full width */
|
||||
body {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
/* Override the .container to use the full available width */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Card-like container for the form, similar to modal-content */
|
||||
.form-container {
|
||||
background-color: #111827; /* Tailwind's bg-gray-900 */
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Labels in a light gray */
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
font-weight: bold;
|
||||
color: #e5e7eb; /* Tailwind's text-gray-200 */
|
||||
}
|
||||
|
||||
/* Input, textarea, and select mimic modal field styles */
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
margin-bottom: 0.75em;
|
||||
background-color: #1f2937; /* Tailwind's bg-gray-800 */
|
||||
color: #f3f4f6; /* Tailwind's text-gray-100 */
|
||||
border: 1px solid #374151; /* Tailwind's border-gray-700 */
|
||||
padding: 0.5em;
|
||||
border-radius: 0.375rem; /* rounded-md */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: #3b82f6; /* blue-500 */
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px #3b82f6;
|
||||
}
|
||||
|
||||
/* Button styled similarly to modal publish button */
|
||||
button {
|
||||
padding: 0.5em 1em;
|
||||
background: #3b82f6; /* blue-500 */
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Status log area */
|
||||
#status {
|
||||
margin-top: 1em;
|
||||
padding: 0.5em;
|
||||
background: #111827;
|
||||
white-space: pre-wrap;
|
||||
min-height: 80px;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.status-line {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
.error {
|
||||
color: #f87171; /* a red tint */
|
||||
}
|
||||
.success {
|
||||
color: #3b82f6; /* blue-500 */
|
||||
}
|
||||
.warn {
|
||||
color: #facc15; /* a yellow tone */
|
||||
}
|
||||
|
||||
/* Custom Scrollbar styling for WebKit browsers */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
/* Custom Scrollbar styling for Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
||||
}
|
||||
</style>
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="../../css/tailwind.generated.css" />
|
||||
<!-- Load nostr‑tools v2.17.0 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="iframe-form" data-ds="new">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
@@ -136,6 +22,7 @@
|
||||
<h2>1. User Information</h2>
|
||||
<label for="contactMethod">Contact Method (if applicable):</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="contactMethod"
|
||||
placeholder="Nostr DM, email, or other"
|
||||
@@ -144,6 +31,7 @@
|
||||
<h2>2. Content Details</h2>
|
||||
<label for="videoTitle">Title of the Video:</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="videoTitle"
|
||||
placeholder="Enter the exact title"
|
||||
@@ -151,19 +39,21 @@
|
||||
|
||||
<label for="magnetLink">Magnet Link:</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="magnetLink"
|
||||
placeholder="Enter the magnet link"
|
||||
/>
|
||||
|
||||
<label for="submissionDate">Date of Content Submission:</label>
|
||||
<input type="date" id="submissionDate" />
|
||||
<input class="form-control" type="date" id="submissionDate" />
|
||||
|
||||
<h2>3. Reason for Appeal</h2>
|
||||
<label for="reasonBlocked"
|
||||
>Why do you believe your content was unfairly blocked?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="reasonBlocked"
|
||||
rows="3"
|
||||
placeholder="Explain in detail"
|
||||
@@ -172,7 +62,7 @@
|
||||
<label for="fitsGuidelines"
|
||||
>Does your content fit within bitvid's Community Guidelines?</label
|
||||
>
|
||||
<select id="fitsGuidelines">
|
||||
<select class="select" id="fitsGuidelines">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -182,6 +72,7 @@
|
||||
>If yes, which guideline(s) support your appeal?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="guidelinesCited"
|
||||
rows="2"
|
||||
placeholder="Cite the specific guidelines"
|
||||
@@ -190,7 +81,7 @@
|
||||
<label for="editedContent"
|
||||
>Was this content edited after being blocked?</label
|
||||
>
|
||||
<select id="editedContent">
|
||||
<select class="select" id="editedContent">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -198,6 +89,7 @@
|
||||
|
||||
<label for="changesMade">If yes, what changes were made?</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="changesMade"
|
||||
rows="2"
|
||||
placeholder="Describe the modifications"
|
||||
@@ -208,6 +100,7 @@
|
||||
>Was there any misunderstanding or misclassification?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="misunderstanding"
|
||||
rows="2"
|
||||
placeholder="Provide context"
|
||||
@@ -217,6 +110,7 @@
|
||||
>Are there external references that validate your appeal?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="externalReferences"
|
||||
rows="2"
|
||||
placeholder="Links, citations, or additional info"
|
||||
@@ -231,17 +125,32 @@
|
||||
decisions are at the discretion of bitvid’s moderation process.
|
||||
</p>
|
||||
<label for="signature">Signature (Digital or Written):</label>
|
||||
<input type="text" id="signature" placeholder="Your signature" />
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="signature"
|
||||
placeholder="Your signature"
|
||||
/>
|
||||
|
||||
<label for="declarationDate">Date:</label>
|
||||
<input type="date" id="declarationDate" />
|
||||
<input class="form-control" type="date" id="declarationDate" />
|
||||
|
||||
<button type="submit">Submit Appeal</button>
|
||||
<button class="btn" type="submit">Submit Appeal</button>
|
||||
</form>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Provide no-op fallback loggers so embeds stay functional if bitvidLogger is unavailable.
|
||||
const noopLogger = {
|
||||
log: () => {},
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
};
|
||||
const dev = window.bitvidLogger?.dev || noopLogger;
|
||||
const user = window.bitvidLogger?.user || noopLogger;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Logging functions for both on-page and console output.
|
||||
function log(msg, type = "info") {
|
||||
@@ -252,7 +161,18 @@
|
||||
if (type === "warn") div.classList.add("warn");
|
||||
div.textContent = msg;
|
||||
document.getElementById("status").appendChild(div);
|
||||
console.log(`[${type.toUpperCase()}] ${msg}`);
|
||||
const formatted = `[${type.toUpperCase()}] ${msg}`;
|
||||
if (type === "error") {
|
||||
user.error(formatted);
|
||||
} else if (type === "warn") {
|
||||
user.warn(formatted);
|
||||
} else if (type === "success") {
|
||||
user.log(formatted);
|
||||
} else if (type === "info") {
|
||||
dev.info(formatted);
|
||||
} else {
|
||||
dev.log(formatted);
|
||||
}
|
||||
}
|
||||
function clear() {
|
||||
document.getElementById("status").innerHTML = "";
|
||||
@@ -455,10 +375,11 @@ For further questions, reach out through bitvid’s Nostr support channels.
|
||||
);
|
||||
} catch (err) {
|
||||
log("Error: " + err.message, "error");
|
||||
console.error(err);
|
||||
user.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,141 +3,13 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>bitvid General Feedback Form</title>
|
||||
<script type="module" src="./theme-sync.js"></script>
|
||||
<!-- Link to your main stylesheet -->
|
||||
<link rel="stylesheet" href="../../css/style.css" />
|
||||
<style>
|
||||
/* Override for form page to match modal field styling */
|
||||
|
||||
/* Remove width constraints from body so our container can be full width */
|
||||
body {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
/* Override the .container to use the full available width */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Card-like container for the form */
|
||||
.form-container {
|
||||
background-color: #111827; /* Tailwind's bg-gray-900 */
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Labels in a light gray */
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
font-weight: bold;
|
||||
color: #e5e7eb; /* Tailwind's text-gray-200 */
|
||||
}
|
||||
|
||||
/* Input, textarea, and select styling */
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
margin-bottom: 0.75em;
|
||||
background-color: #1f2937; /* Tailwind's bg-gray-800 */
|
||||
color: #f3f4f6; /* Tailwind's text-gray-100 */
|
||||
border: 1px solid #374151; /* Tailwind's border-gray-700 */
|
||||
padding: 0.5em;
|
||||
border-radius: 0.375rem; /* rounded-md */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: #3b82f6; /* blue-500 */
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px #3b82f6;
|
||||
}
|
||||
|
||||
/* Checkbox group styling */
|
||||
.checkbox-group label {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Radio group styling (for experience rating) */
|
||||
.radio-group label {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Button styling */
|
||||
button {
|
||||
padding: 0.5em 1em;
|
||||
background: #3b82f6; /* blue-500 */
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Status log area */
|
||||
#status {
|
||||
margin-top: 1em;
|
||||
padding: 0.5em;
|
||||
background: #111827;
|
||||
white-space: pre-wrap;
|
||||
min-height: 80px;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.status-line {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
.error {
|
||||
color: #f87171;
|
||||
}
|
||||
.success {
|
||||
color: #3b82f6;
|
||||
}
|
||||
.warn {
|
||||
color: #facc15;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar styling for WebKit browsers */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
/* Custom Scrollbar styling for Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
||||
}
|
||||
</style>
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="../../css/tailwind.generated.css" />
|
||||
<!-- Load nostr‑tools v2.17.0 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="iframe-form" data-ds="new">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
@@ -150,6 +22,7 @@
|
||||
<h2>1. User Information</h2>
|
||||
<label for="userNpub">Nostr Public Key (npub) (optional):</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="userNpub"
|
||||
placeholder="Enter your npub (optional)"
|
||||
@@ -157,15 +30,26 @@
|
||||
<p>Are you a (check all that apply):</p>
|
||||
<div class="checkbox-group">
|
||||
<label
|
||||
><input type="checkbox" name="userRole" value="Viewer" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="userRole"
|
||||
value="Viewer"
|
||||
/>
|
||||
Viewer</label
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" name="userRole" value="Content Creator" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="userRole"
|
||||
value="Content Creator"
|
||||
/>
|
||||
Content Creator</label
|
||||
>
|
||||
<label
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="userRole"
|
||||
value="Developer/Contributor"
|
||||
@@ -201,12 +85,14 @@
|
||||
</div>
|
||||
<label for="likeMost">What do you like most about bitvid?</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="likeMost"
|
||||
rows="3"
|
||||
placeholder="Describe the features, usability, or content you enjoy"
|
||||
></textarea>
|
||||
<label for="improvements">What would you like to see improved?</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="improvements"
|
||||
rows="3"
|
||||
placeholder="Provide specific areas for improvement"
|
||||
@@ -216,6 +102,7 @@
|
||||
use?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="confusingFeatures"
|
||||
rows="3"
|
||||
placeholder="Explain any challenges you’ve encountered"
|
||||
@@ -227,6 +114,7 @@
|
||||
>Do you have any other suggestions or thoughts about bitvid?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="otherSuggestions"
|
||||
rows="3"
|
||||
placeholder="Share any other feedback"
|
||||
@@ -234,7 +122,7 @@
|
||||
<label for="followUp"
|
||||
>Would you like to be contacted for follow-up discussions?</label
|
||||
>
|
||||
<select id="followUp">
|
||||
<select class="select" id="followUp">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -243,17 +131,28 @@
|
||||
>Preferred contact method (if applicable):</label
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="preferredContact"
|
||||
placeholder="Nostr DM, email, or other"
|
||||
/>
|
||||
|
||||
<button type="submit">Submit General Feedback</button>
|
||||
<button class="btn" type="submit">Submit General Feedback</button>
|
||||
</form>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Provide no-op fallback loggers so embeds stay functional if bitvidLogger is unavailable.
|
||||
const noopLogger = {
|
||||
log: () => {},
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
};
|
||||
const dev = window.bitvidLogger?.dev || noopLogger;
|
||||
const user = window.bitvidLogger?.user || noopLogger;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Logging function for on-page and console output.
|
||||
function log(msg, type = "info") {
|
||||
@@ -264,7 +163,18 @@
|
||||
if (type === "warn") div.classList.add("warn");
|
||||
div.textContent = msg;
|
||||
document.getElementById("status").appendChild(div);
|
||||
console.log(`[${type.toUpperCase()}] ${msg}`);
|
||||
const formatted = `[${type.toUpperCase()}] ${msg}`;
|
||||
if (type === "error") {
|
||||
user.error(formatted);
|
||||
} else if (type === "warn") {
|
||||
user.warn(formatted);
|
||||
} else if (type === "success") {
|
||||
user.log(formatted);
|
||||
} else if (type === "info") {
|
||||
dev.info(formatted);
|
||||
} else {
|
||||
dev.log(formatted);
|
||||
}
|
||||
}
|
||||
function clear() {
|
||||
document.getElementById("status").innerHTML = "";
|
||||
@@ -448,10 +358,11 @@ For additional discussions, reach out via bitvid’s Nostr support channels.
|
||||
);
|
||||
} catch (err) {
|
||||
log("Error: " + err.message, "error");
|
||||
console.error(err);
|
||||
user.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,133 +3,13 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>bitvid Feature Request Form</title>
|
||||
<script type="module" src="./theme-sync.js"></script>
|
||||
<!-- Link to your main stylesheet -->
|
||||
<link rel="stylesheet" href="../../css/style.css" />
|
||||
<style>
|
||||
/* Override for form page to match modal field styling */
|
||||
|
||||
/* Remove width constraints from body so our container can be full width */
|
||||
body {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
/* Full-width container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Card-like container for the form */
|
||||
.form-container {
|
||||
background-color: #111827;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Labels */
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
font-weight: bold;
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
/* Input, textarea, and select styling */
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
margin-bottom: 0.75em;
|
||||
background-color: #1f2937;
|
||||
color: #f3f4f6;
|
||||
border: 1px solid #374151;
|
||||
padding: 0.5em;
|
||||
border-radius: 0.375rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: #3b82f6;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px #3b82f6;
|
||||
}
|
||||
|
||||
/* Checkbox group styling */
|
||||
.checkbox-group label {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Button styling */
|
||||
button {
|
||||
padding: 0.5em 1em;
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Status log area */
|
||||
#status {
|
||||
margin-top: 1em;
|
||||
padding: 0.5em;
|
||||
background: #111827;
|
||||
white-space: pre-wrap;
|
||||
min-height: 80px;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.status-line {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
.error {
|
||||
color: #f87171;
|
||||
}
|
||||
.success {
|
||||
color: #3b82f6;
|
||||
}
|
||||
.warn {
|
||||
color: #facc15;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
||||
}
|
||||
</style>
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="../../css/tailwind.generated.css" />
|
||||
<!-- Load nostr‑tools v2.17.0 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="iframe-form" data-ds="new">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
@@ -142,6 +22,7 @@
|
||||
<h2>1. User Information</h2>
|
||||
<label for="userNpub">Nostr Public Key (npub) (optional):</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="userNpub"
|
||||
placeholder="Enter your npub (optional)"
|
||||
@@ -149,15 +30,26 @@
|
||||
<p>Are you a (check all that apply):</p>
|
||||
<div class="checkbox-group">
|
||||
<label
|
||||
><input type="checkbox" name="userRole" value="Viewer" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="userRole"
|
||||
value="Viewer"
|
||||
/>
|
||||
Viewer</label
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" name="userRole" value="Content Creator" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="userRole"
|
||||
value="Content Creator"
|
||||
/>
|
||||
Content Creator</label
|
||||
>
|
||||
<label
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="userRole"
|
||||
value="Developer/Contributor"
|
||||
@@ -170,6 +62,7 @@
|
||||
<h2>2. Feature Request Details</h2>
|
||||
<label for="featureName">Feature Name:</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="featureName"
|
||||
placeholder="Short, descriptive title"
|
||||
@@ -177,18 +70,21 @@
|
||||
/>
|
||||
<label for="featureDescription">Describe the feature:</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="featureDescription"
|
||||
rows="3"
|
||||
placeholder="Explain in detail what the feature does and how it works"
|
||||
></textarea>
|
||||
<label for="featureImportance">Why is this feature important?</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="featureImportance"
|
||||
rows="2"
|
||||
placeholder="Describe how this will improve the platform and benefit users"
|
||||
></textarea>
|
||||
<label for="beneficiary">Who would benefit from this feature?</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="beneficiary"
|
||||
placeholder="e.g., Content Creators, Viewers, Both"
|
||||
@@ -200,12 +96,14 @@
|
||||
>Are there existing platforms that have this feature?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="existingPlatforms"
|
||||
rows="2"
|
||||
placeholder="Provide examples if applicable"
|
||||
></textarea>
|
||||
<label for="mockups">Do you have any mockups or examples?</label>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="mockups"
|
||||
rows="2"
|
||||
placeholder="Links, screenshots, or diagrams"
|
||||
@@ -214,7 +112,7 @@
|
||||
>Would you be willing to help test this feature if
|
||||
implemented?</label
|
||||
>
|
||||
<select id="willingToTest">
|
||||
<select class="select" id="willingToTest">
|
||||
<option value="">Select an option</option>
|
||||
<option value="Yes">Yes</option>
|
||||
<option value="No">No</option>
|
||||
@@ -225,15 +123,31 @@
|
||||
<p>How urgent is this feature?</p>
|
||||
<div class="checkbox-group">
|
||||
<label
|
||||
><input type="checkbox" name="featurePriority" value="High" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="featurePriority"
|
||||
value="High"
|
||||
/>
|
||||
High (Essential for platform success)</label
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" name="featurePriority" value="Medium" />
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="featurePriority"
|
||||
value="Medium"
|
||||
/>
|
||||
Medium (Would improve experience significantly)</label
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" name="featurePriority" value="Low" /> Low
|
||||
><input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
name="featurePriority"
|
||||
value="Low"
|
||||
/>
|
||||
Low
|
||||
(Nice to have, but not urgent)</label
|
||||
>
|
||||
</div>
|
||||
@@ -241,17 +155,28 @@
|
||||
>Does this feature require technical expertise to implement?</label
|
||||
>
|
||||
<textarea
|
||||
class="textarea"
|
||||
id="techChallenges"
|
||||
rows="2"
|
||||
placeholder="Describe any dependencies or challenges"
|
||||
></textarea>
|
||||
|
||||
<button type="submit">Submit Feature Request</button>
|
||||
<button class="btn" type="submit">Submit Feature Request</button>
|
||||
</form>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Provide no-op fallback loggers so embeds stay functional if bitvidLogger is unavailable.
|
||||
const noopLogger = {
|
||||
log: () => {},
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
};
|
||||
const dev = window.bitvidLogger?.dev || noopLogger;
|
||||
const user = window.bitvidLogger?.user || noopLogger;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Logging function
|
||||
function log(msg, type = "info") {
|
||||
@@ -262,7 +187,18 @@
|
||||
if (type === "warn") div.classList.add("warn");
|
||||
div.textContent = msg;
|
||||
document.getElementById("status").appendChild(div);
|
||||
console.log(`[${type.toUpperCase()}] ${msg}`);
|
||||
const formatted = `[${type.toUpperCase()}] ${msg}`;
|
||||
if (type === "error") {
|
||||
user.error(formatted);
|
||||
} else if (type === "warn") {
|
||||
user.warn(formatted);
|
||||
} else if (type === "success") {
|
||||
user.log(formatted);
|
||||
} else if (type === "info") {
|
||||
dev.info(formatted);
|
||||
} else {
|
||||
dev.log(formatted);
|
||||
}
|
||||
}
|
||||
function clear() {
|
||||
document.getElementById("status").innerHTML = "";
|
||||
@@ -465,10 +401,11 @@ For further discussions, reach out through bitvid’s Nostr support channels.
|
||||
);
|
||||
} catch (err) {
|
||||
log("Error: " + err.message, "error");
|
||||
console.error(err);
|
||||
user.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,192 @@
|
||||
import { THEME_ACCENT_OVERRIDES } from "../../config/instance-config.js";
|
||||
|
||||
(() => {
|
||||
const VALID_THEMES = new Set(["light", "dark"]);
|
||||
const STORAGE_KEY = "bitvid:theme";
|
||||
const FALLBACK_THEME = "dark";
|
||||
const ACCENT_CSS_VARIABLES = Object.freeze({
|
||||
accent: "--color-accent",
|
||||
accentStrong: "--color-accent-strong",
|
||||
accentPressed: "--color-accent-pressed",
|
||||
});
|
||||
const ACCENT_TOKENS = Object.keys(ACCENT_CSS_VARIABLES);
|
||||
const { documentElement: root } = document;
|
||||
|
||||
if (!(root instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sanitizeConfigOverrides = (overrides) => {
|
||||
if (!overrides || typeof overrides !== "object") {
|
||||
return {};
|
||||
}
|
||||
|
||||
const sanitized = {};
|
||||
|
||||
Object.entries(overrides).forEach(([theme, themeOverrides]) => {
|
||||
if (!VALID_THEMES.has(theme) || typeof themeOverrides !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = {};
|
||||
|
||||
ACCENT_TOKENS.forEach((token) => {
|
||||
const value = themeOverrides[token];
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed !== "") {
|
||||
normalized[token] = trimmed;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(normalized).length > 0) {
|
||||
sanitized[theme] = normalized;
|
||||
}
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
};
|
||||
|
||||
const CONFIG_ACCENT_OVERRIDES = sanitizeConfigOverrides(
|
||||
THEME_ACCENT_OVERRIDES
|
||||
);
|
||||
|
||||
const normalizeTheme = (value) => {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (normalized === "" || normalized === "default") {
|
||||
return FALLBACK_THEME;
|
||||
}
|
||||
return VALID_THEMES.has(normalized) ? normalized : null;
|
||||
};
|
||||
|
||||
const getParentRoot = () => {
|
||||
try {
|
||||
if (window.parent && window.parent !== window) {
|
||||
const parentDocument = window.parent.document;
|
||||
const parentRoot = parentDocument?.documentElement;
|
||||
return parentRoot instanceof HTMLElement ? parentRoot : null;
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const readParentTheme = () => {
|
||||
const parentRoot = getParentRoot();
|
||||
if (!parentRoot) {
|
||||
return null;
|
||||
}
|
||||
return normalizeTheme(parentRoot.dataset?.theme);
|
||||
};
|
||||
|
||||
const readStoredTheme = () => {
|
||||
try {
|
||||
return normalizeTheme(window.localStorage.getItem(STORAGE_KEY));
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const applyTheme = (theme) => {
|
||||
if (theme && VALID_THEMES.has(theme)) {
|
||||
root.dataset.theme = theme;
|
||||
} else {
|
||||
root.removeAttribute("data-theme");
|
||||
}
|
||||
};
|
||||
|
||||
const readParentAccentOverrides = () => {
|
||||
const parentRoot = getParentRoot();
|
||||
if (!parentRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const overrides = {};
|
||||
let hasOverride = false;
|
||||
|
||||
Object.values(ACCENT_CSS_VARIABLES).forEach((cssVariable) => {
|
||||
try {
|
||||
const value = parentRoot.style.getPropertyValue(cssVariable);
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed !== "") {
|
||||
overrides[cssVariable] = trimmed;
|
||||
hasOverride = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
/* noop */
|
||||
}
|
||||
});
|
||||
|
||||
return hasOverride ? overrides : null;
|
||||
};
|
||||
|
||||
const applyAccentOverrides = (theme) => {
|
||||
const parentOverrides = readParentAccentOverrides();
|
||||
const configOverrides = CONFIG_ACCENT_OVERRIDES[theme] || null;
|
||||
|
||||
ACCENT_TOKENS.forEach((token) => {
|
||||
const cssVariable = ACCENT_CSS_VARIABLES[token];
|
||||
const parentValue = parentOverrides?.[cssVariable];
|
||||
const configValue = configOverrides?.[token];
|
||||
const nextValue = parentValue || configValue || "";
|
||||
|
||||
if (typeof nextValue === "string" && nextValue.trim() !== "") {
|
||||
root.style.setProperty(cssVariable, nextValue.trim());
|
||||
} else {
|
||||
root.style.removeProperty(cssVariable);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const syncTheme = () => {
|
||||
const resolvedTheme =
|
||||
readParentTheme() ?? readStoredTheme() ?? FALLBACK_THEME;
|
||||
applyTheme(resolvedTheme);
|
||||
applyAccentOverrides(resolvedTheme);
|
||||
};
|
||||
|
||||
syncTheme();
|
||||
|
||||
const parentRoot = getParentRoot();
|
||||
let observer;
|
||||
|
||||
if (parentRoot && typeof MutationObserver !== "undefined") {
|
||||
observer = new MutationObserver(syncTheme);
|
||||
observer.observe(parentRoot, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-theme", "style"],
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("storage", (event) => {
|
||||
if (event?.key === STORAGE_KEY) {
|
||||
syncTheme();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener(
|
||||
"pageshow",
|
||||
() => {
|
||||
syncTheme();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
window.addEventListener(
|
||||
"beforeunload",
|
||||
() => {
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<div
|
||||
id="lockdownModal"
|
||||
class="bv-modal hidden items-center justify-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="lockdownModalTitle"
|
||||
aria-describedby="lockdownModalDescription"
|
||||
data-ds="new"
|
||||
>
|
||||
<div class="bv-modal-backdrop"></div>
|
||||
|
||||
<div class="modal-sheet max-h-modal-shell overflow-hidden" tabindex="-1">
|
||||
<div class="modal-body space-y-8 text-center">
|
||||
<div class="flex justify-center">
|
||||
<span class="inline-flex h-16 items-center text-text bv-logo" role="img" aria-label="bitvid logo">
|
||||
<svg class="h-full w-auto" viewBox="0 0 318.32 108.98" aria-hidden="true" focusable="false">
|
||||
<g class="bv-logo__accent">
|
||||
<rect x="7.1" y="7.8" width="5.84" height="5.48" />
|
||||
<rect x="7.1" y="18.78" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="29.77" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="40.76" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="51.75" width="80.88" height="5.48" />
|
||||
<rect x="7.1" y="62.74" width="61.68" height="5.48" />
|
||||
<rect x="7.1" y="73.73" width="42.85" height="5.48" />
|
||||
<rect x="7.1" y="84.72" width="24.02" height="5.48" />
|
||||
<rect x="7.1" y="95.71" width="5.84" height="5.48" />
|
||||
</g>
|
||||
<g class="bv-logo__wordmark">
|
||||
<path d="M121.28 25.08v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V25.08h5.83Z" />
|
||||
<path d="M168.53 25.08v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
|
||||
<path d="M191.36 25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V25.08h5.83Z" />
|
||||
<path d="M212.88 42.24l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
|
||||
<path d="M264.05 25.08v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83v-41.67h5.83Z" />
|
||||
<path d="M311.21 25.08v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V25.08h5.83Z" />
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="lockdownModalDescription" class="space-y-4">
|
||||
<h2 id="lockdownModalTitle" class="text-2xl font-semibold tracking-tight text-text">
|
||||
bitvid is in lockdown mode
|
||||
</h2>
|
||||
<p class="text-base leading-relaxed text-text-frosted">
|
||||
Only approved administrators and moderators can access the app while lockdown is active. Sign in with an authorized Nostr profile to continue.
|
||||
</p>
|
||||
<p class="text-sm text-text-muted">
|
||||
You will be prompted by your NIP-07 browser extension. Make sure it is installed, unlocked, and ready to approve the request.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<p
|
||||
id="lockdownStatusMessage"
|
||||
class="text-sm text-text"
|
||||
data-lockdown-status
|
||||
aria-live="polite"
|
||||
>
|
||||
Administrator access is required to proceed.
|
||||
</p>
|
||||
<p
|
||||
id="lockdownErrorMessage"
|
||||
class="hidden text-sm text-critical"
|
||||
data-lockdown-error
|
||||
role="alert"
|
||||
></p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="lockdownLoginButton"
|
||||
class="btn w-full"
|
||||
data-variant="danger"
|
||||
data-lockdown-login
|
||||
type="button"
|
||||
>
|
||||
Login with Nostr
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,79 +1,359 @@
|
||||
<div
|
||||
id="loginModal"
|
||||
class="fixed inset-0 z-50 hidden"
|
||||
style="background: transparent"
|
||||
class="bv-modal hidden items-start justify-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="loginModalTitle"
|
||||
aria-describedby="loginModalDescription"
|
||||
data-ds="new"
|
||||
>
|
||||
<!-- Dark overlay with blur (unchanged) -->
|
||||
<div
|
||||
class="absolute inset-0 z-10"
|
||||
style="
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
"
|
||||
></div>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
<!-- Outer container matching video modal's flex layout & wide sizing -->
|
||||
<div
|
||||
class="modal-container relative h-full w-full flex items-start justify-center overflow-y-auto z-20"
|
||||
class="modal-sheet max-h-modal-shell flex min-h-0 flex-col overflow-hidden"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-6xl my-0 rounded-lg overflow-hidden relative"
|
||||
>
|
||||
<!-- Header with the special exit button (unchanged) -->
|
||||
<div
|
||||
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent p-4 flex items-center justify-between"
|
||||
<!-- Header with the special exit button (unchanged) -->
|
||||
<div class="modal-header">
|
||||
<h2 id="loginModalTitle" class="text-xl font-bold text-text">
|
||||
Login Options
|
||||
</h2>
|
||||
<button
|
||||
id="closeLoginModal"
|
||||
class="modal-close"
|
||||
>
|
||||
<h2 class="text-xl font-bold text-white">Login Options</h2>
|
||||
<button
|
||||
id="closeLoginModal"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-gray-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div
|
||||
id="loginModalDescription"
|
||||
class="modal-body flex-1 space-y-6 overflow-y-auto"
|
||||
>
|
||||
<div class="space-y-2 text-left">
|
||||
<p class="text-sm text-text-muted">
|
||||
Choose how you'd like to sign in. Connect a browser extension, link a
|
||||
remote signer, or work directly with your keys when you need to.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Body with buttons (unchanged) -->
|
||||
<div class="p-6 space-y-4">
|
||||
<!-- Application Form button -->
|
||||
<section
|
||||
class="space-y-5 rounded-3xl border border-border bg-overlay-panel-soft/90 p-4 shadow-lg backdrop-blur-sm sm:p-5"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<h3 class="text-base font-semibold text-text">Available methods</h3>
|
||||
<p class="text-sm text-text-muted">
|
||||
Pick the option that matches how you manage your Nostr identity.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Login provider buttons are rendered dynamically -->
|
||||
<div
|
||||
data-login-providers
|
||||
class="grid auto-rows-fr gap-4 sm:grid-cols-2 xl:grid-cols-3"
|
||||
></div>
|
||||
</section>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-1 text-left">
|
||||
<h3 class="text-sm font-semibold uppercase tracking-wide text-text-muted">
|
||||
Need an invite?
|
||||
</h3>
|
||||
<p class="text-sm text-text-muted">
|
||||
Submit an application and we'll send you updates when new slots open.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
id="openApplicationModal"
|
||||
class="w-full bg-indigo-500 text-white px-4 py-2 rounded-md hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
class="btn w-full"
|
||||
type="button"
|
||||
>
|
||||
Application Form
|
||||
</button>
|
||||
|
||||
<!-- Simple horizontal rule -->
|
||||
<div class="my-2 border-t border-gray-700"></div>
|
||||
|
||||
<!-- Existing login buttons -->
|
||||
<button
|
||||
id="loginNIP07"
|
||||
class="w-full bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
>
|
||||
Login with Extension (NIP-07)
|
||||
</button>
|
||||
<button
|
||||
id="loginNSEC"
|
||||
class="w-full bg-gray-500 text-white px-4 py-2 rounded-md cursor-not-allowed"
|
||||
disabled
|
||||
>
|
||||
Login with NSEC (Coming Soon)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<template data-login-provider-template>
|
||||
<button
|
||||
class="login-option"
|
||||
type="button"
|
||||
data-provider-button
|
||||
>
|
||||
<span class="login-option__header">
|
||||
<span class="login-option__copy">
|
||||
<span
|
||||
data-provider-eyebrow
|
||||
class="login-option__eyebrow hidden"
|
||||
></span>
|
||||
<span data-provider-label class="login-option__label"></span>
|
||||
<span
|
||||
data-provider-description
|
||||
class="login-option__description"
|
||||
></span>
|
||||
</span>
|
||||
<span aria-hidden="true" class="login-option__chevron">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
class="h-5 w-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<div data-provider-status-row class="login-option__status hidden">
|
||||
<span data-provider-status class="truncate"></span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-link text-sm hidden"
|
||||
data-provider-disconnect
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
data-provider-capabilities
|
||||
class="login-option__capabilities hidden"
|
||||
></span>
|
||||
</button>
|
||||
</template>
|
||||
<template data-login-nsec-dialog>
|
||||
<form
|
||||
data-nsec-form
|
||||
class="space-y-4 rounded-xl border border-border bg-overlay-panel-soft px-5 py-4"
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-lg font-semibold text-text">Direct key login</h3>
|
||||
<p class="text-sm text-text-muted" data-nsec-warning>
|
||||
"Unsafe by default" — imported keys stay decrypted only for this session
|
||||
unless you protect them with a passphrase.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
data-nsec-error
|
||||
class="hidden rounded-lg border border-danger/60 bg-danger/10 px-4 py-2 text-sm text-danger"
|
||||
role="alert"
|
||||
></div>
|
||||
<fieldset data-nsec-unlock-section class="space-y-3 hidden">
|
||||
<legend class="text-sm font-semibold text-text">Unlock saved key</legend>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-text">
|
||||
<input
|
||||
type="radio"
|
||||
name="nsec-mode"
|
||||
value="unlock"
|
||||
class="radio"
|
||||
data-nsec-mode="unlock"
|
||||
/>
|
||||
<span>Use the encrypted key stored on this device</span>
|
||||
</label>
|
||||
<div class="space-y-2 pl-6" data-nsec-unlock-fields>
|
||||
<label class="block text-sm font-medium text-text" for="nsecUnlockPassphrase">
|
||||
Passphrase
|
||||
</label>
|
||||
<input
|
||||
id="nsecUnlockPassphrase"
|
||||
type="password"
|
||||
class="input"
|
||||
autocomplete="current-password"
|
||||
data-nsec-unlock-passphrase
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset data-nsec-import-section class="space-y-3">
|
||||
<legend class="text-sm font-semibold text-text">Import a new secret</legend>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-text">
|
||||
<input
|
||||
type="radio"
|
||||
name="nsec-mode"
|
||||
value="import"
|
||||
class="radio"
|
||||
data-nsec-mode="import"
|
||||
checked
|
||||
/>
|
||||
<span>Paste an nsec, hex key, or mnemonic seed</span>
|
||||
</label>
|
||||
<div class="space-y-3 pl-6" data-nsec-import-fields>
|
||||
<label class="block text-sm font-medium text-text" for="nsecSecretInput">
|
||||
Secret
|
||||
</label>
|
||||
<textarea
|
||||
id="nsecSecretInput"
|
||||
class="input"
|
||||
rows="3"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
data-nsec-secret
|
||||
required
|
||||
></textarea>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-text">
|
||||
<input type="checkbox" class="checkbox" data-nsec-remember />
|
||||
<span>Remember this key on this device</span>
|
||||
</label>
|
||||
<div class="space-y-2" data-nsec-passphrase-fields hidden>
|
||||
<label class="block text-sm font-medium text-text" for="nsecPassphraseInput">
|
||||
Passphrase
|
||||
</label>
|
||||
<input
|
||||
id="nsecPassphraseInput"
|
||||
type="password"
|
||||
class="input"
|
||||
autocomplete="new-password"
|
||||
data-nsec-passphrase
|
||||
/>
|
||||
<label class="block text-sm font-medium text-text" for="nsecPassphraseConfirm">
|
||||
Confirm passphrase
|
||||
</label>
|
||||
<input
|
||||
id="nsecPassphraseConfirm"
|
||||
type="password"
|
||||
class="input"
|
||||
autocomplete="new-password"
|
||||
data-nsec-passphrase-confirm
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" class="btn-ghost" data-nsec-cancel>Cancel</button>
|
||||
<button type="submit" class="btn" data-nsec-submit>Continue</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template data-login-nip46-dialog>
|
||||
<form
|
||||
data-nip46-form
|
||||
class="space-y-4 rounded-xl border border-border bg-overlay-panel-soft px-5 py-4"
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-lg font-semibold text-text">Pair a remote signer</h3>
|
||||
<p class="text-sm text-text-muted">
|
||||
Scan the QR code with your signer or share the connect link to start a
|
||||
Nostr Connect session.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
data-nip46-error
|
||||
class="hidden rounded-lg border border-danger/60 bg-danger/10 px-4 py-2 text-sm text-danger"
|
||||
role="alert"
|
||||
></div>
|
||||
<div data-nip46-handshake-panel class="space-y-4">
|
||||
<div class="flex flex-col items-center gap-5 sm:items-stretch">
|
||||
<div
|
||||
class="flex aspect-square w-full max-w-sm items-center justify-center rounded-2xl border border-border-strong bg-white p-5 shadow-sm sm:max-w-md sm:p-8"
|
||||
data-nip46-qr
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="w-full text-center text-sm text-text-muted">
|
||||
Generating connect code…
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full space-y-3 sm:max-w-xl">
|
||||
<label class="block text-sm font-medium text-text" for="nip46HandshakeUri">
|
||||
Connect URI
|
||||
</label>
|
||||
<textarea
|
||||
id="nip46HandshakeUri"
|
||||
class="input"
|
||||
rows="3"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
data-nip46-handshake-uri
|
||||
readonly
|
||||
></textarea>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ghost w-full text-sm sm:w-auto"
|
||||
data-nip46-copy-uri
|
||||
disabled
|
||||
>
|
||||
Copy link
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
data-nip46-secret
|
||||
class="hidden break-words text-xs text-text-muted"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-nip46-status
|
||||
class="hidden rounded-lg border border-info/40 bg-info/10 px-4 py-2 text-sm text-info"
|
||||
role="status"
|
||||
></div>
|
||||
<div
|
||||
data-nip46-auth
|
||||
class="hidden rounded-lg border border-warning/50 bg-warning/10 px-4 py-2 text-sm text-warning"
|
||||
role="alert"
|
||||
>
|
||||
<p data-nip46-auth-message class="mb-2"></p>
|
||||
<button type="button" class="btn-link text-sm" data-nip46-auth-open>
|
||||
Open signer challenge
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<button type="button" class="btn-link text-sm" data-nip46-toggle-manual>
|
||||
Paste a signer-provided bunker link instead
|
||||
</button>
|
||||
<div class="hidden space-y-2" data-nip46-manual-fields>
|
||||
<label class="block text-sm font-medium text-text" for="nip46LegacyUri">
|
||||
Signer link
|
||||
</label>
|
||||
<textarea
|
||||
id="nip46LegacyUri"
|
||||
class="input"
|
||||
rows="2"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
placeholder="bunker://…"
|
||||
data-nip46-legacy-uri
|
||||
></textarea>
|
||||
<p class="text-xs text-text-muted">
|
||||
Use this fallback when your signer cannot scan QR codes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-text">
|
||||
<input type="checkbox" class="checkbox" data-nip46-remember checked />
|
||||
<span>Remember this signer for future logins</span>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-link text-sm hidden"
|
||||
data-nip46-reuse
|
||||
>
|
||||
Reconnect using the saved session
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" class="btn-ghost" data-nip46-cancel>Cancel</button>
|
||||
<button type="submit" class="btn" data-nip46-submit>Connect</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,44 +1,38 @@
|
||||
<!-- components/revert-video-modal.html -->
|
||||
<div
|
||||
id="revertVideoModal"
|
||||
class="fixed inset-0 z-50 hidden"
|
||||
style="background: transparent"
|
||||
class="bv-modal hidden items-start justify-center md:items-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="revertModalTitle"
|
||||
aria-describedby="revertModalSubtitle"
|
||||
data-ds="new"
|
||||
>
|
||||
<div
|
||||
id="revertVideoModalOverlay"
|
||||
class="absolute inset-0 z-10"
|
||||
style="
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
"
|
||||
class="bv-modal-backdrop"
|
||||
data-dismiss
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="relative modal-container h-full w-full flex items-start md:items-center justify-center overflow-y-auto z-20"
|
||||
>
|
||||
<div
|
||||
class="modal-content bg-gray-900 w-full max-w-[95%] lg:max-w-5xl my-0 rounded-lg overflow-hidden relative"
|
||||
style="max-height: 92vh"
|
||||
>
|
||||
<div
|
||||
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent transition-transform duration-150 p-4 flex items-center justify-between"
|
||||
>
|
||||
<div class="modal-sheet max-h-modal-sheet overflow-hidden" tabindex="-1">
|
||||
<div class="modal-header transition-transform duration-150">
|
||||
<div class="flex flex-col">
|
||||
<h2 id="revertModalTitle" class="text-xl font-bold text-white">
|
||||
<h2 id="revertModalTitle" class="text-xl font-bold text-text">
|
||||
Revert Video Note
|
||||
</h2>
|
||||
<p id="revertModalSubtitle" class="text-xs text-gray-400">
|
||||
<p id="revertModalSubtitle" class="text-xs text-muted">
|
||||
Review previous versions before restoring an older state.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
id="closeRevertVideoModal"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
|
||||
class="modal-close"
|
||||
type="button"
|
||||
aria-label="Close revert modal"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-gray-300"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -53,63 +47,157 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6 space-y-6 overflow-y-auto" style="max-height: calc(92vh - 4rem)">
|
||||
<div class="rounded-md border border-gray-700 bg-gray-800/60 p-4 text-sm text-gray-300">
|
||||
<div class="modal-body space-y-6 overflow-y-auto max-h-modal-body">
|
||||
<div
|
||||
class="rounded-md border border-border/60 bg-panel/60 p-4 text-sm text-muted-strong"
|
||||
>
|
||||
<p>
|
||||
Selecting an older version will mark any newer edits as reverted.
|
||||
The previous revision will become the active note on the relays.
|
||||
</p>
|
||||
<p class="mt-2 text-xs text-gray-400">
|
||||
Versions are grouped by their <code class="px-1 py-0.5 bg-gray-900/60 rounded border border-gray-700 text-[0.7rem]">d</code>
|
||||
tag or shared <code class="px-1 py-0.5 bg-gray-900/60 rounded border border-gray-700 text-[0.7rem]">videoRootId</code>. This ensures legacy
|
||||
clients see consistent history when multiple edits were published.
|
||||
<p class="mt-2 text-xs text-muted">
|
||||
Versions are grouped by their
|
||||
<code
|
||||
class="rounded border border-border/60 bg-surface/60 px-1 py-0.5 text-2xs"
|
||||
>d</code
|
||||
>
|
||||
tag or shared
|
||||
<code
|
||||
class="rounded border border-border/60 bg-surface/60 px-1 py-0.5 text-2xs"
|
||||
>videoRootId</code
|
||||
>. This ensures legacy clients see consistent history when multiple
|
||||
edits were published, and keeps audience flags (NSFW / for kids)
|
||||
aligned with the Upload modal.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-[minmax(240px,280px)_1fr]">
|
||||
<div class="revert-modal__layout">
|
||||
<aside class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-gray-200">Revision history</h3>
|
||||
<h3 class="text-sm font-semibold text-text">
|
||||
Revision history
|
||||
</h3>
|
||||
<span
|
||||
id="revertHistoryCount"
|
||||
class="rounded-full bg-gray-800 px-2 py-0.5 text-xs text-gray-400"
|
||||
class="rounded-full bg-panel px-2 py-0.5 text-xs text-muted"
|
||||
></span>
|
||||
</div>
|
||||
<div
|
||||
id="revertVersionsList"
|
||||
class="space-y-2 overflow-y-auto pr-1"
|
||||
style="max-height: 48vh"
|
||||
class="space-y-2 overflow-y-auto pr-1 max-h-modal-pane"
|
||||
></div>
|
||||
</aside>
|
||||
|
||||
<section
|
||||
id="revertVersionDetails"
|
||||
class="rounded-lg border border-gray-800 bg-gray-900/70 p-4 text-sm text-gray-200"
|
||||
class="rounded-lg border border-border/70 bg-surface/70 p-4 text-sm text-text"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div id="revertVersionPlaceholder" class="flex h-full items-center justify-center text-center text-gray-500">
|
||||
Select an earlier revision to inspect its metadata.
|
||||
<!-- NOTE: Keep Upload/Edit metadata fields mirrored when adjusting these sections. -->
|
||||
<div
|
||||
class="space-y-6"
|
||||
aria-labelledby="revertVersionPlaceholderHeading"
|
||||
>
|
||||
<div class="space-y-2 text-center">
|
||||
<h3
|
||||
id="revertVersionPlaceholderHeading"
|
||||
class="text-sm font-semibold text-text"
|
||||
>
|
||||
Revision metadata
|
||||
</h3>
|
||||
<p id="revertVersionPlaceholder" class="text-sm text-muted">
|
||||
Select an earlier revision to inspect its metadata.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section class="space-y-2">
|
||||
<h4
|
||||
class="text-xs font-semibold uppercase tracking-wide text-muted"
|
||||
>
|
||||
Playback sources
|
||||
</h4>
|
||||
<dl class="grid gap-2 text-xs text-muted sm:grid-cols-2">
|
||||
<div class="space-y-1">
|
||||
<dt class="font-semibold text-muted-strong">Hosted URL</dt>
|
||||
<dd class="text-muted">Awaiting selection</dd>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<dt class="font-semibold text-muted-strong">Magnet</dt>
|
||||
<dd class="text-muted">Awaiting selection</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="space-y-2">
|
||||
<h4
|
||||
class="text-xs font-semibold uppercase tracking-wide text-muted"
|
||||
>
|
||||
Audience flags
|
||||
</h4>
|
||||
<dl class="grid gap-2 text-xs text-muted sm:grid-cols-2">
|
||||
<div class="space-y-1">
|
||||
<dt class="font-semibold text-muted-strong">NSFW flag</dt>
|
||||
<dd class="text-muted">Awaiting selection</dd>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<dt class="font-semibold text-muted-strong">For kids flag</dt>
|
||||
<dd class="text-muted">Awaiting selection</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="space-y-2">
|
||||
<h4
|
||||
class="text-xs font-semibold uppercase tracking-wide text-muted"
|
||||
>
|
||||
NIP-71 event metadata
|
||||
</h4>
|
||||
<dl class="grid gap-2 text-xs text-muted sm:grid-cols-2">
|
||||
<div class="space-y-1">
|
||||
<dt class="font-semibold text-muted-strong">Kind</dt>
|
||||
<dd class="text-muted">Awaiting selection</dd>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<dt class="font-semibold text-muted-strong">Duration</dt>
|
||||
<dd class="text-muted">Awaiting selection</dd>
|
||||
</div>
|
||||
<div class="space-y-1 sm:col-span-2">
|
||||
<dt class="font-semibold text-muted-strong">Summary</dt>
|
||||
<dd class="text-muted">Awaiting selection</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="space-y-2">
|
||||
<h4
|
||||
class="text-xs font-semibold uppercase tracking-wide text-muted"
|
||||
>
|
||||
NIP-71 media metadata
|
||||
</h4>
|
||||
<p class="text-xs text-muted">
|
||||
Media variants, text tracks, chapters, hashtags, participants,
|
||||
and references will appear once a revision is selected.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<p
|
||||
id="revertSelectionStatus"
|
||||
class="text-xs text-gray-400"
|
||||
></p>
|
||||
<p id="revertSelectionStatus" class="text-xs text-muted"></p>
|
||||
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:justify-end">
|
||||
<button
|
||||
type="button"
|
||||
id="cancelRevertVideo"
|
||||
class="w-full sm:w-auto px-4 py-2 rounded-md bg-gray-800 text-gray-200 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-gray-500"
|
||||
class="btn-ghost w-full sm:w-auto"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="confirmRevertVideo"
|
||||
class="w-full sm:w-auto px-4 py-2 rounded-md bg-red-600 text-white hover:bg-red-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-red-500 disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
class="btn w-full sm:w-auto"
|
||||
data-variant="critical"
|
||||
disabled
|
||||
>
|
||||
Revert to selected version
|
||||
|
||||
@@ -5,176 +5,234 @@
|
||||
-->
|
||||
<aside
|
||||
id="sidebar"
|
||||
class="bg-gray-900 text-white transition-transform duration-300 ease-in-out fixed top-0 left-0 w-64 h-screen overflow-y-auto fade-in fade-in-delay-100"
|
||||
class="fade-in fade-in-delay-100 sidebar-collapsed"
|
||||
data-state="collapsed"
|
||||
data-ds="new"
|
||||
>
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Top Navigation Links -->
|
||||
<div class="flex-1 overflow-y-auto px-4">
|
||||
<div class="bg-gray-800 rounded-lg shadow-md mt-4 p-4">
|
||||
<nav class="flex flex-col space-y-2">
|
||||
<a
|
||||
href="#view=most-recent-videos"
|
||||
class="flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
|
||||
<div class="sidebar__inner">
|
||||
<div class="sidebar-scroll">
|
||||
<!-- Primary navigation -->
|
||||
<section class="sidebar-panel sidebar-panel--primary">
|
||||
<div class="sidebar-panel__header">
|
||||
<button
|
||||
id="sidebarCollapseToggle"
|
||||
type="button"
|
||||
class="sidebar-collapse-toggle"
|
||||
aria-controls="sidebar"
|
||||
aria-expanded="false"
|
||||
aria-label="Expand sidebar"
|
||||
title="Expand sidebar"
|
||||
>
|
||||
<svg
|
||||
data-sidebar-chevron
|
||||
class="sidebar-collapse-toggle__icon sidebar-toggle-icon--rotated"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M10 6l6 6-6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<nav class="sidebar-nav" aria-label="Primary">
|
||||
<a href="#view=most-recent-videos" class="sidebar-nav-link" title="Recent">
|
||||
<img
|
||||
src="assets/svg/recent-icon.svg"
|
||||
alt="Recent"
|
||||
class="w-6 h-6 mr-3 flex-shrink-0"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Recent</span>
|
||||
</a>
|
||||
<a
|
||||
href="#view=explore"
|
||||
class="flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
|
||||
>
|
||||
<a href="#view=explore" class="sidebar-nav-link" title="Explore">
|
||||
<img
|
||||
src="assets/svg/explore-icon.svg"
|
||||
alt="Explore"
|
||||
class="w-6 h-6 mr-3 flex-shrink-0"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Explore</span>
|
||||
</a>
|
||||
<a
|
||||
href="#view=history"
|
||||
class="flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
|
||||
>
|
||||
<a href="#view=history" class="sidebar-nav-link" title="History">
|
||||
<img
|
||||
src="assets/svg/history-icon.svg"
|
||||
alt="History"
|
||||
class="w-6 h-6 mr-3 flex-shrink-0"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">History</span>
|
||||
</a>
|
||||
<a
|
||||
id="subscriptionsLink"
|
||||
href="#view=subscriptions"
|
||||
class="hidden flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
|
||||
class="sidebar-nav-link hidden"
|
||||
title="Subscriptions"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/subscriptions-icon.svg"
|
||||
alt="Subscriptions"
|
||||
class="w-6 h-6 mr-3 flex-shrink-0"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Subscriptions</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer Links Toggle -->
|
||||
<div class="md:mt-auto">
|
||||
<div class="p-4">
|
||||
<!-- Footer Links Toggle -->
|
||||
<footer class="sidebar-footer" data-footer-state="collapsed">
|
||||
<button
|
||||
id="footerDropdownButton"
|
||||
type="button"
|
||||
class="flex items-center w-full py-2 px-4 bg-gray-800 hover:bg-gray-700 text-sm font-semibold text-gray-100 rounded shadow-md focus:outline-none transition-colors duration-200"
|
||||
class="sidebar-dropup-trigger"
|
||||
aria-expanded="false"
|
||||
aria-controls="footerLinksContainer"
|
||||
>
|
||||
<!-- Arrow on the left side -->
|
||||
<img
|
||||
src="assets/svg/ellipsis.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon sidebar-dropup-trigger__glyph"
|
||||
/>
|
||||
<span id="footerDropdownText" class="sidebar-text">More</span>
|
||||
<svg
|
||||
id="footerDropdownIcon"
|
||||
class="w-4 h-4 mr-2 transform transition-transform duration-300"
|
||||
class="sidebar-dropup-trigger__chevron"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
<span id="footerDropdownText">More</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer Links Container -->
|
||||
<div id="footerLinksContainer" class="hidden">
|
||||
<hr class="border-gray-700" />
|
||||
<div class="p-4">
|
||||
<nav class="space-y-2 text-sm opacity-70">
|
||||
<a
|
||||
href="#view=about"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
>
|
||||
<!-- Footer Links Container -->
|
||||
<div
|
||||
id="footerLinksContainer"
|
||||
class="sidebar-dropup-panel"
|
||||
aria-hidden="true"
|
||||
data-state="collapsed"
|
||||
>
|
||||
<nav class="sidebar-dropup-panel__inner" aria-label="Secondary">
|
||||
<a href="#view=about" class="sidebar-nav-link sidebar-nav-link--sub" title="About">
|
||||
<img
|
||||
src="assets/svg/about-icon.svg"
|
||||
alt="About"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">About</span>
|
||||
</a>
|
||||
<a
|
||||
href="#view=community-guidelines"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="Community Guidelines"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/guidelines-icon.svg"
|
||||
alt="Guidelines"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Guidelines</span>
|
||||
</a>
|
||||
<a
|
||||
href="#view=getting-started"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="Getting Started"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/getting-started-icon.svg"
|
||||
alt="Getting Started"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Getting Started</span>
|
||||
</a>
|
||||
<a
|
||||
href="#view=roadmap"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
>
|
||||
<a href="#view=roadmap" class="sidebar-nav-link sidebar-nav-link--sub" title="Roadmap">
|
||||
<img
|
||||
src="assets/svg/roadmap-icon.svg"
|
||||
alt="Roadmap"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Roadmap</span>
|
||||
</a>
|
||||
<a
|
||||
href="#view=blog"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
href="#"
|
||||
data-blog-link
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="Blog"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/blog-icon.svg"
|
||||
alt="Blog"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Blog</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
|
||||
target="_blank"
|
||||
href="#"
|
||||
data-nostr-link
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="Nostr"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/nostr-icon.svg"
|
||||
alt="Nostr"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Nostr</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/PR0M3TH3AN/bitvid"
|
||||
target="_blank"
|
||||
href="#"
|
||||
data-community-link
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="Community"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg
|
||||
class="sidebar-icon"
|
||||
width="24"
|
||||
height="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M24 20h-3v4l-5.333-4h-7.667v-4h2v2h6.333l2.667 2v-2h3v-8.001h-2v-2h4v12.001zm-6-6h-9.667l-5.333 4v-4h-3v-14.001h18v14.001zm-13-8c.552 0 1 .448 1 1s-.448 1-1 1-1-.448-1-1 .448-1 1-1zm4 0c.552 0 1 .448 1 1s-.448 1-1 1-1-.448-1-1 .448-1 1-1zm4 0c.552 0 1 .448 1 1s-.448 1-1 1-1-.448-1-1 .448-1 1-1z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="sidebar-text">Community</span>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
data-github-link
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="GitHub"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/github-icon.svg"
|
||||
alt="GitHub"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">GitHub</span>
|
||||
</a>
|
||||
@@ -182,84 +240,90 @@
|
||||
href="torrent/beacon.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="βeacon"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/beacon-icon.svg"
|
||||
alt="βeacon"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">βeacon</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://beta.bitvid.network/"
|
||||
target="_blank"
|
||||
href="#"
|
||||
data-beta-link
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="Beta"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/beta-icon.svg"
|
||||
alt="Beta"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Beta</span>
|
||||
</a>
|
||||
<a
|
||||
href="#view=links"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
>
|
||||
<a href="#view=links" class="sidebar-nav-link sidebar-nav-link--sub" title="Links">
|
||||
<img
|
||||
src="assets/svg/links-icon.svg"
|
||||
alt="Links"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">Links</span>
|
||||
</a>
|
||||
<a
|
||||
href="#view=ipns"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
>
|
||||
<a href="#view=ipns" class="sidebar-nav-link sidebar-nav-link--sub" title="IPNS">
|
||||
<img
|
||||
src="assets/svg/ipns-icon.svg"
|
||||
alt="IPNS"
|
||||
class="w-5 h-5 mr-3"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">IPNS</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://bitvid.network/"
|
||||
target="_blank"
|
||||
href="#"
|
||||
data-dns-link
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="DNS"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
|
||||
>
|
||||
<img src="assets/svg/dns-icon.svg" alt="DNS" class="w-5 h-5 mr-3" />
|
||||
<img
|
||||
src="assets/svg/dns-icon.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span class="sidebar-text">DNS</span>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
data-tip-jar-link
|
||||
class="sidebar-nav-link sidebar-nav-link--sub"
|
||||
title="Tip Jar"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
class="sidebar-icon"
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm0 18v-1.511h-.5v1.511h-1v-1.511h-2.484l.25-1.489h.539c.442 0 .695-.425.695-.854v-4.444c0-.416-.242-.702-.683-.702h-.817v-1.5h2.5v-1.5h1v1.5h.5v-1.5h1v1.526c2.158.073 3.012.891 3.257 1.812.29 1.09-.429 2.005-1.046 2.228.75.192 1.789.746 1.789 2.026 0 1.742-1.344 2.908-4 2.908v1.5h-1zm-.5-5.503v2.503c1.984 0 3.344-.188 3.344-1.258 0-1.148-1.469-1.245-3.344-1.245zm0-.997c1.105 0 2.789-.078 2.789-1.25 0-1-1.039-1.25-2.789-1.25v2.5z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="sidebar-text">Tip Jar</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var btn = document.getElementById("footerDropdownButton");
|
||||
var footerLinks = document.getElementById("footerLinksContainer");
|
||||
if (btn && footerLinks) {
|
||||
btn.addEventListener("click", function () {
|
||||
footerLinks.classList.toggle("hidden");
|
||||
var isHidden = footerLinks.classList.contains("hidden");
|
||||
btn.setAttribute("aria-expanded", String(!isHidden));
|
||||
var icon = document.getElementById("footerDropdownIcon");
|
||||
var textSpan = document.getElementById("footerDropdownText");
|
||||
if (textSpan) {
|
||||
textSpan.textContent = isHidden ? "More" : "Less";
|
||||
}
|
||||
if (icon) {
|
||||
icon.classList.toggle("rotate-180", !isHidden);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</aside>
|
||||
@@ -1,186 +1,645 @@
|
||||
<!-- components/video-modal.html -->
|
||||
<div id="playerModal" class="player-modal hidden">
|
||||
<div class="player-modal__container">
|
||||
<div class="player-modal__content">
|
||||
<!-- Navigation bar - sliding at top -->
|
||||
<div
|
||||
id="modalNav"
|
||||
class="modal-nav sticky top-0 z-60 transition-transform duration-150"
|
||||
>
|
||||
<div class="flex items-center px-6 py-4">
|
||||
<button
|
||||
id="closeModal"
|
||||
class="back-button flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="w-6 h-6 text-gray-300"
|
||||
>
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="modal-actions">
|
||||
<button id="copyMagnetBtn" class="icon-button">
|
||||
<img
|
||||
src="assets/svg/copy-magnet.svg"
|
||||
alt="Copy Magnet"
|
||||
class="icon-image"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
id="modalZapBtn"
|
||||
type="button"
|
||||
class="icon-button"
|
||||
aria-label="Send a zap"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/lightning-bolt.svg"
|
||||
alt="Zap"
|
||||
class="icon-image"
|
||||
/>
|
||||
</button>
|
||||
<button id="shareBtn" class="icon-button">
|
||||
<img
|
||||
src="assets/svg/share-video.svg"
|
||||
alt="Share Video"
|
||||
class="icon-image"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="modal-actions__overflow"
|
||||
data-more-menu-wrapper="true"
|
||||
>
|
||||
<button
|
||||
id="modalMoreBtn"
|
||||
type="button"
|
||||
class="icon-button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-label="More"
|
||||
data-more-dropdown="modal"
|
||||
>
|
||||
<img src="assets/svg/ellipsis.svg" alt="More" class="w-5 h-5" />
|
||||
</button>
|
||||
<div
|
||||
id="moreDropdown-modal"
|
||||
class="hidden absolute right-0 top-14 w-44 rounded-md shadow-lg bg-gray-800 ring-1 ring-black ring-opacity-5 z-50"
|
||||
role="menu"
|
||||
data-more-menu="true"
|
||||
>
|
||||
<div class="py-1">
|
||||
<button
|
||||
class="block w-full text-left px-4 py-2 text-sm text-gray-100 hover:bg-gray-700"
|
||||
data-action="open-channel"
|
||||
data-context="modal"
|
||||
<div
|
||||
id="playerModal"
|
||||
class="bv-modal hidden"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
data-ds="new"
|
||||
hidden
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
<div class="bv-modal__panel" tabindex="-1">
|
||||
<div class="player-modal__container video-modal__container">
|
||||
<div id="modalNav" class="modal-nav modal-nav--visible">
|
||||
<div class="watch-container modal-nav__bar">
|
||||
<div class="modal-nav__rail">
|
||||
<div class="modal-nav__slot modal-nav__slot--primary">
|
||||
<div class="modal-actions">
|
||||
<button
|
||||
id="closeModal"
|
||||
type="button"
|
||||
class="icon-button focus-ring accent-action-button"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
Open channel
|
||||
</button>
|
||||
<button
|
||||
class="block w-full text-left px-4 py-2 text-sm text-gray-100 hover:bg-gray-700"
|
||||
data-action="copy-link"
|
||||
data-context="modal"
|
||||
>
|
||||
Copy link
|
||||
</button>
|
||||
<button
|
||||
class="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-700 hover:text-white"
|
||||
data-action="blacklist-author"
|
||||
data-context="modal"
|
||||
data-blacklist-menu="true"
|
||||
>
|
||||
Blacklist creator
|
||||
</button>
|
||||
<button
|
||||
class="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-700 hover:text-white"
|
||||
data-action="block-author"
|
||||
data-context="modal"
|
||||
>
|
||||
Block creator
|
||||
</button>
|
||||
<button
|
||||
class="block w-full text-left px-4 py-2 text-sm text-gray-100 hover:bg-gray-700"
|
||||
data-action="report"
|
||||
data-context="modal"
|
||||
>
|
||||
Report
|
||||
</button>
|
||||
</div>
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</svg>
|
||||
<span class="sr-only">Close player</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="modal-nav__slot modal-nav__slot--context"
|
||||
data-modal-nav-context
|
||||
>
|
||||
<div id="modalNavContext" class="modal-nav__context" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="player-modal__video modal-video-shell">
|
||||
<video id="modalVideo" class="w-full aspect-video" controls></video>
|
||||
</div>
|
||||
<div class="player-modal__content video-modal__panel flex flex-col gap-6">
|
||||
<div class="video-modal__layout">
|
||||
<div class="video-modal__primary flex flex-col gap-6">
|
||||
<div class="watch-container video-modal__stage">
|
||||
<div class="video-modal__ambient" aria-hidden="true">
|
||||
<canvas
|
||||
id="ambientCanvas"
|
||||
class="video-modal__ambient-canvas"
|
||||
></canvas>
|
||||
</div>
|
||||
<div class="video-modal__video" data-video-surface>
|
||||
<div
|
||||
class="video-modal__moderation-overlay"
|
||||
data-moderation-overlay
|
||||
hidden
|
||||
>
|
||||
<div
|
||||
class="moderation-badge"
|
||||
data-moderation-badge="true"
|
||||
data-variant="warning"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>
|
||||
<span class="moderation-badge__label inline-flex items-center gap-xs">
|
||||
<span class="moderation-badge__icon" aria-hidden="true">
|
||||
<svg
|
||||
class="moderation-badge__icon-mark"
|
||||
viewBox="0 0 20 20"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm-.75-11.75a.75.75 0 011.5 0v4.5a.75.75 0 01-1.5 0v-4.5zm.75 8.5a1 1 0 100-2 1 1 0 000 2z"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="moderation-badge__text"
|
||||
data-moderation-text
|
||||
>
|
||||
Content or user blocked by a trusted contact.
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class="moderation-badge__actions"
|
||||
data-moderation-actions
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="moderation-badge__action flex-shrink-0"
|
||||
data-moderation-action="override"
|
||||
aria-pressed="false"
|
||||
aria-label="Show anyway"
|
||||
>
|
||||
Show anyway
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<video
|
||||
id="modalVideo"
|
||||
class="video-modal__media"
|
||||
controls
|
||||
></video>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="player-modal__info modal-info-panel">
|
||||
<!-- Title and icons row -->
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h2 id="videoTitle" class="text-2xl font-bold text-white"></h2>
|
||||
</div>
|
||||
|
||||
<!-- Video Timestamp -->
|
||||
<div
|
||||
class="flex items-center justify-between text-sm text-gray-400 mb-4"
|
||||
>
|
||||
<div class="flex items-center gap-2 modal-meta">
|
||||
<span id="videoTimestamp">just now</span>
|
||||
<span id="videoViewCount" class="modal-view-count">– views</span>
|
||||
</div>
|
||||
<div id="modalStatus" class="text-gray-300">
|
||||
Initializing... Just give it a sec.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Creator info -->
|
||||
<div class="flex items-center mb-4 p-4 bg-gray-800/50 rounded-lg">
|
||||
<div class="w-12 h-12 rounded-full bg-gray-700 overflow-hidden">
|
||||
<img
|
||||
id="creatorAvatar"
|
||||
src=""
|
||||
alt="Creator"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<h3 id="creatorName" class="font-medium text-lg text-white">
|
||||
Creator Name
|
||||
</h3>
|
||||
<p id="creatorNpub" class="text-sm text-gray-400">npub...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Video Description -->
|
||||
<div class="bg-gray-800/50 rounded-lg p-4 mb-4">
|
||||
<p id="videoDescription" class="text-gray-300 whitespace-pre-wrap">
|
||||
No description available.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Torrent stats -->
|
||||
<div class="bg-gray-800/50 rounded-lg p-4">
|
||||
<div class="w-full bg-gray-700 rounded-full h-2 mb-2">
|
||||
<div
|
||||
class="bg-blue-500 h-2 rounded-full"
|
||||
id="modalProgress"
|
||||
style="width: 0%"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm text-gray-400">
|
||||
<span id="modalPeers">Peers: 0</span>
|
||||
<span id="modalSpeed">Speed: 0 KB/s</span>
|
||||
<span id="modalDownloaded">Downloaded: 0 MB / 0 MB</span>
|
||||
class="watch-container"
|
||||
data-video-stats-container
|
||||
hidden
|
||||
>
|
||||
<div
|
||||
class="video-modal__stats rounded-2xl border border-border border-opacity-20 bg-surface px-6 py-5 shadow-sm"
|
||||
data-video-stats
|
||||
>
|
||||
<div class="video-modal__progress">
|
||||
<progress
|
||||
id="modalProgress"
|
||||
class="progress"
|
||||
max="100"
|
||||
value="0"
|
||||
data-variant="accent"
|
||||
aria-label="Download progress"
|
||||
></progress>
|
||||
<p
|
||||
id="modalProgressStatus"
|
||||
class="sr-only"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
></p>
|
||||
</div>
|
||||
<div class="video-modal__stat-metrics flex flex-wrap items-center justify-between gap-3 text-sm text-muted">
|
||||
<span id="modalPeers">Peers: 0</span>
|
||||
<span id="modalSpeed">Speed: 0 KB/s</span>
|
||||
<span id="modalDownloaded">Downloaded: 0 MB / 0 MB</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="watch-container">
|
||||
<div class="modal-info-panel">
|
||||
<div class="video-modal__details flex flex-col gap-6">
|
||||
<div
|
||||
class="video-modal__headline flex flex-col gap-3 lg:flex-row lg:flex-wrap lg:items-start lg:justify-between"
|
||||
>
|
||||
<h2 id="videoTitle" class="video-modal__title"></h2>
|
||||
<div
|
||||
id="modalStatus"
|
||||
class="video-modal__status text-sm text-muted lg:text-right lg:ml-auto"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
Initializing... Just give it a sec.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="video-modal__meta flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div class="video-modal__meta-info flex flex-wrap items-center gap-3">
|
||||
<span id="videoTimestamp" class="modal-meta">Posted just now</span>
|
||||
<span id="videoEditedTimestamp" class="modal-meta hidden">Last edited just now</span>
|
||||
<span id="videoViewCount" class="modal-meta modal-view-count">– views</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-modal__actions-row">
|
||||
<div class="video-modal__reactions">
|
||||
<div class="video-modal__reaction-buttons inline-flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
id="modalLikeBtn"
|
||||
type="button"
|
||||
class="video-modal__action-button video-modal__reaction-button focus-ring accent-action-button"
|
||||
data-reaction="+"
|
||||
aria-pressed="false"
|
||||
aria-label="Like this video"
|
||||
>
|
||||
<svg
|
||||
class="video-modal__action-icon icon-image"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M7 10v12H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M7 10 11.34 2.67A2 2 0 0 1 13.14 2H14a2 2 0 0 1 2 2v4h3.37a2 2 0 0 1 1.97 2.5l-1.1 5A2 2 0 0 1 18.3 18H11"
|
||||
/>
|
||||
</svg>
|
||||
<span class="video-modal__action-label">Like</span>
|
||||
<span class="video-modal__reaction-count" data-reaction-like-count>0</span>
|
||||
</button>
|
||||
<button
|
||||
id="modalDislikeBtn"
|
||||
type="button"
|
||||
class="video-modal__action-button video-modal__reaction-button focus-ring accent-action-button"
|
||||
data-reaction="-"
|
||||
aria-pressed="false"
|
||||
aria-label="Dislike this video"
|
||||
>
|
||||
<svg
|
||||
class="video-modal__action-icon icon-image"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M17 14V2h3a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M17 14 12.66 21.33A2 2 0 0 1 10.86 22H10a2 2 0 0 1-2-2v-4H4.63a2 2 0 0 1-1.97-2.5l1.1-5A2 2 0 0 1 4.73 8H13"
|
||||
/>
|
||||
</svg>
|
||||
<span class="video-modal__action-label">Dislike</span>
|
||||
<span class="video-modal__reaction-count" data-reaction-dislike-count>0</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="video-modal__reaction-meter"
|
||||
data-reaction-meter
|
||||
role="meter"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="0"
|
||||
aria-valuetext="No reactions yet"
|
||||
>
|
||||
<span class="video-modal__reaction-meter-label" data-reaction-meter-label>No reactions yet</span>
|
||||
<div class="video-modal__reaction-meter-track" aria-hidden="true">
|
||||
<div
|
||||
class="video-modal__reaction-meter-fill"
|
||||
data-reaction-meter-fill
|
||||
style="width: 0%"
|
||||
></div>
|
||||
</div>
|
||||
<span class="sr-only" data-reaction-meter-sr>No reactions yet</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-modal__actions-group">
|
||||
<button
|
||||
id="copyMagnetBtn"
|
||||
type="button"
|
||||
class="video-modal__action-button focus-ring accent-action-button"
|
||||
aria-label="Copy magnet"
|
||||
>
|
||||
<svg
|
||||
class="video-modal__action-icon icon-image"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M17.374 20.235c2.444-2.981 6.626-8.157 6.626-8.157l-3.846-3.092s-2.857 3.523-6.571 8.097c-4.312 5.312-11.881-2.41-6.671-6.671 4.561-3.729 8.097-6.57 8.097-6.57l-3.092-3.842s-5.173 4.181-8.157 6.621c-2.662 2.175-3.76 4.749-3.76 7.24 0 5.254 4.867 10.139 10.121 10.139 2.487 0 5.064-1.095 7.253-3.765zm4.724-7.953l-1.699 2.111-1.74-1.397 1.701-2.114 1.738 1.4zm-10.386-10.385l1.4 1.738-2.113 1.701-1.397-1.74 2.11-1.699z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="video-modal__action-label">Copy magnet</span>
|
||||
</button>
|
||||
<div class="popover" data-modal-zap-wrapper>
|
||||
<button
|
||||
id="modalZapBtn"
|
||||
type="button"
|
||||
class="video-modal__action-button focus-ring accent-action-button"
|
||||
aria-label="Send a zap"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls="modalZapDialog"
|
||||
>
|
||||
<svg
|
||||
class="video-modal__action-icon icon-image"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<polygon
|
||||
points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span class="video-modal__action-label">Zap</span>
|
||||
</button>
|
||||
<div
|
||||
id="modalZapDialog"
|
||||
class="popover__panel card w-72 max-w-popover-safe px-5 py-4"
|
||||
data-variant="zap"
|
||||
role="dialog"
|
||||
aria-hidden="true"
|
||||
data-state="closed"
|
||||
hidden
|
||||
>
|
||||
<div class="grid gap-4">
|
||||
<div class="mb-4 flex items-center justify-between gap-4">
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wide text-muted">
|
||||
Send a zap
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
id="modalZapCloseBtn"
|
||||
class="focus-ring inline-flex h-8 w-8 items-center justify-center rounded-full text-muted transition hover:text-text"
|
||||
aria-label="Close zap dialog"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
id="modalZapWalletPrompt"
|
||||
class="mb-3 text-sm text-muted"
|
||||
aria-hidden="true"
|
||||
hidden
|
||||
>
|
||||
Connect a wallet in
|
||||
<button
|
||||
type="button"
|
||||
id="modalZapWalletLink"
|
||||
class="focus-ring inline-flex items-center gap-1 text-sm font-semibold text-info underline decoration-dotted underline-offset-2 hover:text-info-strong"
|
||||
>
|
||||
Wallet Connect settings
|
||||
</button>
|
||||
to send zaps.
|
||||
</p>
|
||||
<form id="modalZapForm" class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
for="modalZapAmountInput"
|
||||
class="text-xs font-semibold uppercase tracking-wide text-muted"
|
||||
>
|
||||
Zap amount (sats)
|
||||
</label>
|
||||
<input
|
||||
id="modalZapAmountInput"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
inputmode="numeric"
|
||||
class="input"
|
||||
placeholder="Enter sats"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
for="modalZapCommentInput"
|
||||
class="text-xs font-semibold uppercase tracking-wide text-muted"
|
||||
>
|
||||
Comment (optional)
|
||||
</label>
|
||||
<textarea
|
||||
id="modalZapCommentInput"
|
||||
rows="2"
|
||||
maxlength="280"
|
||||
class="input resize-y"
|
||||
placeholder="Include a note with your zap"
|
||||
></textarea>
|
||||
</div>
|
||||
<p
|
||||
id="modalZapSplitSummary"
|
||||
class="text-sm text-muted"
|
||||
aria-live="polite"
|
||||
>
|
||||
Enter an amount to view the split.
|
||||
</p>
|
||||
<p
|
||||
id="modalZapStatus"
|
||||
class="text-xs text-muted"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
></p>
|
||||
<ul
|
||||
id="modalZapReceipts"
|
||||
class="space-y-2 text-xs text-muted"
|
||||
></ul>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
id="modalZapSendBtn"
|
||||
class="btn"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
id="shareBtn"
|
||||
type="button"
|
||||
class="video-modal__action-button focus-ring accent-action-button"
|
||||
aria-label="Share video"
|
||||
>
|
||||
<svg
|
||||
class="video-modal__action-icon icon-image"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5 7a5 5 0 1 1 0 10 5 5 0 0 1 0-10Zm11.122 12.065A4 4 0 1 1 24 20a4 4 0 0 1-7.878-.935l-5.488-2.927a6.009 6.009 0 0 1-.943 1.764l5.488 2.927ZM24 4a4 4 0 1 1-7.878.935l-5.488 2.927a6.01 6.01 0 0 1-.943-1.764l5.488-2.927A4 4 0 0 1 24 4Z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="video-modal__action-label">Share</span>
|
||||
</button>
|
||||
<div class="relative" data-more-menu-wrapper="true">
|
||||
<button
|
||||
id="modalMoreBtn"
|
||||
type="button"
|
||||
class="video-modal__action-button focus-ring accent-action-button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-label="More actions"
|
||||
data-more-dropdown="modal"
|
||||
>
|
||||
<svg
|
||||
class="video-modal__action-icon icon-image"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<circle cx="5" cy="12" r="2" fill="currentColor" />
|
||||
<circle cx="12" cy="12" r="2" fill="currentColor" />
|
||||
<circle cx="19" cy="12" r="2" fill="currentColor" />
|
||||
</svg>
|
||||
<span class="video-modal__action-label">More</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="channel-card">
|
||||
<div class="channel-card__avatar">
|
||||
<img
|
||||
id="creatorAvatar"
|
||||
src=""
|
||||
alt="Creator"
|
||||
class="h-full w-full rounded-full object-cover cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 id="creatorName" class="channel-card__name cursor-pointer">
|
||||
Creator Name
|
||||
</h3>
|
||||
<p id="creatorNpub" class="channel-card__npub">npub...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="description-panel">
|
||||
<p
|
||||
id="videoDescription"
|
||||
class="video-modal__description-body whitespace-pre-wrap"
|
||||
>
|
||||
No description available.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="comment-thread__disabled"
|
||||
data-comments-disabled-placeholder
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
hidden
|
||||
>
|
||||
Comments have been turned off for this video.
|
||||
</div>
|
||||
|
||||
<section
|
||||
class="video-modal__comments comment-thread"
|
||||
aria-labelledby="videoModalCommentsHeading"
|
||||
role="region"
|
||||
data-comments-root
|
||||
>
|
||||
<div class="comment-thread__header">
|
||||
<h2
|
||||
id="videoModalCommentsHeading"
|
||||
class="comment-thread__title"
|
||||
>
|
||||
Comments
|
||||
</h2>
|
||||
<p
|
||||
class="comment-thread__count"
|
||||
data-comments-count
|
||||
aria-live="polite"
|
||||
>
|
||||
0 comments
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="comment-thread__empty"
|
||||
data-comments-empty
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
No comments yet. Be the first to share your thoughts.
|
||||
</p>
|
||||
|
||||
<ol
|
||||
class="comment-thread__list"
|
||||
data-comments-list
|
||||
role="list"
|
||||
aria-live="polite"
|
||||
></ol>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="comment-thread__load-more focus-ring"
|
||||
data-comments-load-more
|
||||
hidden
|
||||
>
|
||||
Load more comments
|
||||
</button>
|
||||
|
||||
<form
|
||||
class="comment-composer"
|
||||
data-comments-composer
|
||||
aria-labelledby="videoModalCommentsHeading"
|
||||
novalidate
|
||||
>
|
||||
<div class="comment-composer__field">
|
||||
<label
|
||||
for="commentComposerInput"
|
||||
class="comment-composer__label"
|
||||
>
|
||||
Add a comment
|
||||
</label>
|
||||
<textarea
|
||||
id="commentComposerInput"
|
||||
name="comment"
|
||||
class="textarea comment-composer__textarea"
|
||||
rows="3"
|
||||
maxlength="500"
|
||||
placeholder="Share your thoughts"
|
||||
aria-describedby="commentComposerHelp commentComposerCount"
|
||||
data-comments-input
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="comment-composer__footer">
|
||||
<p
|
||||
id="commentComposerHelp"
|
||||
class="comment-composer__hint"
|
||||
>
|
||||
Keep it respectful and follow the community guidelines.
|
||||
</p>
|
||||
<div class="comment-composer__actions">
|
||||
<p
|
||||
id="commentComposerCount"
|
||||
class="comment-composer__char-count"
|
||||
data-comments-char-count
|
||||
aria-live="polite"
|
||||
>
|
||||
0 / 500
|
||||
</p>
|
||||
<button
|
||||
type="submit"
|
||||
class="comment-composer__submit btn"
|
||||
data-comments-submit
|
||||
disabled
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="comment-composer__status sr-only"
|
||||
data-comments-status
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
></p>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="video-modal__secondary flex flex-col gap-4">
|
||||
<div class="watch-container video-modal__similar">
|
||||
<div
|
||||
id="videoTags"
|
||||
class="video-modal__tags"
|
||||
hidden
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<section
|
||||
class="player-modal__module"
|
||||
aria-labelledby="playerModalSimilarContentHeading"
|
||||
>
|
||||
<h2
|
||||
id="playerModalSimilarContentHeading"
|
||||
class="player-modal__module-title"
|
||||
>
|
||||
Similar Content
|
||||
</h2>
|
||||
<ul
|
||||
id="playerModalSimilarContentList"
|
||||
class="player-modal__module-list player-modal__module-scroll"
|
||||
role="list"
|
||||
></ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const ASSET_VERSION = "2024.11.09";
|
||||
@@ -1,22 +1,51 @@
|
||||
// config/instance-config.js
|
||||
// -----------------------------------------------------------------------------
|
||||
// BitVid instance configuration
|
||||
// bitvid instance configuration
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// This file consolidates the values that operators usually customize when they
|
||||
// deploy their own BitVid instance. Update the exports below to match your
|
||||
// deploy their own bitvid instance. Update the exports below to match your
|
||||
// environment, commit the changes, and redeploy. Leaving this file in the repo
|
||||
// (instead of hidden in environment variables) makes it easy for future
|
||||
// maintainers to understand how your instance is configured.
|
||||
//
|
||||
// Each setting includes guidance about how BitVid uses the value and what
|
||||
// Each setting includes guidance about how bitvid uses the value and what
|
||||
// adjustments are safe. When in doubt, mirror the structure shown here so other
|
||||
// contributors can follow along.
|
||||
|
||||
/**
|
||||
* The primary administrator for this BitVid instance.
|
||||
* Whether the current deployment should enable developer-centric behavior.
|
||||
*
|
||||
* BitVid treats this Nostr public key (npub) as the "Super Admin". This user
|
||||
* When `true`, bitvid surfaces extra logging, debug helpers, and experimental
|
||||
* UI affordances intended for development environments. Set the value to
|
||||
* `false` before deploying to production so that end users receive the stable
|
||||
* experience. Downstream modules read this flag via `js/config.js`.
|
||||
*/
|
||||
export const IS_DEV_MODE = false;
|
||||
|
||||
/**
|
||||
* Whether development builds should surface extra verbose diagnostics.
|
||||
*
|
||||
* Set this to `true` to keep high-volume development warnings (like COUNT
|
||||
* request failures) visible in the console. Flip it to `false` when you want
|
||||
* a quieter dev mode without disabling other development helpers entirely.
|
||||
*/
|
||||
export const IS_VERBOSE_DEV_MODE = false;
|
||||
|
||||
/**
|
||||
* Whether the current deployment should operate in lockdown mode.
|
||||
*
|
||||
* When `true`, bitvid restricts access to operator-approved functionality and
|
||||
* enables defensive UI copy intended for incidents or maintenance windows.
|
||||
* Leave this `false` for normal operation so downstream modules (via
|
||||
* `js/config.js`) continue exposing the full experience.
|
||||
*/
|
||||
export const IS_LOCKDOWN_MODE = false;
|
||||
|
||||
/**
|
||||
* The primary administrator for this bitvid instance.
|
||||
*
|
||||
* bitvid treats this Nostr public key (npub) as the "Super Admin". This user
|
||||
* cannot be removed from moderator lists, and only they can promote new
|
||||
* moderators or toggle whitelist-only mode. Replace the string below with the
|
||||
* npub of the account you want to act as the ultimate authority for your
|
||||
@@ -25,6 +54,134 @@
|
||||
export const ADMIN_SUPER_NPUB =
|
||||
"npub15jnttpymeytm80hatjqcvhhqhzrhx6gxp8pq0wn93rhnu8s9h9dsha32lx";
|
||||
|
||||
/**
|
||||
* Canonical URL for the public bitvid site.
|
||||
*
|
||||
* Surfaces in admin outreach copy and moderation DMs so that recipients can
|
||||
* quickly jump back to the primary destination. Update this if your
|
||||
* deployment relies on a different canonical hostname.
|
||||
*/
|
||||
export const BITVID_WEBSITE_URL = "https://bitvid.network/";
|
||||
|
||||
/**
|
||||
* Optional external destination for the blog menu link.
|
||||
*
|
||||
* Sidebar navigation routes visitors to this URL when they select "Blog".
|
||||
* Update the value to point at the long-form publishing platform you
|
||||
* maintain for release notes or community updates. Leave empty (null or an
|
||||
* empty string) to hide the Blog link entirely.
|
||||
*/
|
||||
export const BLOG_URL =
|
||||
"https://habla.news/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe";
|
||||
|
||||
/**
|
||||
* Optional external destination for the community menu link.
|
||||
*
|
||||
* Configure this with a URL to your preferred community hub (e.g., Discord,
|
||||
* Telegram, Flotilla). When unset (null or an empty string) the Community link
|
||||
* is omitted from the sidebar dropup menu.
|
||||
*/
|
||||
export const COMMUNITY_URL = "https://groups.nip29.com/?relay=wss://relay.groups.nip29.com&groupId=d92ef5";
|
||||
|
||||
/**
|
||||
* Optional external destination for the Nostr menu link.
|
||||
*
|
||||
* Point this at the profile or relay hub you want visitors to land on when
|
||||
* they tap "Nostr" in the sidebar dropup. Leave empty (null or an empty
|
||||
* string) to hide the link entirely.
|
||||
*/
|
||||
export const NOSTR_URL =
|
||||
"https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe";
|
||||
|
||||
/**
|
||||
* Optional external destination for the GitHub menu link.
|
||||
*
|
||||
* Set this to the repository you want surfaced to operators and contributors.
|
||||
* Leave empty (null or an empty string) to remove the GitHub entry from the
|
||||
* sidebar dropup menu.
|
||||
*/
|
||||
export const GITHUB_URL = "https://github.com/PR0M3TH3AN/bitvid";
|
||||
|
||||
/**
|
||||
* Optional external destination for the Beta menu link.
|
||||
*
|
||||
* Deployments that maintain a staging or beta environment can direct users to
|
||||
* that surface here. Leave empty (null or an empty string) if there is no
|
||||
* public beta to advertise.
|
||||
*/
|
||||
export const BETA_URL = "https://beta.bitvid.network/";
|
||||
|
||||
/**
|
||||
* Optional external destination for the DNS menu link.
|
||||
*
|
||||
* Operators can expose their canonical DNS landing page here. Leave empty
|
||||
* (null or an empty string) to hide the DNS entry from the sidebar.
|
||||
*/
|
||||
export const DNS_URL = "https://bitvid.network/";
|
||||
|
||||
/**
|
||||
* Optional external destination for the Tip Jar menu link.
|
||||
*
|
||||
* Configure this with the Lightning tipping experience you want to surface in
|
||||
* the sidebar dropup. When left empty (null or an empty string) the Tip Jar
|
||||
* entry is removed entirely so operators that do not collect donations are not
|
||||
* forced to show the control.
|
||||
*/
|
||||
export const TIP_JAR_URL =
|
||||
"https://nostrtipjar.netlify.app/?n=npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe";
|
||||
|
||||
/**
|
||||
* Default image included in automated moderation DMs.
|
||||
*
|
||||
* bitvid embeds this media asset at the top of notification messages so the
|
||||
* payload renders with a recognizable preview. Provide a fully qualified URL
|
||||
* that points to a hosted image accessible by the intended recipients.
|
||||
*/
|
||||
export const ADMIN_DM_IMAGE_URL =
|
||||
"https://beta.bitvid.network/assets/jpg/video-thumbnail-fallback.jpg";
|
||||
|
||||
/**
|
||||
* Maximum satoshi value allowed when storing the default zap amount.
|
||||
*
|
||||
* Wallet settings clamp user input to this value before persistence to guard
|
||||
* against accidental overpayment. Tune the ceiling to match your instance's
|
||||
* risk tolerance.
|
||||
*/
|
||||
export const MAX_WALLET_DEFAULT_ZAP = 100000000;
|
||||
|
||||
/**
|
||||
* Percentage of every Lightning payment the platform retains as a fee.
|
||||
*
|
||||
* Accepts numbers between 0 and 100 (inclusive). Decimals are supported when
|
||||
* you want to keep a fractional cut—e.g., `2.5` represents a 2.5% fee. The
|
||||
* default of `0` disables the fee so creators receive the full payment.
|
||||
*/
|
||||
export const PLATFORM_FEE_PERCENT = 30;
|
||||
|
||||
/**
|
||||
* Lightning address to fall back to when authors omit their own `lud16`.
|
||||
*
|
||||
* Supply a string like `"[email protected]"` to force a deployment-wide
|
||||
* fallback Lightning address. When `PLATFORM_FEE_PERCENT` is greater than 0,
|
||||
* this override also acts as the Lightning target for the platform’s split.
|
||||
* Leave the value as `null` to respect the creator’s metadata and rely on the
|
||||
* Super Admin profile publishing a `lud16` so bitvid still knows where to route
|
||||
* fees when they are enabled.
|
||||
*/
|
||||
export const PLATFORM_LUD16_OVERRIDE = "[email protected]";
|
||||
|
||||
/**
|
||||
* Optional list of relays to seed new sessions with instead of the defaults.
|
||||
*
|
||||
* Provide WSS URLs (e.g., `"wss://relay.example.com"`). Leave the array empty
|
||||
* to keep bitvid’s bundled defaults. Operators that need a custom bootstrap set
|
||||
* should list the relays in priority order; entries later in the list are used
|
||||
* as fallbacks when earlier relays fail.
|
||||
*/
|
||||
export const DEFAULT_RELAY_URLS_OVERRIDE = Object.freeze([
|
||||
// "wss://relay.example.com",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Storage key used to persist whitelist-only mode in the browser.
|
||||
*
|
||||
@@ -38,17 +195,105 @@ export const ADMIN_WHITELIST_MODE_STORAGE_KEY = "bitvid_admin_whitelist_mode";
|
||||
* Whether whitelist-only mode should be enabled the first time an operator
|
||||
* loads the admin dashboard.
|
||||
*
|
||||
* Set this to `true` if you want BitVid to start with whitelist-only access and
|
||||
* Set this to `true` if you want bitvid to start with whitelist-only access and
|
||||
* require an explicit opt-out. Set to `false` to allow all creators by default.
|
||||
* Operators can still toggle the mode at runtime; this value only controls the
|
||||
* default state when no preference has been stored in localStorage yet.
|
||||
*/
|
||||
export const DEFAULT_WHITELIST_MODE_ENABLED = true;
|
||||
|
||||
/**
|
||||
* Whether the public client should display content flagged as NSFW.
|
||||
*
|
||||
* When set to `false`, bitvid filters out any video notes marked with the
|
||||
* `isNsfw` flag so they never appear in feeds or video lists. Toggle to `true`
|
||||
* only if your deployment allows NSFW media to surface publicly.
|
||||
*/
|
||||
export const ALLOW_NSFW_CONTENT = false;
|
||||
|
||||
/**
|
||||
* Emergency fallback accounts for seeding the moderation graph.
|
||||
*
|
||||
* Runtime moderation now derives its trust seeds from the Super Admin and the
|
||||
* active moderator roster, ensuring their reports shape anonymous/default
|
||||
* visitor filters automatically. The array below is kept only as a last-resort
|
||||
* fallback when the live moderator lists cannot be loaded (for example,
|
||||
* bootstrapping a brand-new deployment or recovering from relay outages).
|
||||
* Populate it with the operator keys you want to lean on during that rare
|
||||
* failure mode; under normal conditions these values are ignored. bitvid will
|
||||
* freeze and sanitize the npubs at runtime when reading from `js/config.js`.
|
||||
*/
|
||||
export const DEFAULT_TRUST_SEED_NPUBS = [
|
||||
"npub15jnttpymeytm80hatjqcvhhqhzrhx6gxp8pq0wn93rhnu8s9h9dsha32lx",
|
||||
"npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe",
|
||||
];
|
||||
|
||||
/**
|
||||
* Baseline trusted-report count that triggers thumbnail blurring.
|
||||
*
|
||||
* When a video receives at least this many trusted reports for the active
|
||||
* category (for example, `nudity`), thumbnails render with the blurred preview
|
||||
* by default. Align this value with your moderation policy — the upstream
|
||||
* deployment targets 3 trusted reports before blurring.
|
||||
*/
|
||||
export const DEFAULT_BLUR_THRESHOLD = 1;
|
||||
|
||||
/**
|
||||
* Baseline trusted-report count that blocks autoplay during browsing.
|
||||
*
|
||||
* Videos that meet or exceed this count will not autoplay in feeds unless the
|
||||
* viewer explicitly opts in. Increase the number for a more permissive stance
|
||||
* or decrease it if you want autoplay to stop sooner. The upstream deployment
|
||||
* targets 2 trusted reports before blocking autoplay.
|
||||
*/
|
||||
export const DEFAULT_AUTOPLAY_BLOCK_THRESHOLD = 1;
|
||||
|
||||
/**
|
||||
* Trusted mute threshold that hides creators globally.
|
||||
*
|
||||
* Once this many trusted accounts mute a creator, their videos disappear from
|
||||
* feeds until an operator intervenes. Keep the value low (1–2) to rapidly
|
||||
* enforce community standards or raise it to tolerate more reports before
|
||||
* hiding.
|
||||
*/
|
||||
export const DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD = 1;
|
||||
|
||||
/**
|
||||
* Trusted spam/report threshold that hides videos globally.
|
||||
*
|
||||
* When a video's trusted report count reaches this number, bitvid treats it as
|
||||
* spam or policy violating content and removes it from default feeds. Align
|
||||
* the value with your moderation policy — 3 trusted reports is the upstream
|
||||
* baseline.
|
||||
*/
|
||||
export const DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD = 1;
|
||||
|
||||
/**
|
||||
* Optional theme accent overrides for light and dark mode.
|
||||
*
|
||||
* Operators can provide hex color strings (e.g., `"#2563eb"`) to customize the
|
||||
* accent tokens without editing the generated CSS bundle. Leave values as
|
||||
* `null` to keep the upstream defaults defined in `css/tokens.css`. Each theme
|
||||
* accepts overrides for the base accent color as well as the stronger and
|
||||
* pressed states used for hover/active presentations.
|
||||
*/
|
||||
export const THEME_ACCENT_OVERRIDES = Object.freeze({
|
||||
light: Object.freeze({
|
||||
accent: "#540011",
|
||||
accentStrong: "#fe0032",
|
||||
accentPressed: "#a90021",
|
||||
}),
|
||||
dark: Object.freeze({
|
||||
accent: "#540011",
|
||||
accentStrong: "#fe0032",
|
||||
accentPressed: "#a90021",
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Nostr kind used when persisting watch history events.
|
||||
*
|
||||
* BitVid’s roadmap standardizes on kind 30079 so that watch events, view logs,
|
||||
* bitvid’s roadmap standardizes on kind 30079 so that watch events, view logs,
|
||||
* and media metadata stay in the same family of documents. During the rollout
|
||||
* from the legacy 30078 payloads, clients query both kinds so historical data
|
||||
* keeps syncing; remove the compatibility fetch once all writers emit the new
|
||||
@@ -56,20 +301,20 @@ export const DEFAULT_WHITELIST_MODE_ENABLED = true;
|
||||
* example, a NIP-51 collection) can flip this number so long as their relays
|
||||
* accept the chosen kind.
|
||||
*/
|
||||
export const WATCH_HISTORY_KIND = 30079;
|
||||
export const WATCH_HISTORY_KIND = 30078;
|
||||
|
||||
/**
|
||||
* Identifier applied to the watch-history list when storing it on relays.
|
||||
*
|
||||
* This becomes the value of the `d` tag so clients can find the correct list
|
||||
* without guessing. Customize the slug if you need isolation between multiple
|
||||
* BitVid deployments that share relays, but keep it stable once clients begin
|
||||
* bitvid deployments that share relays, but keep it stable once clients begin
|
||||
* syncing history.
|
||||
*/
|
||||
export const WATCH_HISTORY_LIST_IDENTIFIER = "watch-history";
|
||||
|
||||
/**
|
||||
* Legacy identifiers that BitVid clients should continue honoring when
|
||||
* Legacy identifiers that bitvid clients should continue honoring when
|
||||
* fetching historical watch-history snapshots.
|
||||
*/
|
||||
export const WATCH_HISTORY_LEGACY_LIST_IDENTIFIERS = Object.freeze([
|
||||
@@ -83,7 +328,7 @@ export const WATCH_HISTORY_LEGACY_LIST_IDENTIFIERS = Object.freeze([
|
||||
* deployments stick with the analytics-only view flow until operators opt in.
|
||||
* To enable V2, set `window.__BITVID_RUNTIME_FLAGS__.FEATURE_WATCH_HISTORY_V2 = true`
|
||||
* in a bootstrap script (or override the value before the app loads). When the
|
||||
* flag stays off, BitVid still emits legacy view events and will honor
|
||||
* flag stays off, bitvid still emits legacy view events and will honor
|
||||
* existing watch-history reads per plan §12, but the sync UI will surface a
|
||||
* disabled banner instead of querying relays.
|
||||
*/
|
||||
@@ -100,7 +345,7 @@ export const WATCH_HISTORY_MAX_ITEMS = 1500;
|
||||
/**
|
||||
* Whether clients should resolve watch-history entries in batches.
|
||||
*
|
||||
* When enabled, BitVid fetches video metadata in grouped queries instead of
|
||||
* When enabled, bitvid fetches video metadata in grouped queries instead of
|
||||
* issuing one request per entry. Operators with relays that struggle under
|
||||
* bursty loads can disable batching at the cost of additional round trips.
|
||||
*/
|
||||
@@ -115,13 +360,13 @@ export const WATCH_HISTORY_BATCH_RESOLVE = true;
|
||||
* that page through histories in smaller slices can set a positive integer to
|
||||
* cap the resolver output so pagination and API responses stay aligned.
|
||||
*/
|
||||
export const WATCH_HISTORY_BATCH_PAGE_SIZE = null;
|
||||
export const WATCH_HISTORY_BATCH_PAGE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* Maximum size of the JSON payload for each watch-history chunk, measured
|
||||
* before encryption.
|
||||
*
|
||||
* BitVid splits large histories across multiple events to stay within relay
|
||||
* bitvid splits large histories across multiple events to stay within relay
|
||||
* limits. This cap keeps individual chunks under roughly 60 KB so that
|
||||
* encrypted payloads remain relay-friendly even after base64 expansion.
|
||||
* Lower the number if your relays enforce tighter limits.
|
||||
@@ -150,7 +395,7 @@ export const WATCH_HISTORY_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
||||
/**
|
||||
* How long the view counter should treat repeat plays as duplicates.
|
||||
*
|
||||
* BitVid de-duplicates view events that occur within this rolling window so
|
||||
* bitvid de-duplicates view events that occur within this rolling window so
|
||||
* quick refreshes or stalled replays do not inflate the totals. The default of
|
||||
* 24 hours mirrors common analytics tooling, but you can tighten or relax the
|
||||
* window depending on how aggressively you want to filter repeat traffic.
|
||||
@@ -177,3 +422,13 @@ export const VIEW_COUNT_BACKFILL_MAX_DAYS = 90;
|
||||
* fresher dashboards or raise it if your analytics traffic is heavy.
|
||||
*/
|
||||
export const VIEW_COUNT_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Cooldown window (in seconds) between manual "Rebroadcast" attempts.
|
||||
*
|
||||
* Rebroadcasting is an escape hatch for creators to nudge stale relays. A
|
||||
* short cooldown keeps this safeguard from turning into an accidental DDoS.
|
||||
* Five minutes provides enough breathing room for relays to catch up while
|
||||
* still letting operators retry if a publish genuinely failed.
|
||||
*/
|
||||
export const ENSURE_PRESENCE_REBROADCAST_COOLDOWN_SECONDS = 5 * 60;
|
||||
@@ -0,0 +1,184 @@
|
||||
// config/validate-config.js
|
||||
// -----------------------------------------------------------------------------
|
||||
// Runtime validation helpers for instance-level configuration
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import {
|
||||
ADMIN_SUPER_NPUB,
|
||||
PLATFORM_FEE_PERCENT,
|
||||
PLATFORM_LUD16_OVERRIDE,
|
||||
THEME_ACCENT_OVERRIDES,
|
||||
BLOG_URL,
|
||||
COMMUNITY_URL,
|
||||
NOSTR_URL,
|
||||
GITHUB_URL,
|
||||
BETA_URL,
|
||||
DNS_URL,
|
||||
TIP_JAR_URL,
|
||||
} from "./instance-config.js";
|
||||
|
||||
function assertSuperAdminConfigured(value) {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error(
|
||||
"ADMIN_SUPER_NPUB must be a string exported from config/instance-config.js."
|
||||
);
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error(
|
||||
"ADMIN_SUPER_NPUB is required. Populate it with the Super Admin npub in config/instance-config.js."
|
||||
);
|
||||
}
|
||||
|
||||
if (!trimmed.startsWith("npub")) {
|
||||
throw new Error(
|
||||
"ADMIN_SUPER_NPUB should be a bech32 npub (e.g., starting with 'npub')."
|
||||
);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function assertPlatformFeeInRange(value) {
|
||||
const numericValue =
|
||||
typeof value === "number" ? value : Number.parseFloat(value);
|
||||
|
||||
if (!Number.isFinite(numericValue)) {
|
||||
throw new Error(
|
||||
"PLATFORM_FEE_PERCENT must be a finite number between 0 and 100."
|
||||
);
|
||||
}
|
||||
|
||||
if (numericValue < 0 || numericValue > 100) {
|
||||
throw new Error("PLATFORM_FEE_PERCENT must be between 0 and 100 inclusive.");
|
||||
}
|
||||
|
||||
return numericValue;
|
||||
}
|
||||
|
||||
function getPlatformLud16Override(value) {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
const ACCENT_HEX_PATTERN = /^#(?:[0-9a-fA-F]{6})$/;
|
||||
|
||||
function assertThemeAccentOverrides(overrides) {
|
||||
const source =
|
||||
overrides && typeof overrides === "object"
|
||||
? overrides
|
||||
: THEME_ACCENT_OVERRIDES;
|
||||
|
||||
const allowedThemes = new Set(["light", "dark"]);
|
||||
const allowedTokens = ["accent", "accentStrong", "accentPressed"];
|
||||
|
||||
Object.entries(source).forEach(([theme, themeOverrides]) => {
|
||||
if (!allowedThemes.has(theme) || themeOverrides == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof themeOverrides !== "object") {
|
||||
throw new Error(
|
||||
`THEME_ACCENT_OVERRIDES.${theme} must be an object with accent overrides.`
|
||||
);
|
||||
}
|
||||
|
||||
allowedTokens.forEach((token) => {
|
||||
const rawValue = themeOverrides[token];
|
||||
if (rawValue == null || rawValue === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof rawValue !== "string") {
|
||||
throw new Error(
|
||||
`THEME_ACCENT_OVERRIDES.${theme}.${token} must be a hex string (e.g., '#2563eb').`
|
||||
);
|
||||
}
|
||||
|
||||
const trimmed = rawValue.trim();
|
||||
if (!ACCENT_HEX_PATTERN.test(trimmed)) {
|
||||
throw new Error(
|
||||
`THEME_ACCENT_OVERRIDES.${theme}.${token} must be a #RRGGBB hex color.`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resolveConfig(overrides = {}) {
|
||||
return {
|
||||
adminSuperNpub:
|
||||
overrides.ADMIN_SUPER_NPUB ?? ADMIN_SUPER_NPUB,
|
||||
platformFeePercent:
|
||||
overrides.PLATFORM_FEE_PERCENT ?? PLATFORM_FEE_PERCENT,
|
||||
platformLud16Override:
|
||||
overrides.PLATFORM_LUD16_OVERRIDE ?? PLATFORM_LUD16_OVERRIDE,
|
||||
themeAccentOverrides:
|
||||
overrides.THEME_ACCENT_OVERRIDES ?? THEME_ACCENT_OVERRIDES,
|
||||
blogUrl: overrides.BLOG_URL ?? BLOG_URL,
|
||||
communityUrl: overrides.COMMUNITY_URL ?? COMMUNITY_URL,
|
||||
nostrUrl: overrides.NOSTR_URL ?? NOSTR_URL,
|
||||
githubUrl: overrides.GITHUB_URL ?? GITHUB_URL,
|
||||
betaUrl: overrides.BETA_URL ?? BETA_URL,
|
||||
dnsUrl: overrides.DNS_URL ?? DNS_URL,
|
||||
tipJarUrl: overrides.TIP_JAR_URL ?? TIP_JAR_URL,
|
||||
};
|
||||
}
|
||||
|
||||
function assertOptionalHttpUrl(url, settingName) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof url !== "string") {
|
||||
throw new Error(`${settingName} must be a string when provided.`);
|
||||
}
|
||||
|
||||
const trimmed = url.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(trimmed);
|
||||
if (!parsed.protocol || !/^https?:$/.test(parsed.protocol)) {
|
||||
throw new Error(`${settingName} must use http or https.`);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`${settingName} must be a valid http(s) URL defined in config/instance-config.js.`,
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function validateInstanceConfig(overrides) {
|
||||
const config = resolveConfig(overrides);
|
||||
assertSuperAdminConfigured(config.adminSuperNpub);
|
||||
const platformFeePercent = assertPlatformFeeInRange(
|
||||
config.platformFeePercent
|
||||
);
|
||||
const trimmedOverride = getPlatformLud16Override(
|
||||
config.platformLud16Override
|
||||
);
|
||||
|
||||
if (platformFeePercent > 0 && !trimmedOverride) {
|
||||
throw new Error(
|
||||
"PLATFORM_FEE_PERCENT is positive but PLATFORM_LUD16_OVERRIDE is empty. Set PLATFORM_LUD16_OVERRIDE to the Lightning address that should receive the platform's split, or publish a lud16 value on the Super Admin profile so bitvid can route platform fees."
|
||||
);
|
||||
}
|
||||
|
||||
assertThemeAccentOverrides(config.themeAccentOverrides);
|
||||
assertOptionalHttpUrl(config.blogUrl, "BLOG_URL");
|
||||
assertOptionalHttpUrl(config.communityUrl, "COMMUNITY_URL");
|
||||
assertOptionalHttpUrl(config.nostrUrl, "NOSTR_URL");
|
||||
assertOptionalHttpUrl(config.githubUrl, "GITHUB_URL");
|
||||
assertOptionalHttpUrl(config.betaUrl, "BETA_URL");
|
||||
assertOptionalHttpUrl(config.dnsUrl, "DNS_URL");
|
||||
assertOptionalHttpUrl(config.tipJarUrl, "TIP_JAR_URL");
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
Welcome to **bitvid**, a decentralized video-sharing platform built on Nostr. These Community Guidelines outline the types of content allowed and prohibited on the client hosted at this domain. As bitvid is still in early access, enforcement will occur at the client level, meaning violations will result in content being blocked from display rather than removed from relays. These policies will evolve as we implement robust user blocking and reporting features.
|
||||
Welcome to **bitvid**, a decentralized video-sharing platform built on Nostr. These Community Guidelines outline the types of content allowed and prohibited on the client hosted at this domain. Enforcement occurs at the **client level** with blur, hide, and downrank rules that mirror the current moderator tooling. Personal blocklists, mute controls, admin blacklists, and NIP-56 reporting are already available so you can tune your own experience.
|
||||
|
||||
> ⚠️ **Note:** Don't like the Guidelines? [Fork the code](https://github.com/PR0M3TH3AN/bitvid) and host the client on your own domain.
|
||||
|
||||
@@ -52,21 +52,44 @@ To maintain a functional and ethical platform, the following types of content ar
|
||||
- **Deepfake impersonations** of real people used for deceptive purposes.
|
||||
- AI-generated content must be **clearly labeled** to avoid misinformation.
|
||||
|
||||
## **4. Enforcement Actions**
|
||||
## **4. Enforcement & Visibility Controls**
|
||||
|
||||
Until robust reporting and user moderation features are fully implemented, bitvid will enforce these guidelines at the **client level**:
|
||||
bitvid enforces these guidelines through client-side visibility rules that match the current `moreMenuController` and `userBlocks` behavior:
|
||||
|
||||
- **Content Filtering:** Prohibited content will not be displayed within the client, though it may still exist on relays.
|
||||
- **Account Restrictions:** Users violating guidelines may be **blocked from the whitelisted early access list**.
|
||||
- **Reporting Mechanism:** Users are encouraged to report violations via NIP-56 reports, which will be reviewed manually.
|
||||
- **Blurred previews:** When trusted NIP-56 `nudity` reports reach the configured blur threshold (`DEFAULT_BLUR_THRESHOLD` in [`config/instance-config.js`](../config/instance-config.js)), thumbnails render blurred and autoplay previews pause. Every blur includes a “show anyway” override. The upstream configuration currently uses three trusted reporters, but adjust the export to match your policy.
|
||||
- **Downranked authors:** Muting a creator adds them to your NIP-51 mute list (kind `10000`). Videos from muted creators are deprioritized but still visible with clear badges so you can reverse the action.
|
||||
- **Personal blocks:** Blocking a creator adds them to your private block list (`userBlocks`). Videos from blocked creators are hidden from feeds, carousels, and modals unless you explicitly unblock them.
|
||||
- **Admin hard hides:** Moderator-maintained blacklists (NIP-51 kind `30000`) remove creators from default feeds for everyone using the hosted client. Moderators can still review the content through “show anyway” controls when auditing decisions.
|
||||
- **Relay transparency:** Because enforcement is client-side, blocked or blurred videos may still exist on relays and appear in other Nostr clients that do not apply these rules.
|
||||
|
||||
## **5. User Moderation Features (In Development)**
|
||||
## **5. User Moderation Controls**
|
||||
|
||||
We are working on features that will give users more control over their experience, including:
|
||||
You can manage moderation directly from the client using the existing menus rendered by `videoMenuRenderers`:
|
||||
|
||||
- **Personal blocklists** (prevent content from specific creators from appearing in feeds).
|
||||
- **Community-curated moderation lists** (opt-in to trusted content curation groups).
|
||||
- **Tag-based filtering** (users can toggle visibility of certain content categories).
|
||||
### **5.1 Personal blocklist (`userBlocks`)**
|
||||
|
||||
- Open any video card or modal and select the **More** (`···`) button. Choose **Block creator** to hide all future uploads from that pubkey. `moreMenuController` will confirm the action and refresh your feeds.
|
||||
- To review or remove blocks, open your profile avatar → **Profile** → **Blocked**. The list is private to your account; use **Unblock** next to a creator to restore their videos.
|
||||
|
||||
### **5.2 Mute list management**
|
||||
|
||||
- Use the same **More** menu and pick **Mute creator**. Muted creators stay visible but are downranked, aligning with the viewer mute list logic in `moreMenuController`.
|
||||
- Return to a muted creator’s video later and select **Unmute creator** from the menu to restore their normal ranking.
|
||||
|
||||
### **5.3 Moderator blacklist controls**
|
||||
|
||||
- Moderators with admin permissions see **Blacklist creator** inside the **More** menu. This updates the shared admin list managed by `moreMenuController` and immediately removes the creator from default feeds.
|
||||
- Only accounts with edit rights can add or remove creators. Attempting to blacklist without sufficient permissions will surface an error toast.
|
||||
|
||||
### **5.4 Reporting workflow (NIP-56)**
|
||||
|
||||
- Select **Report video** from the **More** menu to launch the reporting modal. Provide a category (e.g., `nudity`, `spam`, `illegal`) and submit; the client signs a NIP-56 report from your connected key.
|
||||
- Reports feed the same trusted-report scoring used for blur and hide decisions. Moderators monitor these queues to escalate urgent cases.
|
||||
|
||||
### **5.5 Safety & Moderation thresholds**
|
||||
|
||||
- Open your profile avatar → **Profile** → **Safety & Moderation** to adjust blur and autoplay thresholds. Leave a field blank to return to the defaults exported from [`config/instance-config.js`](../config/instance-config.js) (`DEFAULT_BLUR_THRESHOLD`, `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD`, `DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD`, `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD`). The upstream repo ships example values of 3 trusted reports for blur, 2 for autoplay blocking, 1 trusted mute to hide authors, and 3 trusted spam reports to hide videos—tune the config file before deployment if your community needs different baselines.
|
||||
- These controls update immediately and sync with the moderation overlays applied by `moreMenuController` and `VideoCard` components. Operators can edit the config file to change the baseline before deploying their own instance.
|
||||
|
||||
## **6. Appeals and Feedback**
|
||||
|
||||
|
||||
@@ -19,27 +19,24 @@ Ready to jump in? Here's everything you need to know to start watching and shari
|
||||
|
||||
### Step 2: Prepare Your Video
|
||||
|
||||
1. Download WebTorrent Desktop app from [webtorrent.io/desktop](https://webtorrent.io/desktop/)
|
||||
2. Open your video file in WebTorrent Desktop
|
||||
3. It will create a special "magnet link" for your video
|
||||
4. Keep WebTorrent Desktop running to share your video
|
||||
1. Host your video at an HTTPS URL that browsers can stream directly (MP4/WebM/HLS/DASH all work). You can use your own CDN, Cloudflare Stream, an R2 bucket, or any static site host—as long as the link begins with `https://`.
|
||||
2. (Optional but recommended) Generate a WebTorrent magnet so viewers can fall back to peer-to-peer delivery. Desktop apps like [WebTorrent Desktop](https://webtorrent.io/desktop/) or command-line tools can create the magnet for you.
|
||||
3. Double-check that either the hosted URL or the magnet (or both) is ready before you open the Upload modal.
|
||||
|
||||
### Step 3: Share on bitvid
|
||||
|
||||
1. Click "Share a Video" on bitvid
|
||||
2. Paste your video's magnet link
|
||||
3. Add a title, description, and thumbnail
|
||||
|
||||
> ⚠️ **Note:** I recomend using [PostImage](https://postimages.org/) to host your image. You can copy the "Direct link" after uploading the image and use that link as your Thumbnail image.
|
||||
|
||||
4. Click "Post" to share!
|
||||
1. Click "Share a Video" on bitvid to open the upload modal.
|
||||
2. In **Custom** mode, enter your title, paste the hosted HTTPS URL, and (optionally) add the magnet plus any `ws`/`xs` hints. Switch to the experimental **Cloudflare** quick upload mode if you want bitvid to push the file straight to your Cloudflare R2 bucket—have your Account ID, S3 Access Key ID, and Secret Access Key ready (the advanced toggle also accepts an API token, Zone ID, and base domain if you use them).
|
||||
3. Review the rest of the form: add a description and thumbnail, decide whether to allow comments, and set the NSFW or "For Kids" toggles to match your content. Explore the metadata section to fill in captions, duration, publish time, hashtags, participants, and other fields so first-time viewers have all the context they need.
|
||||
4. Click "Post" to publish once everything looks right.
|
||||
|
||||
## Tips for Success
|
||||
|
||||
- Keep WebTorrent Desktop running while sharing videos
|
||||
- Add eye-catching thumbnails to attract viewers
|
||||
- Write clear descriptions to help people find your content
|
||||
- Use the "Private" option if you only want to share with specific people
|
||||
- After posting, open a card’s **More → Edit** action and use the [visibility toggle in the Edit Video modal](../components/edit-video-modal.html) to keep it private until you’re ready to share
|
||||
- Encrypted watch-history sync lives in the [Watch History view](history.html), where you can clear devices or pause tracking whenever you need.
|
||||
|
||||
## Need Help?
|
||||
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
## Shipped
|
||||
|
||||
- **Private video filtering** – VideoListView now hides private uploads from shared feeds while still rendering them for their owners, keeping private sharing contained to the uploader’s views.
|
||||
- **Edit & Upload modals** – Editing and publishing flows now rely on first-class modal forms with shared NIP-71 field management instead of browser pop-ups.
|
||||
- **Universal more/settings menus** – Video cards expose consistent “more” and owner settings menus with channel navigation, sharing, blocking, and reporting actions wired through dedicated renderers.
|
||||
- **Watch History view** – A dedicated watch history page ships with privacy messaging, pagination, relay sync, and local cache controls.
|
||||
- **Zap controls** – Channel profiles manage zap visibility, login gating, retry flows, and receipt rendering via the Lightning controller on the page.
|
||||
- **Source visibility filtering** – Lists now hide cards whose hosted URLs are offline unless their WebTorrent fallback is healthy, reusing the shared guard in `js/utils/cardSourceVisibility.js`.
|
||||
- **Video modal comments** – The video modal now surfaces in-context comment threads so viewers can follow discussions without leaving the player.
|
||||
- **Channel profile views** – `views/channel-profile.html` and `js/channelProfile.js` render dedicated channel landing pages with banners, follow stats, playlists, and contact links.
|
||||
- **Audience targeting toggles** – The [Upload modal](components/upload-modal.html) and [Edit Video modal](components/edit-video-modal.html) ship **NSFW** and **For Kids** toggles that map to the note’s moderation flags.
|
||||
- **Hashtag metadata repeater** – Upload metadata repeaters now support hashtag sets so creators can publish discovery tags alongside variants, captions, and participants.
|
||||
- **Dev/Live publishing fix** – Publishing honors the selected mode, preventing Dev-mode uploads from leaking into Live feeds.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
> ⚠️ **Note:** If you find a new bug thats not listed here. Please submit a [Bug Report](https://bitvid.network/?modal=bug).
|
||||
|
||||
- Speed up loading in subscriptions. Save to local cache?
|
||||
- Fix "Dev Mode" publishing "Live Mode" notes—add a flag for dev mode posts.
|
||||
- Fix issue with video post set to private.
|
||||
- Disable "private video" until I can make it work better.
|
||||
- Make "private video" work better using nip-04 encryption for magnet field.
|
||||
- Fix slow back button issues on Firefox.
|
||||
- Add Amber login support for mobile.
|
||||
- Add settings (three dots) button for all videos, not the same as gear menu. Only show delete and edit gear for logged-in users videos.
|
||||
- Add support for "Playlist" lists and other custom lists (named whatever) and also "Watch Later" list and "Watch History".
|
||||
- Add support for "Playlist" lists and other custom lists (named whatever) and also a "Watch Later" list.
|
||||
- Add an "add to playlist" button to the edit button.
|
||||
- Add "add to watch later" option to edit button.
|
||||
- Add block button to settings button.
|
||||
- Add report button to settings button.
|
||||
- Fix autoplay broken on iPhone chrome.
|
||||
- Fix playback broken on Safari on iPhone.
|
||||
- **[14]** Get Watch History working reliably so the view stays in sync across devices and sessions.
|
||||
- **[15]** Fix Watch History bugs for various login methods to keep timelines aligned regardless of how users authenticate.
|
||||
- Update note spec v3 transition to include new fields that have been added like "has previous" or "videoRootID" or enentID if null.
|
||||
- Change "explore" view to "kids" view and add flag to all notes to see if they are for kids.
|
||||
- Add "seed" lists.
|
||||
- Make "edit" video a form modal rather than a browser pop-up.
|
||||
- Add "upload thumbnail" as option to add/edit video form. (use https://apidocs.imgur.com/)
|
||||
- Fix issue where page refreshes when pulling up a video in the video modal.
|
||||
- Fix sidebar media query settings on medium-sized screens. (tablet/laptops)
|
||||
- Fix various "text wrap" issues causing scroll left and right on profile and modal pages.
|
||||
- Add zaps to profile and video modal pages.
|
||||
- Add comments to video modal pages.
|
||||
|
||||
## Feature Additions
|
||||
|
||||
@@ -34,24 +38,22 @@
|
||||
|
||||
- Allow users to set custom relay settings, stored in local cache.
|
||||
- Add a "Publish" step in the video editing process.
|
||||
- Add Profile/Channel Views. [Click for more details](https://github.com/PR0M3TH3AN/bitvid/blob/main/content/roadmap/04_bitvid_Enhanced_Profile_Channel_Views_Specification.md)
|
||||
- Add a relay note search surface with filters for post date, hashtags, title keywords, view counts, and the publishing account.
|
||||
- Add a mobile search control with a floating icon that expands into a full search field on small screens.
|
||||
- Improve event spec for migration to new versions. [Click for more details](https://github.com/PR0M3TH3AN/bitvid/blob/main/content/roadmap/02_bitvid_Enhanced_Migration_of_Note_Spec_Logic.md)
|
||||
- Migrate event spec to v3 and add support for Audio/Podcast content. [Click for more details](https://github.com/PR0M3TH3AN/bitvid/blob/main/content/roadmap/03_bitvid_Enhanced_Nostr_Video_%26_Audio_Note_Specification_Version%203.md)
|
||||
- Add Block List, Subscription List, Playlist, and Reporting Specification. [Click for more details](https://github.com/PR0M3TH3AN/bitvid/blob/main/content/roadmap/05_bitvid_Enhanced_Block_Subscription_%26_Reporting_Specification.md)
|
||||
- Add comments to the video modal. [Click for more details](https://github.com/PR0M3TH3AN/bitvid/blob/main/content/roadmap/06_bitvid_Enhanced_Video_Comment_System_Specification.md)
|
||||
- Implement an "Adult Content" flag for note submissions.
|
||||
- Enable custom hashtags in the submission spec and form. (Use with future search system)
|
||||
- Allow multiple video resolutions with a selector in the video player. (v3 event spec needed)
|
||||
- Introduce a subscription mechanism with notifications.
|
||||
- Add zaps to videos, profiles, and comments.
|
||||
- Create a VRR (View, Rating, and Retention) Penalty Scoring system. [Click for more details](https://github.com/PR0M3TH3AN/bitvid/blob/main/content/roadmap/07_bitvid_Enhanced_View_Rating_%26_Retention_Penalty_Scoring.md)
|
||||
- Implement visibility filtering for videos:
|
||||
- Run lightweight URL-health probes so cards stay visible when the hosted playback link responds.
|
||||
- When a URL is unreachable, fall back to WebTorrent and surface items only if their magnets can stream via peers or web seeds.
|
||||
- Align the filtering logic with the player’s URL-first, WebTorrent-fallback behavior during list rendering.
|
||||
- Expand visibility tooling around the shipped source guard to add operator overrides, richer status messaging, and better diagnostics when both URL and magnet sources fail.
|
||||
- Dynamic Home Page and Video Tracking Specification. [Click for more details](https://github.com/PR0M3TH3AN/bitvid/blob/main/content/roadmap/08_bitvid_Enhanced_Dynamic_Home_Page_%26_Video_Tracking_Specification.md)
|
||||
- Add multi-language support for content and filtration. (v4?)
|
||||
- Create a settings menu for local account preferences, including relay, adult content, theme, and language.
|
||||
- Better integrate with other Nostr/torrent ecosystem. (NIP-35 + WebRTC Check Integration) [Click for more details](https://github.com/PR0M3TH3AN/bitvid/blob/main/content/roadmap/09_bitvid_Enhanced_NIP-35_%2B_WebRTC_Check_Integration.md)
|
||||
- **[16]** Add ability to write and send DMs so creators and viewers can coordinate privately.
|
||||
- **[17]** Add a "Live" live stream view (view only for now) that imports Zapstream notes and displays the live streams within the bitvid environment.
|
||||
- **[18]** Add ability to create and post livestreams to R2 storage so streamers can broadcast directly from bitvid.
|
||||
|
||||
> ⚠️ **Note:** Your feedback helps us improve bitvid! Whether it’s a suggestion, a concern, or general thoughts on the platform, we’d love to hear from you. Please tell us what you think [with this form](https://bitvid.network/?modal=feedback).
|
||||
@@ -31,7 +31,7 @@ This new file could export:
|
||||
- `buildNewNote(data, pubkey)`: Takes basic form data and returns a fully structured Nostr note (an object) ready to be signed.
|
||||
- `buildEditNote(originalEvent, updatedData)`: Merges old note content with new fields.
|
||||
- `softDeleteNote(originalEvent)`: Constructs a note with `deleted = true`.
|
||||
- `encryptMagnet(magnet)`, `decryptMagnet(magnet)`: Real or placeholder encryption functions.
|
||||
- `encryptMagnet(magnet)`, `decryptMagnet(magnet)`: Legacy pass-through helpers kept in case encryption returns; private listings now hide rather than encrypt.
|
||||
- `validateNoteContent(content)`: Ensures essential fields (title, magnet, mode, etc.) are present and valid.
|
||||
|
||||
Because you’re not implementing Version 3 yet, keep your existing version logic. If you do plan to adopt Version 3 later, the new file is where you’d add or change fields without scattering edits across multiple files.
|
||||
@@ -53,7 +53,7 @@ export function prepareNewNote(formInput, pubkey) {
|
||||
// Combine user inputs with defaults
|
||||
const isPrivate = formInput.isPrivate === true;
|
||||
const finalMagnet = isPrivate
|
||||
? encryptMagnet(formInput.magnet)
|
||||
? encryptMagnet(formInput.magnet) // currently a pass-through; hiding happens at the feed layer
|
||||
: formInput.magnet;
|
||||
|
||||
return {
|
||||
@@ -94,7 +94,12 @@ async handleSubmit(e) {
|
||||
magnet: document.getElementById("magnet")?.value.trim() || "",
|
||||
thumbnail: document.getElementById("thumbnail")?.value.trim() || "",
|
||||
description: document.getElementById("description")?.value.trim() || "",
|
||||
mode: isDevMode ? "dev" : "live",
|
||||
mode:
|
||||
(document.getElementById("mode")?.value || "")
|
||||
.trim()
|
||||
.toLowerCase() === "dev"
|
||||
? "dev"
|
||||
: "live",
|
||||
isPrivate: this.isPrivateCheckbox.checked
|
||||
};
|
||||
|
||||
@@ -178,7 +183,7 @@ By delegating the actual note-building to `buildEditNote` and `buildDeleteNote`,
|
||||
|
||||
6. **Test Thoroughly:**
|
||||
- Create, edit, and delete events to ensure everything behaves the same.
|
||||
- Confirm that private videos are still encrypted as expected.
|
||||
- Confirm that private videos stay hidden from shared grids as expected.
|
||||
|
||||
---
|
||||
|
||||
@@ -189,13 +194,13 @@ Below is a small outline showing how you might organize the new file. The actual
|
||||
```js
|
||||
// bitvidNoteSpec.js
|
||||
|
||||
// A placeholder or real encryption method
|
||||
// Legacy placeholder kept for future encryption experiments (currently returns the raw magnet)
|
||||
export function encryptMagnet(magnet) {
|
||||
return magnet.split("").reverse().join("");
|
||||
return magnet; // private cards are hidden instead of encrypted
|
||||
}
|
||||
|
||||
export function decryptMagnet(magnet) {
|
||||
return magnet.split("").reverse().join("");
|
||||
return magnet; // private cards are hidden instead of encrypted
|
||||
}
|
||||
|
||||
function generateUniqueDTag() {
|
||||
@@ -206,7 +211,7 @@ function generateUniqueDTag() {
|
||||
export function prepareNewNote(formInput, pubkey) {
|
||||
const isPrivate = formInput.isPrivate === true;
|
||||
const finalMagnet = isPrivate
|
||||
? encryptMagnet(formInput.magnet)
|
||||
? encryptMagnet(formInput.magnet) // currently a pass-through; hiding happens at the feed layer
|
||||
: formInput.magnet;
|
||||
|
||||
return {
|
||||
@@ -234,7 +239,7 @@ export function prepareNewNote(formInput, pubkey) {
|
||||
export function buildEditNote(originalEvent, updatedData) {
|
||||
// parse old content
|
||||
// combine with new fields
|
||||
// handle old vs new encryption
|
||||
// handle legacy visibility toggles / future encryption experiments
|
||||
// return the final event object
|
||||
}
|
||||
|
||||
|
||||
@@ -43,19 +43,19 @@ A typical event:
|
||||
| `videoRootId` | String (optional) | A stable root ID used to link multiple versions (edits) and a delete event together. **Recommended** for overshadow logic. |
|
||||
| `version` | Integer | Now set to `3`. |
|
||||
| `deleted` | Boolean | `true` marks the post as “soft deleted.” |
|
||||
| `isPrivate` | Boolean | Indicates if `magnet` (and possibly other fields) are encrypted. |
|
||||
| `isPrivate` | Boolean | Indicates the card should stay off shared grids and remain visible only to its owner; payloads stay plaintext. |
|
||||
| `title` | String | Display title for the media. |
|
||||
| `magnet` | String | Magnet link for primary media (encrypted if `isPrivate = true`). |
|
||||
| `magnet` | String | Magnet link for primary media (still stored in plaintext even when `isPrivate = true`). |
|
||||
| `extraMagnets` | Array (optional) | Additional magnet links (e.g., multiple resolutions). |
|
||||
| `thumbnail` | String (optional) | URL or magnet link to a thumbnail image (encrypted if `isPrivate = true` and `encryptedMeta = true`). |
|
||||
| `description` | String (optional) | A textual description (encrypted if `isPrivate = true` and `encryptedMeta = true`). |
|
||||
| `thumbnail` | String (optional) | URL or magnet link to a thumbnail image. |
|
||||
| `description` | String (optional) | A textual description. |
|
||||
| `mode` | String | Typically `live` or `dev`. |
|
||||
| `adult` | Boolean (optional) | `true` if content is adult-only. Default: `false` or omitted. |
|
||||
| `categories` | Array (optional) | Array of categories, e.g. `["comedy", "music"]`. |
|
||||
| `language` | String (optional) | Language code (e.g. `"en"`, `"es"`). |
|
||||
| `payment` | String (optional) | Monetization field (e.g. a Lightning address). |
|
||||
| `i18n` | Object (optional) | Internationalization map (e.g. `{"title_en": "...", "description_es": "..."}`). |
|
||||
| `encryptedMeta` | Boolean (optional) | Indicates if fields like `description` or `thumbnail` are encrypted. |
|
||||
| `encryptedMeta` | Boolean (optional) | Legacy flag from earlier encryption experiments; leave `false` unless a future spec revives encrypted metadata. |
|
||||
| **Audio/Podcast-Specific Fields** | | |
|
||||
| `contentType` | String (optional) | E.g., `"video"`, `"music"`, `"podcast"`, `"audiobook"`. |
|
||||
| `albumName` | String (optional) | Name of the album (for music). |
|
||||
@@ -194,7 +194,7 @@ A typical event:
|
||||
### **8.2 Deletion**
|
||||
|
||||
1. **deleted = true**: Mark the item as deleted in the `content` JSON.
|
||||
2. **Remove or encrypt** sensitive fields (`magnet`, `description`, etc.).
|
||||
2. **Clear** sensitive fields (`magnet`, `description`, etc.) if you do not want them mirrored in older clients.
|
||||
3. **videoRootId**: Must remain the same as the original so clients remove/overshadow the old item.
|
||||
4. **subscribeVideos** Logic:
|
||||
```js
|
||||
@@ -217,7 +217,7 @@ A typical event:
|
||||
1. **Adult Content**: If `adult = true`, clients typically hide the post unless the user enables adult content in settings.
|
||||
2. **Categories**: Provide optional grouping or searching by `categories`.
|
||||
3. **Language**: If specified, clients can filter by language.
|
||||
4. **Encryption**: If `isPrivate = true`, some fields (e.g., `magnet`) may need client-side decryption.
|
||||
4. **Privacy handling**: If `isPrivate = true`, clients should hide the card from shared grids instead of attempting client-side decryption.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ Add placeholders for different views in `index.html`:
|
||||
- Fetch videos (`kind 30078`) filtered by the user's `npub`.
|
||||
|
||||
### **Subscriptions**
|
||||
- Use `kind 30002` to manage the subscription list:
|
||||
- Use `kind 30000` to manage the subscription follow set:
|
||||
- Subscribe: Add user to the list.
|
||||
- Unsubscribe: Remove user.
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ This document describes how to implement **Block Lists**, **Subscription Lists**
|
||||
A **Subscription List** lets users follow video creators independently of their main “following” list on Nostr. It supports categorization and can be made private via encryption.
|
||||
|
||||
### Event Kind
|
||||
- **Kind**: `30002`
|
||||
- **Description**: “Video Subscription List” (inspired by NIP-51 but for custom lists)
|
||||
- **Kind**: `30000`
|
||||
- **Description**: “Video Subscription Follow Set” aligned with [NIP-51 follow sets](../../docs/nips/51.md#sets)
|
||||
|
||||
### JSON Structure
|
||||
**Public Tags**
|
||||
@@ -64,7 +64,7 @@ A **Subscription List** lets users follow video creators independently of their
|
||||
### Example
|
||||
```json
|
||||
{
|
||||
"kind": 30002,
|
||||
"kind": 30000,
|
||||
"tags": [
|
||||
["d", "favorite-creators"],
|
||||
["p", "npub1creator1pubkey"],
|
||||
@@ -208,7 +208,7 @@ The `p` tag references a pubkey, and the `e` tag references a note. The third el
|
||||
## Implementation Details
|
||||
|
||||
### Replaceable Events
|
||||
- **Subscription Lists** (`30002`) and **Block Lists** (`10001`) can be implemented as replaceable events by using a deterministic `d` tag.
|
||||
- **Subscription Lists** (`30000`) and **Block Lists** (`10001`) can be implemented as replaceable events by using a deterministic `d` tag.
|
||||
- For example, `["d","my-blocklist"]` ensures older events with the same `d` are replaced when new ones arrive.
|
||||
|
||||
### Encryption
|
||||
@@ -220,7 +220,7 @@ The `p` tag references a pubkey, and the `e` tag references a note. The third el
|
||||
- Query each relay for events matching:
|
||||
```jsonc
|
||||
{
|
||||
"kinds": [10001, 30002],
|
||||
"kinds": [10001, 30000],
|
||||
"authors": [<user-pubkey>]
|
||||
}
|
||||
```
|
||||
@@ -275,7 +275,7 @@ The `p` tag references a pubkey, and the `e` tag references a note. The third el
|
||||
|
||||
## Future Considerations
|
||||
1. **Paid Subscriptions**
|
||||
- Could layer subscription tiers on top of `30002` events.
|
||||
- Could layer subscription tiers on top of `30000` events.
|
||||
2. **Global Block Lists**
|
||||
- Let users publish or subscribe to a curated blocklist (or share one in a group).
|
||||
3. **Web of Trust**
|
||||
@@ -284,4 +284,4 @@ The `p` tag references a pubkey, and the `e` tag references a note. The third el
|
||||
---
|
||||
|
||||
### Summary
|
||||
By leveraging Nostr event kinds (`30002` for subscriptions, `10001` for blocks, and `1984` for reports), bitvid can maintain a decentralized, user-controlled moderation system. Users can sync their lists across devices through relays, while administrators can choose how to handle flagged content on a platform level. This approach keeps moderation flexible and transparent.
|
||||
By leveraging Nostr event kinds (`30000` for subscriptions, `10001` for blocks, and `1984` for reports), bitvid can maintain a decentralized, user-controlled moderation system. Users can sync their lists across devices through relays, while administrators can choose how to handle flagged content on a platform level. This approach keeps moderation flexible and transparent.
|
||||
@@ -8,10 +8,10 @@ To implement a decentralized comment system for videos shared on a Nostr-based p
|
||||
### **Features**
|
||||
1. **Post Comments**:
|
||||
- Users can post comments on videos.
|
||||
- Comments are associated with a specific video event using the `e` tag.
|
||||
- Comments scope to the video definition or root event with NIP-22 uppercase tags (`A`/`E`/`I`, `K`, `P`) while still emitting lowercase fallbacks for legacy clients.
|
||||
|
||||
2. **Structured Threading**:
|
||||
- Comments support threading by referencing parent comments using NIP-27 conventions.
|
||||
- Comments support threading by referencing parent comments with the lowercase NIP-22 set (`a`/`e`/`i`, `k`, `p`).
|
||||
- Threaded replies are visually nested.
|
||||
|
||||
3. **Reactions**:
|
||||
@@ -40,15 +40,21 @@ To implement a decentralized comment system for videos shared on a Nostr-based p
|
||||
Each component (comments, reactions, live chat) is represented as a Nostr event.
|
||||
|
||||
##### **Comment Event**:
|
||||
bitvid publishes kind `1111` comments following [NIP-22](https://github.com/nostr-protocol/nips/blob/master/22.md). Each event declares its root scope with uppercase tags and keeps the lowercase fallbacks so legacy clients continue to render threads.
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": 1311,
|
||||
"kind": 1111,
|
||||
"pubkey": "abcdef1234567890...",
|
||||
"created_at": 1675000000,
|
||||
"tags": [
|
||||
["e", "video-event-id"], // Reference to the video
|
||||
["e", "parent-comment-id"], // Reference to the parent comment (optional, for replies)
|
||||
["p", "commenter-pubkey"] // Optional: commenter pubkey
|
||||
["A", "30078:video-author-pubkey:video-root-id", "wss://video.example"],
|
||||
["K", "30078"],
|
||||
["P", "video-author-pubkey", "wss://video.example"],
|
||||
["a", "30078:video-author-pubkey:video-root-id", "wss://video.example"],
|
||||
["e", "parent-comment-id", "parent-author-pubkey"],
|
||||
["k", "1111"],
|
||||
["p", "parent-author-pubkey"]
|
||||
],
|
||||
"content": "This is a great video!"
|
||||
}
|
||||
@@ -86,13 +92,14 @@ Each component (comments, reactions, live chat) is represented as a Nostr event.
|
||||
Metadata tags are added to comments or live chat events as needed:
|
||||
```json
|
||||
{
|
||||
"kind": 1311,
|
||||
"kind": 1111,
|
||||
"pubkey": "abcdef1234567890...",
|
||||
"created_at": 1675000000,
|
||||
"tags": [
|
||||
["e", "video-event-id"],
|
||||
["a", "30078:video-author-pubkey:video-root-id"],
|
||||
["m", "featured-comment"], // Example metadata tag
|
||||
["a", "admin-tag"] // Administrative tag
|
||||
["m", "admin-tag"] // Administrative tag
|
||||
],
|
||||
"content": "Highlighted comment for this video."
|
||||
}
|
||||
@@ -104,20 +111,33 @@ Metadata tags are added to comments or live chat events as needed:
|
||||
|
||||
#### **1. Posting a Comment**
|
||||
To post a comment:
|
||||
1. The client constructs a comment event using NIP-22 for `created_at` validation.
|
||||
2. The event includes references to the video (mandatory) and parent comments (optional).
|
||||
1. The client constructs a kind `1111` comment event following NIP-22 threading semantics.
|
||||
2. The event includes uppercase root scope tags (`['A'\|`E`\|`I`, <pointer>], `['K', <root kind>]`, `['P', <root author>]`) and carries lowercase fallbacks (`['a', ...]`, `['e', ...]`, `['k', ...]`, `['p', ...]`) for older relays and clients. Parent references append `['e', <parent id>, <relay?>?, <author?>]`, `['k', <parent kind>]`, and `['p', <parent author>]` when available.
|
||||
3. The event is signed and published to relays.
|
||||
|
||||
##### API Example:
|
||||
```javascript
|
||||
async function postComment(videoId, commentText, parentCommentId = null) {
|
||||
async function postComment(
|
||||
{ videoEventId, videoAuthorPubkey, videoRootId },
|
||||
commentText,
|
||||
parentCommentId = null
|
||||
) {
|
||||
const event = {
|
||||
kind: 1311,
|
||||
kind: 1111,
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
['e', videoId],
|
||||
...(parentCommentId ? [['e', parentCommentId]] : []),
|
||||
['A', `30078:${videoAuthorPubkey}:${videoRootId}`],
|
||||
['K', '30078'],
|
||||
['P', videoAuthorPubkey],
|
||||
['a', `30078:${videoAuthorPubkey}:${videoRootId}`],
|
||||
...(parentCommentId
|
||||
? [
|
||||
['e', parentCommentId, videoAuthorPubkey],
|
||||
['k', '1111'],
|
||||
['p', videoAuthorPubkey],
|
||||
]
|
||||
: [['k', '30078'], ['p', videoAuthorPubkey]]),
|
||||
],
|
||||
content: commentText
|
||||
};
|
||||
@@ -151,10 +171,11 @@ Comments are retrieved using `REQ` messages filtered by the `e` tag for the vide
|
||||
|
||||
##### API Example:
|
||||
```javascript
|
||||
async function fetchComments(videoId) {
|
||||
async function fetchComments({ videoEventId, videoAuthorPubkey, videoRootId }) {
|
||||
const filter = {
|
||||
kinds: [1311],
|
||||
'#e': [videoId],
|
||||
kinds: [1111],
|
||||
'#e': [videoEventId],
|
||||
'#a': [`30078:${videoAuthorPubkey}:${videoRootId}`],
|
||||
limit: 100
|
||||
};
|
||||
|
||||
@@ -192,7 +213,7 @@ Use NIP-24 to attach metadata to events for administrative or user-specific tags
|
||||
- User creates a comment event.
|
||||
- Client signs and publishes the event to relays.
|
||||
2. **Fetching Comments**:
|
||||
- Client requests comments for a video by filtering events with the `e` tag.
|
||||
- Client requests comments for a video by filtering events with the combined `#e` and `#a` tags so both the specific video event and its root definition are represented.
|
||||
3. **Reacting to Comments**:
|
||||
- Users react to comments by posting reaction events.
|
||||
4. **Live Discussions**:
|
||||
@@ -200,6 +221,8 @@ Use NIP-24 to attach metadata to events for administrative or user-specific tags
|
||||
5. **Metadata Management**:
|
||||
- Metadata is attached during event creation or editing.
|
||||
|
||||
> **Tagging Requirement:** Clients must keep the combined `#e`/`#a` tagging pattern shown above. `discussionCountService.buildDiscussionCountFilters` depends on those tags when querying relays so UI components can surface accurate comment totals.
|
||||
|
||||
---
|
||||
|
||||
### **UI/UX Considerations**
|
||||
@@ -220,6 +243,9 @@ Use NIP-24 to attach metadata to events for administrative or user-specific tags
|
||||
4. **Nested Threading**:
|
||||
- Indent replies to show comment hierarchy.
|
||||
|
||||
5. **Comment Gate**:
|
||||
- UI components such as `VideoCard.buildDiscussionCount` already hide comment affordances when `enableComments` is false. Future enhancements must honor the same feature flag so comment entry points remain consistent with existing gating.
|
||||
|
||||
---
|
||||
|
||||
### **Testing and Validation**
|
||||
|
||||
@@ -253,7 +253,7 @@ export default VideoTracker;
|
||||
|
||||
import VideoTracker from './videoTracker.js';
|
||||
|
||||
class BitvidApp {
|
||||
class bitvidApp {
|
||||
constructor(nostrClient) {
|
||||
this.nostrClient = nostrClient;
|
||||
this.videoTracker = null;
|
||||
@@ -276,7 +276,7 @@ class BitvidApp {
|
||||
}
|
||||
}
|
||||
|
||||
export default BitvidApp;
|
||||
export default bitvidApp;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -87,7 +87,7 @@ Rather than measuring active peers, bitvid now counts views by logging them as N
|
||||
- Compare the count of new views against a previous period, or calculate the rate of increase.
|
||||
|
||||
3. **For You**
|
||||
- Look at what the user watched or subscribed to (via kind `30002` subscription lists).
|
||||
- Look at what the user watched or subscribed to (via kind `30000` subscription follow sets).
|
||||
- Track the user’s or session’s recent view tags, then recommend videos with overlapping tags or from the same channels.
|
||||
|
||||
---
|
||||
@@ -124,7 +124,7 @@ Your `index.html` might include placeholders for each section:
|
||||
|
||||
1. **Fetch Data**
|
||||
- Pull video metadata from kind `30078` events that contain video info or from your existing approach.
|
||||
- Pull subscription data (kind `30002`).
|
||||
- Pull subscription data (kind `30000`).
|
||||
- Pull view events (kind `30078` with `"t", "view"`).
|
||||
|
||||
2. **Process Rankings**
|
||||
|
||||
@@ -105,7 +105,7 @@ magnet:?xt=urn:btih:abc123def...&tr=wss%3A%2F%2Ftracker.webtorrent.io&tr=udp%3A%
|
||||
```jsonc
|
||||
["tracker", "wss://tracker.webtorrent.io"]
|
||||
```
|
||||
2. **Active WebRTC seeder**: Even with a `wss://` tracker, the torrent must be seeded in real time by at least one WebTorrent/WebRTC peer. Without a WebRTC seed, browser-based clients (like Bitvid or other WebTorrent-powered apps) cannot download or stream.
|
||||
2. **Active WebRTC seeder**: Even with a `wss://` tracker, the torrent must be seeded in real time by at least one WebTorrent/WebRTC peer. Without a WebRTC seed, browser-based clients (like bitvid or other WebTorrent-powered apps) cannot download or stream.
|
||||
|
||||
3. **No `.torrent` file on Nostr**: This NIP is purely metadata (info hash, tracker URLs). Traditional BitTorrent clients can still use the magnet link if it has UDP/HTTP trackers.
|
||||
|
||||
@@ -202,4 +202,4 @@ Below is a more complete JSON example following NIP-35 conventions, demonstratin
|
||||
|
||||
### End of Updated Spec Sheet
|
||||
|
||||
This revision aims to integrate WebTorrent/WebRTC considerations seamlessly into the existing NIP-35 structure. It remains optional to provide `wss://` trackers, but doing so is strongly recommended if you want real-time streaming in browser-based clients like Bitvid or other WebTorrent-powered frontends.
|
||||
This revision aims to integrate WebTorrent/WebRTC considerations seamlessly into the existing NIP-35 structure. It remains optional to provide `wss://` trackers, but doing so is strongly recommended if you want real-time streaming in browser-based clients like bitvid or other WebTorrent-powered frontends.
|
||||
@@ -1 +1,10 @@
|
||||
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@mediLine truncated
|
||||
:root{--surface-base:var(--color-surface);--surface-alt:var(--color-surface-alt);--surface-raised:var(--color-card);--surface-raised-private:var(--color-card-private);--surface-raised-critical:var(--color-card-critical);--surface-panel:var(--color-panel);--surface-panel-hover:var(--color-panel-hover);--surface-sunken:var(--color-surface-sunken);--surface-muted:var(--color-surface-muted);--surface-veil:var(--color-overlay-veil);--surface-inverse:var(--color-page);--surface-overlay:var(--color-overlay);--surface-overlay-muted:var(--color-overlay-muted);--surface-overlay-strong:var(--color-overlay-strong);--surface-overlay-darker:var(--color-overlay-darker);--surface-overlay-black:var(--color-overlay-black);--surface-overlay-banner-base:var(--color-page);--surface-overlay-banner:var(--surface-overlay-banner-base);--surface-overlay-banner-opaque:var(--surface-overlay-banner-base);--surface-overlay-banner-strong:color-mix(in srgb,var(--surface-overlay-banner-base) 82%,transparent);--surface-overlay-banner-bottom:color-mix(in srgb,var(--surface-overlay-banner-base) 50%,transparent);--surface-overlay-banner-channel-mid:color-mix(in srgb,var(--surface-overlay-banner-base) 24%,transparent);--surface-overlay-banner-channel-faint:color-mix(in srgb,var(--surface-overlay-banner-base) 10%,transparent);--surface-overlay-banner-soft:color-mix(in srgb,var(--surface-overlay-banner-base) 56%,transparent);--surface-overlay-banner-faint:color-mix(in srgb,var(--surface-overlay-banner-base) 18%,transparent);--surface-overlay-banner-transparent:transparent;--surface-overlay-banner-gradient-header:linear-gradient(to bottom,var(--surface-overlay-banner-opaque) 0%,var(--surface-overlay-banner-strong) 38%,var(--surface-overlay-banner-soft) 70%,var(--surface-overlay-banner-faint) 88%,var(--surface-overlay-banner-transparent) 100%);--surface-overlay-banner-gradient-channel:linear-gradient(to top,var(--surface-overlay-banner-bottom) 0%,var(--surface-overlay-banner-channel-mid) 28%,var(--surface-overlay-banner-channel-faint) 42%,var(--surface-overlay-banner-transparent) 50%);--surface-overlay-banner-gradient:var(
|
||||
--surface-overlay-banner-gradient-header
|
||||
);--surface-overlay-panel:var(--color-overlay-panel);--surface-overlay-panel-soft:var(--color-overlay-panel-soft);--surface-overlay-panel-tint:var(--color-overlay-panel-tint);--surface-overlay-panel-glass:var(--color-overlay-panel-glass);--surface-overlay-panel-glass-soft:var(--color-overlay-panel-glass-soft);--surface-modal-backdrop:var(--color-modal-backdrop);--surface-modal:var(--color-modal-surface);--blur-modal-backdrop:18px;--blur-modal-alert:14px;--blur-thumbnail-blur:18px;--modal-nav-backdrop-blur:6px;--app-header-centerline-offset:calc(var(--space-sm) + var(--space-2xl-plus)/2);--border-default:var(--color-border);--border-subtle:var(--color-border-subtle);--border-strong:var(--color-border-translucent-stronger);--border-overlay:var(--color-border-translucent);--border-overlay-strong:var(--color-border-translucent-strongest);--border-overlay-glow:var(--color-border-translucent-glow);--border-neutral-soft:var(--color-border-neutral-soft);--border-info-soft:var(--color-border-info-soft);--border-sidebar-strong:var(--color-border-sidebar-strong);--border-width-hairline:1px;--border-width-fine:0.5px;--text-primary:var(--color-text);--text-inverse:var(--color-text-inverse);--text-muted:var(--color-text-muted);--text-muted-strong:var(--color-muted-strong);--text-subtle:var(--color-subtle-text);--text-frosted-primary:var(--color-text-frosted);--text-frosted-strong:var(--color-text-frosted-strong);--text-frosted-soft:var(--color-text-frosted-soft);--status-info:var(--color-info);--status-info-strong:var(--color-info-strong);--status-info-surface:var(--color-info-glass);--status-info-border:var(--color-info-border);--status-info-on:var(--color-info-on);--status-success:var(--color-success);--status-success-strong:var(--color-success-strong);--status-success-surface:var(--color-success-surface);--status-success-border:var(--color-success-border);--status-success-on:var(--color-success-on);--status-warning:var(--color-warning);--status-warning-strong:var(--color-warning-strong);--status-warning-surface:var(--color-warning-surface);--status-warning-border:var(--color-warning-border);--status-warning-on:var(--color-warning-on);--status-danger:var(--color-critical);--status-danger-strong:var(--color-critical-strong);--status-danger-surface:var(--color-danger-surface);--status-danger-border:var(--color-danger-border);--status-danger-on:var(--color-danger-on);--status-private:var(--color-private);--status-private-strong:var(--color-private-strong);--status-private-surface:var(--color-private-surface);--status-private-border:var(--color-private-border);--status-private-on:var(--color-private-on);--status-neutral:var(--color-muted);--status-neutral-strong:var(--color-muted-strong);--status-neutral-surface:var(--color-neutral-surface);--status-neutral-border:var(--color-neutral-border);--status-neutral-on:var(--color-neutral-on);--scrollbar-width:0.5rem;--scrollbar-track:var(--color-white-soft);--scrollbar-thumb:var(--color-white-softer);--scrollbar-thumb-hover:var(--color-white-softest);--z-app-header:40;--z-overlay-nav:60;--z-overlay-floating:70;--z-overlay-popover:140;--z-overlay-toast:180;--z-sidebar-overlay:10;--z-sidebar:20;--z-modal-root:100;--z-modal-overlay:110;--z-modal-content:120;--z-modal-player:130;--z-modal-top-root:140;--z-modal-top-overlay:150;--z-modal-top-content:160;--z-modal-peak-root:165;--z-modal-peak-overlay:175;--z-modal-peak-content:185;--overlay-backdrop-color:var(--color-overlay-backdrop);--overlay-backdrop-blur:12px;--sidebar-width-expanded-base:16rem;--sidebar-dropup-content-width:0px;--sidebar-width-expanded:max(var(--sidebar-width-expanded-base),var(--sidebar-dropup-content-width));--sidebar-width-collapsed:4rem;--sidebar-width:var(--sidebar-width-expanded);--sidebar-control-size:2.75rem;--sidebar-content-gap:1rem;--space-sidebar-shell-inline:var(--space-lg);--space-sidebar-shell-block-start:var(--space-sm);--space-sidebar-shell-block-end:1.75rem;--space-sidebar-scroll-gap:1.75rem;--space-sidebar-panel-gap:1.35rem;--space-sidebar-panel-padding-block:1.35rem;--space-sidebar-panel-padding-inline:1.1rem;--space-sidebar-footer-gap:0.85rem;--space-sidebar-trigger-padding-block:0.78rem;--space-sidebar-popover-offset:0.85rem;--space-sidebar-popover-padding-block:0.85rem;--space-sidebar-popover-padding-inline:0.65rem;--space-sidebar-popover-gap:0.45rem;--space-sidebar-dropup-scroll-reserve:calc(var(--scrollbar-thin-width) + var(--space-sm) + var(--space-2xs));--sidebar-dropup-max-height:18rem;--space-sidebar-nav-padding-block:0.7rem;--space-sidebar-nav-padding-inline-start:var(--space-xs);--space-sidebar-nav-padding-inline-end:calc(var(--space-lg) + var(--space-2xs));--space-sidebar-subnav-padding-block:0.55rem;--space-sidebar-nav-translate:0.35rem;--grid-card-min-width:20rem;--popover-backdrop-blur:12px;--overlay-panel-radius:var(--radius-lg);--overlay-panel-shadow:var(--shadow-lg);--overlay-panel-padding-inline:var(--popup-padding-inline);--overlay-panel-padding-block:var(--popup-padding-Line truncated
|
||||
/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family-sans);font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--font-family-mono);font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{font-size:80%}@media (min-width:768px){html{font-size:100%}}@media (min-width:1024px){html{font-size:75%}}*,:after,:before{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) var(--scrollbar-track)}::-webkit-scrollbar{width:var(--scrollbar-width);height:var(--scrollbar-width)}::-webkit-scrollbar-track{background:var(--scrollbar-track)}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:var(--radius-sm)}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}body{--app-header-centerline:calc(env(safe-area-inset-top, 0) + var(--app-header-centerline-offset));font-family:var(--font-family-sans);background-color:var(--color-page);color:var(--color-text-strong);line-height:1.5;margin:0;padding:0;overflow-x:hidden}#uiOverlay{position:fixed;inset:0;pointer-events:none;z-index:var(--z-overlay-popover)}#uiOverlay>*{pointer-events:auto}#editNip71TagsFieldset>legend,#nip71TagsFieldset>legend{margin-left:0;padding:0}#app{background-color:var(--color-page);color:inherit}body.modal-open,html.modal-open{overflow:hidden;height:100%}body.modal-open #app{overflow:hidden}body.modal-open #sidebar{pointer-events:none;opacity:0;visibility:hidden;z-index:var(--z-sidebar-overlay)}.ng-cloak,.ng-hide:not(.ng-hide-animate),.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}ng\:form{display:block}.\!container{width:100%!important}.container{width:100%}@media (min-width:25rem){.\!container{max-width:25rem!important}.container{max-width:25rem}}@media (min-width:30rem){.\!container{max-width:30rem!important}.container{max-width:30rem}}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}#playerModal.ds-header{display:block}.ds-header{display:flex;align-items:center;justify-content:flex-start;padding-top:var(--space-sm);padding-bottom:var(--space-sm)}.app-shell__header{position:sticky;top:env(safe-area-inset-top,0);z-index:var(--z-app-header);padding-top:calc(var(--space-sm) + env(safe-area-inset-top, 0));padding-bottom:var(--space-sm);background-color:transparent;background-image:var(--surface-overlay-banner-gradient-header);background-repeat:no-repeat;background-size:100% 100%}.ds-metric-probe{position:absolute;tLine truncated
|
||||
--player-modal-inline-padding,clamp(var(--space-md),4vw,var(--space-xl-plus))
|
||||
);overflow:visible}#playerModal .watch-container.video-modal__similar{padding-inline:clamp(var(--space-3xs),.6vw,var(--space-2xs))}#playerModal .video-modal__primary>.watch-container:first-child{padding-block-start:var(--player-modal-vertical-padding)}#playerModal .video-modal__secondary>.watch-container:last-child{padding-block-end:var(--player-modal-vertical-padding)}#playerModal .modal-nav .watch-container{max-width:none;margin-inline:0}#playerModal .modal-info-panel{display:flex;flex-direction:column;gap:var(--space-md-plus)}#playerModal h2#videoTitle{margin:0;font-size:clamp(var(--font-size-xl),calc(var(--font-size-modal-title-base) + .5vw),var(--font-size-xl-plus));font-weight:600;line-height:1.3;color:var(--color-text)}#playerModal .modal-meta{font-size:var(--font-size-sm);line-height:1.4;color:var(--color-text-muted)}#playerModal .modal-view-count{font-weight:600;color:var(--color-text)}#playerModal .channel-card{align-items:center;gap:var(--space-md);padding:var(--space-md) var(--space-lg)}#playerModal .channel-card,#playerModal .player-modal__module{display:flex;border-radius:var(--radius-xl);border:var(--border-width-hairline) solid var(--color-border-translucent);background-color:var(--color-surface);box-shadow:var(--shadow-sm)}#playerModal .player-modal__module{flex-direction:column;gap:clamp(var(--space-2xs),1vw,var(--space-sm));width:100%;padding:clamp(var(--space-xs),1vw,var(--space-sm))}#playerModal .player-modal__module-title{margin:0;font-size:var(--font-size-lg);font-weight:600;line-height:1.3;color:var(--color-text)}#playerModal .player-modal__module-list{display:flex;flex-direction:column;gap:var(--space-sm);width:100%;margin:0;padding:0;list-style:none}#playerModal .video-modal__similar .player-modal__module{gap:clamp(var(--space-3xs),.6vw,var(--space-xs));padding:clamp(var(--space-2xs),.6vw,var(--space-xs));border-width:0;box-shadow:none;background-color:transparent}#playerModal .video-modal__similar .player-modal__module-title{padding-inline:clamp(var(--space-3xs),.6vw,var(--space-2xs))}#playerModal .video-modal__similar .player-modal__module-list{gap:clamp(var(--space-3xs),.6vw,var(--space-xs));padding-inline:clamp(var(--space-3xs),.6vw,var(--space-2xs));padding-bottom:clamp(var(--space-3xs),.6vw,var(--space-2xs))}#playerModal .player-modal__module-scroll{max-height:clamp(14rem,calc(100vh - var(--player-modal-vertical-padding)*2 - var(--space-2xl)),28rem);overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;scrollbar-gutter:stable;padding-inline-end:var(--space-xs)}#playerModal .video-modal__similar .player-modal__module-scroll{max-height:none;overflow:visible;padding-inline-end:0;scrollbar-gutter:auto}#playerModal .player-modal__module-scroll>li{scroll-margin-block:var(--space-xs)}@media (max-width:30rem){#playerModal .modal-nav .icon-button{width:var(--space-xl);height:var(--space-xl);min-width:var(--space-xl);min-height:var(--space-xl)}#playerModal .watch-container{padding-inline:var(--space-sm)}#playerModal .channel-card{gap:var(--space-sm);padding:var(--space-sm) var(--space-md)}}#playerModal .channel-card__avatar{flex-shrink:0;width:var(--avatar-size-lg);height:var(--avatar-size-lg);border-radius:var(--radius-full);overflow:hidden;background-color:var(--color-border-subtle)}#playerModal .channel-card__avatar img{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}#playerModal .channel-card__name{margin:0;font-size:var(--font-size-lg);font-weight:600;color:var(--color-text)}#playerModal .channel-card__npub{margin:0;font-size:var(--font-size-sm);color:var(--color-text-muted)}#playerModal .description-panel{padding:var(--space-md-plus) var(--space-lg);border-radius:var(--radius-xl);border:var(--border-width-hairline) solid var(--color-border-translucent);background-color:var(--color-surface);box-shadow:var(--shadow-sm)}#playerModal .description-panel p{margin:0;font-size:var(--font-size-sm);line-height:1.6;color:var(--color-text)}.player-modal__content{position:relative;z-index:var(--z-modal-content);display:flex;flex:1 1 auto;width:100%;min-height:100%;flex-direction:column;overflow:visible;padding:0;margin:0;border:none;border-radius:0;background-color:var(--color-surface);box-shadow:none}@media (prefers-reduced-motion:reduce){.player-modal__content{transition-property:none}}#playerModal .modal-nav{position:sticky;top:0;z-index:var(--z-overlay-nav);--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;background-color:var(--surface-overlay-panel);background-image:linear-gradient(180deg,color-mix(in srgb,var(--surface-overlay-panel) 94%,transparent) 0,color-mix(in srgb,var(--surface-overlay-panel) 88%,transparent) 100%);border-bottom:var(--border-width-hairline) solid var(--border-oveLine truncated
|
||||
--player-modal-primary-inline-padding-lg,clamp(var(--space-sm),2.75vw,var(--space-lg))
|
||||
)}}.revert-modal__layout{display:grid;gap:1.5rem}@media (min-width:1024px){.revert-modal__layout{grid-template-columns:minmax(var(--revert-modal-sidebar-min),var(--revert-modal-sidebar-width)) minmax(0,1fr)}}#playerModal.video-modal__primary{display:block}.video-modal__primary{display:flex;flex-direction:column;gap:1.5rem;min-width:0;min-height:0}#playerModal.video-modal__secondary{display:block}.video-modal__secondary{display:flex;flex-direction:column;gap:1rem;min-width:0;min-height:0;width:100%;align-self:stretch}.video-modal__similar{display:none}@media (min-width:1024px){#playerModal.video-modal__similar{display:block}.video-modal__similar{display:flex;flex-direction:column;gap:1.5rem;align-self:stretch;flex:1 1 auto;min-height:0}#playerModal .video-modal__similar .player-modal__module{height:100%;display:flex;flex:1 1 auto;min-height:0;flex-direction:column}#playerModal .video-modal__similar .player-modal__module-list{flex:1 1 auto;min-height:0}#playerModal .video-modal__similar .player-modal__module-scroll{height:100%;max-height:none;min-height:0}}#playerModal .player-modal__module-item{width:100%}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__media{width:clamp(5.75rem,18vw,8rem)}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__media{width:clamp(5.75rem,18vw,8rem)!important}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__media,#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__media{flex-shrink:0;overflow:hidden;border-radius:var(--radius-lg)}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))!important}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}#playerModal .video-modal__similar .player-modal__module-list{gap:clamp(var(--space-2xs),.85vw,var(--space-sm))}.player-modal__similar-card{--player-modal-similar-thumb:clamp(11rem,calc(var(--video-modal-secondary-min, 320px)*0.62),17rem);--player-modal-similar-gap:clamp(var(--space-4xs),0.65vw,var(--space-2xs));--player-modal-similar-padding:clamp(var(--space-4xs),0.7vw,var(--space-sm));position:relative;display:grid;grid-template-columns:var(--player-modal-similar-thumb) minmax(0,1fr);align-items:stretch;gap:var(--player-modal-similar-gap);padding:var(--player-modal-similar-padding);width:100%;min-width:0;transform:translate3d(0,var(--video-card-lift-translate-y,0),0)}.player-modal__similar-card:before{content:"";position:absolute;inset:0;z-index:0;opacity:var(--video-card-backdrop-opacity,0);background-image:var(
|
||||
--similar-card-thumb-url,var(--video-card-thumb-url,none)
|
||||
);background-position:50%;background-repeat:no-repeat;background-size:cover;transform:translate3d(0,var(--video-card-backdrop-translate-y,0),0) scale(var(--video-card-backdrop-scale,1));filter:blur(var(--video-card-backdrop-blur,0));transition:opacity var(--motion-duration-base) var(--motion-ease-in-out),transform var(--motion-duration-base) var(--motion-ease-in-out),filter var(--motion-duration-base) var(--motion-ease-in-out);pointer-events:none;will-change:opacity,transform,filter}.player-modal__similar-card>*{position:relative;z-index:1}.player-modal__similar-card:focus-within,.player-modal__similar-card:hover{--video-card-backdrop-opacity:0.425;--video-card-backdrop-translate-y:0.15rem;--video-card-backdrop-scale:1.12;--video-card-backdrop-blur:24px}.player-modal__similar-card-media{display:block;position:relative;inline-size:var(--player-modal-similar-thumb);block-size:100%;max-inline-size:clamp(calc(var(--space-2xl-plus)*1.1),28vw,calc(var(--space-2xl-plus)*2.1));border-radius:var(--radius-lg);overflow:hidden;isolation:isolate}.player-modal__similar-card-media img{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover;border-radius:inherit}.player-modal__similar-card-content{display:flex;flex-direction:column;gap:clamp(var(--space-4xs),.6vw,var(--space-2xs));min-width:0}.player-modal__similar-card-title{display:-webkit-box;margin:0;font-size:clamp(var(--font-size-sm),1.05vw,var(--font-size-base));font-weight:600;line-height:1.35;color:var(--color-text);text-decoration:none;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical;transition:color var(--motion-duration-base) var(--motion-ease-in-out)}.player-modal__similar-card-title:focus-visible,.player-modal__similar-card-title:hover{color:var(--color-accent)}.player-modal__similar-card-author{display:flex;align-items:center;-moz-column-gap:clamp(var(--space-5xs),.6vw,var(--space-3xs));column-gap:clamp(var(--space-5xs),.6vw,var(--space-3xs));font-size:var(--font-size-xs);color:var(--color-text-muted);min-width:0}.player-modal__similar-card-avatar{flex:0 0 auto;display:block;inline-size:clamp(2.5rem,.8vw + 2.1rem,3rem);block-size:clamp(2.5rem,.8vw + 2.1rem,3rem);border-radius:var(--radius-full);overflow:hidden;background-color:var(--color-surface-alt);box-shadow:0 0 0 1px color-mix(in srgb,var(--color-border-strong) 65%,transparent)}.player-modal__similar-card-avatar-img{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.player-modal__similar-card-author-meta{display:flex;flex-direction:column;align-items:flex-start;gap:clamp(var(--space-6xs),.35vw,var(--space-5xs));min-width:0}.player-modal__similar-card-author-name{font-weight:500;color:var(--color-text);min-width:0}.player-modal__similar-card-author-npub{display:inline-flex;padding-inline:var(--space-6xs);border-radius:var(--radius-full);font-family:var(--font-family-mono);line-height:1.2;background-color:color-mix(in srgb,var(--color-surface-alt) 80%,transparent)}.player-modal__similar-card-author-npub,.player-modal__similar-card-meta{align-items:center;font-size:var(--font-size-2xs,.75rem);color:var(--color-muted-strong)}.player-modal__similar-card-meta{display:flex;flex-wrap:wrap;gap:clamp(var(--space-5xs),.5vw,var(--space-4xs));min-width:0}.player-modal__similar-card-timestamp{white-space:nowrap}.player-modal__similar-card-separator{color:inherit;opacity:.8}.player-modal__similar-card-views{white-space:nowrap}.video-modal__stage{position:relative;isolation:isolate;overflow:visible;--video-modal-ambient-bleed:clamp(var(--space-2xl),12vw,var(--space-5xl));--video-modal-ambient-scale:1.38;--video-modal-ambient-blur:clamp(128px,20vw,256px);--video-modal-ambient-opacity:1}.video-modal__video{position:relative;z-index:1;border-radius:var(--radius-xl);padding-block:clamp(var(--space-xs),2.5vw,var(--space-md-plus));background-color:transparent;box-shadow:0 0 0 1px color-mix(in srgb,var(--color-border-strong) 8%,transparent) inset;overflow:visible}.video-modal__ambient{position:absolute;inset:calc(var(--video-modal-ambient-bleed)*-1);border-radius:calc(var(--radius-3xl) + var(--video-modal-ambient-bleed));pointer-events:none;z-index:0;display:block;overflow:visible}.video-modal__ambient:before{content:"";position:absolute;inset:0;border-radius:inherit;background:radial-gradient(circle at 50% 50%,color-mix(in srgb,var(--color-overlay-strong) 18%,transparent) 0,color-mix(in srgb,var(--color-overlay) 34%,transparent) 62%,transparent 100%);opacity:.9;mix-blend-mode:screen;pointer-events:none}.video-modal__ambient-canvas{position:relative;width:100%;height:100%;display:block;transform:scale(var(--video-modal-ambient-scale));filter:blur(var(--video-modal-ambient-blur)) saturate(2) brightness(1.24);opacity:var(--video-modal-ambient-opacity)}@media (min-width:1024px){.video-modal__video{position:sticky;top:calc(var(--space-2xl) + var(--space-xl))}}#playerModal .video-modal__video video{position:relative;z-index:1;background-color:var(--color-black);border-Line truncated
|
||||
@@ -0,0 +1,749 @@
|
||||
:root {
|
||||
/* Semantic palette */
|
||||
--surface-base: var(--color-surface);
|
||||
--surface-alt: var(--color-surface-alt);
|
||||
--surface-raised: var(--color-card);
|
||||
--surface-raised-private: var(--color-card-private);
|
||||
--surface-raised-critical: var(--color-card-critical);
|
||||
--surface-panel: var(--color-panel);
|
||||
--surface-panel-hover: var(--color-panel-hover);
|
||||
--surface-sunken: var(--color-surface-sunken);
|
||||
--surface-muted: var(--color-surface-muted);
|
||||
--surface-veil: var(--color-overlay-veil);
|
||||
--surface-inverse: var(--color-page);
|
||||
|
||||
--surface-overlay: var(--color-overlay);
|
||||
--surface-overlay-muted: var(--color-overlay-muted);
|
||||
--surface-overlay-strong: var(--color-overlay-strong);
|
||||
--surface-overlay-darker: var(--color-overlay-darker);
|
||||
--surface-overlay-black: var(--color-overlay-black);
|
||||
--surface-overlay-banner-base: var(--color-page);
|
||||
--surface-overlay-banner: var(--surface-overlay-banner-base);
|
||||
--surface-overlay-banner-opaque: var(--surface-overlay-banner-base);
|
||||
--surface-overlay-banner-strong: color-mix(
|
||||
in srgb,
|
||||
var(--surface-overlay-banner-base) 82%,
|
||||
transparent
|
||||
);
|
||||
--surface-overlay-banner-bottom: color-mix(
|
||||
in srgb,
|
||||
var(--surface-overlay-banner-base) 50%,
|
||||
transparent
|
||||
);
|
||||
--surface-overlay-banner-channel-mid: color-mix(
|
||||
in srgb,
|
||||
var(--surface-overlay-banner-base) 24%,
|
||||
transparent
|
||||
);
|
||||
--surface-overlay-banner-channel-faint: color-mix(
|
||||
in srgb,
|
||||
var(--surface-overlay-banner-base) 10%,
|
||||
transparent
|
||||
);
|
||||
--surface-overlay-banner-soft: color-mix(
|
||||
in srgb,
|
||||
var(--surface-overlay-banner-base) 56%,
|
||||
transparent
|
||||
);
|
||||
--surface-overlay-banner-faint: color-mix(
|
||||
in srgb,
|
||||
var(--surface-overlay-banner-base) 18%,
|
||||
transparent
|
||||
);
|
||||
--surface-overlay-banner-transparent: transparent;
|
||||
--surface-overlay-banner-gradient-header: linear-gradient(
|
||||
to bottom,
|
||||
var(--surface-overlay-banner-opaque) 0%,
|
||||
var(--surface-overlay-banner-strong) 38%,
|
||||
var(--surface-overlay-banner-soft) 70%,
|
||||
var(--surface-overlay-banner-faint) 88%,
|
||||
var(--surface-overlay-banner-transparent) 100%
|
||||
);
|
||||
--surface-overlay-banner-gradient-channel: linear-gradient(
|
||||
to top,
|
||||
var(--surface-overlay-banner-bottom) 0%,
|
||||
var(--surface-overlay-banner-channel-mid) 28%,
|
||||
var(--surface-overlay-banner-channel-faint) 42%,
|
||||
var(--surface-overlay-banner-transparent) 50%
|
||||
);
|
||||
--surface-overlay-banner-gradient: var(
|
||||
--surface-overlay-banner-gradient-header
|
||||
);
|
||||
--surface-overlay-panel: var(--color-overlay-panel);
|
||||
--surface-overlay-panel-soft: var(--color-overlay-panel-soft);
|
||||
--surface-overlay-panel-tint: var(--color-overlay-panel-tint);
|
||||
--surface-overlay-panel-glass: var(--color-overlay-panel-glass);
|
||||
--surface-overlay-panel-glass-soft: var(--color-overlay-panel-glass-soft);
|
||||
--surface-modal-backdrop: var(--color-modal-backdrop);
|
||||
--surface-modal: var(--color-modal-surface);
|
||||
--blur-modal-backdrop: 18px;
|
||||
--blur-modal-alert: 14px;
|
||||
--blur-thumbnail-blur: 18px;
|
||||
--modal-nav-backdrop-blur: 6px;
|
||||
|
||||
--app-header-centerline-offset: calc(
|
||||
var(--space-sm) + (var(--space-2xl-plus) / 2)
|
||||
);
|
||||
--border-default: var(--color-border);
|
||||
--border-subtle: var(--color-border-subtle);
|
||||
--border-strong: var(--color-border-translucent-stronger);
|
||||
--border-overlay: var(--color-border-translucent);
|
||||
--border-overlay-strong: var(--color-border-translucent-strongest);
|
||||
--border-overlay-glow: var(--color-border-translucent-glow);
|
||||
--border-neutral-soft: var(--color-border-neutral-soft);
|
||||
--border-info-soft: var(--color-border-info-soft);
|
||||
--border-sidebar-strong: var(--color-border-sidebar-strong);
|
||||
--border-width-hairline: 1px;
|
||||
--border-width-fine: 0.5px;
|
||||
|
||||
--text-primary: var(--color-text);
|
||||
--text-inverse: var(--color-text-inverse);
|
||||
--text-muted: var(--color-text-muted);
|
||||
--text-muted-strong: var(--color-muted-strong);
|
||||
--text-subtle: var(--color-subtle-text);
|
||||
--text-frosted-primary: var(--color-text-frosted);
|
||||
--text-frosted-strong: var(--color-text-frosted-strong);
|
||||
--text-frosted-soft: var(--color-text-frosted-soft);
|
||||
|
||||
--status-info: var(--color-info);
|
||||
--status-info-strong: var(--color-info-strong);
|
||||
--status-info-surface: var(--color-info-glass);
|
||||
--status-info-border: var(--color-info-border);
|
||||
--status-info-on: var(--color-info-on);
|
||||
|
||||
--status-success: var(--color-success);
|
||||
--status-success-strong: var(--color-success-strong);
|
||||
--status-success-surface: var(--color-success-surface);
|
||||
--status-success-border: var(--color-success-border);
|
||||
--status-success-on: var(--color-success-on);
|
||||
|
||||
--status-warning: var(--color-warning);
|
||||
--status-warning-strong: var(--color-warning-strong);
|
||||
--status-warning-surface: var(--color-warning-surface);
|
||||
--status-warning-border: var(--color-warning-border);
|
||||
--status-warning-on: var(--color-warning-on);
|
||||
|
||||
--status-danger: var(--color-critical);
|
||||
--status-danger-strong: var(--color-critical-strong);
|
||||
--status-danger-surface: var(--color-danger-surface);
|
||||
--status-danger-border: var(--color-danger-border);
|
||||
--status-danger-on: var(--color-danger-on);
|
||||
|
||||
--status-private: var(--color-private);
|
||||
--status-private-strong: var(--color-private-strong);
|
||||
--status-private-surface: var(--color-private-surface);
|
||||
--status-private-border: var(--color-private-border);
|
||||
--status-private-on: var(--color-private-on);
|
||||
|
||||
--status-neutral: var(--color-muted);
|
||||
--status-neutral-strong: var(--color-muted-strong);
|
||||
--status-neutral-surface: var(--color-neutral-surface);
|
||||
--status-neutral-border: var(--color-neutral-border);
|
||||
--status-neutral-on: var(--color-neutral-on);
|
||||
|
||||
/* Layout & stacking */
|
||||
--scrollbar-width: 0.5rem;
|
||||
--scrollbar-track: var(--color-white-soft);
|
||||
--scrollbar-thumb: var(--color-white-softer);
|
||||
--scrollbar-thumb-hover: var(--color-white-softest);
|
||||
--z-app-header: 40;
|
||||
--z-overlay-nav: 60;
|
||||
--z-overlay-floating: 70;
|
||||
--z-overlay-popover: 140;
|
||||
--z-overlay-toast: 180;
|
||||
--z-sidebar-overlay: 10;
|
||||
--z-sidebar: 20;
|
||||
--z-modal-root: 100;
|
||||
--z-modal-overlay: 110;
|
||||
--z-modal-content: 120;
|
||||
--z-modal-player: 130;
|
||||
--z-modal-top-root: 140;
|
||||
--z-modal-top-overlay: 150;
|
||||
--z-modal-top-content: 160;
|
||||
--z-modal-peak-root: 165;
|
||||
--z-modal-peak-overlay: 175;
|
||||
--z-modal-peak-content: 185;
|
||||
--overlay-backdrop-color: var(--color-overlay-backdrop);
|
||||
--overlay-backdrop-blur: 12px;
|
||||
--sidebar-width-expanded-base: 16rem;
|
||||
--sidebar-dropup-content-width: 0px;
|
||||
--sidebar-width-expanded: max(
|
||||
var(--sidebar-width-expanded-base),
|
||||
var(--sidebar-dropup-content-width)
|
||||
);
|
||||
--sidebar-width-collapsed: 4rem;
|
||||
--sidebar-width: var(--sidebar-width-expanded);
|
||||
--sidebar-control-size: 2.75rem;
|
||||
--sidebar-content-gap: 1rem;
|
||||
--space-sidebar-shell-inline: var(--space-lg);
|
||||
--space-sidebar-shell-block-start: var(--space-sm);
|
||||
--space-sidebar-shell-block-end: 1.75rem;
|
||||
--space-sidebar-scroll-gap: 1.75rem;
|
||||
--space-sidebar-panel-gap: 1.35rem;
|
||||
--space-sidebar-panel-padding-block: 1.35rem;
|
||||
--space-sidebar-panel-padding-inline: 1.1rem;
|
||||
--space-sidebar-footer-gap: 0.85rem;
|
||||
--space-sidebar-trigger-padding-block: 0.78rem;
|
||||
--space-sidebar-popover-offset: 0.85rem;
|
||||
--space-sidebar-popover-padding-block: 0.85rem;
|
||||
--space-sidebar-popover-padding-inline: 0.65rem;
|
||||
--space-sidebar-popover-gap: 0.45rem;
|
||||
--space-sidebar-dropup-scroll-reserve: calc(
|
||||
var(--scrollbar-thin-width) + var(--space-sm) + var(--space-2xs)
|
||||
);
|
||||
--sidebar-dropup-max-height: calc(3rem * 6);
|
||||
--space-sidebar-nav-padding-block: 0.7rem;
|
||||
--space-sidebar-nav-padding-inline-start: var(--space-xs);
|
||||
--space-sidebar-nav-padding-inline-end: calc(
|
||||
var(--space-lg) + var(--space-2xs)
|
||||
);
|
||||
--space-sidebar-subnav-padding-block: 0.55rem;
|
||||
--space-sidebar-nav-translate: 0.35rem;
|
||||
--grid-card-min-width: 20rem;
|
||||
--popover-backdrop-blur: 12px;
|
||||
--overlay-panel-radius: var(--radius-lg);
|
||||
--overlay-panel-shadow: var(--shadow-lg);
|
||||
--overlay-panel-padding-inline: var(--popup-padding-inline);
|
||||
--overlay-panel-padding-block: var(--popup-padding-block);
|
||||
--toast-translate: -1.5rem;
|
||||
--sidebar-mobile-gutter: 3rem;
|
||||
--popup-padding-inline: 1rem;
|
||||
--popup-padding-block: 0.75rem;
|
||||
--video-modal-primary-fr: 1.85fr;
|
||||
--video-modal-secondary-min: 320px;
|
||||
--video-modal-secondary-max: 44rem;
|
||||
--video-modal-secondary-fr: 1fr;
|
||||
--video-modal-layout-max: calc(
|
||||
var(--layout-player-max-width) + var(--video-modal-secondary-max) +
|
||||
var(--space-2xl-plus)
|
||||
);
|
||||
--revert-modal-sidebar-min: 240px;
|
||||
--revert-modal-sidebar-width: 280px;
|
||||
--popover-inline-safe-max: calc(100vw - var(--space-xl));
|
||||
|
||||
/* Progress primitives */
|
||||
--progress-height-sm: 0.25rem;
|
||||
--progress-height-md: 0.5rem;
|
||||
--progress-radius: var(--radius-full);
|
||||
--progress-track-color: var(--color-border-subtle);
|
||||
--progress-fill-color: var(--color-info);
|
||||
--progress-transition-duration: var(--motion-duration-fast);
|
||||
--progress-transition-ease: var(--motion-ease-out);
|
||||
|
||||
/* Spacing scale */
|
||||
--space-4xs: 0.0625rem;
|
||||
--space-3xs: 0.125rem;
|
||||
--space-2xs: 0.25rem;
|
||||
--space-xs: 0.5rem;
|
||||
--space-xs-snug: 0.4rem;
|
||||
--space-sm: 0.75rem;
|
||||
--space-md: 1rem;
|
||||
--space-md-plus: 1.25rem;
|
||||
--space-lg: 1.5rem;
|
||||
--space-xl: 2rem;
|
||||
--space-xl-compact: 2.25rem;
|
||||
--space-xl-plus: 2.5rem;
|
||||
--space-2xl: 3rem;
|
||||
--space-2xl-plus: 3.5rem;
|
||||
|
||||
/* Border radii */
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
--radius-full: 9999px;
|
||||
--radius-modal-xl: 1.75rem;
|
||||
--radius-sidebar-shell: 1.5rem;
|
||||
--radius-sidebar-panel: 1.1rem;
|
||||
--radius-sidebar-nav: 0.95rem;
|
||||
--radius-toggle-thumb: 0.5625rem;
|
||||
|
||||
/* Component sizing */
|
||||
--menu-min-width: 12rem;
|
||||
--modal-max-width: 62.5rem;
|
||||
--modal-sheet-inline-max: var(--modal-max-width);
|
||||
--modal-shell-max-height: 90vh;
|
||||
--modal-sheet-max-height: 92vh;
|
||||
--modal-body-max-height: calc(92vh - 4rem);
|
||||
--modal-pane-max-height: 48vh;
|
||||
--modal-pane-min-height: 20rem;
|
||||
--profile-modal-nav-min-width: max-content;
|
||||
--profile-modal-nav-max-width: max-content;
|
||||
--profile-modal-scrollbar-offset: var(--space-2xs);
|
||||
--modal-embed-height: 80vh;
|
||||
--profile-history-max-height: 26.25rem;
|
||||
--status-log-min-height: 5rem;
|
||||
--layout-player-max-width: 68.75rem;
|
||||
--layout-docs-max-width: 92.5rem;
|
||||
--icon-size-sm: var(--font-size-lg);
|
||||
--icon-size-md: var(--space-md-plus);
|
||||
--icon-size-lg: var(--space-xl-plus);
|
||||
--avatar-size-lg: var(--space-2xl-plus);
|
||||
--form-textarea-min-height: 8rem;
|
||||
--scrollbar-thin-width: 0.4rem;
|
||||
--modal-progress-height: calc(var(--space-3xs) * 3);
|
||||
--video-modal-action-min-width: 2.75rem;
|
||||
--outline-thick-width: 0.1875rem;
|
||||
--icon-button-ring-width: var(--outline-thick-width);
|
||||
|
||||
/* Form controls */
|
||||
--form-select-icon-offset: var(--space-sm);
|
||||
--form-select-icon-gap: var(--space-xs-snug);
|
||||
--form-select-icon-hit-area: var(--space-xl-plus);
|
||||
--form-select-icon-size: 0.6rem;
|
||||
--form-select-divider-width: var(--border-width-hairline);
|
||||
--form-checkbox-checkmark-offset: calc(var(--space-3xs) * -1);
|
||||
|
||||
/* Utility measurements */
|
||||
--offscreen-offset: -9999px;
|
||||
|
||||
/* Motion */
|
||||
--motion-card-enter-translate: var(--space-xs);
|
||||
--motion-fade-in-translate: var(--space-md-plus);
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 0.0625rem 0.125rem 0 var(--color-shadow-floating);
|
||||
--shadow-md: 0 0.25rem 0.375rem -0.0625rem var(--color-shadow-floating);
|
||||
--shadow-lg: 0 0.625rem 0.9375rem -0.1875rem var(--color-shadow-elevated);
|
||||
--shadow-modal: 0 2.75rem 6rem -1.25rem var(--color-shadow-modal);
|
||||
--shadow-sidebar-shell: 0 1.875rem 3.75rem -1.5rem var(--color-shadow-sidebar);
|
||||
--shadow-sidebar-accent: 0 1.65rem 3.25rem -1.35rem var(--color-sidebar-glow);
|
||||
--shadow-sidebar-trigger: 0 1.25rem 2.5rem -1.35rem var(--color-shadow-panel);
|
||||
--shadow-sidebar-dropup: 0 1.75rem 3.25rem -1.45rem var(--color-sidebar-glow);
|
||||
--shadow-sidebar-panel: 0 2.25rem 4.5rem -1.85rem var(--color-shadow-sidebar);
|
||||
--shadow-sidebar-focus-ring:
|
||||
0 0 0 0.125rem var(--color-sidebar-glow),
|
||||
0 0 0 0.25rem var(--color-sidebar-hover-strong);
|
||||
--shadow-popover-zap: 0 22px 48px -18px var(--color-shadow-intense);
|
||||
--shadow-video-modal: 0 1rem 2.25rem var(--color-shadow-elevated);
|
||||
|
||||
/* Typography */
|
||||
--font-family-sans: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
|
||||
--font-family-mono: "Fira Code", "SFMono-Regular", ui-monospace, monospace;
|
||||
--font-size-4xs: 0.65rem;
|
||||
--font-size-3xs: 0.6875rem;
|
||||
--font-size-2xs: 0.7rem;
|
||||
--font-size-xs: 0.75rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.125rem;
|
||||
--font-size-xl: 1.5rem;
|
||||
--font-size-2xl: 2rem;
|
||||
--font-size-xl-plus: 1.875rem;
|
||||
--font-size-modal-title-base: 1.25rem;
|
||||
--font-size-sidebar-nav: 0.95rem;
|
||||
--font-size-sidebar-subnav: 0.85rem;
|
||||
--font-size-sidebar-trigger: 0.9rem;
|
||||
--line-height-tight: 1.1;
|
||||
--line-height-snug: 1.25;
|
||||
--line-height-base: 1.5;
|
||||
--line-height-relaxed: 1.75;
|
||||
--tracking-extra-wide: 0.2em;
|
||||
|
||||
/* Beacon UI tokens */
|
||||
--grid-border-width: calc(var(--space-2xs) / 4);
|
||||
--grid-canvas-padding-top: var(--grid-border-width);
|
||||
--grid-viewport-min-height: calc(var(--space-xl) - var(--space-sm));
|
||||
--grid-cell-padding-block: var(--space-sm);
|
||||
--grid-cell-padding-inline: var(--space-md);
|
||||
--grid-header-font-size: var(--font-size-xs);
|
||||
--grid-header-letter-spacing: 0.08em;
|
||||
--grid-overlay-margin: 10%;
|
||||
--grid-overlay-padding-block: var(--space-md);
|
||||
--grid-overlay-padding-inline: var(--space-xl);
|
||||
--grid-overlay-radius: var(--radius-lg);
|
||||
--grid-overlay-font-size: var(--font-size-lg);
|
||||
--grid-overlay-text-opacity: 0.66;
|
||||
--grid-resizer-width: calc(var(--space-2xs) * 1.25);
|
||||
--grid-resize-overlay-width: var(--grid-border-width);
|
||||
--grid-sort-indicator-size: calc(var(--space-lg) - var(--space-2xs));
|
||||
--grid-sort-indicator-offset: var(--space-xs);
|
||||
--grid-sort-indicator-stroke: calc(var(--space-2xs) / 2);
|
||||
--grid-sort-indicator-thickness: var(--grid-border-width);
|
||||
--notify-padding-block: var(--space-md);
|
||||
--notify-padding-inline: var(--space-2xl);
|
||||
--notify-font-size: var(--font-size-base);
|
||||
--notify-dismiss-size: calc(var(--space-xl) + var(--space-xs));
|
||||
--notify-dismiss-size-compact: calc(var(--space-lg) + var(--space-2xs));
|
||||
--notify-dismiss-font-size: var(--font-size-sm);
|
||||
--notify-dismiss-radius: var(--radius-full);
|
||||
--notify-dismiss-color: var(--color-subtle);
|
||||
--notify-dismiss-background: var(--color-overlay-panel);
|
||||
--notify-dismiss-background-hover: var(--color-overlay-panel-soft);
|
||||
--z-notify: 1200;
|
||||
|
||||
/* Motion */
|
||||
--motion-duration-fast: 120ms;
|
||||
--motion-duration-base: 160ms;
|
||||
--motion-duration-slow: 280ms;
|
||||
--motion-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--motion-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--modal-motion-duration: var(--motion-duration-base);
|
||||
}
|
||||
:root[data-theme="light"] {
|
||||
/* Light theme palette */
|
||||
--color-page: rgb(247 248 252);
|
||||
--color-page-alt: rgb(242 244 248);
|
||||
--color-surface: rgb(255 255 255);
|
||||
--color-surface-alt: rgb(248 250 252);
|
||||
--color-bg: var(--color-surface);
|
||||
--color-card: rgb(255 255 255);
|
||||
--color-card-private: rgb(243 236 255);
|
||||
--color-card-critical: rgb(255 236 238);
|
||||
--color-panel: rgb(252 253 255);
|
||||
--color-panel-hover: rgb(239 242 249);
|
||||
--color-border: rgb(210 219 236);
|
||||
--color-border-subtle: rgb(229 234 242);
|
||||
|
||||
--color-accent: rgb(99 102 241);
|
||||
--color-accent-strong: rgb(79 70 229);
|
||||
--color-accent-pressed: rgb(67 56 202);
|
||||
--color-primary: var(--color-accent-strong);
|
||||
--color-secondary: var(--color-accent);
|
||||
|
||||
--color-logo-accent: var(--color-accent-strong);
|
||||
--color-logo-wordmark: rgb(40 54 77);
|
||||
--color-logo-wordmark-inverse: var(--color-white);
|
||||
--color-logo-background: rgb(15 23 42);
|
||||
--color-logo-background-inverse: var(--color-white);
|
||||
|
||||
--color-info: rgb(59 130 246);
|
||||
--color-info-strong: rgb(37 99 235);
|
||||
--color-info-pressed: rgb(29 78 216);
|
||||
--color-info-glass: rgb(59 130 246 / 0.14);
|
||||
--color-info-border: rgb(59 130 246 / 0.35);
|
||||
--color-info-on: rgb(227 242 255);
|
||||
|
||||
--color-muted-strong: rgb(71 85 105);
|
||||
--color-text: rgb(24 31 44);
|
||||
--color-text-strong: rgb(12 16 26);
|
||||
--color-text-inverse: rgb(255 255 255);
|
||||
--color-text-muted: rgb(102 112 133);
|
||||
--color-muted: rgb(136 146 166);
|
||||
--color-muted-alt: rgb(120 130 150);
|
||||
--color-subtle-text: rgb(136 146 166);
|
||||
--color-subtle: rgb(243 245 250);
|
||||
--color-surface-sunken: rgb(232 235 244);
|
||||
--color-surface-muted: rgb(236 240 249);
|
||||
|
||||
--color-critical: rgb(220 54 70);
|
||||
--color-critical-strong: rgb(185 32 55);
|
||||
--color-danger-surface: rgb(252 226 230);
|
||||
--color-danger-border: rgb(220 54 70 / 0.35);
|
||||
--color-danger-on: rgb(96 18 31);
|
||||
|
||||
--color-warning: rgb(234 179 8);
|
||||
--color-warning-strong: rgb(202 138 4);
|
||||
--color-warning-surface: rgb(253 224 71 / 0.35);
|
||||
--color-warning-border: rgb(234 179 8 / 0.35);
|
||||
--color-warning-glass: rgb(253 224 71 / 0.18);
|
||||
--color-warning-on: rgb(92 55 2);
|
||||
|
||||
--color-success: rgb(22 163 74);
|
||||
--color-success-strong: rgb(21 128 61);
|
||||
--color-success-surface: rgb(222 250 236);
|
||||
--color-success-border: rgb(34 197 94 / 0.3);
|
||||
--color-success-on: rgb(18 84 48);
|
||||
|
||||
--color-private: rgb(139 92 246);
|
||||
--color-private-strong: rgb(124 58 237);
|
||||
--color-private-surface: rgb(237 233 254);
|
||||
--color-private-border: rgb(139 92 246 / 0.32);
|
||||
--color-private-on: rgb(74 29 150);
|
||||
|
||||
--color-neutral-700: rgb(71 85 105);
|
||||
--color-neutral-600: rgb(102 112 133);
|
||||
--color-neutral-surface: rgb(232 236 244);
|
||||
--color-neutral-border: rgb(205 213 226);
|
||||
--color-neutral-on: rgb(62 72 92);
|
||||
|
||||
--color-white: rgb(255 255 255);
|
||||
--color-black: rgb(0 0 0);
|
||||
|
||||
--color-overlay: rgb(15 23 42 / 0.3);
|
||||
--color-overlay-black: rgb(15 23 42 / 0.7);
|
||||
--color-overlay-muted: rgb(60 75 105 / 0.18);
|
||||
--color-overlay-strong: rgb(15 23 42 / 0.56);
|
||||
--color-overlay-darker: rgb(12 18 32 / 0.68);
|
||||
--color-overlay-panel: rgb(248 250 255 / 0.92);
|
||||
--color-overlay-panel-soft: rgb(244 247 255 / 0.78);
|
||||
--color-overlay-panel-tint: rgb(146 170 211 / 0.24);
|
||||
--color-overlay-panel-glass: rgb(255 255 255 / 0.85);
|
||||
--color-overlay-panel-glass-soft: rgb(246 249 255 / 0.7);
|
||||
--color-overlay-panel-gradient-start: rgb(255 255 255 / 0.95);
|
||||
--color-overlay-panel-gradient-end: rgb(232 236 244 / 0.92);
|
||||
--color-modal-backdrop: rgb(15 23 42 / 0.28);
|
||||
--color-modal-surface: rgb(255 255 255 / 0.94);
|
||||
--color-overlay-veil: rgb(103 112 133 / 0.45);
|
||||
--color-overlay-banner-bottom: rgb(255 255 255 / 0.6);
|
||||
--color-overlay-banner-transparent: rgb(255 255 255 / 0);
|
||||
--color-overlay-backdrop: rgb(15 23 42 / 0.2);
|
||||
|
||||
--scrollbar-track: color-mix(
|
||||
in srgb,
|
||||
var(--color-sidebar-border-strong) 40%,
|
||||
transparent
|
||||
);
|
||||
--scrollbar-thumb: color-mix(in srgb, var(--color-accent) 64%, transparent);
|
||||
--scrollbar-thumb-hover: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent-strong) 76%,
|
||||
transparent
|
||||
);
|
||||
|
||||
--color-sidebar-surface: rgb(255 255 255 / 0.9);
|
||||
--color-sidebar-surface-alt: rgb(236 240 247 / 0.72);
|
||||
--color-sidebar-border: rgb(184 194 212 / 0.28);
|
||||
--color-sidebar-border-strong: rgb(148 163 184 / 0.36);
|
||||
--color-sidebar-hover: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent) 16%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-hover-strong: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent-strong) 24%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-glow: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent) 36%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-accent-start: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent-strong) 88%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-accent-end: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent) 82%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-text-muted: var(--color-text-muted);
|
||||
--sidebar-icon-filter: brightness(0) saturate(100%);
|
||||
|
||||
--color-surface-tint-subtle: rgb(15 23 42 / 0.08);
|
||||
--color-border-translucent: rgb(148 163 184 / 0.18);
|
||||
--color-border-translucent-strong: rgb(148 163 184 / 0.22);
|
||||
--color-border-translucent-bright: rgb(148 163 184 / 0.28);
|
||||
--color-border-translucent-stronger: rgb(148 163 184 / 0.32);
|
||||
--color-border-translucent-strongest: rgb(148 163 184 / 0.36);
|
||||
--color-border-translucent-glow: rgb(148 163 184 / 0.45);
|
||||
--color-border-translucent-medium: rgb(148 163 184 / 0.2);
|
||||
--color-border-neutral-soft: rgb(148 163 184 / 0.25);
|
||||
--color-border-info-soft: rgb(59 130 246 / 0.32);
|
||||
--color-border-sidebar-strong: rgb(102 112 133 / 0.75);
|
||||
|
||||
--color-ring-private: rgb(139 92 246 / 0.5);
|
||||
--color-ring-private-hover: rgb(168 85 247 / 0.6);
|
||||
--color-ring-critical: rgb(220 54 70 / 0.35);
|
||||
--color-ring-critical-soft: rgb(220 54 70 / 0.3);
|
||||
--color-ring-critical-hover: rgb(220 54 70 / 0.4);
|
||||
--color-ring-critical-strong: rgb(220 54 70 / 0.5);
|
||||
--color-ring-alert: rgb(239 68 68 / 0.48);
|
||||
|
||||
--color-gradient-visibility-start: rgb(99 102 241 / 0.28);
|
||||
--color-gradient-visibility-end: rgb(129 140 248 / 0.24);
|
||||
|
||||
--color-white-soft: rgb(255 255 255 / 0.08);
|
||||
--color-white-softer: rgb(255 255 255 / 0.24);
|
||||
--color-white-softest: rgb(255 255 255 / 0.32);
|
||||
|
||||
--color-shadow-floating: rgb(15 23 42 / 0.18);
|
||||
--color-shadow-elevated: rgb(15 23 42 / 0.16);
|
||||
--color-shadow-panel: rgb(15 23 42 / 0.12);
|
||||
--color-shadow-notification: rgb(15 23 42 / 0.14);
|
||||
--color-shadow-intense: rgb(15 23 42 / 0.22);
|
||||
--color-shadow-sidebar: rgb(15 23 42 / 0.28);
|
||||
--color-shadow-modal: rgb(15 23 42 / 0.28);
|
||||
|
||||
--color-divider-strong: rgb(203 213 225 / 0.6);
|
||||
--color-text-frosted-strong: rgb(40 54 77 / 0.85);
|
||||
--color-text-frosted: rgb(61 74 98 / 0.8);
|
||||
--color-text-frosted-soft: rgb(82 96 122 / 0.72);
|
||||
}
|
||||
:root[data-theme="dark"],
|
||||
:root:not([data-theme]) {
|
||||
/* Dark theme palette */
|
||||
--color-page: rgb(6 10 18);
|
||||
--color-page-alt: rgb(8 12 22);
|
||||
--color-surface: rgb(10 14 26);
|
||||
--color-surface-alt: rgb(14 20 34);
|
||||
--color-bg: var(--color-surface);
|
||||
--color-card: rgb(18 26 44);
|
||||
--color-card-private: rgb(38 28 62);
|
||||
--color-card-critical: rgb(58 20 28);
|
||||
--color-panel: rgb(16 24 40);
|
||||
--color-panel-hover: rgb(24 34 52);
|
||||
--color-border: rgb(61 80 112);
|
||||
--color-border-subtle: rgb(38 52 76);
|
||||
|
||||
--color-accent: rgb(129 140 248);
|
||||
--color-accent-strong: rgb(99 102 241);
|
||||
--color-accent-pressed: rgb(79 70 229);
|
||||
--color-primary: var(--color-accent-strong);
|
||||
--color-secondary: rgb(56 189 248);
|
||||
|
||||
--color-logo-accent: var(--color-accent-strong);
|
||||
--color-logo-wordmark: rgb(198 207 222);
|
||||
--color-logo-wordmark-inverse: var(--color-white);
|
||||
--color-logo-background: rgb(12 18 30);
|
||||
--color-logo-background-inverse: var(--color-white);
|
||||
|
||||
--color-info: rgb(96 165 250);
|
||||
--color-info-strong: rgb(56 132 255);
|
||||
--color-info-pressed: rgb(37 99 235);
|
||||
--color-info-glass: rgb(56 132 255 / 0.22);
|
||||
--color-info-border: rgb(96 165 250 / 0.42);
|
||||
--color-info-on: rgb(235 244 255);
|
||||
|
||||
--color-muted-strong: rgb(206 215 232);
|
||||
--color-text: rgb(226 232 240);
|
||||
--color-text-strong: rgb(244 247 255);
|
||||
--color-text-inverse: rgb(12 18 30);
|
||||
--color-text-muted: rgb(165 179 201);
|
||||
--color-muted: rgb(148 163 184);
|
||||
--color-muted-alt: rgb(128 139 162);
|
||||
--color-subtle-text: rgb(168 180 205);
|
||||
--color-subtle: rgb(28 38 58);
|
||||
--color-surface-sunken: rgb(13 19 32 / 0.92);
|
||||
--color-surface-muted: rgb(18 26 44 / 0.82);
|
||||
|
||||
--color-critical: rgb(239 83 98);
|
||||
--color-critical-strong: rgb(220 38 38);
|
||||
--color-danger-surface: rgb(220 38 38 / 0.28);
|
||||
--color-danger-border: rgb(220 38 38 / 0.45);
|
||||
--color-danger-on: rgb(255 228 233);
|
||||
|
||||
--color-warning: rgb(250 204 21);
|
||||
--color-warning-strong: rgb(234 179 8);
|
||||
--color-warning-surface: rgb(253 224 71 / 0.22);
|
||||
--color-warning-border: rgb(250 204 21 / 0.42);
|
||||
--color-warning-glass: rgb(253 224 71 / 0.2);
|
||||
--color-warning-on: rgb(26 18 0);
|
||||
|
||||
--color-success: rgb(34 197 94);
|
||||
--color-success-strong: rgb(22 163 74);
|
||||
--color-success-surface: rgb(22 163 74 / 0.24);
|
||||
--color-success-border: rgb(34 197 94 / 0.45);
|
||||
--color-success-on: rgb(210 252 228);
|
||||
|
||||
--color-private: rgb(168 85 247);
|
||||
--color-private-strong: rgb(139 92 246);
|
||||
--color-private-surface: rgb(124 58 237 / 0.26);
|
||||
--color-private-border: rgb(168 85 247 / 0.45);
|
||||
--color-private-on: rgb(239 230 255);
|
||||
|
||||
--color-neutral-700: rgb(58 75 110);
|
||||
--color-neutral-600: rgb(90 105 133);
|
||||
--color-neutral-surface: rgb(99 114 144 / 0.24);
|
||||
--color-neutral-border: rgb(99 114 144 / 0.35);
|
||||
--color-neutral-on: rgb(210 220 238);
|
||||
|
||||
--color-white: rgb(255 255 255);
|
||||
--color-black: rgb(0 0 0);
|
||||
|
||||
--color-overlay: rgb(8 11 20 / 0.72);
|
||||
--color-overlay-black: rgb(0 0 0 / 0.88);
|
||||
--color-overlay-muted: rgb(12 18 30 / 0.6);
|
||||
--color-overlay-strong: rgb(8 12 24 / 0.82);
|
||||
--color-overlay-darker: rgb(5 8 16 / 0.9);
|
||||
--color-overlay-panel: rgb(14 20 34 / 0.92);
|
||||
--color-overlay-panel-soft: rgb(18 26 42 / 0.78);
|
||||
--color-overlay-panel-tint: rgb(56 93 160 / 0.36);
|
||||
--color-overlay-panel-glass: rgb(14 24 42 / 0.86);
|
||||
--color-overlay-panel-glass-soft: rgb(16 28 48 / 0.72);
|
||||
--color-overlay-panel-gradient-start: rgb(18 26 44 / 0.92);
|
||||
--color-overlay-panel-gradient-end: rgb(10 16 30 / 0.92);
|
||||
--color-modal-backdrop: rgb(5 8 14 / 0.68);
|
||||
--color-modal-surface: rgb(14 22 38 / 0.88);
|
||||
--color-overlay-veil: rgb(4 6 12 / 0.94);
|
||||
--color-overlay-banner-bottom: rgb(0 0 0 / 0.6);
|
||||
--color-overlay-banner-transparent: rgb(0 0 0 / 0);
|
||||
--color-overlay-backdrop: rgb(5 8 14 / 0.75);
|
||||
|
||||
--scrollbar-track: color-mix(
|
||||
in srgb,
|
||||
var(--color-sidebar-surface-alt) 54%,
|
||||
transparent
|
||||
);
|
||||
--scrollbar-thumb: color-mix(in srgb, var(--color-accent) 72%, transparent);
|
||||
--scrollbar-thumb-hover: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent-strong) 84%,
|
||||
transparent
|
||||
);
|
||||
|
||||
--color-sidebar-surface: rgb(12 18 30 / 0.94);
|
||||
--color-sidebar-surface-alt: rgb(10 18 32 / 0.8);
|
||||
--color-sidebar-border: rgb(86 106 142 / 0.26);
|
||||
--color-sidebar-border-strong: rgb(112 133 170 / 0.42);
|
||||
--color-sidebar-hover: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent) 22%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-hover-strong: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent-strong) 32%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-glow: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent) 42%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-accent-start: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent) 92%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-accent-end: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent-strong) 88%,
|
||||
transparent
|
||||
);
|
||||
--color-sidebar-text-muted: var(--color-text-muted);
|
||||
--sidebar-icon-filter: brightness(0) invert(1);
|
||||
|
||||
--color-surface-tint-subtle: rgb(94 113 161 / 0.12);
|
||||
--color-border-translucent: rgb(99 114 144 / 0.22);
|
||||
--color-border-translucent-strong: rgb(110 130 170 / 0.28);
|
||||
--color-border-translucent-bright: rgb(148 163 184 / 0.35);
|
||||
--color-border-translucent-stronger: rgb(148 163 184 / 0.38);
|
||||
--color-border-translucent-strongest: rgb(198 207 222 / 0.45);
|
||||
--color-border-translucent-glow: rgb(129 140 248 / 0.55);
|
||||
--color-border-translucent-medium: rgb(99 114 144 / 0.28);
|
||||
--color-border-neutral-soft: rgb(99 114 144 / 0.3);
|
||||
--color-border-info-soft: rgb(96 165 250 / 0.35);
|
||||
--color-border-sidebar-strong: rgb(32 46 68 / 0.88);
|
||||
|
||||
--color-ring-private: rgb(168 85 247 / 0.55);
|
||||
--color-ring-private-hover: rgb(192 132 252 / 0.65);
|
||||
--color-ring-critical: rgb(239 83 98 / 0.42);
|
||||
--color-ring-critical-soft: rgb(239 83 98 / 0.35);
|
||||
--color-ring-critical-hover: rgb(239 83 98 / 0.5);
|
||||
--color-ring-critical-strong: rgb(220 38 38 / 0.6);
|
||||
--color-ring-alert: rgb(220 38 38 / 0.6);
|
||||
|
||||
--color-gradient-visibility-start: rgb(129 140 248 / 0.4);
|
||||
--color-gradient-visibility-end: rgb(192 132 252 / 0.32);
|
||||
|
||||
--color-white-soft: rgb(255 255 255 / 0.1);
|
||||
--color-white-softer: rgb(255 255 255 / 0.3);
|
||||
--color-white-softest: rgb(255 255 255 / 0.4);
|
||||
|
||||
--color-shadow-floating: rgb(8 11 30 / 0.55);
|
||||
--color-shadow-elevated: rgb(8 11 30 / 0.45);
|
||||
--color-shadow-panel: rgb(15 23 42 / 0.35);
|
||||
--color-shadow-notification: rgb(15 23 42 / 0.3);
|
||||
--color-shadow-intense: rgb(250 204 21 / 0.48);
|
||||
--color-shadow-sidebar: rgb(0 0 0 / 0.45);
|
||||
--color-shadow-modal: rgb(8 11 30 / 0.55);
|
||||
|
||||
--color-divider-strong: rgb(15 23 42 / 0.35);
|
||||
--color-text-frosted-strong: rgb(226 232 240 / 0.94);
|
||||
--color-text-frosted: rgb(226 232 240 / 0.9);
|
||||
--color-text-frosted-soft: rgb(226 232 240 / 0.82);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
import { __testExports } from "./js/nostr/commentEvents.js";
|
||||
const { isVideoCommentEvent, normalizeCommentTarget } = __testExports;
|
||||
import { devLogger, userLogger } from "./js/utils/logger.js";
|
||||
|
||||
// Logger mocking removed
|
||||
|
||||
const videoEventId = "0000000000000000000000000000000000000000000000000000000000000001";
|
||||
const rootKind = "30078";
|
||||
const rootAuthor = "0000000000000000000000000000000000000000000000000000000000000002";
|
||||
|
||||
const targetInput = {
|
||||
videoEventId,
|
||||
videoKind: rootKind,
|
||||
videoAuthorPubkey: rootAuthor,
|
||||
};
|
||||
|
||||
const descriptor = normalizeCommentTarget(targetInput);
|
||||
|
||||
console.log("Descriptor:", JSON.stringify(descriptor, null, 2));
|
||||
|
||||
const validEvent = {
|
||||
kind: 1111,
|
||||
tags: [
|
||||
["e", videoEventId, "wss://relay.example.com", "root"],
|
||||
["K", rootKind],
|
||||
["P", rootAuthor],
|
||||
],
|
||||
};
|
||||
|
||||
const missingKTagEvent = {
|
||||
kind: 1111,
|
||||
tags: [
|
||||
["e", videoEventId, "wss://relay.example.com", "root"],
|
||||
["P", rootAuthor],
|
||||
],
|
||||
};
|
||||
|
||||
const mismatchKTagEvent = {
|
||||
kind: 1111,
|
||||
tags: [
|
||||
["e", videoEventId, "wss://relay.example.com", "root"],
|
||||
["K", "12345"], // Wrong kind
|
||||
["P", rootAuthor],
|
||||
],
|
||||
};
|
||||
|
||||
const legacyEvent = {
|
||||
kind: 1,
|
||||
tags: [
|
||||
["e", videoEventId, "wss://relay.example.com", "root"],
|
||||
],
|
||||
};
|
||||
|
||||
console.log("Testing Valid Event:", isVideoCommentEvent(validEvent, descriptor));
|
||||
console.log("Testing Missing K Tag Event:", isVideoCommentEvent(missingKTagEvent, descriptor));
|
||||
console.log("Testing Mismatch K Tag Event:", isVideoCommentEvent(mismatchKTagEvent, descriptor));
|
||||
console.log("Testing Legacy Event:", isVideoCommentEvent(legacyEvent, descriptor));
|
||||
@@ -0,0 +1,43 @@
|
||||
# Beacon vendor CSS migration
|
||||
|
||||
This inventory captures the selectors that mattered in `torrent/dist/beacon.vendor.css` and how they now map to design-system driven rules. The goal is to keep future tweaks aligned with shared tokens rather than re-introducing ad-hoc styles or vendor bundles.
|
||||
|
||||
> **Contributor note:** Beacon CSS updates must follow the [“Styling & Theming Rules (token-first)” guidance in `AGENTS.md`](../../AGENTS.md#styling--theming-rules-token-first) and the [README CSS build pipeline](../../README.md#css-build-pipeline). Run the relevant scripts before committing changes—at minimum `npm run lint:css`, `npm run build`, and `npm run build:beacon` to ensure the Tailwind output and bundled assets stay in sync with token rules.
|
||||
|
||||
## Angular UI Grid
|
||||
|
||||
| Legacy selector(s) | Tailwind component mapping | Notes |
|
||||
| --- | --- | --- |
|
||||
| `.ui-grid`, `.ui-grid-render-container`, `.ui-grid-contents-wrapper` | `.ui-grid` block in `css/tailwind.source.css` | Uses shared border/box styling via `--grid-border-width`, preserving the translated container shell. |
|
||||
| `.ui-grid-top-panel-background`, `.ui-grid-footer-panel-background`, `.ui-grid-header`, `.ui-grid-top-panel` | Same block | Backgrounds and dividers now pull from `var(--color-surface-alt)` and `var(--color-border-translucent-medium)`. |
|
||||
| `.ui-grid-header-viewport`, `.ui-grid-footer-viewport`, `.ui-grid-header-canvas`, `.ui-grid-footer-canvas`, `.ui-grid-header-cell-wrapper`, `.ui-grid-footer-cell-wrapper`, `.ui-grid-header-cell-row`, `.ui-grid-footer-cell-row` | Same block | Table layout and overflow behaviour carried over verbatim. |
|
||||
| `.ui-grid-header-cell`, `.ui-grid-footer-cell`, `.ui-grid-header-cell .sortable`, `.ui-grid-header-cell .ui-grid-sort-priority-number` | Same block | Header typography now keyed off `--grid-header-font-size` and `--grid-header-letter-spacing`; sortable affordances reference spacing tokens. |
|
||||
| `.ui-grid-sortarrow`, `.ui-grid-sortarrow.down`, (icon font triangle) | `.ui-grid-sortarrow` + pseudo-element | Arrow is now rendered with CSS borders driven by `--grid-sort-indicator-*` tokens, eliminating the ui-grid icon font. |
|
||||
| `.ui-grid-vertical-bar` + child selectors | Same block | Divider widths reuse the shared border width token. |
|
||||
| `.ui-grid-viewport`, `.ui-grid-canvas` | Same block | Min heights and padding resolve through grid tokens instead of literal px. |
|
||||
| `.ui-grid-row`, row parity/hover/last-child selectors, `.ui-grid-row-selected > [ui-grid-row] > .ui-grid-cell` | Same block | Alternating row fills reference surface tokens and preserve selection colours. |
|
||||
| `.ui-grid-cell`, `.ui-grid-cell:last-child`, `.ui-grid-cell-contents`, `.ui-grid-cell-contents-hidden` | Same block | Cell padding now uses `--grid-cell-padding-*`; hidden content helper retained. |
|
||||
| `.ui-grid-cell-focus`, `.ui-grid-focuser`, `.ui-grid-offscreen` | Same block | Focus handling switches to tokenised shadows while keeping accessibility helpers. |
|
||||
| `.ui-grid-no-row-overlay`, `.ui-grid-no-row-overlay > *` | Same block | Empty-state panel adopts design-system surface/background tokens. |
|
||||
| `.ui-grid-menu-button`, `.ui-grid-menu` | Same block | Menu chrome now uses shared spacing, shadows, and border tokens. |
|
||||
| `.ui-grid-column-resizer`, `.ui-grid.column-resizing`, `.ui-grid.column-resizing .ui-grid-resize-overlay` | Same block | Resizer hit boxes use `--grid-resizer-width`; overlay colour references translucent border tokens. |
|
||||
| `.ui-grid-row-saving .ui-grid-cell`, `.ui-grid-row-dirty .ui-grid-cell`, `.ui-grid-row-error .ui-grid-cell` | Same block | Status colours mapped to `var(--color-muted-strong)`, `var(--color-warning-strong)`, and `var(--color-critical-strong)`. |
|
||||
| `.ui-grid-disable-selection` | Same block | Still prevents text selection with consistent cursor feedback. |
|
||||
| `.ui-grid-animate-spin`, `@keyframes ui-grid-spin` | Same block | Animation now hooks into the global motion duration token. |
|
||||
|
||||
> **Omitted vendor selectors:** Column menus, tree, filter icons, and other ui-grid extension classes remain unused in the beacon routes, so the icon font `@font-face` payload was intentionally dropped.
|
||||
|
||||
## ng-notify
|
||||
|
||||
| Legacy selector(s) | Tailwind component mapping | Notes |
|
||||
| --- | --- | --- |
|
||||
| `.ngn`, `.ngn.ngn-visible`, `.ngn.ngn-enter-active`, `.ngn.ngn-leave-active` | `.ngn` block in `css/tailwind.source.css` | Layout, typography, and z-index now rely on `--notify-*` and `--z-notify`. Pointer-events handling mirrors vendor behaviour. |
|
||||
| `.ngn-top`, `.ngn-bottom`, `.ngn-component` | Same block | Placement helpers rewritten with logical properties (`inset-block-*`). |
|
||||
| `.ngn-dismiss`, `.ngn-sticky .ngn-dismiss`, `.ngn-dismiss:hover`, `.ngn-dismiss:active` | Same block | Close button sizing derives from tokens; hover/active states use `color-mix` against overlay tokens. |
|
||||
| `.ngn-info`, `.ngn-error`, `.ngn-success`, `.ngn-warn`, `.ngn-grimace` | Same block | Severity palettes tie directly into existing info/warning/critical tokens. |
|
||||
| `.ngn-prime.*`, `.ngn-pastel.*`, `.ngn-pitchy.*` | Same block | Theme variants blend design-system colours via `color-mix`, preserving tonal differences without hard-coded hex values. |
|
||||
| `@media (max-width: 30rem)` overrides | Same block | Compact sizing switches to shared spacing and font-size tokens. |
|
||||
|
||||
## Font faces
|
||||
|
||||
The legacy bundle shipped three `@font-face` blocks for the `ui-grid` icon font. Sort indicators and menu affordances now render through CSS borders and shared tokens, so the icon font is no longer required or embedded in the Tailwind build.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Application Responsibility Audit
|
||||
|
||||
This document catalogs the major responsibilities still implemented inside `Application` (formerly `bitvidApp`). Each area is a candidate for future extraction into dedicated service or UI modules.
|
||||
|
||||
## Auth & Profile Coordination
|
||||
- Instantiates `AuthService`, wires login/logout/profile listeners, and exposes helper methods for cached profile state management.【F:js/app.js†L221-L314】【F:js/app.js†L620-L772】
|
||||
- Manages profile modal lifecycle, navigation between panes, and account switching UI state.【F:js/app.js†L283-L420】【F:js/app.js†L1328-L1822】
|
||||
|
||||
## Playback Orchestration
|
||||
- Configures `PlaybackService`, logs telemetry callbacks, and tracks the active playback session lifecycle.【F:js/app.js†L224-L323】【F:js/app.js†L2805-L3352】
|
||||
- Coordinates view logging, cooldown keys, and watch-count subscriptions for the primary player and modal overlays.【F:js/app.js†L4305-L4592】【F:js/app.js†L4972-L5332】
|
||||
|
||||
## Modal & UI Composition
|
||||
- Constructs upload, edit, revert, and video modals; registers their event listeners; and bridges UI events back into application logic.【F:js/app.js†L340-L615】【F:js/app.js†L2382-L2799】
|
||||
- Builds and manages the `VideoListView` component, injecting helpers for health badges, share links, and context menu actions.【F:js/app.js†L516-L744】【F:js/app.js†L2149-L2376】
|
||||
|
||||
## Routing & View Bootstrapping
|
||||
- Handles initial view loading, hash-based navigation, and video list mounting during application startup.【F:js/app.js†L903-L959】【F:js/app.js†L1932-L2124】
|
||||
- Provides navigation helpers for channel/profile transitions tied to hash routing.【F:js/app.js†L934-L968】
|
||||
|
||||
## State & Watch History Management
|
||||
- Synchronizes watch-history preferences, local metadata caching, and publication of watch history events to services.【F:js/app.js†L1975-L2093】【F:js/app.js†L4087-L4478】
|
||||
- Tracks app-level state such as active intervals, cleanup routines, view counters, and URL/magnet health caches.【F:js/app.js†L200-L323】【F:js/app.js†L3354-L4075】
|
||||
|
||||
These clusters outline the remaining cross-cutting responsibilities that can be incrementally migrated into focused modules under `js/services` and `js/ui`.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Deployment Notes
|
||||
|
||||
## Design system rollout
|
||||
|
||||
- **Status:** The design system styling is always active. The former `FEATURE_DESIGN_SYSTEM` runtime flag has been removed, so there is no rollback toggle during deployment.
|
||||
- **Template contract:** Every layout, view, and partial must keep `data-ds` on its root element. The entrypoint stamps `data-ds="new"` during bootstrap so controllers inherit the correct primitives.
|
||||
- **Controllers:** UI controllers still receive a `designSystem` context, but `context.isNew()` now always returns `true`. Use the context for consistency, but do not branch on legacy styling paths.
|
||||
@@ -0,0 +1,484 @@
|
||||
# bitvid Design System Primitives
|
||||
|
||||
bitvid's visual language is now powered by Tailwind utilities backed by our design tokens. This document lists the primitives available via `css/tailwind.source.css`, how they behave across states, and when to mix them with Tailwind utilities.
|
||||
|
||||
All templates and embeds must load `css/tailwind.generated.css`; that compiled bundle is the only stylesheet shipped with the app. The file is generated during CI/Netlify deploys (and remains gitignored locally), so commit only the source styles that feed the build. Remove any lingering references to legacy `tailwind.min.css` assets when porting surfaces into the design system.
|
||||
|
||||
> **UI Styling:** For day-to-day decisions, start with the [Styling & Theming Rules](../AGENTS.md#styling--theming-rules-token-first) in `AGENTS.md` and the [CSS build pipeline overview](../README.md#css-build-pipeline). Run `npm run lint:css` before committing so design-token regressions are caught early.
|
||||
|
||||
## Runtime contract
|
||||
|
||||
The design system is now the canonical styling path across bitvid—there is no runtime flag or rollback toggle. Every template renders with `data-ds="new"` on its root container, and controllers always receive the "new" mode from the `designSystem` context (see `js/designSystem.js`). When migrating surfaces, ensure their root nodes include `data-ds` so the entrypoint can stamp the attribute during bootstrap.
|
||||
|
||||
## Dynamic runtime styling
|
||||
|
||||
Inline styles are blocked in production builds, so runtime components that need bespoke coordinates or measurements must route their overrides through `js/designSystem/dynamicStyles.js`.
|
||||
|
||||
- `registerScope(id, selectors)` creates (or reuses) a constructable stylesheet when supported, allocates a unique identifier, and injects empty rules scoped to `[data-ds-style-id="<id>"]`. Callers attach the attribute to any element that should inherit those rules.
|
||||
- `setVariables(id, tokens)` updates CSS custom properties on the registered scope. Pass CSS variable names (for example, `"--ds-metric-probe-length"`) and string or number values—`setVariables` handles serialization and removes entries when they resolve to empty strings.
|
||||
- `releaseScope(id)` removes the generated rules and frees the identifier. Always invoke it from component teardown paths to avoid leaking rules between renders.
|
||||
|
||||
Design-system metrics lean on this helper to keep runtime measurements inline-style free:
|
||||
|
||||
- `readDesignTokenAsPixels` measures non-`px`/`rem` tokens with an offscreen probe that ships in core CSS (`.ds-metric-probe`). The probe is registered through the helper so dynamic lengths are fed into `--ds-metric-probe-length` without touching inline styles.
|
||||
|
||||
When introducing a new runtime feature that requires dynamic styling:
|
||||
|
||||
1. Register a dedicated scope during setup and attach the returned attribute to the relevant DOM node.
|
||||
2. Update custom properties with `setVariables` instead of touching `.style` or appending `<style>` tags.
|
||||
3. Clean up by calling `releaseScope` when the component unmounts.
|
||||
4. Prefer reusable primitives (like `.ds-metric-probe`) for offscreen measurement or layout shims so multiple components can share the same CSS.
|
||||
|
||||
## Typography & Layout Tokens
|
||||
|
||||
Small-type utilities and modal geometry are now backed by dedicated semantic tokens in `css/tokens.css`. The scale introduces
|
||||
`--font-size-4xs` (0.65rem), `--font-size-3xs` (0.6875rem), `--font-size-2xs` (0.7rem), and `--tracking-extra-wide`
|
||||
(0.2em) alongside modal measurements such as `--modal-shell-max-height`, `--modal-sheet-max-height`,
|
||||
`--modal-body-max-height`, `--modal-pane-max-height`, and `--profile-history-max-height`. Tailwind exposes the tokens through
|
||||
utility aliases including `text-4xs`, `text-3xs`, `text-2xs`, `tracking-extra-wide`, `max-h-modal-shell`, `max-h-modal-sheet`,
|
||||
`max-h-modal-body`, `max-h-modal-pane`, and `max-h-profile-history`. Use these utilities instead of arbitrary values so future
|
||||
scale adjustments can ship from a single source of truth.
|
||||
|
||||
### Migration notes
|
||||
|
||||
- Replace literal classes such as `text-[11px]`, `text-[0.7rem]`, and `text-[0.65rem]` with the new semantic helpers
|
||||
(`text-3xs`, `text-2xs`, `text-4xs`). The controller logic for saved profiles and modal renderers has been updated to follow
|
||||
the same pattern.
|
||||
- Swap bespoke tracking overrides (for example, `tracking-[0.2em]`) to `tracking-extra-wide` so the widened CTA headings stay
|
||||
consistent across themes.
|
||||
- Move modal shells and scroll containers off arbitrary `max-h-[...]` utilities in favour of `max-h-modal-shell`,
|
||||
`max-h-modal-sheet`, `max-h-modal-body`, `max-h-modal-pane`, and `max-h-profile-history`.
|
||||
- Keep the legacy search strings (like `text-[11px]`) in regression checklists—repositories should no longer include those
|
||||
selectors after migrating a template or controller.
|
||||
|
||||
### Tailwind arbitrary values lint
|
||||
|
||||
Run `npm run lint:tailwind-brackets` after touching markup or prose that embeds HTML. The lint walks `.html`, `.njk`, and `.md`
|
||||
sources and fails when Tailwind's bracket syntax introduces hard-coded measurements (`text-[11px]`, `max-w-[18rem]`, etc.).
|
||||
|
||||
- Prefer the semantic utilities shipped alongside the tokens (`max-w-popover-safe`, `max-h-modal-shell`, `text-3xs`).
|
||||
- When you truly need an arbitrary value, drive it with a design token by referencing the CSS custom property (`w-[var(--layout-player-max-width)]`, `min-h-[var(--form-textarea-min-height)]`).
|
||||
- Update the allowlist sparingly—every entry should correspond to a documented token so future adjustments cascade automatically.
|
||||
|
||||
The lint shares the same underlying script as `npm run lint:tokens`, so both commands stay in lockstep with the design-system
|
||||
guardrails.
|
||||
|
||||
## Theming
|
||||
|
||||
### `[data-theme]` contract
|
||||
|
||||
The default experience renders the production dark palette. Pages and embeds can opt into alternates by setting the `data-theme` attribute on the `<html>` (or any ancestor that wraps the component). `data-theme="light"` promotes the light tokens defined in `css/tokens.css`, swapping surface, panel, border, muted, and overlay colors while keeping semantic accents consistent. Omit the attribute or set it back to `default` to inherit the dark baseline. Reserve additional theme keys (for example, `data-theme="contrast"`) for upcoming accessibility palettes—the same hook will activate them once their tokens land. When scoping components inside legacy templates, prefer wrapping the smallest possible container so downstream shims continue to function.
|
||||
|
||||
### Token overrides & experiments
|
||||
|
||||
Tokens are sourced from `css/tokens.css` and consumed as CSS custom properties. To try temporary overrides—such as custom backgrounds for a partner landing page—stack a higher-specificity selector and reassign the variable without hard-coding literal colors:
|
||||
|
||||
```css
|
||||
.landing[data-theme="light"] {
|
||||
--color-bg-page: var(--color-bg-surface);
|
||||
--shadow-card: 0 10px 40px rgba(12, 14, 23, 0.25);
|
||||
}
|
||||
```
|
||||
|
||||
Limit overrides to variables defined in `css/tokens.css` and document any long-lived changes in this file so the design tokens remain the source of truth. Feature-flagged experiments should wrap overrides in a `data-flag-<name>` selector or toggle them via controller logic so the defaults remain stable when the flag is off.
|
||||
|
||||
## Semantic Palette
|
||||
|
||||
All interactive surfaces now draw from a shared semantic color map exposed through `css/tokens.css` and Tailwind utility aliases. The palette is grouped into text, surface, overlay, and status slots so controllers can swap state styles without reaching for raw Tailwind hues:
|
||||
|
||||
- **Text:** `text-primary`, `text-inverse`, `text-muted`, `text-muted-strong`, and `text-subtle` map to `--text-*` tokens. Use them for copy, helper text, and frosted overlays instead of `text-gray-*`.
|
||||
- **Surfaces:** `bg-surface`, `bg-surface-alt`, `bg-surface-raised`, `bg-surface-muted`, and `bg-overlay-*` wrap cards, modals, and scrims. Pair them with `border-surface`, `border-overlay`, or `border-overlay-strong` when rendering shells.
|
||||
- **Statuses:** `status-*` tokens expose matching text, background, and border slots. Tailwind utilities include `text-status-success`, `bg-status-warning-surface`, `border-status-danger-border`, and chip helpers (`.pill`, `.badge`) that accept `data-variant="info" | "success" | "warning" | "critical" | "private" | "neutral"`.
|
||||
- **Overlays:** `.overlay-scrim` and `.overlay-panel` helpers consume the overlay tier tokens for modals, sheets, and popovers. Adjust the strength via `data-strength`/`data-variant` rather than applying translucent grays. Blur states now ride semantic variables—reuse `--blur-modal-backdrop` for scrims, `--blur-modal-alert` for inline notices, and `--blur-thumbnail-blur` for frosted imagery instead of hard-coded pixel values.
|
||||
|
||||
### Migration guidance
|
||||
|
||||
- Swap raw Tailwind palette utilities (for example, `text-gray-400`, `bg-amber-500/10`, `border-red-700/70`) for the matching semantic utilities above. The new lint (`npm run lint:tailwind-colors`) fails when legacy color classes appear outside deprecated directories.
|
||||
- Replace ad-hoc chips (`inline-flex ... border-red-700/70 bg-red-900/40`) with `.pill` or `.badge` plus a `data-variant`. These helpers automatically pull the correct status text/border/fill tokens.
|
||||
- Use `text-info`/`hover:text-info-strong` for links that previously relied on `text-blue-400` so anchors adapt in both light and dark modes.
|
||||
- When rendering tinted descriptions or placeholders, prefer `text-muted`, `text-muted-strong`, or `text-subtle` over semi-transparent whites. Token overrides keep the same classes usable across the default and light themes.
|
||||
|
||||
### Layout & spacing extensions
|
||||
|
||||
The core spacing scale gained intermediate stops for tighter micro-layouts and larger hero treatments. `css/tokens.css` now exports `--space-4xs`, `--space-3xs`, `--space-xs-snug`, `--space-md-plus`, `--space-xl-compact`, `--space-xl-plus`, and `--space-2xl-plus`; Tailwind surfaces matching utilities such as `p-4xs`, `p-3xs`, `px-xs-snug`, `py-md-plus`, `gap-xl-compact`, `px-xl-plus`, and `gap-2xl-plus`. Pair them with the new layout primitives—`--menu-min-width` (`min-w-menu`), `--modal-max-width` (`max-w-modal`), `--layout-player-max-width` (`max-w-player`), and `--layout-docs-max-width` (`max-w-docs`)—to anchor consistent breakpoints across popovers, modals, and documentation shells without reintroducing raw `rem`/`px` literals.
|
||||
|
||||
### Previewing themes
|
||||
|
||||
The design-system kitchen sink (`docs/kitchen-sink.html`) exposes a theme switcher that mirrors the production toggle contract. Use the "Theme" select menu in the header to flip between `default`, `light`, and any preview keys (for example, `contrast`). This control updates `document.documentElement.dataset.theme` at runtime, letting you verify hover/focus/disabled states without redeploying. When authoring new palettes, add them to `css/tokens.css`, confirm the kitchen-sink toggle picks them up, and record before/after screenshots for review so the CI snapshots have clear context.
|
||||
|
||||
## Focus Handling
|
||||
|
||||
All interactive primitives mix in the shared `.focus-ring` helper. It removes default outlines and applies a branded ring using `focus-visible` only. When building bespoke components, add `.focus-ring` to your root interactive element or replicate its rules:
|
||||
|
||||
```html
|
||||
<button class="focus-ring inline-flex items-center gap-2 ...">Label</button>
|
||||
```
|
||||
|
||||
The helper includes:
|
||||
|
||||
- `focus-visible:ring-2` with `ring-info` and `ring-opacity-70`
|
||||
- `focus-visible:ring-offset-2` with an offset on `bg-surface`
|
||||
- `focus-visible:transition-shadow` using the tokenized `duration-fast` easing curve
|
||||
|
||||
Never hide focus styles without providing an accessible alternative.
|
||||
|
||||
## Motion Guardrails
|
||||
|
||||
### Motion inventory
|
||||
|
||||
The `rg "transition"` audit surfaces the components that animate by default. Keep this list in sync when new motion primitives land:
|
||||
|
||||
- **Buttons:** `.btn` and `.btn-ghost` transition their backgrounds, borders, and shadows using the shared token durations. 【F:css/tailwind.source.css†L18-L55】
|
||||
- **Cards and watch history:** `.card`, `.watch-history-card`, and their nested controls use hover/focus transitions plus optional entry animations via `data-motion="enter"`. 【F:css/tailwind.source.css†L238-L804】
|
||||
- **Popovers:** `.popover__panel` fades and scales between `[data-state]` values. 【F:css/tailwind.source.css†L119-L141】
|
||||
- **Modals:** `.bv-modal__panel`, `.video-modal__panel`, and `.player-modal__content` animate in concert with the modal nav/header layout helpers. 【F:docs/kitchen-sink.html†L335-L391】【F:css/tailwind.source.css†L385-L640】
|
||||
- **Feedback & loading:** `.progress-bar-fill`, `.status-spinner--inline`, and `.status-banner .status-spinner` communicate state changes with width transitions or spin animations. 【F:css/tailwind.source.css†L907-L948】
|
||||
- **Sidebar controls:** `.sidebar-nav-link`, `.sidebar-dropup-trigger`, `.sidebar-collapse-toggle`, and related chevrons/toggles translate, fade, and resize during rail expansion. 【F:css/tailwind.source.css†L1164-L1598】
|
||||
|
||||
### Shadow inventory
|
||||
|
||||
- `--shadow-popover-zap` keeps the zap tip elevation consistent across themes; apply it to `.popover__panel[data-variant="zap"]` or related toast surfaces when you need the intense popover glow. 【F:css/tokens.css†L406-L415】【F:css/tailwind.source.css†L293-L298】
|
||||
- `--shadow-video-modal` anchors modal playback chrome so large embeds reuse the same lift as `#playerModal .video-modal__video video`. 【F:css/tokens.css†L406-L415】【F:css/tailwind.source.css†L1374-L1378】
|
||||
- `--color-shadow-intense` still exposes the raw color for bespoke drop-shadow recipes—pair it with `drop-shadow-intense` utilities when only the hue needs to change. 【F:css/tokens.css†L93-L101】【F:tailwind.config.cjs†L28-L32】【F:tailwind.config.cjs†L147-L157】
|
||||
|
||||
### Reduced-motion policy
|
||||
|
||||
- A consolidated `@media (prefers-reduced-motion: reduce)` block now zeroes out transition and animation durations globally, removes motion-only transforms, and leaves opacity state changes in place so surfaces still show and hide instantly. 【F:css/tailwind.source.css†L1766-L1981】
|
||||
- Motion tokens also collapse to `0s` inside Tailwind's component layer so the primitives (`.btn`, `.card`, `.popover__panel`, watch-history tiles) inherit the same behaviour without per-selector overrides. 【F:css/tailwind.source.css†L229-L270】
|
||||
- Spinners fall back to static indicators when reduced motion is requested, while focus rings remain intact because the underlying styles are unaffected—only the timing curves are reset. 【F:css/tailwind.source.css†L1778-L1981】
|
||||
- When adding a new animated component, register its selector in this block (or reuse the tokenised durations) so reduced-motion users never see unintended transitions. Document the addition here alongside the inventory.
|
||||
|
||||
## Component Primitives
|
||||
|
||||
### Buttons
|
||||
|
||||
| Class | Description |
|
||||
| --- | --- |
|
||||
| `.btn` | Primary action button (`bg-primary`, white text). Hover lifts to `bg-info-strong`, focus pushes to `bg-info-pressed`, disabled reduces opacity. |
|
||||
| `.btn-ghost` | Framed secondary action. Transparent by default with subtle border, fills with `bg-panel-hover` on hover/focus. |
|
||||
|
||||
Both buttons:
|
||||
|
||||
- Use `.focus-ring` internally
|
||||
- Include pointer-blocking and opacity changes on `:disabled`
|
||||
- Are uppercase with wide tracking to match our existing CTA styling
|
||||
|
||||
Mix in Tailwind utilities for icon spacing (`gap-3`), sizing (`px-lg`, `py-sm`), or layout (`w-full`). Avoid overriding colors unless introducing a new semantic variant.
|
||||
|
||||
#### Variants & Toggle States
|
||||
|
||||
- Apply `data-variant="critical"` to `.btn-ghost` when rendering destructive affordances (for example, "Remove variant" pills inside repeater rows). The primitive swaps the border and text colors to the critical palette and deepens the fill on hover/focus.
|
||||
- Toggle groups can rely on `[aria-pressed="true"]` or `data-state="active"` without extra utilities. When a `.btn-ghost` reports an active state it automatically promotes to the filled primary style.
|
||||
|
||||
```html
|
||||
<div class="inline-flex items-center rounded-full bg-panel/80 p-1" role="group">
|
||||
<button class="btn-ghost flex-1" type="button" aria-pressed="true">Custom</button>
|
||||
<button class="btn-ghost flex-1" type="button" aria-pressed="false">Cloudflare</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Badges
|
||||
|
||||
`.badge` renders an inline pill (`bg-panel`, uppercase text). Opt into semantic variants with `data-variant="info"`, `data-variant="critical"`, or `data-variant="neutral"`. Layer on utilities for icon alignment or truncation.
|
||||
|
||||
### Cards
|
||||
|
||||
`.card` is the base surface for grid items and panels:
|
||||
|
||||
- Rounded corners (`rounded-lg`) and `bg-card`
|
||||
- Token shadows on hover
|
||||
- Focus states reuse `.focus-ring`
|
||||
- Accepts `data-state="private" | "critical" | "disabled"` for special treatments
|
||||
|
||||
Combine with padding utilities per layout. Interactive cards should maintain `cursor-pointer` manually (see legacy shim example below) until templates adopt dedicated controller classes.
|
||||
|
||||
> **2025-10-11 update:** The legacy `.video-card*` shim selectors were removed. Video grids should now render plain `.card` elements and opt into semantic variants via `data-state`, `data-alert`, and `data-motion` attributes. Runtime code no longer maps the deprecated classes, so older selectors will not receive token updates.
|
||||
|
||||
### Form Inputs
|
||||
|
||||
`.form-control` is the base primitive for text entry surfaces. It handles layout (`block`, `w-full`), rounded corners, subtle borders, muted placeholders, and the shared focus ring. Hovering deepens the border, focus highlights with `border-info-strong`, `:invalid` raises a critical border, and `:disabled` mutes the field while blocking pointer events.
|
||||
|
||||
Specialisations reuse `.form-control`:
|
||||
|
||||
- `.input` – Standard single-line text input.
|
||||
- `.textarea` – Multi-line input with a minimum height (`min-h-[var(--form-textarea-min-height)]`) and `resize-y` enabled.
|
||||
- `.select` – Native `<select>` element with `appearance-none`, an integrated arrow glyph, and consistent spacing (`pr-10`).
|
||||
|
||||
All three share hover, focus-visible, disabled, and invalid treatments. Layer on utilities (`h-12`, `text-lg`, icon padding) as needed without re-declaring tokens.
|
||||
|
||||
Controllers can mark read-only fields by setting `data-state="locked"` alongside the existing `readonly` attribute. The attribute shifts the background to `bg-overlay-panel-soft`, deepens the border (`border-border-sidebar-strong`), and suppresses the focus ring so locked inputs stay visually distinct without falling back to `!important` overrides. The same hook works on `.input`, `.textarea`, `.select`, and any raw `.form-control` element—disabled states keep the locked palette.
|
||||
|
||||
Arrow and checkbox adornments now lean entirely on tokens: `--form-select-icon-offset`, `--form-select-icon-gap`, `--form-select-icon-hit-area`, and `--form-select-icon-size` position the native select chevron, while `--form-checkbox-checkmark-offset` keeps the checkmark aligned at any scale. Textareas pull their baseline from `--form-textarea-min-height`, and thin scrollbars reuse the shared `--scrollbar-thin-width` so embedded panels feel consistent across browsers.
|
||||
|
||||
```html
|
||||
<form class="bv-stack bv-stack--tight" aria-labelledby="zapAmountLabel">
|
||||
<label
|
||||
id="zapAmountLabel"
|
||||
class="text-xs font-semibold uppercase tracking-wide text-muted-strong"
|
||||
>
|
||||
Zap amount (sats)
|
||||
</label>
|
||||
<input
|
||||
id="zapAmountInput"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class="input"
|
||||
placeholder="Enter sats"
|
||||
/>
|
||||
<button class="btn" type="submit">Send</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
For toggles:
|
||||
|
||||
- `.checkbox` resets native appearance, applies the token border/background, and swaps to the info palette when checked. The checkmark is rendered via `::after`, and disabled states lower contrast while keeping the focus ring behaviour.
|
||||
- `.switch` renders a pill track with a translating thumb (`::before`). Apply `[aria-checked="true"]` or `.is-on` to move the thumb and recolor the track. Motion respects `prefers-reduced-motion` while maintaining the focus ring.
|
||||
|
||||
### Modal Surface
|
||||
|
||||
- `.bv-modal` anchors fixed overlays (full-viewport flexbox centered layout).
|
||||
- `.bv-modal-backdrop` applies the tokenized overlay color with blur.
|
||||
- `.bv-modal__panel` hosts modal content (`bg-surface`, `shadow-modal`, focus ring).
|
||||
|
||||
Sizing now comes from dedicated tokens: `max-w-modal` and `rounded-modal-xl` map to `--modal-max-width` and `--radius-modal-xl`, while icon controls reuse `--icon-size-lg`, `--icon-size-md`, and `--icon-button-ring-width` for consistent hit targets. Progress indicators pick up `--modal-progress-height`, and the player modal inherits `max-w-player` so hero metadata never exceeds `--layout-player-max-width`.
|
||||
|
||||
Use `--modal-sheet-inline-max` when you need a sheet to breathe on narrow viewports without exceeding the standard modal width. The token resolves to the smaller of `90vw` and `--modal-max-width`, so base sheet templates hug the viewport while large-screen overrides (for example, `lg:max-w-4xl`) can still opt into broader layouts. Reach for `--modal-max-width` directly when the panel should stay anchored to the canonical modal width across breakpoints.
|
||||
|
||||
Compose them together when mounting new modal portals. If you need a drawer or sheet, start from these primitives and adjust spacing/positioning via utilities.
|
||||
|
||||
#### Modal Actions
|
||||
|
||||
Action rows in login, disclaimer, and upload flows now pair `.btn-ghost` and `.btn` directly. Layer layout utilities such as `w-full` or `gap-3` without re-adding custom focus rings.
|
||||
|
||||
```html
|
||||
<footer class="modal-footer flex gap-3">
|
||||
<button class="btn-ghost flex-1" type="button">Cancel</button>
|
||||
<button class="btn flex-1" type="submit">Publish</button>
|
||||
</footer>
|
||||
```
|
||||
|
||||
Static modal partials (login, application, feedback, etc.) should call the helpers exported from `js/ui/components/staticModalAccessibility.js`—`prepareStaticModal`, `openStaticModal`, and `closeStaticModal`—to attach focus trapping, Escape/backdrop dismissal, and the shared `data-open`/`html.modal-open` toggles without duplicating controller logic.
|
||||
|
||||
### Headers
|
||||
|
||||
`.ds-header` replaces the legacy `header { … }` reset. Apply it to page chrome that should inherit the shared rhythm: flex alignment (`items-center`, `justify-start`) and tokenized vertical padding (`py-md`). Layer layout utilities per surface—for example, `justify-between` on the kitchen-sink demo or `mb-8` on the app shell.
|
||||
|
||||
Pair `.ds-header__logo` with `<img>` or inline logo wrappers to standardize sizing (`h-24`, `w-auto`, `max-w-none`) without reintroducing element selectors. Compose additional utilities (`h-16`, responsive overrides) when a view needs alternate scale.
|
||||
|
||||
Logo glyphs now rely on dedicated selectors inside the SVG: apply `.bv-logo__accent` to the nine-block mark, `.bv-logo__wordmark` to the lettering, and `.bv-logo__background` to any surrounding tile. These hooks read from the CSS custom properties that `.bv-logo` seeds, so themes can flip palettes purely through attributes (`data-variant`, `data-wordmark`, etc.) without reintroducing inline `fill` or `color` overrides.
|
||||
|
||||
### Popovers
|
||||
|
||||
Use `.popover` as the relative anchor for floating content. Nest `.popover__panel` for the floating surface—it’s positioned with the shared popover engine, inherits the shared focus ring, and animates with opacity/scale tokens. Toggle `[data-state="open"]` on the panel to switch between the collapsed (`opacity-0 scale-95`) and expanded (`opacity-100 scale-100`) states.
|
||||
|
||||
```html
|
||||
<div class="popover">
|
||||
<button class="btn" type="button">Open filters</button>
|
||||
<div class="popover__panel" role="dialog" data-state="open">
|
||||
<p class="text-sm text-muted">Fine-tune the current feed using tags and duration filters.</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use the shared popover engine (`js/ui/overlay/popoverEngine.js`) instead of manual offsets. The helper attaches panels to the overlay root, flips placement when they collide with the viewport, and toggles `aria` + `data-popover-state` attributes as visibility changes. Width and height clamps can be sourced from design tokens (`maxWidthToken`, `maxHeightToken`) so surfaces inherit theme-specific limits without inline constants. Consult [docs/menus.md](./menus.md) for overlay-root wiring, Tailwind overlay helpers, and the manual QA surface that exercises card and modal menus. 【F:js/ui/overlay/popoverEngine.js†L252-L266】【F:js/ui/overlay/popoverEngine.js†L633-L706】【F:css/tailwind.source.css†L358-L399】
|
||||
|
||||
```js
|
||||
import createPopover from "../overlay/popoverEngine.js";
|
||||
|
||||
const popover = createPopover(triggerEl, ({ container }) => {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "popover__panel card";
|
||||
panel.dataset.state = "closed";
|
||||
panel.innerHTML = "<p class=\"text-sm text-muted\">Filters</p>";
|
||||
container.appendChild(panel);
|
||||
return panel;
|
||||
}, {
|
||||
placement: "bottom-start",
|
||||
maxWidthToken: "--popover-inline-safe-max",
|
||||
});
|
||||
|
||||
await popover.open();
|
||||
panel.dataset.state = "open"; // reveal animation hooks
|
||||
|
||||
// Later, close the surface and restore focus.
|
||||
panel.dataset.state = "closed";
|
||||
popover.close();
|
||||
```
|
||||
|
||||
The render callback runs once the engine has created its portal container. Return the panel (and optional arrow) element so the helper can manage focus targets, `aria-controls`, and collision handling. Consumers remain responsible for their own visual state toggles (`data-state`) so Tailwind transitions continue to apply. Use `popover.update()` when the trigger or panel size changes, and call `popover.destroy()` during teardown to drop listeners and portals. 【F:js/ui/overlay/popoverEngine.js†L420-L583】【F:js/ui/overlay/popoverEngine.js†L888-L1014】
|
||||
|
||||
Recommended container pattern:
|
||||
|
||||
- Wrap the trigger and panel in `.popover` so the markup stays discoverable and inherits spacing tokens.
|
||||
- Leave the wrapper’s overflow visible; the helper defaults to `position: fixed`, so floating surfaces escape scroll containers without getting clipped. Opt into `{ strategy: "absolute" }` if you intentionally want the panel constrained.
|
||||
- When tearing down a component, call `popover.destroy()` to drop scroll/resize listeners and remove the portal container.
|
||||
|
||||
The motion helpers defer to the global motion tokens so Task 5 can adjust timing globally without revisiting individual components. Animations respect `prefers-reduced-motion` by collapsing to opacity-only transitions.
|
||||
|
||||
### Sidebar shell & controls
|
||||
|
||||
The navigation rail pulls its rhythm from dedicated sidebar tokens in `css/tokens.css`. The shell, panels, and footer reuse shared spacing primitives (`--space-sidebar-shell-inline`, `--space-sidebar-shell-block-start`, `--space-sidebar-shell-block-end`, `--space-sidebar-panel-gap`, `--space-sidebar-panel-padding-block`, `--space-sidebar-panel-padding-inline`, `--space-sidebar-footer-gap`) alongside component-specific radii and shadows (`--radius-sidebar-shell`, `--radius-sidebar-panel`, `--radius-sidebar-nav`, `--shadow-sidebar-shell`, `--shadow-sidebar-accent`, `--shadow-sidebar-trigger`, `--shadow-sidebar-dropup`, `--shadow-sidebar-panel`, `--shadow-sidebar-focus-ring`). These values also size fallback classes like `.sidebar-nav-link`, `.sidebar-dropup-trigger`, and `.sidebar-dropup-panel` so legacy templates stay legible. 【F:css/tokens.css†L94-L138】【F:css/tailwind.source.css†L1829-L2182】
|
||||
|
||||
Frosted glass treatments share a blur stack as well. Reference `--sidebar-backdrop-blur` across the shell, nav links, and footer triggers, then pull the matching saturation tokens (`--sidebar-shell-backdrop-saturation`, `--sidebar-nav-backdrop-saturation`, `--sidebar-trigger-backdrop-saturation`, `--sidebar-panel-backdrop-saturation`) or `--sidebar-panel-backdrop-blur` for popovers instead of reintroducing literal `blur()`/`saturate()` values. 【F:css/tokens.css†L70-L118】【F:css/tailwind.source.css†L2059-L2402】
|
||||
|
||||
Tailwind now exposes matching utilities for layouts that opt into the design system directly: use `px-sidebar-shell-inline`, `pt-sidebar-shell-block-start`, `pb-sidebar-shell-block-end`, `gap-sidebar-panel-gap`, and `rounded-sidebar-shell`/`rounded-sidebar-nav` for structure, then `shadow-sidebar-shell`, `shadow-sidebar-accent`, or `shadow-sidebar-dropup` to match the bespoke glow states. Combine them with existing color utilities to compose new sidebar sections without reintroducing raw measurements. 【F:tailwind.config.cjs†L115-L155】
|
||||
|
||||
Iconography and motion helpers rely on fresh tokens as well: `--icon-size-sm`, `--icon-size-md`, and `--icon-size-lg` standardise glyph sizing across nav links, collapse toggles, and dropup triggers, while `--space-sidebar-nav-translate` powers the translate offsets used when collapsing the rail. Thin overlays inherit `--scrollbar-thin-width` so modal and sidebar scroll containers feel identical.
|
||||
|
||||
### Search surfaces backlog
|
||||
|
||||
Discovery work is queued behind two search affordances that will need close coordination with the navigation shell tokens:
|
||||
|
||||
- **Relay note search filters** — Stand up a dedicated search surface that can index relay-published notes and expose filter chips for posted date ranges, hashtags, title keywords, view-count ranges, and the publishing account. Reuse `.menu`, `.input`, and `.chip` primitives for the filter controls so the surface stays aligned with existing command panels, and plan on routing results through the popover/filter pipeline described above.
|
||||
- **Mobile floating search** — Introduce a floating search icon on small breakpoints that expands into a full-width search field when tapped. The trigger should inherit the circular icon tokens used by sidebar toggles, snap to the `z-overlay-nav` layer, and collapse back to the icon state once the user blurs or submits the field.
|
||||
|
||||
### Menus
|
||||
|
||||
`.menu` normalises list-based command menus with consistent padding. Pair it with `.menu__heading` for optional section labels, `.menu__separator` for dividers, and `.menu__item` for actionable rows. Each item supports hover, active (`data-state="active"`), and critical (`data-variant="critical"`) palettes plus disabled fallbacks through either `disabled` or `aria-disabled="true"`.
|
||||
|
||||
```html
|
||||
<div class="popover">
|
||||
<button class="btn-ghost" type="button">Command palette</button>
|
||||
<div class="popover__panel" role="menu" data-state="open">
|
||||
<div class="menu" role="none">
|
||||
<p class="menu__heading" role="presentation">Navigation</p>
|
||||
<button class="menu__item" role="menuitem">Search videos</button>
|
||||
<button class="menu__item" role="menuitem" data-state="active">Go live dashboard</button>
|
||||
<div class="menu__separator" role="separator"></div>
|
||||
<button class="menu__item" role="menuitem" data-variant="critical">Delete current draft</button>
|
||||
<button class="menu__item" role="menuitem" disabled>Switch profile (pro only)</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
The helpers rely on layout utilities (`flex`, `gap-*`) so you can safely inject icons, shortcuts, or nested badges without restyling the base tokens.
|
||||
|
||||
### Zap Popover
|
||||
|
||||
Compose `.popover` with a zap-specific panel variant when you need to surface the tipping form inline. Toggle `data-state="open"` on the `.popover__panel` and remove its `hidden` attribute when the dialog is visible. The `data-variant="zap"` hook reuses the frosted-glass styling defined in `css/tailwind.source.css`.
|
||||
|
||||
```html
|
||||
<div class="popover">
|
||||
<button class="btn-ghost h-10 w-10 rounded-full p-0" type="button" aria-haspopup="dialog" aria-expanded="false">
|
||||
<img src="assets/svg/lightning-bolt.svg" alt="Zap" class="h-5 w-5" />
|
||||
</button>
|
||||
<div
|
||||
class="popover__panel card w-72 max-w-popover-safe space-y-4"
|
||||
role="dialog"
|
||||
aria-hidden="true"
|
||||
data-variant="zap"
|
||||
data-state="closed"
|
||||
hidden
|
||||
>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wide text-muted">Send a zap</h3>
|
||||
<button class="btn-ghost h-8 w-8 rounded-full p-0" type="button" aria-label="Close zap dialog">✕</button>
|
||||
</div>
|
||||
<form class="space-y-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wide text-muted" for="zapAmountExample">
|
||||
Zap amount (sats)
|
||||
</label>
|
||||
<input id="zapAmountExample" class="input" type="number" min="1" step="1" inputmode="numeric" />
|
||||
<button class="btn w-full" type="submit">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Grid Stack
|
||||
|
||||
`.grid-stack` offers gap tokens for dense layouts. Add `data-variant="cards"` for responsive card grids (`grid-cols-1` → `sm:grid-cols-2` → `xl:grid-cols-3`). Switch to `data-orientation="vertical"` for stacked flex columns that reuse the same spacing tokens.
|
||||
|
||||
### Layout Helpers
|
||||
|
||||
- `.bv-stack` creates a vertical flex stack with the default large gap (`var(--space-lg)`). Use it for page sections such as the watch-history summary blocks.
|
||||
- `.bv-stack--tight` reduces the gap to `var(--space-md)` while inheriting the `.bv-stack` flex behaviour. Combine the modifier when stacking smaller card fragments or form controls inside profile templates.
|
||||
- `.bv-grid-video` centralises the responsive video grid (`repeat(auto-fill, minmax(20rem, 1fr))`) with the standard gutter (`gap-xl`) and vertical padding (`py-lg`). Apply it to feed, subscriptions, and channel views instead of repeating inline grid rules.
|
||||
|
||||
## Legacy Compatibility Shims
|
||||
|
||||
During migration we keep existing selectors alive by layering `@apply` calls in `css/tailwind.source.css`. Notable shims:
|
||||
|
||||
- `#profileModal .profile-switcher*` → `.card`, `.badge`, `.btn-ghost`
|
||||
|
||||
> **2025-10-11:** The `.video-card` shim family was removed. Templates must emit `.card` elements with the appropriate `data-state` and `data-alert` attributes instead of relying on the legacy class names.
|
||||
|
||||
We continue to expose aliases such as `.profile-switcher` and nested variations (for example, `.profile-switcher__item`) so existing profile and admin controllers stay functional while downstream templates finish their migration. These shims live under the "Legacy component compatibility" section and will be removed once templates switch to the new primitives.
|
||||
|
||||
> **Modal primitives:** Always mount `.bv-modal`, `.bv-modal-backdrop`, and `.bv-modal__panel` together. Controllers expect this trio, so there is no compatibility layer for alternate selectors.
|
||||
|
||||
**Shim removal checklist (Task DS-742):**
|
||||
|
||||
- [ ] Confirm no production templates emit `.profile-switcher*` selectors (owners: @ui-templates, @design-systems).
|
||||
- [ ] Verify the torrent beacon renders `.card`, `.btn`, `.btn-ghost`, and `.input` primitives with no fallback selectors (owner: @torrent-beacon).
|
||||
- [ ] Verify staging renders with `data-ds="new"` across surfaces and run kitchen-sink visual snapshots to ensure no regressions.
|
||||
- [ ] Delete residual `@apply` rules under "Legacy component compatibility" in `css/tailwind.source.css`.
|
||||
- [ ] Announce the removal in release notes and update downstream embed documentation.
|
||||
|
||||
**Owners:** Design Systems Guild (@design-systems) with support from the Templates crew (@ui-templates) and the torrent beacon maintainers (@torrent-beacon).
|
||||
|
||||
**Target removal release:** 2025.04 mainline (aligns with the shim removal milestone tracked in DS-742). Audit usage after the Q1 2025 UI refresh and delete remaining shims no later than the April 2025 deployment window. At that point the compatibility layer will be removed once dependent feature flags confirm that no legacy selectors remain in production HTML.
|
||||
|
||||
### Selector migration table
|
||||
|
||||
| Legacy selector | Replacement primitive(s) | Beacon / app notes | Feature-flag strategy |
|
||||
| --- | --- | --- | --- |
|
||||
| `.video-card`, `.video-card__meta`, `.video-card--loading` | `.card` with `data-state` + utility classes | Removed in web app; ensure beacon dashboards render `.card` with skeleton states instead of `.video-card--loading`. | Deploy directly and validate in staging before rolling to beacon.
|
||||
| `.profile-switcher`, `.profile-switcher__item` | `.card` rows + `.btn-ghost` toggles | Keep shim until profile modal Reactors deploy; beacon does not consume this selector. | Roll out once QA signs off; no flag gating remains.
|
||||
| `.button`, `.button-danger` (Skeleton) | `.btn`, `.btn-ghost[data-variant="critical"]` | Critical for torrent beacon—replace the Angular templates before removing Skeleton CDN. | Coordinate a staged deploy; the design system is always on so fallbacks must ship in a separate release if required.
|
||||
| `.u-full-width`, `.input-text` | `.input`, `.select`, `.form-control` | Applies to upload forms and beacon filter controls; also update tests that target legacy classes. | Update markup globally; controllers now always run in the design system mode.
|
||||
| `.modal-container`, `.modal-content` | `.bv-modal`, `.bv-modal-backdrop`, `.bv-modal__panel` | Retired selectors—emit the primitives exactly so accessibility, animation, and controller hooks stay wired. | N/A — unified primitives only. |
|
||||
|
||||
### Migration Notes (Q4 2024)
|
||||
|
||||
- Modal templates across the app now mount `.bv-modal`, `.bv-modal-backdrop`, and `.bv-modal__panel` primitives directly. Controllers such as `ProfileModalController` and the upload modal orchestrator were updated to expect these primitives, so any new modal should follow the same structure for consistent accessibility and animation hooks.
|
||||
- The compatibility shim keeps the `.profile-switcher` selector family alive temporarily so third-party embeds and cached HTML fragments continue to render while operators redeploy updated templates. Keep the aliases until the April 2025 removal window above, then delete the shim and update any last callers to the new `.card`/`.btn-ghost` structure.
|
||||
|
||||
### Overlay layering scale
|
||||
|
||||
Overlay stacks now use semantic z-index tokens. Reach for these utilities instead of raw numeric values so floating surfaces remain predictable across the app:
|
||||
|
||||
| Token | Utility class | Primary usage |
|
||||
| --- | --- | --- |
|
||||
| `--z-overlay-nav` | `z-overlay-nav` | Mobile navigation toggles, sticky modal nav bars |
|
||||
| `--z-overlay-floating` | `z-overlay-floating` | Popovers, floating status overlays, async progress shells |
|
||||
| `--z-overlay-toast` | `z-overlay-toast` | Toast managers and transient notifications |
|
||||
| `--z-modal-root` / `--z-modal-overlay` / `--z-modal-content` | `z-[var(--z-modal-root)]` primitives | Modal scaffolding (`.bv-modal`, `.bv-modal-backdrop`, `.bv-modal__panel`) |
|
||||
|
||||
Backdrops share a common helper: `.ds-overlay-backdrop` applies the fixed positioning, tone, and blur defined by `--overlay-backdrop-color` / `--overlay-backdrop-blur`. Pair it with the appropriate `z-overlay-*` utility when you need to tune stacking above or below other surfaces.
|
||||
|
||||
## Beacon client migration
|
||||
|
||||
The torrent beacon now shares the Tailwind bundle used across bitvid. The standalone `torrent/beacon.html` drops Skeleton/FoA CDNs in favour of `css/tailwind.generated.css`, adopts the `.card`, `.btn`, `.btn-ghost`, and `.input` primitives, and relies on `.bv-stack` for spacing inside Angular partials. Inline SVG icons replace FontAwesome so controls stay lightweight while inheriting tokenised focus and hover states.
|
||||
|
||||
Angular templates under `torrent/views/` should follow the refreshed structure:
|
||||
|
||||
- Wrap primary sections in `.card` and layer Tailwind utilities for layout (`grid`, `gap-xl`, `overflow-auto`).
|
||||
- Use `.input`, `.select`, `.btn`, and `.btn-ghost` (with `data-variant="critical"` for destructive actions) instead of legacy `.u-full-width`, `.button`, or `.button-danger` classes.
|
||||
- Prefer token-backed utilities for spacing and typography (`px-6`, `py-6`, `text-muted`) over ad-hoc CSS.
|
||||
- Reuse the fixed overlay spinner pattern (`ds-overlay-backdrop z-overlay-floating` plus an `animate-spin text-info-strong` SVG) for async states so overlays stay consistent with the new palette.
|
||||
|
||||
When adding new torrent affordances, mirror the existing markup: inline 24×24 SVG strokes at 2px, `.focus-ring` on interactive anchors, and `.bv-stack` for vertical rhythm. This keeps the beacon aligned with the design system without reintroducing the deprecated Skeleton grid.
|
||||
|
||||
## When to Mix Utilities
|
||||
|
||||
Use the primitives for foundation and lean on Tailwind utilities for:
|
||||
|
||||
- Layout (`flex`, `grid`, `gap-*`, `justify-*`)
|
||||
- Sizing (`w-*`, `h-*`, `min-w-*`)
|
||||
- Typography tweaks (`text-left`, `font-light`)
|
||||
- State variations not yet encoded as tokens (e.g., success/neutral buttons)
|
||||
|
||||
Avoid re-declaring token values manually. If a new pattern repeats, promote it into a primitive or request new tokens to keep styling consistent.
|
||||
|
||||
## Visual snapshot review workflow
|
||||
|
||||
Continuous integration captures Playwright-powered screenshots of `docs/kitchen-sink.html` (`tests/visual/kitchen-sink.spec.ts`) on every pull request. When a snapshot job fails:
|
||||
|
||||
1. Read the diff produced by Playwright (attached as an artifact) and compare it with your local preview. The harness highlights per-pixel changes so reviewers can distinguish deliberate theme updates from regressions.
|
||||
2. Reproduce locally with `npm run test:visual -- --update-snapshots` to refresh the expected images after verifying the change in the kitchen sink.
|
||||
3. Document the intent in the PR description (include before/after screenshots or reference the kitchen-sink toggle state) so reviewers and QA know the snapshot change is expected.
|
||||
|
||||
The design system doc is the single source of truth for reviewing these failures—if a diff contradicts the guidance above, treat it as a regression, request updates, and block the merge until the visual snapshot stabilises.
|
||||
@@ -0,0 +1,111 @@
|
||||
# Feed Engine Architecture
|
||||
|
||||
The feed engine is a lightweight pipeline for composing bitvid feeds from one
|
||||
or more data sources. It lives in `js/feedEngine/` and exposes factories for
|
||||
sources, stages, sorters, and the engine itself. The goal is to make it easy to
|
||||
register new feeds today while leaving room for future "open algorithm" work
|
||||
that surfaces optional "why this video" metadata.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
- **Feed definition** – A feed consists of a source, zero or more pipeline
|
||||
stages, an optional sorter, and optional decorators. Each feed can expose its
|
||||
own hooks and configuration defaults.
|
||||
- **Feed items** – The engine normalizes everything into a `{ video, pointer?,
|
||||
metadata }` DTO so stages can reason about inputs without caring about the
|
||||
original source.
|
||||
- **Context** – Every source and stage receives a context object containing the
|
||||
feed configuration, runtime helpers, registered hooks, and a `addWhy()`
|
||||
method for collecting metadata about why an item was filtered or prioritised.
|
||||
|
||||
## Getting Started
|
||||
|
||||
```js
|
||||
import {
|
||||
createFeedEngine,
|
||||
createActiveNostrSource,
|
||||
createDedupeByRootStage,
|
||||
createBlacklistFilterStage,
|
||||
createChronologicalSorter,
|
||||
} from "../js/feedEngine/index.js";
|
||||
|
||||
const engine = createFeedEngine();
|
||||
|
||||
engine.registerFeed("recent", {
|
||||
source: createActiveNostrSource(),
|
||||
stages: [
|
||||
createBlacklistFilterStage(),
|
||||
createDedupeByRootStage(),
|
||||
],
|
||||
sorter: createChronologicalSorter(),
|
||||
});
|
||||
|
||||
const { videos, metadata } = await engine.runFeed("recent", {
|
||||
runtime: {
|
||||
blacklistedEventIds: new Set(["..."]),
|
||||
isAuthorBlocked: (pubkey) => false,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`metadata.why` collects the audit trail from each stage so later phases of the
|
||||
open algorithm project can surface transparency UI.
|
||||
|
||||
## Sources
|
||||
|
||||
| Factory | Description |
|
||||
| --- | --- |
|
||||
| `createActiveNostrSource` | Wraps `nostrService.getFilteredActiveVideos(...)` and emits DTOs with a `metadata.source` of `nostr:active`. |
|
||||
| `createSubscriptionAuthorsSource` | Filters the active video list down to subscribed authors using runtime hooks or `config.actorFilters`. |
|
||||
| `createWatchHistoryPointerSource` | Loads pointer DTOs from `watchHistoryService.getQueuedPointers(...)`. Optional hooks can resolve the backing video if a pointer is missing it. |
|
||||
|
||||
Every source resolves blacklist and author-block runtime helpers so feeds
|
||||
constructed today behave exactly like the existing UI.
|
||||
|
||||
## Stages
|
||||
|
||||
| Stage | Behavior |
|
||||
| --- | --- |
|
||||
| `createDedupeByRootStage` | Reuses the application’s `dedupeVideosByRoot` helper (falling back to `dedupeToNewestByRoot`) to drop older versions of the same videoRoot. Adds "why" metadata for removed entries. |
|
||||
| `createBlacklistFilterStage` | Calls `nostrService.shouldIncludeVideo(...)` so moderators and block lists stay enforced. Each rejection logs a "blacklist" reason in the why-trail. |
|
||||
| `createWatchHistorySuppressionStage` | Invokes feed-provided hooks to optionally suppress watched items. Useful for per-feed watch history preferences. |
|
||||
|
||||
Stages receive `(items, context)` and should return the transformed list. They
|
||||
can rely on `context.addWhy(...)` to annotate decisions without mutating the
|
||||
items in place.
|
||||
|
||||
## Sorting & Decorators
|
||||
|
||||
`createChronologicalSorter` is the baseline sorter that orders DTOs by
|
||||
`video.created_at` (newest first by default). Additional decorators can run
|
||||
after sorting to attach extra metadata or inject presentation hints.
|
||||
|
||||
## Configuration Hooks
|
||||
|
||||
Every feed inherits the default config contract:
|
||||
|
||||
```json
|
||||
{
|
||||
"timeWindow": null,
|
||||
"actorFilters": [],
|
||||
"tagFilters": [],
|
||||
"sortOrder": "recent"
|
||||
}
|
||||
```
|
||||
|
||||
Feeds can override defaults or expose richer schemas through
|
||||
`definition.defaultConfig` and `definition.configSchema`. At execution time the
|
||||
engine merges `options.config` with those defaults and passes them to the
|
||||
pipeline via `context.config`.
|
||||
|
||||
Hooks can be provided globally when registering the feed or per-execution via
|
||||
`engine.runFeed(name, { hooks: { ... } })`. The watch-history suppression stage
|
||||
uses this mechanism so Phase 1 feeds can plug in actor-specific suppression
|
||||
logic later without changing the core pipeline.
|
||||
|
||||
## Why-Metadata
|
||||
|
||||
`context.addWhy()` records structured audit entries. All built-in stages use it
|
||||
for dedupe drops, blacklist filtering, and watch-history suppression. The
|
||||
engine returns these records alongside the final video list so UI components can
|
||||
render transparency affordances when the open algorithm effort ships.
|
||||