mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #468 from PR0M3TH3AN/codex/refactor-to-centralized-styling-system
Refactor styling to use centralized system
This commit is contained in:
15 files changed
+801
-420
No files matched your search
@@ -2026,15 +2026,15 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
id="cloudflareProgressBar"
|
||||
class="hidden h-2 w-full overflow-hidden rounded-full bg-panel"
|
||||
>
|
||||
<div
|
||||
id="cloudflareProgressBar"
|
||||
class="hidden h-2 w-full overflow-hidden rounded-full bg-panel"
|
||||
>
|
||||
<div
|
||||
id="cloudflareProgressFill"
|
||||
class="h-full w-0 bg-info transition-all duration-150"
|
||||
></div>
|
||||
</div>
|
||||
id="cloudflareProgressFill"
|
||||
class="cloudflare-progress__fill h-full bg-info transition-all duration-150"
|
||||
></div>
|
||||
</div>
|
||||
<p
|
||||
id="cloudflareUploadStatus"
|
||||
class="text-xs text-muted"
|
||||
|
||||
+341
-326
@@ -5,374 +5,389 @@
|
||||
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 class="player-modal__content video-modal__panel grid gap-6">
|
||||
<!-- Navigation bar - sliding at top -->
|
||||
<div class="player-modal__content video-modal__panel flex flex-col gap-6">
|
||||
<div
|
||||
id="modalNav"
|
||||
class="modal-nav video-modal__nav sticky top-0 z-60 transition-transform duration-150"
|
||||
class="modal-nav modal-nav--visible video-modal__nav sticky top-0 z-60"
|
||||
>
|
||||
<div
|
||||
class="card flex w-full items-center justify-between gap-4 px-4 py-3 u-max-400-flex-wrap u-max-400-gap-y-sm u-max-400-px-sm"
|
||||
>
|
||||
<div class="flex items-center gap-3 u-max-400-w-full">
|
||||
<button
|
||||
id="closeModal"
|
||||
type="button"
|
||||
class="focus-ring inline-flex h-10 w-10 items-center justify-center rounded-full bg-panel text-text transition duration-200 ease-out hover:bg-panel-hover u-max-400-square-9"
|
||||
<div class="video-modal__nav-bar">
|
||||
<button
|
||||
id="closeModal"
|
||||
type="button"
|
||||
class="video-modal__close focus-ring"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-actions video-modal__actions flex items-center gap-2 u-max-400-ml-0 u-max-400-w-full u-max-400-flex-wrap u-max-400-justify-between u-max-400-gap-y-xs">
|
||||
<button
|
||||
id="copyMagnetBtn"
|
||||
type="button"
|
||||
class="btn-ghost focus-ring h-10 w-10 rounded-full p-0 u-max-400-square-9"
|
||||
aria-label="Copy magnet"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/copy-magnet.svg"
|
||||
alt="Copy Magnet"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</button>
|
||||
<div class="popover" data-modal-zap-wrapper>
|
||||
<button
|
||||
id="modalZapBtn"
|
||||
type="button"
|
||||
class="btn-ghost focus-ring h-10 w-10 rounded-full p-0 u-max-400-square-9"
|
||||
aria-label="Send a zap"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls="modalZapDialog"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/lightning-bolt.svg"
|
||||
alt="Zap"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
id="modalZapDialog"
|
||||
class="popover__panel card w-72 max-w-[calc(100vw-2rem)] 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="btn-ghost focus-ring h-10 w-10 rounded-full p-0 u-max-400-square-9"
|
||||
aria-label="Share video"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/share-video.svg"
|
||||
alt="Share Video"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</button>
|
||||
<div class="relative" data-more-menu-wrapper="true">
|
||||
<button
|
||||
id="modalMoreBtn"
|
||||
type="button"
|
||||
class="btn-ghost focus-ring h-10 w-10 rounded-full p-0 u-max-400-square-9"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-label="More"
|
||||
data-more-dropdown="modal"
|
||||
>
|
||||
<img src="assets/svg/ellipsis.svg" alt="More" class="h-5 w-5" />
|
||||
</button>
|
||||
<div
|
||||
id="moreDropdown-modal"
|
||||
class="popover__panel w-48"
|
||||
role="menu"
|
||||
data-more-menu="true"
|
||||
data-state="closed"
|
||||
aria-hidden="true"
|
||||
hidden
|
||||
>
|
||||
<div class="menu" role="none">
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="open-channel"
|
||||
data-context="modal"
|
||||
>
|
||||
Open channel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="copy-link"
|
||||
data-context="modal"
|
||||
>
|
||||
Copy link
|
||||
</button>
|
||||
<p class="menu__heading" role="presentation">
|
||||
Boost on Nostr…
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="repost-event"
|
||||
data-context="modal"
|
||||
>
|
||||
Repost (kind 6)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="mirror-video"
|
||||
data-context="modal"
|
||||
>
|
||||
Mirror (kind 1063)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="ensure-presence"
|
||||
data-context="modal"
|
||||
>
|
||||
Rebroadcast
|
||||
</button>
|
||||
<div class="menu__separator" role="separator"></div>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="blacklist-author"
|
||||
data-context="modal"
|
||||
data-blacklist-menu="true"
|
||||
data-variant="critical"
|
||||
>
|
||||
Blacklist creator
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="block-author"
|
||||
data-context="modal"
|
||||
data-variant="critical"
|
||||
>
|
||||
Block creator
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="report"
|
||||
data-context="modal"
|
||||
>
|
||||
Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</svg>
|
||||
<span class="sr-only">Close player</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="player-modal__body grid gap-6 pt-6 lg:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)] lg:pt-0"
|
||||
class="video-modal__layout grid gap-8 lg:grid-cols-[minmax(0,1.85fr)_minmax(260px,1fr)]"
|
||||
>
|
||||
<div class="player-modal__video video-modal__video modal-video-shell space-y-4 pt-4 sm:pt-6 lg:space-y-0 lg:pt-0">
|
||||
<div class="card overflow-hidden p-0 lg:sticky lg:top-24">
|
||||
<div class="video-modal__primary flex flex-col gap-6">
|
||||
<div class="video-modal__video">
|
||||
<video
|
||||
id="modalVideo"
|
||||
class="video-modal__media"
|
||||
controls
|
||||
></video>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="player-modal__info video-modal__info modal-info-panel grid gap-4">
|
||||
<div class="card flex flex-col gap-4 px-6 py-5">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="video-modal__details flex flex-col gap-6">
|
||||
<div
|
||||
class="video-modal__headline flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between"
|
||||
>
|
||||
<h2
|
||||
id="videoTitle"
|
||||
class="text-2xl font-semibold leading-tight text-text"
|
||||
class="video-modal__title text-2xl font-semibold leading-tight text-text sm:text-3xl"
|
||||
></h2>
|
||||
<div
|
||||
id="modalStatus"
|
||||
class="text-right text-sm text-muted"
|
||||
class="video-modal__status text-sm text-muted lg:text-right"
|
||||
>
|
||||
Initializing... Just give it a sec.
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3 text-sm text-muted">
|
||||
<span id="videoTimestamp">Posted just now</span>
|
||||
<span
|
||||
id="videoEditedTimestamp"
|
||||
class="hidden"
|
||||
>Last edited just now</span>
|
||||
<span id="videoViewCount" class="modal-view-count">– views</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card flex items-center gap-4 px-6 py-5">
|
||||
<div class="h-14 w-14 overflow-hidden rounded-full bg-border-subtle">
|
||||
<img
|
||||
id="creatorAvatar"
|
||||
src=""
|
||||
alt="Creator"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3
|
||||
id="creatorName"
|
||||
class="text-lg font-semibold text-text"
|
||||
>
|
||||
Creator Name
|
||||
</h3>
|
||||
<p id="creatorNpub" class="text-sm text-muted">npub...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card px-6 py-5">
|
||||
<p
|
||||
id="videoDescription"
|
||||
class="whitespace-pre-wrap text-sm leading-relaxed text-text"
|
||||
<div
|
||||
class="video-modal__meta flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
No description available.
|
||||
</p>
|
||||
</div>
|
||||
<div class="video-modal__meta-info flex flex-wrap items-center gap-3 text-sm text-muted">
|
||||
<span id="videoTimestamp">Posted just now</span>
|
||||
<span id="videoEditedTimestamp" class="hidden">Last edited just now</span>
|
||||
<span id="videoViewCount" class="video-modal__view-count">– views</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card space-y-4 px-6 py-5">
|
||||
<div class="h-2 w-full overflow-hidden rounded-full bg-border-subtle">
|
||||
<div class="video-modal__actions-row flex flex-wrap items-center gap-3">
|
||||
<button
|
||||
id="copyMagnetBtn"
|
||||
type="button"
|
||||
class="video-modal__action-button focus-ring"
|
||||
aria-label="Copy magnet"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/copy-magnet.svg"
|
||||
alt=""
|
||||
class="video-modal__action-icon h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<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"
|
||||
aria-label="Send a zap"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls="modalZapDialog"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/lightning-bolt.svg"
|
||||
alt=""
|
||||
class="video-modal__action-icon h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="video-modal__action-label">Zap</span>
|
||||
</button>
|
||||
<div
|
||||
id="modalZapDialog"
|
||||
class="popover__panel card w-72 max-w-[calc(100vw-2rem)] 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"
|
||||
aria-label="Share video"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/share-video.svg"
|
||||
alt=""
|
||||
class="video-modal__action-icon h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<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"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-label="More actions"
|
||||
data-more-dropdown="modal"
|
||||
>
|
||||
<img
|
||||
src="assets/svg/ellipsis.svg"
|
||||
alt=""
|
||||
class="video-modal__action-icon h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="video-modal__action-label">More</span>
|
||||
</button>
|
||||
<div
|
||||
id="moreDropdown-modal"
|
||||
class="popover__panel w-48"
|
||||
role="menu"
|
||||
data-more-menu="true"
|
||||
data-state="closed"
|
||||
aria-hidden="true"
|
||||
hidden
|
||||
>
|
||||
<div class="menu" role="none">
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="open-channel"
|
||||
data-context="modal"
|
||||
>
|
||||
Open channel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="copy-link"
|
||||
data-context="modal"
|
||||
>
|
||||
Copy link
|
||||
</button>
|
||||
<p class="menu__heading" role="presentation">
|
||||
Boost on Nostr…
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="repost-event"
|
||||
data-context="modal"
|
||||
>
|
||||
Repost (kind 6)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="mirror-video"
|
||||
data-context="modal"
|
||||
>
|
||||
Mirror (kind 1063)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="ensure-presence"
|
||||
data-context="modal"
|
||||
>
|
||||
Rebroadcast
|
||||
</button>
|
||||
<div class="menu__separator" role="separator"></div>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="blacklist-author"
|
||||
data-context="modal"
|
||||
data-blacklist-menu="true"
|
||||
data-variant="critical"
|
||||
>
|
||||
Blacklist creator
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="block-author"
|
||||
data-context="modal"
|
||||
data-variant="critical"
|
||||
>
|
||||
Block creator
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu__item justify-start"
|
||||
role="menuitem"
|
||||
data-action="report"
|
||||
data-context="modal"
|
||||
>
|
||||
Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-modal__channel flex flex-wrap items-center justify-between gap-4 rounded-2xl border border-border border-opacity-20 bg-surface px-6 py-5 shadow-sm">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="video-modal__avatar h-14 w-14 overflow-hidden rounded-full bg-border-subtle">
|
||||
<img
|
||||
id="creatorAvatar"
|
||||
src=""
|
||||
alt="Creator"
|
||||
class="h-full w-full cursor-pointer object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3
|
||||
id="creatorName"
|
||||
class="cursor-pointer text-lg font-semibold text-text"
|
||||
>
|
||||
Creator Name
|
||||
</h3>
|
||||
<p id="creatorNpub" class="text-sm text-muted">npub...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-modal__description rounded-2xl border border-border border-opacity-20 bg-surface px-6 py-5">
|
||||
<p
|
||||
id="videoDescription"
|
||||
class="whitespace-pre-wrap text-sm leading-relaxed text-text"
|
||||
>
|
||||
No description available.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="video-modal__secondary flex flex-col gap-4">
|
||||
<div class="video-modal__stats rounded-2xl border border-border border-opacity-20 bg-surface px-6 py-5 shadow-sm">
|
||||
<div class="video-modal__progress h-2 w-full overflow-hidden rounded-full bg-border-subtle">
|
||||
<div
|
||||
id="modalProgress"
|
||||
class="modal-progress h-full rounded-full bg-info"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 text-sm text-muted">
|
||||
<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>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
:root{--color-page:#fff;--color-page-alt:#f9fafb;--color-surface:#0f172a;--color-surface-alt:#111827;--color-bg:var(--color-surface);--color-card:#1e293b;--color-card-private:#3b1d74;--color-card-critical:#3f0b13;--color-panel:#1f2937;--color-panel-hover:#334155;--color-border:#374151;--color-border-subtle:#d1d5db;--color-primary:#fe0032;--color-secondary:#ff93a5;--color-info:#60a5fa;--color-info-strong:#2563eb;--color-info-pressed:#1d4ed8;--color-muted-strong:#6b7280;--color-text:#f8fafc;--color-text-strong:#0f172a;--color-text-muted:var(--color-muted);--color-muted:#94a3b8;--color-muted-alt:#9ca3af;--color-subtle-text:#e2e8f0;--color-subtle:#e5e7eb;--color-critical:#7f1d1d;--color-critical-strong:#991b1b;--color-warning:#facc15;--color-warning-strong:#d97706;--color-warning-surface:rgba(146,64,14,.24);--color-warning-glass:rgba(250,204,21,.18);--color-neutral-700:#3f3f46;--color-neutral-600:#52525b;--color-white:#fff;--color-black:#000;--color-overlay:#1a2234;--color-overlay-black:rgba(0,0,0,.9);--color-overlay-muted:rgba(15,23,42,.55);--color-overlay-strong:rgba(15,23,42,.75);--color-overlay-darker:rgba(15,23,42,.88);--color-overlay-panel:rgba(26,34,52,.82);--color-overlay-panel-soft:rgba(26,34,52,.65);--color-overlay-panel-tint:rgba(31,41,55,.5);--color-overlay-panel-glass:rgba(28,37,58,.72);--color-overlay-panel-glass-soft:rgba(31,41,55,.55);--color-modal-backdrop:rgba(8,12,24,.58);--color-modal-surface:rgba(20,28,46,.78);--color-overlay-veil:rgba(11,17,32,.96);--color-overlay-banner:rgba(51,65,85,.95);--color-sidebar-surface:rgba(17,24,39,.94);--color-sidebar-surface-alt:rgba(11,22,48,.78);--color-sidebar-border:rgba(148,163,184,.28);--color-sidebar-border-strong:rgba(148,163,184,.42);--color-sidebar-hover:rgba(56,189,248,.18);--color-sidebar-hover-strong:rgba(129,140,248,.28);--color-sidebar-glow:rgba(56,189,248,.35);--color-sidebar-accent-start:rgba(56,189,248,.9);--color-sidebar-accent-end:rgba(129,140,248,.85);--color-sidebar-text-muted:rgba(203,213,225,.75);--color-overlay-panel-gradient-start:rgba(30,41,59,.92);--color-overlay-panel-gradient-end:rgba(15,23,42,.92);--color-surface-tint-subtle:rgba(148,163,184,.08);--color-border-translucent:rgba(148,163,184,.18);--color-border-translucent-strong:rgba(148,163,184,.25);--color-border-translucent-bright:rgba(148,163,184,.32);--color-border-translucent-stronger:rgba(148,163,184,.35);--color-border-translucent-strongest:rgba(148,163,184,.4);--color-border-translucent-glow:rgba(148,163,184,.55);--color-border-translucent-medium:rgba(148,163,184,.2);--color-border-neutral-soft:rgba(229,231,235,.35);--color-border-info-soft:rgba(96,165,250,.35);--color-border-sidebar-strong:rgba(55,65,81,.9);--color-ring-private:rgba(168,85,247,.55);--color-ring-private-hover:rgba(192,132,252,.65);--color-ring-critical:rgba(239,68,68,.4);--color-ring-critical-soft:rgba(239,68,68,.35);--color-ring-critical-hover:rgba(239,68,68,.45);--color-ring-critical-strong:rgba(239,68,68,.55);--color-ring-alert:rgba(220,38,38,.6);--color-gradient-visibility-start:rgba(129,140,248,.4);--color-gradient-visibility-end:rgba(192,132,252,.32);--color-white-soft:hsla(0,0%,100%,.1);--color-white-softer:hsla(0,0%,100%,.3);--color-white-softest:hsla(0,0%,100%,.4);--color-shadow-floating:rgba(8,11,30,.55);--color-shadow-elevated:rgba(8,11,30,.45);--color-shadow-panel:rgba(15,23,42,.35);--color-shadow-notification:rgba(15,23,42,.3);--color-shadow-sidebar:rgba(0,0,0,.45);--color-divider-strong:rgba(15,23,42,.35);--color-text-frosted-strong:rgba(226,232,240,.94);--color-text-frosted:rgba(226,232,240,.9);--color-text-frosted-soft:rgba(226,232,240,.82);--scrollbar-width:0.5rem;--scrollbar-track:var(--color-white-soft);--scrollbar-thumb:var(--color-white-softer);--scrollbar-thumb-hover:var(--color-white-softest);--z-sidebar-overlay:10;--z-sidebar:20;--z-modal-root:100;--z-modal-overlay:110;--z-modal-content:120;--z-modal-player:130;--sidebar-width-expanded:16rem;--sidebar-width-collapsed:4rem;--sidebar-width:var(--sidebar-width-expanded);--sidebar-control-size:2.75rem;--sidebar-content-gap:1rem;--blog-embed-height:100rem;--color-blog-transparent:transparent;--color-blog-cyan-500:#06b6d4;--color-blog-sky-500:#0bf;--color-blog-teal-600:#0e7490;--color-blog-green-800:#14532d;--color-blog-green-700:#15803d;--color-blog-teal-700:#164e63;--color-blog-gray-900:#171717;--color-blog-coffee-900:#1c1917;--color-blog-green-500:#22c55e;--color-blog-gray-800:#2b2b2b;--color-blog-gray-700:#3b3b3b;--color-blog-teal-accent:#3e8b8a;--color-blog-gray-650:#404040;--color-blog-brown-700:#44403c;--color-blog-purple-900:#581c87;--color-blog-gray-500:#666;--color-blog-gray-450:#737373;--color-blog-gray-440:#747474;--color-blog-brown-500:#78716c;--color-blog-rust-700:#7c2d12;--color-blog-purple-700:#7e22ce;--color-blog-purple-accent:#92379c;--color-blog-purple-muted:#96609c;--color-blog-gray-300:#999;--color-blog-purple-500:#a855f7;--color-blog-gray-275:#a9a9a9;--color-blog-red-600:#b91c1c;--color-blog-rust-600:#c2410c;--colLine 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:100%}@media (min-width:1024px){html{font-size:75%}}*,:after,:before{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent}::-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:.25rem}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}body{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}#editNip71TagsFieldset>legend,#nip71TagsFieldset>legend{margin-left:0;padding:0}#app{background-color:var(--color-page);color:inherit}header{margin-bottom:2rem;padding:1rem 0;display:flex;justify-content:flex-start}header img{height:6rem;width:auto;max-width:none}body.modal-open,html.modal-open{overflow:hidden;height:100%}body.modal-open #app{overflow:hidden}body.modal-open #sidebar,body.modal-open #sidebarOverlay{pointer-events:none;opacity:0;visibility:hidden}body.modal-open #sidebar{z-index:var(--z-sidebar-overlay)}body.modal-open #sidebarOverlay{z-index:calc(var(--z-sidebar-overlay) - 1)}.\!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}}@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}}.focus-ring{outline:2px solid transparent;outline-offset:2px}.focus-ring:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:var(--color-info);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--color-surface);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-fast);transition-timing-function:cubic-bezier(0,0,.2,1)}.popover{position:relative;display:inline-flex}.popover__panel{outline:2px solid transparent;outline-offset:2px}.popover__panel:focus-visible{--tw-ring-offset-shadow:var(--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:100%}@media (min-width:1024px){html{font-size:75%}}*,:after,:before{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent}::-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:.25rem}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}body{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}#editNip71TagsFieldset>legend,#nip71TagsFieldset>legend{margin-left:0;padding:0}#app{background-color:var(--color-page);color:inherit}header{margin-bottom:2rem;padding:1rem 0;display:flex;justify-content:flex-start}header img{height:6rem;width:auto;max-width:none}body.modal-open,html.modal-open{overflow:hidden;height:100%}body.modal-open #app{overflow:hidden}body.modal-open #sidebar,body.modal-open #sidebarOverlay{pointer-events:none;opacity:0;visibility:hidden}body.modal-open #sidebar{z-index:var(--z-sidebar-overlay)}body.modal-open #sidebarOverlay{z-index:calc(var(--z-sidebar-overlay) - 1)}.\!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}}@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}}.focus-ring{outline:2px solid transparent;outline-offset:2px}.focus-ring:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:var(--color-info);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--color-surface);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-fast);transition-timing-function:cubic-bezier(0,0,.2,1)}.popover{position:relative;display:inline-flex}.popover__panel{outline:2px solid transparent;outline-offset:2px}.popover__panel:focus-visible{--tw-ring-offset-shadow:var(--Line truncated
|
||||
+75
-20
@@ -192,6 +192,12 @@
|
||||
var(--motion-ease-out, ease-out);
|
||||
}
|
||||
|
||||
.cloudflare-progress__fill {
|
||||
width: var(--cloudflare-progress-width, 0%);
|
||||
transition: width var(--motion-duration-base, 160ms)
|
||||
var(--motion-ease-out, ease-out);
|
||||
}
|
||||
|
||||
.blog-embed-shell {
|
||||
height: var(--blog-embed-height);
|
||||
}
|
||||
@@ -439,6 +445,11 @@
|
||||
@apply fixed inset-0 z-[var(--z-modal-root)] flex items-center justify-center p-6 sm:p-8;
|
||||
}
|
||||
|
||||
.bv-modal[hidden],
|
||||
.bv-modal.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.bv-modal__panel {
|
||||
@apply focus-ring relative z-[var(--z-modal-content)] w-full max-w-[38rem] rounded-[1.75rem] border border-border-translucent px-8 pb-8 pt-7 text-left text-text shadow-modal transition duration-base ease-out;
|
||||
background-color: var(--color-modal-surface, var(--color-overlay-panel));
|
||||
@@ -619,43 +630,85 @@
|
||||
}
|
||||
|
||||
.video-modal__nav {
|
||||
@apply sticky top-0 z-[60] border-b border-border border-opacity-40 bg-surface;
|
||||
@apply sticky top-0 z-[60] border-b border-border border-opacity-20 bg-surface bg-opacity-95 backdrop-blur-md;
|
||||
}
|
||||
|
||||
.video-modal__nav-bar {
|
||||
@apply flex w-full items-center justify-between gap-3 px-2 py-2 sm:px-4;
|
||||
}
|
||||
|
||||
.video-modal__close {
|
||||
@apply inline-flex h-10 w-10 items-center justify-center rounded-full bg-panel text-text transition duration-base ease-out hover:bg-panel-hover;
|
||||
}
|
||||
|
||||
.modal-nav {
|
||||
@apply video-modal__nav;
|
||||
@apply video-modal__nav transition-transform duration-150 translate-y-0;
|
||||
}
|
||||
|
||||
.video-modal__actions {
|
||||
@apply ml-auto flex flex-nowrap items-center justify-end gap-2;
|
||||
.modal-nav--hidden {
|
||||
@apply -translate-y-full;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
@apply video-modal__actions;
|
||||
.modal-nav--visible {
|
||||
@apply translate-y-0;
|
||||
}
|
||||
|
||||
.video-modal__layout {
|
||||
@apply grid gap-6 lg:gap-8;
|
||||
}
|
||||
|
||||
.video-modal__primary {
|
||||
@apply flex flex-col gap-6;
|
||||
}
|
||||
|
||||
.video-modal__secondary {
|
||||
@apply flex flex-col gap-4;
|
||||
}
|
||||
|
||||
.video-modal__video {
|
||||
@apply z-[51] flex w-full flex-shrink-0 items-center justify-center border-b border-border border-opacity-20 bg-surface lg:sticky lg:top-0;
|
||||
}
|
||||
|
||||
.player-modal__video {
|
||||
@apply video-modal__video;
|
||||
@apply relative overflow-hidden rounded-3xl bg-black shadow-2xl lg:sticky lg:top-20;
|
||||
}
|
||||
|
||||
.video-modal__media {
|
||||
@apply block aspect-video w-full rounded-xl bg-black object-contain shadow-2xl;
|
||||
@apply block aspect-video w-full object-contain;
|
||||
}
|
||||
|
||||
.player-modal__video video {
|
||||
@apply video-modal__media;
|
||||
.video-modal__details {
|
||||
@apply flex flex-col gap-6;
|
||||
}
|
||||
|
||||
.video-modal__info {
|
||||
@apply flex-none border-t border-border border-opacity-20 bg-surface;
|
||||
.video-modal__meta {
|
||||
@apply border-b border-border border-opacity-20 pb-4;
|
||||
}
|
||||
|
||||
.player-modal__info {
|
||||
@apply video-modal__info;
|
||||
.video-modal__actions-row {
|
||||
@apply flex flex-wrap items-center gap-3;
|
||||
}
|
||||
|
||||
.video-modal__action-button {
|
||||
@apply inline-flex h-11 min-w-[2.75rem] items-center justify-center gap-2 rounded-full border border-border border-opacity-30 bg-panel px-3 text-sm font-semibold text-text transition duration-base ease-out hover:bg-panel-hover;
|
||||
}
|
||||
|
||||
.video-modal__action-label {
|
||||
@apply hidden sm:inline;
|
||||
}
|
||||
|
||||
.video-modal__action-icon {
|
||||
@apply h-5 w-5;
|
||||
}
|
||||
|
||||
.video-modal__channel,
|
||||
.video-modal__description,
|
||||
.video-modal__stats {
|
||||
@apply rounded-2xl border border-border border-opacity-20 bg-surface px-6 py-5 shadow-sm;
|
||||
}
|
||||
|
||||
.video-modal__progress {
|
||||
@apply h-2 w-full overflow-hidden rounded-full bg-border-subtle;
|
||||
}
|
||||
|
||||
.video-modal__stat-metrics {
|
||||
@apply flex flex-wrap items-center justify-between gap-3 text-sm text-muted;
|
||||
}
|
||||
|
||||
.profile-pane {
|
||||
@@ -1757,12 +1810,14 @@
|
||||
}
|
||||
|
||||
body.sidebar-collapsed,
|
||||
#sidebar.sidebar-collapsed {
|
||||
#sidebar.sidebar-collapsed,
|
||||
#app.sidebar-collapsed {
|
||||
--sidebar-width: var(--sidebar-width-collapsed);
|
||||
}
|
||||
|
||||
body.sidebar-expanded,
|
||||
#sidebar.sidebar-expanded {
|
||||
#sidebar.sidebar-expanded,
|
||||
#app.sidebar-expanded {
|
||||
--sidebar-width: var(--sidebar-width-expanded);
|
||||
}
|
||||
|
||||
|
||||
@@ -1346,6 +1346,12 @@ class Application {
|
||||
}
|
||||
|
||||
const targetVideo = video || this.currentVideo;
|
||||
if (!targetVideo) {
|
||||
this.log(
|
||||
"[Application] Skipping video modal open; no target video is available.",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const { root } = await this.ensureVideoModalReady({
|
||||
@@ -2966,7 +2972,6 @@ class Application {
|
||||
if (this.loginButton) {
|
||||
this.loginButton.classList.add("hidden");
|
||||
this.loginButton.setAttribute("hidden", "");
|
||||
this.loginButton.style.display = "none";
|
||||
}
|
||||
|
||||
if (this.logoutButton) {
|
||||
@@ -2980,13 +2985,11 @@ class Application {
|
||||
if (this.uploadButton) {
|
||||
this.uploadButton.classList.remove("hidden");
|
||||
this.uploadButton.removeAttribute("hidden");
|
||||
this.uploadButton.style.display = "inline-flex";
|
||||
}
|
||||
|
||||
if (this.profileButton) {
|
||||
this.profileButton.classList.remove("hidden");
|
||||
this.profileButton.removeAttribute("hidden");
|
||||
this.profileButton.style.display = "inline-flex";
|
||||
}
|
||||
|
||||
if (this.subscriptionsLink) {
|
||||
@@ -2998,7 +3001,6 @@ class Application {
|
||||
if (this.loginButton) {
|
||||
this.loginButton.classList.remove("hidden");
|
||||
this.loginButton.removeAttribute("hidden");
|
||||
this.loginButton.style.display = "";
|
||||
}
|
||||
|
||||
if (this.logoutButton) {
|
||||
@@ -3016,13 +3018,11 @@ class Application {
|
||||
if (this.uploadButton) {
|
||||
this.uploadButton.classList.add("hidden");
|
||||
this.uploadButton.setAttribute("hidden", "");
|
||||
this.uploadButton.style.display = "none";
|
||||
}
|
||||
|
||||
if (this.profileButton) {
|
||||
this.profileButton.classList.add("hidden");
|
||||
this.profileButton.setAttribute("hidden", "");
|
||||
this.profileButton.style.display = "none";
|
||||
}
|
||||
|
||||
if (this.subscriptionsLink) {
|
||||
@@ -3759,7 +3759,17 @@ class Application {
|
||||
this.videoModal.updateStatus(status.textContent);
|
||||
}
|
||||
if (progress) {
|
||||
this.videoModal.updateProgress(progress.style.width);
|
||||
const doc = progress.ownerDocument;
|
||||
const view = doc?.defaultView;
|
||||
let widthValue = "";
|
||||
if (view && typeof view.getComputedStyle === "function") {
|
||||
const computed = view.getComputedStyle(progress);
|
||||
widthValue =
|
||||
computed?.getPropertyValue("--progress-width")?.trim() ||
|
||||
computed?.getPropertyValue("width")?.trim() ||
|
||||
"";
|
||||
}
|
||||
this.videoModal.updateProgress(widthValue);
|
||||
}
|
||||
if (peers) {
|
||||
this.videoModal.updatePeers(peers.textContent);
|
||||
|
||||
+14
-24
@@ -256,36 +256,26 @@ async function bootstrapInterface() {
|
||||
};
|
||||
|
||||
const applySidebarDensity = (collapsed) => {
|
||||
const widthTargets = [document.documentElement, document.body].filter(
|
||||
(element) => element instanceof HTMLElement,
|
||||
const state = collapsed ? "collapsed" : "expanded";
|
||||
const toggleTargets = new Set(
|
||||
[document.documentElement, document.body, sidebar].filter(
|
||||
(element) => element instanceof HTMLElement
|
||||
),
|
||||
);
|
||||
if (sidebar instanceof HTMLElement) {
|
||||
widthTargets.push(sidebar);
|
||||
}
|
||||
|
||||
const appShell = document.getElementById("app");
|
||||
if (appShell instanceof HTMLElement) {
|
||||
widthTargets.push(appShell);
|
||||
}
|
||||
const state = collapsed ? "collapsed" : "expanded";
|
||||
const nextWidth = collapsed
|
||||
? SIDEBAR_WIDTH_COLLAPSED
|
||||
: SIDEBAR_WIDTH_EXPANDED;
|
||||
|
||||
widthTargets.forEach((element) => {
|
||||
element.style.setProperty("--sidebar-width", nextWidth);
|
||||
});
|
||||
|
||||
if (appShell instanceof HTMLElement) {
|
||||
toggleTargets.add(appShell);
|
||||
appShell.dataset.sidebarState = state;
|
||||
appShell.classList.toggle("sidebar-collapsed", collapsed);
|
||||
appShell.classList.toggle("sidebar-expanded", !collapsed);
|
||||
}
|
||||
|
||||
[sidebar, document.body]
|
||||
.filter((element) => element instanceof HTMLElement)
|
||||
.forEach((element) => {
|
||||
element.classList.toggle("sidebar-collapsed", collapsed);
|
||||
element.classList.toggle("sidebar-expanded", !collapsed);
|
||||
element.setAttribute("data-state", state);
|
||||
});
|
||||
toggleTargets.forEach((element) => {
|
||||
element.classList.toggle("sidebar-collapsed", collapsed);
|
||||
element.classList.toggle("sidebar-expanded", !collapsed);
|
||||
element.setAttribute("data-state", state);
|
||||
});
|
||||
|
||||
if (collapseToggle) {
|
||||
const actionLabel = collapsed ? "Expand sidebar" : "Collapse sidebar";
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
import { createModalAccessibility } from "./modalAccessibility.js";
|
||||
import { Nip71FormManager } from "./nip71FormManager.js";
|
||||
import {
|
||||
applyDynamicStyles,
|
||||
removeDynamicStyles,
|
||||
} from "../styleSystem.js";
|
||||
|
||||
export class UploadModal {
|
||||
constructor({
|
||||
@@ -761,7 +765,7 @@ export class UploadModal {
|
||||
if (!Number.isFinite(fraction) || fraction < 0) {
|
||||
this.cloudflareProgressBar.classList.add("hidden");
|
||||
this.cloudflareProgressBar.setAttribute("aria-hidden", "true");
|
||||
this.cloudflareProgressFill.style.width = "0%";
|
||||
removeDynamicStyles(this.cloudflareProgressFill, { slot: "r2-progress" });
|
||||
this.cloudflareProgressFill.setAttribute("aria-valuenow", "0");
|
||||
this.emit("upload:r2-progress", { fraction: null });
|
||||
return;
|
||||
@@ -772,7 +776,11 @@ export class UploadModal {
|
||||
|
||||
this.cloudflareProgressBar.classList.remove("hidden");
|
||||
this.cloudflareProgressBar.setAttribute("aria-hidden", "false");
|
||||
this.cloudflareProgressFill.style.width = `${percent}%`;
|
||||
applyDynamicStyles(
|
||||
this.cloudflareProgressFill,
|
||||
{ "--cloudflare-progress-width": `${percent}%` },
|
||||
{ slot: "r2-progress" },
|
||||
);
|
||||
this.cloudflareProgressFill.setAttribute("aria-valuenow", `${percent}`);
|
||||
this.emit("upload:r2-progress", { fraction: clamped });
|
||||
}
|
||||
|
||||
@@ -478,24 +478,22 @@ export class VideoCard {
|
||||
alt: "Placeholder"
|
||||
}
|
||||
});
|
||||
avatar.classList.add("author-pic");
|
||||
avatar.classList.add("author-pic", "cursor-pointer");
|
||||
if (this.video.pubkey) {
|
||||
avatar.dataset.pubkey = this.video.pubkey;
|
||||
}
|
||||
avatar.style.cursor = "pointer";
|
||||
|
||||
avatarWrapper.appendChild(avatar);
|
||||
|
||||
const authorMeta = this.createElement("div", { classNames: ["min-w-0"] });
|
||||
|
||||
const authorName = this.createElement("p", {
|
||||
classNames: ["text-sm", "text-muted", "author-name"],
|
||||
classNames: ["text-sm", "text-muted", "author-name", "cursor-pointer"],
|
||||
textContent: "Loading name..."
|
||||
});
|
||||
if (this.video.pubkey) {
|
||||
authorName.dataset.pubkey = this.video.pubkey;
|
||||
}
|
||||
authorName.style.cursor = "pointer";
|
||||
|
||||
const metadata = this.createElement("div", {
|
||||
classNames: [
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { createModalAccessibility } from "./modalAccessibility.js";
|
||||
import positionFloatingPanel from "../utils/positionFloatingPanel.js";
|
||||
import { applyDesignSystemAttributes } from "../../designSystem.js";
|
||||
import {
|
||||
applyDynamicStyles,
|
||||
removeDynamicStyles,
|
||||
} from "../styleSystem.js";
|
||||
|
||||
export class VideoModal {
|
||||
constructor({
|
||||
@@ -339,15 +343,18 @@ export class VideoModal {
|
||||
const modalNav = playerModal.querySelector("#modalNav");
|
||||
if (modalNav && this.scrollRegion) {
|
||||
let lastScrollY = 0;
|
||||
const updateNavVisibility = (shouldShowNav) => {
|
||||
modalNav.classList.toggle("modal-nav--hidden", !shouldShowNav);
|
||||
modalNav.classList.toggle("modal-nav--visible", shouldShowNav);
|
||||
};
|
||||
this.modalNavScrollHandler = () => {
|
||||
const currentScrollY = this.scrollRegion.scrollTop;
|
||||
const shouldShowNav =
|
||||
currentScrollY <= lastScrollY || currentScrollY < 50;
|
||||
modalNav.style.transform = shouldShowNav
|
||||
? "translateY(0)"
|
||||
: "translateY(-100%)";
|
||||
updateNavVisibility(shouldShowNav);
|
||||
lastScrollY = currentScrollY;
|
||||
};
|
||||
updateNavVisibility(true);
|
||||
this.scrollRegion.addEventListener("scroll", this.modalNavScrollHandler);
|
||||
} else {
|
||||
this.modalNavScrollHandler = null;
|
||||
@@ -369,6 +376,13 @@ export class VideoModal {
|
||||
this.setCopyEnabled(false);
|
||||
this.setShareEnabled(false);
|
||||
this.resetStats();
|
||||
|
||||
if (!this.activeVideo && this.playerModal) {
|
||||
this.playerModal.classList.add("hidden");
|
||||
this.playerModal.setAttribute("hidden", "");
|
||||
this.document?.body?.classList?.remove("modal-open");
|
||||
this.document?.documentElement?.classList?.remove("modal-open");
|
||||
}
|
||||
}
|
||||
|
||||
bindVideoEvents() {
|
||||
@@ -496,14 +510,12 @@ export class VideoModal {
|
||||
}
|
||||
|
||||
if (this.creatorAvatar) {
|
||||
this.creatorAvatar.style.cursor = "pointer";
|
||||
this.creatorAvatar.addEventListener(
|
||||
"click",
|
||||
this.handleCreatorNavigation
|
||||
);
|
||||
}
|
||||
if (this.creatorName) {
|
||||
this.creatorName.style.cursor = "pointer";
|
||||
this.creatorName.addEventListener("click", this.handleCreatorNavigation);
|
||||
}
|
||||
}
|
||||
@@ -535,8 +547,8 @@ export class VideoModal {
|
||||
return;
|
||||
}
|
||||
|
||||
this.playerModal.style.display = "flex";
|
||||
this.playerModal.classList.remove("hidden");
|
||||
this.playerModal.removeAttribute("hidden");
|
||||
this.document.body.classList.add("modal-open");
|
||||
this.document.documentElement.classList.add("modal-open");
|
||||
const triggerElement =
|
||||
@@ -552,8 +564,8 @@ export class VideoModal {
|
||||
close() {
|
||||
this.activeVideo = null;
|
||||
if (this.playerModal) {
|
||||
this.playerModal.style.display = "none";
|
||||
this.playerModal.classList.add("hidden");
|
||||
this.playerModal.setAttribute("hidden", "");
|
||||
}
|
||||
this.document.body.classList.remove("modal-open");
|
||||
this.document.documentElement.classList.remove("modal-open");
|
||||
@@ -655,20 +667,25 @@ export class VideoModal {
|
||||
return;
|
||||
}
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
this.modalProgress.style.setProperty(
|
||||
"--progress-width",
|
||||
`${Math.max(0, value)}%`,
|
||||
applyDynamicStyles(
|
||||
this.modalProgress,
|
||||
{ "--progress-width": `${Math.max(0, value)}%` },
|
||||
{ slot: "progress" },
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed) {
|
||||
this.modalProgress.style.setProperty("--progress-width", trimmed);
|
||||
applyDynamicStyles(
|
||||
this.modalProgress,
|
||||
{ "--progress-width": trimmed },
|
||||
{ slot: "progress" },
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.modalProgress.style.removeProperty("--progress-width");
|
||||
removeDynamicStyles(this.modalProgress, { slot: "progress" });
|
||||
}
|
||||
|
||||
setCopyEnabled(enabled) {
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
const STYLE_TAG_ID = "bitvid-style-system";
|
||||
|
||||
const sheetRegistry = new WeakMap();
|
||||
const styleCache = new Map();
|
||||
const elementClassRegistry = new WeakMap();
|
||||
const classDefinitionRegistry = new Map();
|
||||
|
||||
let classSequence = 0;
|
||||
|
||||
function resolveHTMLElementConstructor(documentRef) {
|
||||
return (
|
||||
documentRef?.defaultView?.HTMLElement ||
|
||||
globalThis.HTMLElement ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function isElementCandidate(element) {
|
||||
return element && typeof element === "object";
|
||||
}
|
||||
|
||||
function isHTMLElement(element, documentRef) {
|
||||
const Constructor = resolveHTMLElementConstructor(documentRef);
|
||||
if (!Constructor) {
|
||||
return false;
|
||||
}
|
||||
return element instanceof Constructor;
|
||||
}
|
||||
|
||||
function ensureDocument(element) {
|
||||
if (element && element.ownerDocument) {
|
||||
return element.ownerDocument;
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
return document;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function ensureStyleSheet(documentRef) {
|
||||
if (!documentRef) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (sheetRegistry.has(documentRef)) {
|
||||
return sheetRegistry.get(documentRef);
|
||||
}
|
||||
|
||||
const root = documentRef.documentElement || documentRef.body;
|
||||
const head = documentRef.head || documentRef.getElementsByTagName("head")[0];
|
||||
if (!head && root) {
|
||||
// If the document does not yet have a <head>, create one so styles attach cleanly.
|
||||
const createdHead = documentRef.createElement("head");
|
||||
documentRef.insertBefore(createdHead, root);
|
||||
}
|
||||
|
||||
const container = documentRef.getElementById(STYLE_TAG_ID);
|
||||
const StyleElementConstructor =
|
||||
documentRef.defaultView?.HTMLStyleElement ||
|
||||
globalThis.HTMLStyleElement ||
|
||||
null;
|
||||
const isStyleElement =
|
||||
(StyleElementConstructor && container instanceof StyleElementConstructor) ||
|
||||
(!StyleElementConstructor && container?.tagName === "STYLE");
|
||||
const styleElement = isStyleElement
|
||||
? container
|
||||
: documentRef.createElement("style");
|
||||
|
||||
styleElement.type = "text/css";
|
||||
styleElement.id = STYLE_TAG_ID;
|
||||
|
||||
if (!styleElement.parentNode) {
|
||||
(documentRef.head || documentRef.documentElement)?.appendChild(styleElement);
|
||||
}
|
||||
|
||||
const sheet = styleElement.sheet;
|
||||
sheetRegistry.set(documentRef, sheet);
|
||||
return sheet;
|
||||
}
|
||||
|
||||
function toCssPropertyName(property) {
|
||||
if (!property) {
|
||||
return "";
|
||||
}
|
||||
if (property.startsWith("--")) {
|
||||
return property;
|
||||
}
|
||||
return property
|
||||
.replace(/([A-Z])/g, "-$1")
|
||||
.replace(/_/g, "-")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function serializeStyleObject(styleObject) {
|
||||
return Object.keys(styleObject)
|
||||
.sort()
|
||||
.map((key) => `${key}:${String(styleObject[key])}`)
|
||||
.join(";");
|
||||
}
|
||||
|
||||
function createCssRule(styleObject) {
|
||||
return Object.keys(styleObject)
|
||||
.sort()
|
||||
.map((key) => `${toCssPropertyName(key)}: ${styleObject[key]};`)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function getSlotKey(slot) {
|
||||
if (!slot) {
|
||||
return "default";
|
||||
}
|
||||
return String(slot);
|
||||
}
|
||||
|
||||
export function applyDynamicStyles(element, styleObject = {}, options = {}) {
|
||||
if (!isElementCandidate(element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const doc = ensureDocument(element);
|
||||
if (!doc || !isHTMLElement(element, doc)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entries = Object.entries(styleObject).filter(([, value]) => value !== null && value !== undefined);
|
||||
if (!entries.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = entries.reduce((acc, [key, value]) => {
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const sheet = ensureStyleSheet(doc);
|
||||
if (!sheet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheKey = serializeStyleObject(normalized);
|
||||
let className = styleCache.get(cacheKey);
|
||||
if (!className) {
|
||||
classSequence += 1;
|
||||
className = `bvds-${classSequence.toString(36)}`;
|
||||
const cssRule = createCssRule(normalized);
|
||||
try {
|
||||
sheet.insertRule(`.${className} { ${cssRule} }`, sheet.cssRules.length);
|
||||
} catch (error) {
|
||||
console.warn("Failed to insert dynamic style", { cssRule, error });
|
||||
return null;
|
||||
}
|
||||
styleCache.set(cacheKey, className);
|
||||
classDefinitionRegistry.set(className, { ...normalized });
|
||||
}
|
||||
|
||||
let slotRegistry = elementClassRegistry.get(element);
|
||||
if (!slotRegistry) {
|
||||
slotRegistry = new Map();
|
||||
elementClassRegistry.set(element, slotRegistry);
|
||||
}
|
||||
|
||||
const slot = getSlotKey(options.slot);
|
||||
const previousClass = slotRegistry.get(slot);
|
||||
if (previousClass && previousClass !== className) {
|
||||
element.classList.remove(previousClass);
|
||||
}
|
||||
|
||||
if (!element.classList.contains(className)) {
|
||||
element.classList.add(className);
|
||||
}
|
||||
|
||||
slotRegistry.set(slot, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function removeDynamicStyles(element, options = {}) {
|
||||
if (!isElementCandidate(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = ensureDocument(element);
|
||||
if (!doc || !isHTMLElement(element, doc)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const slot = getSlotKey(options.slot);
|
||||
const slotRegistry = elementClassRegistry.get(element);
|
||||
if (!slotRegistry) {
|
||||
return;
|
||||
}
|
||||
|
||||
const className = slotRegistry.get(slot);
|
||||
if (className) {
|
||||
element.classList.remove(className);
|
||||
slotRegistry.delete(slot);
|
||||
}
|
||||
|
||||
if (!slotRegistry.size) {
|
||||
elementClassRegistry.delete(element);
|
||||
}
|
||||
}
|
||||
|
||||
export function clearAllDynamicStyles(element) {
|
||||
if (!isElementCandidate(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = ensureDocument(element);
|
||||
if (!doc || !isHTMLElement(element, doc)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const slotRegistry = elementClassRegistry.get(element);
|
||||
if (!slotRegistry) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const className of slotRegistry.values()) {
|
||||
element.classList.remove(className);
|
||||
}
|
||||
|
||||
elementClassRegistry.delete(element);
|
||||
}
|
||||
|
||||
export function getAppliedDynamicStyles(element, options = {}) {
|
||||
if (!isElementCandidate(element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const doc = ensureDocument(element);
|
||||
if (!doc || !isHTMLElement(element, doc)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const slot = getSlotKey(options.slot);
|
||||
const slotRegistry = elementClassRegistry.get(element);
|
||||
if (!slotRegistry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const className = slotRegistry.get(slot);
|
||||
if (!className) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const definition = classDefinitionRegistry.get(className);
|
||||
if (!definition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { ...definition };
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
applyDynamicStyles,
|
||||
clearAllDynamicStyles,
|
||||
} from "../styleSystem.js";
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
placement: "bottom",
|
||||
alignment: "start",
|
||||
@@ -240,18 +245,24 @@ export function positionFloatingPanel(anchor, panel, options = {}) {
|
||||
};
|
||||
|
||||
const applyPosition = ({ top, left, placement }) => {
|
||||
safePanel.style.position = config.strategy;
|
||||
safePanel.style.top = `${top}px`;
|
||||
safePanel.style.left = `${left}px`;
|
||||
safePanel.style.right = "auto";
|
||||
safePanel.style.bottom = "auto";
|
||||
safePanel.dataset.placement = placement;
|
||||
const transformOrigin = computeTransformOrigin({
|
||||
placement,
|
||||
alignment: config.alignment,
|
||||
rtl,
|
||||
});
|
||||
safePanel.style.setProperty("--floating-transform-origin", transformOrigin);
|
||||
applyDynamicStyles(
|
||||
safePanel,
|
||||
{
|
||||
position: config.strategy,
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
right: "auto",
|
||||
bottom: "auto",
|
||||
"--floating-transform-origin": transformOrigin,
|
||||
},
|
||||
{ slot: "floating-position" },
|
||||
);
|
||||
if (typeof config.onUpdate === "function") {
|
||||
config.onUpdate({ top, left, placement, alignment: config.alignment, rtl });
|
||||
}
|
||||
@@ -398,6 +409,7 @@ export function positionFloatingPanel(anchor, panel, options = {}) {
|
||||
// Ignore cleanup errors to avoid breaking consumer teardown flows.
|
||||
}
|
||||
}
|
||||
clearAllDynamicStyles(safePanel);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
+12
-1
@@ -1,5 +1,12 @@
|
||||
// js/utils/mediaLoader.js
|
||||
|
||||
import { applyDynamicStyles } from "../ui/styleSystem.js";
|
||||
|
||||
function createBackgroundImageValue(src) {
|
||||
const safe = String(src).replace(/["\\)\n\r]/g, (match) => `\\${match}`);
|
||||
return `url("${safe}")`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple IntersectionObserver-based lazy loader for images (or videos).
|
||||
*
|
||||
@@ -46,7 +53,11 @@ export class MediaLoader {
|
||||
el.poster = fallbackSrc;
|
||||
}
|
||||
} else {
|
||||
el.style.backgroundImage = `url('${lazySrc}')`;
|
||||
applyDynamicStyles(
|
||||
el,
|
||||
{ backgroundImage: createBackgroundImageValue(lazySrc) },
|
||||
{ slot: "lazy-background" },
|
||||
);
|
||||
}
|
||||
|
||||
delete el.dataset.lazy;
|
||||
|
||||
@@ -2,6 +2,7 @@ import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { JSDOM } from "jsdom";
|
||||
import positionFloatingPanel from "../js/ui/utils/positionFloatingPanel.js";
|
||||
import { getAppliedDynamicStyles } from "../js/ui/styleSystem.js";
|
||||
|
||||
test("flips to top when bottom placement collides with viewport", () => {
|
||||
const dom = new JSDOM(
|
||||
@@ -54,8 +55,10 @@ test("flips to top when bottom placement collides with viewport", () => {
|
||||
positioner.update();
|
||||
|
||||
assert.equal(panel.dataset.placement, "top");
|
||||
assert.equal(panel.style.top, "132px");
|
||||
assert.equal(panel.style.left, "100px");
|
||||
const styles = getAppliedDynamicStyles(panel, { slot: "floating-position" });
|
||||
assert.ok(styles);
|
||||
assert.equal(styles.top, "132px");
|
||||
assert.equal(styles.left, "100px");
|
||||
|
||||
positioner.destroy();
|
||||
});
|
||||
@@ -113,7 +116,9 @@ test("respects RTL alignment and clamps within the viewport", () => {
|
||||
positioner.update();
|
||||
|
||||
// In RTL mode, start alignment hugs the right edge of the trigger.
|
||||
assert.equal(panel.style.left, "140px");
|
||||
const rtlStyles = getAppliedDynamicStyles(panel, { slot: "floating-position" });
|
||||
assert.ok(rtlStyles);
|
||||
assert.equal(rtlStyles.left, "140px");
|
||||
assert.equal(panel.dataset.placement, "bottom");
|
||||
|
||||
positioner.destroy();
|
||||
@@ -178,12 +183,16 @@ test("updates when scroll containers move the trigger", async () => {
|
||||
panel.dataset.state = "open";
|
||||
positioner.update();
|
||||
|
||||
assert.equal(panel.style.top, "152px");
|
||||
const initialStyles = getAppliedDynamicStyles(panel, { slot: "floating-position" });
|
||||
assert.ok(initialStyles);
|
||||
assert.equal(initialStyles.top, "152px");
|
||||
assert.ok(scrollHandler, "scroll listener should be registered");
|
||||
|
||||
anchorTop = 60;
|
||||
scrollHandler?.({ type: "scroll" });
|
||||
assert.equal(panel.style.top, "112px");
|
||||
const updatedStyles = getAppliedDynamicStyles(panel, { slot: "floating-position" });
|
||||
assert.ok(updatedStyles);
|
||||
assert.equal(updatedStyles.top, "112px");
|
||||
|
||||
positioner.destroy();
|
||||
window.addEventListener = originalAddEventListener;
|
||||
|
||||
@@ -208,17 +208,20 @@ for (const designSystemEnabled of [false, true]) {
|
||||
scrollRegion.scrollTop = 120;
|
||||
assert.equal(modal.scrollRegion.scrollTop, 120);
|
||||
modal.modalNavScrollHandler?.();
|
||||
assert.equal(nav.style.transform, "translateY(-100%)");
|
||||
assert.ok(nav.classList.contains("modal-nav--hidden"));
|
||||
assert.ok(!nav.classList.contains("modal-nav--visible"));
|
||||
|
||||
scrollRegion.scrollTop = 60;
|
||||
assert.equal(modal.scrollRegion.scrollTop, 60);
|
||||
modal.modalNavScrollHandler?.();
|
||||
assert.equal(nav.style.transform, "translateY(0)");
|
||||
assert.ok(nav.classList.contains("modal-nav--visible"));
|
||||
assert.ok(!nav.classList.contains("modal-nav--hidden"));
|
||||
|
||||
scrollRegion.scrollTop = 10;
|
||||
assert.equal(modal.scrollRegion.scrollTop, 10);
|
||||
modal.modalNavScrollHandler?.();
|
||||
assert.equal(nav.style.transform, "translateY(0)");
|
||||
assert.ok(nav.classList.contains("modal-nav--visible"));
|
||||
assert.ok(!nav.classList.contains("modal-nav--hidden"));
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ test.describe("video modal mobile regression", () => {
|
||||
}
|
||||
|
||||
modal.classList.remove("hidden");
|
||||
modal.style.display = "flex";
|
||||
modal.removeAttribute("hidden");
|
||||
|
||||
const mode = designSystemEnabled ? "new" : "legacy";
|
||||
modal.setAttribute("data-ds", mode);
|
||||
|
||||
Reference in new issue
Block a user