Compare commits
5 Commits
671a4d9b90
...
6f0b6892a8
| Author | SHA1 | Date | |
|---|---|---|---|
|
6f0b6892a8
|
|||
|
913bbccd0b
|
|||
|
77631f2a6c
|
|||
|
185001308d
|
|||
|
bbb9ad0e35
|
@@ -21,6 +21,151 @@ class IPUBElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
class IPUBAudio extends IPUBElement {
|
||||
static elementName = "ipub-audio";
|
||||
|
||||
/**
|
||||
* @param {boolean} [forced=false]
|
||||
* @throws {Error}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
play(forced = false) {
|
||||
if (!this.#audioElement.readyState > HTMLMediaElement.HAVE_CURRENT_DATA) {
|
||||
throw new Error("IPUBAudio: audio is not ready");
|
||||
}
|
||||
if (forced) {
|
||||
this.setAttribute("forced", "true");
|
||||
}
|
||||
return this.#audioElement.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} [forced=false]
|
||||
*/
|
||||
pause(forced = false) {
|
||||
if (forced) {
|
||||
this.setAttribute("forced", "true");
|
||||
}
|
||||
this.#audioElement.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} state
|
||||
*/
|
||||
setLoop(state) {
|
||||
this.#audioElement.loop = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getLoop() {
|
||||
return this.#audioElement.loop;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
const audio = this.querySelector("audio");
|
||||
if (!audio) {
|
||||
console.error("IPUBAudio: Missing child <audio> element");
|
||||
return;
|
||||
}
|
||||
|
||||
this.#audioElement = audio;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {HTMLAudioElement}
|
||||
*/
|
||||
#audioElement;
|
||||
|
||||
/**
|
||||
* @param {number} volume
|
||||
* @param {Object} options
|
||||
* @param {number} [options.fadetime=0]
|
||||
* @param {() => void} [options.onfinish=() => {}]
|
||||
*/
|
||||
setVolume(volume, { fadetimeMS = 0, onFinishFade = () => {} } = {}) {
|
||||
if (fadetimeMS === 0) {
|
||||
this.#audioElement.volume = volume / 100;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#isFading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#onFinishFade = onFinishFade;
|
||||
|
||||
const diff = volume - this.#audioElement.volume * 100;
|
||||
const ticks = diff < 0 ? Math.abs(diff) : diff;
|
||||
let tick = 0;
|
||||
|
||||
const interval = fadetimeMS / ticks;
|
||||
|
||||
this.#isFading = true;
|
||||
this.#fadeTask = setInterval(() => {
|
||||
tick++;
|
||||
|
||||
const cancel = () => {
|
||||
this.#isFading = false;
|
||||
if (onFinishFade) {
|
||||
onFinishFade();
|
||||
}
|
||||
clearInterval(this.#fadeTask);
|
||||
this.#onFinishFade = null;
|
||||
};
|
||||
|
||||
if (!this.#audioElement) {
|
||||
cancel();
|
||||
console.error("IPUBAudio: Missing child <audio> element");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ticks < tick) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (volume === this.getVolume()) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (diff === 0) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
const nvol =
|
||||
(diff > 0
|
||||
? Math.ceil(this.#audioElement.volume * 100 + 1)
|
||||
: Math.floor(this.#audioElement.volume * 100 - 1)) / 100;
|
||||
|
||||
if (nvol > 1 || nvol < 0) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
this.#audioElement.volume = nvol;
|
||||
}, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
getVolume() {
|
||||
return Math.floor((this.#audioElement?.volume ?? 0) * 100);
|
||||
}
|
||||
|
||||
#isFading = false;
|
||||
#fadeTask = 0;
|
||||
/** @type {() => void | null} */
|
||||
#onFinishFade = null;
|
||||
}
|
||||
|
||||
class IPUBBackground extends IPUBElement {
|
||||
static elementName = "ipub-background";
|
||||
static observedAttributes = ["nofade"].concat(super.observedAttributes);
|
||||
@@ -28,49 +173,84 @@ class IPUBBackground extends IPUBElement {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static #observer = (() => {
|
||||
/** @type {Map<string, IPUBBackground>} */
|
||||
const instancesOnScreen = new Map();
|
||||
|
||||
document.addEventListener("scroll", () => {
|
||||
for (const [_, instance] of instancesOnScreen) {
|
||||
const perc = getPercentageInView(
|
||||
instance.querySelector("img") || instance,
|
||||
static #instancesOnScreen = {
|
||||
/** @type {Map<IPUBBody, Set<IPUBBackground>>} */
|
||||
map: new Map(),
|
||||
/**
|
||||
* @param {IPUBBackground} background
|
||||
*/
|
||||
add(background) {
|
||||
const body = getAncestor(background, IPUBBody.elementName);
|
||||
if (!body) {
|
||||
console.error(
|
||||
`IPUBBackground: ${background.id} does not have a valid ipub-body ancestor`,
|
||||
);
|
||||
instance.fade(perc);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return new IntersectionObserver((entries) =>
|
||||
entries.forEach((e) => {
|
||||
let instance = e.target.parentElement;
|
||||
let set = this.map.get(body);
|
||||
if (!set) {
|
||||
set = new Set();
|
||||
body.addEventListener("scroll", () => {
|
||||
for (const instance of set.values()) {
|
||||
const perc = getPercentageInView(
|
||||
instance.querySelector("img") || instance,
|
||||
);
|
||||
instance.fade(perc);
|
||||
}
|
||||
});
|
||||
}
|
||||
set.add(background);
|
||||
this.map.set(body, set);
|
||||
},
|
||||
/**
|
||||
* @param {IPUBBackground} background
|
||||
*/
|
||||
remove(background) {
|
||||
const body = getAncestor(background, IPUBBody.elementName);
|
||||
if (!body) {
|
||||
console.error(
|
||||
`IPUBBackground: ${background.id} does not have a valid ipub-body ancestor`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
instance.tagName.toLowerCase() !==
|
||||
IPUBBackground.elementName.toLowerCase()
|
||||
) {
|
||||
instance = instance.parentElement;
|
||||
}
|
||||
const set = this.map.get(body);
|
||||
if (!set) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
instance.tagName.toLowerCase() !==
|
||||
IPUBBackground.elementName.toLowerCase()
|
||||
) {
|
||||
console.error(
|
||||
"IPUBBackground: malformed <ipub-background> element",
|
||||
e.target,
|
||||
);
|
||||
return;
|
||||
}
|
||||
set.delete(background);
|
||||
|
||||
if (e.intersectionRatio > 0 && instance.id) {
|
||||
instancesOnScreen.set(instance.id, instance);
|
||||
} else if (instance.id) {
|
||||
instancesOnScreen.delete(instance.id);
|
||||
}
|
||||
}),
|
||||
);
|
||||
})();
|
||||
if (set.size === 0) {
|
||||
this.map.delete(body);
|
||||
}
|
||||
},
|
||||
};
|
||||
static addToScreen() {}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static #observer = new IntersectionObserver((entries) => {
|
||||
for (const { intersectionRatio, target: image } of entries) {
|
||||
const instance = getAncestor(image, IPUBBackground.elementName);
|
||||
|
||||
if (!instance) {
|
||||
console.error(
|
||||
"IPUBBackground: malformed <ipub-background> element",
|
||||
image,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (intersectionRatio > 0 && instance.id) {
|
||||
IPUBBackground.#instancesOnScreen.add(instance);
|
||||
} else if (instance.id) {
|
||||
IPUBBackground.#instancesOnScreen.remove(instance);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
@@ -126,8 +306,6 @@ class IPUBBackground extends IPUBElement {
|
||||
* @returns {void | Promise<void>}
|
||||
*/
|
||||
fade(perc) {
|
||||
console.debug(`IPUBBackground: ${this.id} is ${perc} on screen`);
|
||||
|
||||
if (!this.style.getPropertyValue("--ipub-fade")) {
|
||||
this.style.setProperty("--ipub-fade", `${perc}%`);
|
||||
return;
|
||||
@@ -144,8 +322,109 @@ class IPUBBackground extends IPUBElement {
|
||||
}
|
||||
}
|
||||
|
||||
class IPUBContent extends IPUBElement {
|
||||
static elementName = "ipub-content";
|
||||
class IPUBBody extends IPUBElement {
|
||||
static elementName = "ipub-body";
|
||||
|
||||
static defineContentElements() {
|
||||
for (const e of [
|
||||
IPUBAudio,
|
||||
IPUBBackground,
|
||||
IPUBImage,
|
||||
IPUBInteraction,
|
||||
IPUBSoundtrack,
|
||||
]) {
|
||||
console.info(`IPUBBody: Defining custom element <${e.elementName}>`);
|
||||
globalThis.customElements.define(e.elementName, e);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.setAttribute("aria-busy", "true");
|
||||
|
||||
// TODO?: Move IPUBCover's "can-play" logic to here
|
||||
|
||||
console.log("IPUBBody: Defining custom element <ipub-cover>");
|
||||
globalThis.customElements.define(IPUBCover.elementName, IPUBCover);
|
||||
|
||||
/** @type {IPUBCover} */
|
||||
const cover = this.querySelector("ipub-cover");
|
||||
if (!cover) {
|
||||
// TODO: automatically create IPUBCover element if it doesn't exists
|
||||
console.error("IPUBBody: Document doesn't has <ipub-cover> element");
|
||||
IPUBBody.defineContentElements();
|
||||
}
|
||||
cover.onclose = IPUBBody.defineContentElements;
|
||||
|
||||
this.setAttribute("aria-busy", "false");
|
||||
}
|
||||
}
|
||||
|
||||
globalThis.addEventListener("load", () => {
|
||||
console.info("IPUB: Starting IPUB elements");
|
||||
|
||||
console.log("IPUB: Defining custom element <ipub-body>");
|
||||
globalThis.customElements.define(IPUBBody.elementName, IPUBBody);
|
||||
});
|
||||
|
||||
class IPUBCover extends IPUBElement {
|
||||
static elementName = "ipub-cover";
|
||||
|
||||
/**
|
||||
* @type {() => void} callback
|
||||
*/
|
||||
onclose = () => {};
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
console.debug("IPUBCover: Setting up cover");
|
||||
this.setAttribute("aria-busy", "true");
|
||||
|
||||
const dialog = this.querySelector("dialog");
|
||||
|
||||
// HACK: Test if we can autoplay interactions, soundtracks, etc
|
||||
|
||||
/** @type {HTMLMediaElement | null} */
|
||||
const media =
|
||||
this.parentElement.querySelector("audio") ??
|
||||
this.parentElement.querySelector("video");
|
||||
|
||||
if (!media) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pastVolume = media.volume;
|
||||
media.volume = 0.1; // don't let the user hear the test audio
|
||||
|
||||
media
|
||||
.play()
|
||||
.then(() => {
|
||||
media.pause();
|
||||
media.volume = pastVolume;
|
||||
media.currentTime = 0;
|
||||
|
||||
console.debug("IPUBCover: Can autoplay interactions, removing cover");
|
||||
dialog.close();
|
||||
this.onclose();
|
||||
})
|
||||
.catch(() => {
|
||||
console.debug(
|
||||
"IPUBCover: Cannot autoplay interactions, covering content",
|
||||
);
|
||||
|
||||
dialog.show();
|
||||
|
||||
dialog.parentElement.addEventListener("click", () => {
|
||||
dialog.close();
|
||||
this.onclose();
|
||||
});
|
||||
|
||||
this.setAttribute("aria-busy", "false");
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class IPUBImage extends IPUBElement {
|
||||
@@ -156,42 +435,146 @@ class IPUBInteraction extends IPUBElement {
|
||||
static elementName = "ipub-interaction";
|
||||
}
|
||||
|
||||
globalThis.addEventListener("load", () => {
|
||||
console.info("IPUB: STARTING DEFINITIONS");
|
||||
|
||||
[IPUBBackground, IPUBContent, IPUBImage, IPUBInteraction].forEach((e) => {
|
||||
console.info(`IPUB: Defining custom element <${e.elementName}>`);
|
||||
globalThis.customElements.define(e.elementName, e);
|
||||
});
|
||||
|
||||
console.info("IPUB: FINISHED DEFINITIONS");
|
||||
|
||||
/** @type {Map<string, Element>} */
|
||||
const onScreenMap = new Map();
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((e) => {
|
||||
if (e.intersectionRatio > 0) {
|
||||
// console.debug(
|
||||
// `IntersectionObserver: adding element #${e.target.id} to onScreenMap`,
|
||||
// );
|
||||
onScreenMap.set(e.target.id, e.target);
|
||||
} else {
|
||||
// console.debug(
|
||||
// `IntersectionObserver: removing element #${e.target.id} to onScreenMap`,
|
||||
// );
|
||||
onScreenMap.delete(e.target.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
class IPUBSoundtrack extends IPUBElement {
|
||||
static elementName = "ipub-soundtrack";
|
||||
|
||||
// TODO: Toggle automatic soundtrack playing
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static #player = setInterval(() => {
|
||||
const last = Array.from(this.#onScreenStack).pop();
|
||||
if (!last) {
|
||||
// TODO: Fallback to previous soundtrack if there's no audio
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Get siblings based by group OR parent
|
||||
/** @type {NodeListOf<IPUBSoundtrack> | undefined} */
|
||||
const siblings = last.parentElement?.querySelectorAll(
|
||||
IPUBSoundtrack.elementName,
|
||||
);
|
||||
|
||||
try {
|
||||
if (siblings) {
|
||||
siblings.forEach((el) => {
|
||||
if (el !== last) {
|
||||
el.fadeOut();
|
||||
}
|
||||
});
|
||||
}
|
||||
last.fadeIn();
|
||||
} catch (e) {
|
||||
// TODO: Fallback to previous soundtrack on error
|
||||
console.error(
|
||||
`IPUBSoundtrack: error while trying to play audio, error: ${e}`,
|
||||
{
|
||||
error: e,
|
||||
audio: audio,
|
||||
},
|
||||
);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static #observer = (() => {
|
||||
return new IntersectionObserver((entries) => {
|
||||
for (const { intersectionRatio, target, time } of entries) {
|
||||
/** @type {IPUBSoundtrack} */
|
||||
const soundtrack = target;
|
||||
|
||||
if (intersectionRatio > 0) {
|
||||
console.debug(`${soundtrack.id} is on screen at ${time}`, soundtrack);
|
||||
this.#onScreenStack.add(soundtrack);
|
||||
} else {
|
||||
console.debug(
|
||||
`${soundtrack.id} is not on screen ${time}`,
|
||||
soundtrack,
|
||||
);
|
||||
this.#onScreenStack.delete(soundtrack);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Set<IPUBSoundtrack>}
|
||||
*/
|
||||
static #onScreenStack = new Set();
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
fadeIn() {
|
||||
/** @type {IPUBAudio | undefined} */
|
||||
const audio = this.querySelector(IPUBAudio.elementName);
|
||||
if (!audio) {
|
||||
throw new Error("IPUBSoundtrack.fadeIn: missing audio element");
|
||||
}
|
||||
|
||||
// TODO: Global volume settings
|
||||
|
||||
audio.play();
|
||||
audio.setVolume(10, { fadetimeMS: IPUBSoundtrack.FADE_TIME_MS });
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
fadeOut() {
|
||||
/** @type {IPUBAudio | undefined} */
|
||||
const audio = this.querySelector(IPUBAudio.elementName);
|
||||
if (!audio) {
|
||||
throw new Error("IPUBSoundtrack.fadeIn: missing audio element");
|
||||
}
|
||||
|
||||
audio.setVolume(0, {
|
||||
fadetimeMS: IPUBSoundtrack.FADE_TIME_MS,
|
||||
onFinishFade: () => {
|
||||
audio.pause();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Readonly<number>} */
|
||||
static FADE_TIME_MS = 1000 * 3;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
/** @type {IPUBAudio | undefined} */
|
||||
const audio = this.querySelector(IPUBAudio.elementName);
|
||||
if (audio) {
|
||||
audio.setVolume(0);
|
||||
} else {
|
||||
console.error("IPUBSoundtrack: missing audio element");
|
||||
return;
|
||||
}
|
||||
|
||||
IPUBSoundtrack.#observer.observe(this);
|
||||
}
|
||||
|
||||
// TODO(guz013): Handle if element is moved, it's group should be updated
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} el
|
||||
* @param {string} tagName
|
||||
* @returns {HTMLElement | undefined}
|
||||
*/
|
||||
function getAncestor(el, tagName) {
|
||||
if (!el.parentElement) {
|
||||
return undefined;
|
||||
}
|
||||
if (el.parentElement.tagName.toLowerCase() === tagName.toLowerCase()) {
|
||||
return el.parentElement;
|
||||
}
|
||||
return getAncestor(el.parentElement, tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Readonly<Element>} el
|
||||
@@ -257,3 +640,4 @@ function getPercentageInView(element) {
|
||||
|
||||
return Math.round((inView / globalThis.innerHeight) * 100);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="x-ipub-version" content="0.1" />
|
||||
<meta name="viewport"
|
||||
content="initial-scale=1,width=device-width,height=device-height,viewport-fit=contain" />
|
||||
<link href="../styles/stylesheet.css" rel="stylesheet" type="text/css" />
|
||||
<!-- <script type="module" src="../scripts/ipub.js" fetchpriority="high"></script> -->
|
||||
<script defer="true" src="../scripts/ipub.js" fetchpriority="high">
|
||||
@@ -9,11 +11,37 @@
|
||||
</script>
|
||||
</head>
|
||||
<body xmlns:epub="http://www.idpf.org/2007/ops" class="body">
|
||||
<ipub-content style="--ipub-padding: 10%;">
|
||||
<main>
|
||||
<ipub-body style="--ipub-padding: 10%;">
|
||||
<ipub-cover>
|
||||
<dialog>
|
||||
<header>
|
||||
<h1>Test comic</h1>
|
||||
<form method="dialog">
|
||||
<p>Click anywhere to
|
||||
<button>start Reading</button></p>
|
||||
</form>
|
||||
</header>
|
||||
</dialog>
|
||||
</ipub-cover>
|
||||
<main id="content">
|
||||
<ipub-background id="background0001">
|
||||
<img src="../images/background0001.jpg" width="100" height="100" />
|
||||
</ipub-background>
|
||||
<ipub-soundtrack style="--ipub-color:cyan">
|
||||
<!-- TODO: Search on how to make this more accessible, more semantic as using <details> -->
|
||||
<figure>
|
||||
<label>
|
||||
<input type="checkbox" />
|
||||
<figcaption>Soundtrack 1</figcaption>
|
||||
</label>
|
||||
<ipub-audio>
|
||||
<audio controls="true" volume="0" controlslist="nofullscreen"
|
||||
disableremoteplayback="">
|
||||
<source src="../audios/track1.webm" />
|
||||
</audio>
|
||||
</ipub-audio>
|
||||
</figure>
|
||||
</ipub-soundtrack>
|
||||
<ipub-image>
|
||||
<img src="../images/image0001.png" />
|
||||
<ipub-interaction style="--ipub-y:88.5%;--ipub-x:6%" circle="">
|
||||
@@ -28,6 +56,20 @@
|
||||
<ipub-image>
|
||||
<img src="../images/image0002.png" />
|
||||
</ipub-image>
|
||||
<ipub-soundtrack style="--ipub-color:green;">
|
||||
<figure>
|
||||
<label>
|
||||
<input type="checkbox" />
|
||||
<figcaption>Soundtrack 2</figcaption>
|
||||
</label>
|
||||
<ipub-audio>
|
||||
<audio controls="true" volume="0" controlslist="nofullscreen"
|
||||
disableremoteplayback="">
|
||||
<source src="../audios/track2.webm" />
|
||||
</audio>
|
||||
</ipub-audio>
|
||||
</figure>
|
||||
</ipub-soundtrack>
|
||||
<ipub-background id="background0002">
|
||||
<picture>
|
||||
<img src="../images/background0002.jpg" />
|
||||
@@ -47,6 +89,20 @@
|
||||
<ipub-image>
|
||||
<img src="../images/image0002.png" />
|
||||
</ipub-image>
|
||||
<ipub-soundtrack style="--ipub-color:yellow;">
|
||||
<figure>
|
||||
<label>
|
||||
<input type="checkbox" />
|
||||
<figcaption>Soundtrack 3</figcaption>
|
||||
</label>
|
||||
<ipub-audio>
|
||||
<audio controls="true" volume="0" controlslist="nofullscreen"
|
||||
disableremoteplayback="">
|
||||
<source src="../audios/track3.webm" />
|
||||
</audio>
|
||||
</ipub-audio>
|
||||
</figure>
|
||||
</ipub-soundtrack>
|
||||
<ipub-image>
|
||||
<img src="../images/image0003.png" />
|
||||
</ipub-image>
|
||||
@@ -54,6 +110,6 @@
|
||||
<img src="../images/image0004.png" />
|
||||
</ipub-image>
|
||||
</main>
|
||||
</ipub-content>
|
||||
</ipub-body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,9 +8,45 @@
|
||||
margin: 0;
|
||||
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
overflow: clip;
|
||||
display: flex;
|
||||
|
||||
--z-cover: 9;
|
||||
ipub-cover > dialog[open] {
|
||||
--ipub-accent-color: #fff;
|
||||
z-index: var(--z-cover);
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
backdrop-filter: blur(1rem);
|
||||
background-image: linear-gradient(
|
||||
rgba(from var(--ipub-accent-color) r g b / 0) 0%,
|
||||
rgba(from var(--ipub-accent-color) r g b / 0.5)
|
||||
calc(100% + calc(var(--ipub-fade, 50%) * -1))
|
||||
);
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ipub-content {
|
||||
ipub-body {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
overflow: scroll;
|
||||
&:has(ipub-cover > dialog[open]) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
--ipub-padding: 0%;
|
||||
--ipub-gap: 0%;
|
||||
--ipub-padding-x: var(--ipub-padding, 0%);
|
||||
|
||||
Reference in New Issue
Block a user