mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
feat: image full screen overlay for popoups
This commit is contained in:
@@ -3692,6 +3692,93 @@ div.day-empty {
|
|||||||
object-position: center;
|
object-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup-media-fullscreenable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.popup-media-fullscreenable:hover {
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
.popup-media-fullscreenable:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.popup-media-fullscreenable:focus-visible {
|
||||||
|
outline: 2px solid color-mix(in srgb, var(--text-primary) 70%, transparent);
|
||||||
|
outline-offset: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-popup-media-overlay-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2147483646;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: clamp(20px, 4vw, 48px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.28s cubic-bezier(0.22, 0.03, 0.26, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-popup-media-overlay-backdrop.bsplus-popup-media-overlay-backdrop--visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-popup-media-overlay-backdrop.bsplus-popup-media-overlay--instant {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-popup-media-overlay-inner {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
width: 100%;
|
||||||
|
max-width: min(96vw, 1320px);
|
||||||
|
max-height: calc(100vh - clamp(40px, 10vw, 96px));
|
||||||
|
border-radius: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--background-primary);
|
||||||
|
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.35);
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.94) translateY(12px);
|
||||||
|
transition:
|
||||||
|
opacity 0.28s cubic-bezier(0.22, 0.03, 0.26, 1),
|
||||||
|
transform 0.28s cubic-bezier(0.22, 0.03, 0.26, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-popup-media-overlay-backdrop.bsplus-popup-media-overlay-backdrop--visible
|
||||||
|
.bsplus-popup-media-overlay-inner {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-popup-media-overlay-backdrop.bsplus-popup-media-overlay--instant
|
||||||
|
.bsplus-popup-media-overlay-inner {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-popup-media-overlay-slot {
|
||||||
|
width: 100%;
|
||||||
|
max-height: inherit;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: clamp(16px, 3vw, 28px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-popup-media-overlay-media {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: calc(100vh - clamp(120px, 22vh, 200px));
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes shimmer {
|
@keyframes shimmer {
|
||||||
0% {
|
0% {
|
||||||
background-position: -1000px 0;
|
background-position: -1000px 0;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import stringToHTML from "../stringToHTML";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { settingsState } from "../listeners/SettingsState";
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
import { openPopup } from "./PopupManager";
|
import { openPopup } from "./PopupManager";
|
||||||
|
import { attachPopupMediaFullscreenIfPresent } from "./attachPopupMediaFullscreen";
|
||||||
|
|
||||||
/** Same hosting pattern as the privacy statement branding images (avoids page-relative extension URLs on Engage). */
|
/** Same hosting pattern as the privacy statement branding images (avoids page-relative extension URLs on Engage). */
|
||||||
const ENGAGE_PROMO_IMG_URL =
|
const ENGAGE_PROMO_IMG_URL =
|
||||||
@@ -49,6 +50,8 @@ export function showEngageParentsAnnouncement(onDismissed?: () => void) {
|
|||||||
</div>
|
</div>
|
||||||
`).firstChild as HTMLElement;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
attachPopupMediaFullscreenIfPresent(text, ".engageParentsPromoImg");
|
||||||
|
|
||||||
settingsState.engageParentsAnnouncementShown = true;
|
settingsState.engageParentsAnnouncementShown = true;
|
||||||
|
|
||||||
openPopup({
|
openPopup({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import stringToHTML from "../stringToHTML";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { settingsState } from "../listeners/SettingsState";
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
import { openPopup } from "./PopupManager";
|
import { openPopup } from "./PopupManager";
|
||||||
|
import { attachPopupMediaFullscreenIfPresent } from "./attachPopupMediaFullscreen";
|
||||||
|
|
||||||
const PRIVACY_STATEMENT_VERSION = "2025-12-19";
|
const PRIVACY_STATEMENT_VERSION = "2025-12-19";
|
||||||
|
|
||||||
@@ -59,6 +60,8 @@ export function showPrivacyNotification(onDismissed?: () => void) {
|
|||||||
</div>
|
</div>
|
||||||
`).firstChild as HTMLElement;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
attachPopupMediaFullscreenIfPresent(text, "img.aboutImg");
|
||||||
|
|
||||||
settingsState.privacyStatementLastUpdated = "2025-12-20";
|
settingsState.privacyStatementLastUpdated = "2025-12-20";
|
||||||
settingsState.privacyStatementShown = true;
|
settingsState.privacyStatementShown = true;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import stringToHTML from "../stringToHTML";
|
|||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import kofi from "@/resources/kofi.png?base64";
|
import kofi from "@/resources/kofi.png?base64";
|
||||||
import { openPopup } from "./PopupManager";
|
import { openPopup } from "./PopupManager";
|
||||||
|
import { attachPopupMediaFullscreen } from "./attachPopupMediaFullscreen";
|
||||||
|
|
||||||
export function OpenWhatsNewPopup(onDismissed?: () => void) {
|
export function OpenWhatsNewPopup(onDismissed?: () => void) {
|
||||||
const header = stringToHTML(
|
const header = stringToHTML(
|
||||||
@@ -28,6 +29,7 @@ export function OpenWhatsNewPopup(onDismissed?: () => void) {
|
|||||||
video.appendChild(source);
|
video.appendChild(source);
|
||||||
video.classList.add("whatsnewImg");
|
video.classList.add("whatsnewImg");
|
||||||
imageContainer.appendChild(video);
|
imageContainer.appendChild(video);
|
||||||
|
attachPopupMediaFullscreen(video);
|
||||||
|
|
||||||
const text = stringToHTML(/* html */ `
|
const text = stringToHTML(/* html */ `
|
||||||
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: auto;">
|
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: auto;">
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* Makes popup hero images/videos open a padded overlay (not browser fullscreen) on click.
|
||||||
|
* Escape or backdrop click dismisses it. Clicks use stopPropagation so the
|
||||||
|
* parent SEQTA popup does not close.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
|
|
||||||
|
const FULLSCREENABLE_CLASS = "popup-media-fullscreenable";
|
||||||
|
const OVERLAY_VISIBLE_CLASS = "bsplus-popup-media-overlay-backdrop--visible";
|
||||||
|
const OVERLAY_ANIM_MS = 280;
|
||||||
|
|
||||||
|
function isImageOrVideo(el: Element): el is HTMLImageElement | HTMLVideoElement {
|
||||||
|
return el instanceof HTMLImageElement || el instanceof HTMLVideoElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function attachPopupMediaFullscreen(el: HTMLImageElement | HTMLVideoElement) {
|
||||||
|
el.classList.add(FULLSCREENABLE_CLASS);
|
||||||
|
el.setAttribute("tabindex", "0");
|
||||||
|
el.setAttribute("role", "button");
|
||||||
|
el.setAttribute("aria-label", "View larger");
|
||||||
|
el.title = "Click to view larger";
|
||||||
|
|
||||||
|
const open = (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
openMediaOverlayViewer(el);
|
||||||
|
};
|
||||||
|
|
||||||
|
el.addEventListener("click", open);
|
||||||
|
el.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
|
open(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMediaOverlayViewer(source: HTMLImageElement | HTMLVideoElement) {
|
||||||
|
const backdrop = document.createElement("div");
|
||||||
|
backdrop.id = "bsplus-popup-media-overlay";
|
||||||
|
backdrop.className = "bsplus-popup-media-overlay-backdrop";
|
||||||
|
|
||||||
|
const inner = document.createElement("div");
|
||||||
|
inner.className = "bsplus-popup-media-overlay-inner";
|
||||||
|
|
||||||
|
const slot = document.createElement("div");
|
||||||
|
slot.className = "bsplus-popup-media-overlay-slot";
|
||||||
|
|
||||||
|
let media: HTMLImageElement | HTMLVideoElement;
|
||||||
|
if (source instanceof HTMLVideoElement) {
|
||||||
|
const v = source;
|
||||||
|
const nv = document.createElement("video");
|
||||||
|
nv.classList.add("bsplus-popup-media-overlay-media");
|
||||||
|
nv.controls = true;
|
||||||
|
nv.playsInline = true;
|
||||||
|
nv.loop = v.loop;
|
||||||
|
nv.muted = v.muted;
|
||||||
|
nv.volume = v.volume;
|
||||||
|
for (const s of v.querySelectorAll("source")) {
|
||||||
|
const ns = document.createElement("source");
|
||||||
|
ns.src = (s as HTMLSourceElement).src;
|
||||||
|
const t = (s as HTMLSourceElement).type;
|
||||||
|
if (t) ns.type = t;
|
||||||
|
nv.appendChild(ns);
|
||||||
|
}
|
||||||
|
nv.addEventListener(
|
||||||
|
"loadeddata",
|
||||||
|
() => {
|
||||||
|
try {
|
||||||
|
nv.currentTime = v.currentTime;
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
void nv.play().catch(() => {});
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
|
v.pause();
|
||||||
|
nv.load();
|
||||||
|
media = nv;
|
||||||
|
} else {
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.classList.add("bsplus-popup-media-overlay-media");
|
||||||
|
img.src = source.currentSrc || source.src;
|
||||||
|
img.alt = source.alt || "";
|
||||||
|
media = img;
|
||||||
|
}
|
||||||
|
|
||||||
|
media.addEventListener("click", (e) => e.stopPropagation());
|
||||||
|
|
||||||
|
slot.appendChild(media);
|
||||||
|
inner.append(slot);
|
||||||
|
backdrop.appendChild(inner);
|
||||||
|
document.body.append(backdrop);
|
||||||
|
|
||||||
|
if (!settingsState.animations) {
|
||||||
|
backdrop.classList.add("bsplus-popup-media-overlay--instant");
|
||||||
|
backdrop.classList.add(OVERLAY_VISIBLE_CLASS);
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
backdrop.classList.add(OVERLAY_VISIBLE_CLASS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.addEventListener("click", (e) => e.stopPropagation());
|
||||||
|
|
||||||
|
let done = false;
|
||||||
|
const removeOverlay = () => {
|
||||||
|
if (source instanceof HTMLVideoElement && media instanceof HTMLVideoElement) {
|
||||||
|
try {
|
||||||
|
source.currentTime = media.currentTime;
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
void source.play().catch(() => {});
|
||||||
|
}
|
||||||
|
backdrop.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
if (done) return;
|
||||||
|
done = true;
|
||||||
|
document.removeEventListener("keydown", onDocKey, true);
|
||||||
|
|
||||||
|
if (!settingsState.animations) {
|
||||||
|
removeOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
backdrop.classList.remove(OVERLAY_VISIBLE_CLASS);
|
||||||
|
window.setTimeout(removeOverlay, OVERLAY_ANIM_MS);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDocKey = (ev: KeyboardEvent) => {
|
||||||
|
if (ev.key === "Escape") {
|
||||||
|
ev.stopPropagation();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("keydown", onDocKey, true);
|
||||||
|
|
||||||
|
backdrop.addEventListener("click", () => {
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function attachPopupMediaFullscreenIfPresent(
|
||||||
|
root: ParentNode,
|
||||||
|
selector: string,
|
||||||
|
) {
|
||||||
|
const el = root.querySelector(selector);
|
||||||
|
if (el && isImageOrVideo(el)) {
|
||||||
|
attachPopupMediaFullscreen(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user