"use strict"; class RenderDecks { static SETTINGS = { cardLayout: new SettingsUtil.EnumSetting({ type: "enum", name: "Card Layout", help: `The layout to use when displaying cards in a deck.`, defaultVal: "list", enumVals: [ { value: "list", name: "List", }, { value: "grid", name: "Grid", }, ], }), }; static getCardTextHtml ({card, deck = null}) { const ptText = Renderer.get() .setFirstSection(true) .setPartPageExpandCollapseDisabled(true) .render({name: card.name, entries: Renderer.card.getFullEntries(card, {backCredit: deck?.back?.credit})}, 1); Renderer.get().setPartPageExpandCollapseDisabled(false); return ptText; } static getRenderedDeckMeta ( ent, { settingsManager, cardStateManager, } = {}, ) { const fnsCleanup = []; const hashDeck = UrlUtil.autoEncodeHash(ent); const $rowsCards = ent.cards .map((card, ixCard) => { const ptText = this.getCardTextHtml({card}); const $btnMarkDrawn = $(``) .click(async evt => { evt.stopPropagation(); await cardStateManager.pDrawCard(ent, card); }); const $btnReplace = $(``) .click(async evt => { evt.stopPropagation(); await cardStateManager.pReplaceCard(ent, card); }); const $btnViewer = $(``) .click(async evt => { evt.stopPropagation(); try { $btnViewer.prop("disabled", true); await RenderDecks.pRenderStgCard({deck: ent, card}); } finally { $btnViewer.prop("disabled", false); } }); const $wrpFace = $$`
${$btnMarkDrawn} ${$btnReplace} ${$btnViewer}
${Renderer.get().setFirstSection(true).render({...card.face, title: card.name, altText: card.name})}
`; const $imgFace = $wrpFace.find("img"); const title = $imgFace.closest(`[title]`).title(); const propCardDrawn = cardStateManager.getPropCardDrawn({hashDeck, ixCard}); const hkCardDrawn = cardStateManager.addHookBase(propCardDrawn, () => { const isDrawn = !!cardStateManager.get(propCardDrawn); $btnMarkDrawn.prop("disabled", isDrawn); $btnReplace.prop("disabled", !isDrawn); $wrpFace.toggleClass("decks__wrp-card-face--drawn", isDrawn); $imgFace.title(isDrawn ? `${title} (Drawn)` : title); }); fnsCleanup.push(() => cardStateManager.removeHookBase(propCardDrawn, hkCardDrawn)); hkCardDrawn(); return $$`
${$wrpFace}
${ptText}
`; }); const $wrpCardRows = $$`
${$rowsCards}
`; const $ptCards = $$`

Cards

${$wrpCardRows}
`; const hkCardLayout = settingsManager.addHookBase("cardLayout", () => { const mode = settingsManager.get("cardLayout"); $wrpCardRows.toggleClass(`decks__wrp-card-rows--list`, false); $wrpCardRows.toggleClass(`decks__wrp-card-rows--grid`, false); $wrpCardRows.toggleClass(`decks__wrp-card-rows--${mode}`); }); fnsCleanup.push(() => settingsManager.removeHookBase("cardLayout", hkCardLayout)); hkCardLayout(); const $ele = $$` ${Renderer.utils.getBorderTr()} ${Renderer.utils.getExcludedTr({entity: ent, dataProp: "deck"})} ${Renderer.utils.getNameTr(ent, {page: UrlUtil.PG_DECKS})} ${Renderer.get().setFirstSection(true).render({entries: ent.entries}, 1)}
${$ptCards} ${Renderer.utils.getPageTr(ent)} ${Renderer.utils.getBorderTr()}`; return {$ele, fnsCleanup}; } /* -------------------------------------------- */ static async pRenderStgCard ({deck, card}) { const imgUrlBack = (card.back || deck.back) ? Renderer.utils.getEntryMediaUrl(card.back || deck.back, "href", "img") : null; const imgUrlCard = Renderer.utils.getEntryMediaUrl(card.face, "href", "img"); const imgBack = imgUrlBack ? await AnimationUtil.pLoadImage(imgUrlBack) : null; if (imgBack) { e_({ ele: imgBack, clazz: "decks-draw__img-card-back absolute", }); } const imgCard = await AnimationUtil.pLoadImage(imgUrlCard); e_({ ele: imgCard, clazz: "decks-draw__img-card", }); const dispGlint = e_({ tag: "div", clazz: "decks-draw__disp-glint no-events no-select absolute", }); const wrpCard = e_({ tag: "div", clazz: "decks-draw__wrp-card relative", children: [ imgBack, imgCard, dispGlint, ], }); const wrpCardFlip = e_({ tag: "div", clazz: "decks-draw__wrp-card-flip", children: [ wrpCard, ], }); const $wrpCardSway = $$`
${wrpCardFlip}
` .click(evt => evt.stopPropagation()); const metasSparkles = await [...new Array(8)] .pSerialAwaitMap(async (_, i) => { const imgSparkle = i % 2 ? await AnimationUtil.pLoadImage(Renderer.get().getMediaUrl("img", "decks/page/medium-2.webp")) : await AnimationUtil.pLoadImage(Renderer.get().getMediaUrl("img", "decks/page/medium-1.webp")); e_({ ele: imgSparkle, clazz: "decks-draw__img-sparkle relative", }); imgSparkle.style.animationDuration = `${4_500 + (Math.random() * 3_000)}ms, ${60_000 + (Math.random() * 60_000)}ms`; imgSparkle.style.animationDelay = `-${i + 1}00ms, -${i + 1}00ms`; const wrpSparkleSway = e_({ tag: "div", clazz: "decks-draw__wrp-sparkle-sway ve-flex-col absolute", children: [ imgSparkle, ], }); wrpSparkleSway.style.top = `${-10 + (120 * Math.random())}%`; wrpSparkleSway.style.left = `${-10 + (120 * Math.random())}%`; wrpSparkleSway.style.width = `min(67%, ${imgSparkle.width}px)`; wrpSparkleSway.style.height = `min(67%, ${imgSparkle.height}px)`; wrpSparkleSway.style.animationDuration = `${10_000 + (Math.random() * 4_000)}ms`; wrpSparkleSway.style.animationDelay = `-${3_000 + (Math.random() * 1_500)}ms`; return {wrpSparkleSway, imgSparkle}; }); const $wrpCardOuter = $$`
${metasSparkles.map(it => it.wrpSparkleSway)} ${$wrpCardSway}
`; const ptText = RenderDecks.getCardTextHtml({card, deck}); const $wrpInfo = $$`
${ptText}
` .click(evt => evt.stopPropagation()); const $btnFlip = imgBack ? $(``) .click(evt => { evt.stopPropagation(); wrpCardFlip.classList.toggle("decks-draw__wrp-card-flip--flipped"); }) : null; const $wrpRhs = $$`
${$wrpInfo}
${$btnFlip}
` .click(evt => evt.stopPropagation()); const onDeviceorientation = evt => { // region Emulate mouse position by projecting orientation angle from a point `depth` behind the screen const bcr = wrpCard.getBoundingClientRect(); const cCenterX = bcr.left + bcr.width / 2; const cCenterY = bcr.top + bcr.height / 2; const depth = (window.innerWidth + window.innerHeight) / 2; const gammaRot = Math.max(Math.min(Math.abs(evt.gamma - 90), 135), 45); const betaRot = Math.max(Math.min(Math.abs(evt.beta - 90), 135), 45); const fromCenterX = depth / Math.tan(-gammaRot * Math.PI / 180); const fromCenterY = depth / Math.tan(-betaRot * Math.PI / 180); const mouseX = cCenterX + fromCenterX; const mouseY = cCenterY + fromCenterY; // endregion this._pRenderStgCard_onMouseMove_mutElements({mouseX, mouseY, wrpCard, dispGlint}); }; const $wrpDrawn = $$`
${$wrpCardOuter} ${$wrpRhs}
` .click(evt => { evt.stopPropagation(); $wrpDrawn.remove(); window.removeEventListener("deviceorientation", onDeviceorientation); }) .mousemove(evt => { const mouseX = EventUtil.getClientX(evt); const mouseY = EventUtil.getClientY(evt); requestAnimationFrame(() => { this._pRenderStgCard_onMouseMove_mutElements({mouseX, mouseY, wrpCard, dispGlint}); }); }); window.addEventListener("deviceorientation", onDeviceorientation); const {x: mouseX, y: mouseY} = EventUtil.getMousePos(); this._pRenderStgCard_onMouseMove_mutElements({mouseX, mouseY, wrpCard, dispGlint}); $wrpDrawn.appendTo(document.body); await AnimationUtil.pRecomputeStyles(); $wrpDrawn.addClass("decks-draw__stg--visible"); wrpCard.classList.add("decks-draw__wrp-card--visible"); $wrpRhs.addClass("decks-draw__wrp-rhs--visible"); metasSparkles.forEach(it => it.imgSparkle.classList.add("decks-draw__img-sparkle--visible")); } static _pRenderStgCard_onMouseMove_mutElements ({mouseX, mouseY, wrpCard, dispGlint}) { const perStyles = this._pRenderStgCard_getPerspectiveStyles({mouseX, mouseY, ele: wrpCard}); wrpCard.style.transform = perStyles.cardTransform; dispGlint.style.background = perStyles.glintBackground; } static _pRenderStgCard_getPerspectiveStyles ({mouseX, mouseY, ele}) { const bcr = ele.getBoundingClientRect(); const hView = window.innerHeight; const cCenterX = bcr.left + bcr.width / 2; const cCenterY = bcr.top + bcr.height / 2; const cMouseX = mouseX - cCenterX; const cMouseY = (hView - mouseY) - (hView - cCenterY); const scaleFactor = hView * 2; const rotX = cMouseY / scaleFactor; const rotY = cMouseX / scaleFactor; return { ...this._pRenderStgCard_getPerspectiveStyles_card({mouseX, mouseY, bcr, hView, rotX, rotY}), ...this._pRenderStgCard_getPerspectiveStyles_glint({mouseX, mouseY, bcr, hView, rotX, rotY}), }; } static _pRenderStgCard_getPerspectiveStyles_card ({rotX, rotY}) { return { cardTransform: `perspective(100vh) rotateX(${rotX}rad) rotateY(${rotY}rad)`, }; } static _pRenderStgCard_getPerspectiveStyles_glint ({mouseX, mouseY, bcr, hView, rotX, rotY}) { const cCenterX = bcr.left + bcr.width / 2; const cCenterY = bcr.top + bcr.height / 2; const cMouseX = mouseX - cCenterX; const cMouseY = (hView - mouseY) - (hView - cCenterY); const glintDist = Math.sqrt(Math.pow(cMouseX, 2) + Math.pow(cMouseY, 2)); const glintDistRatio = glintDist / hView; const pctLeft = ((mouseX - bcr.left) / bcr.width) * 100; const pctTop = ((mouseY - bcr.top) / bcr.height) * 100; const pctLeftClamped = Math.max(0, Math.min(100, pctLeft)); const pctTopClamped = Math.max(0, Math.min(100, pctTop)); const glintOpacityFalloff = glintDistRatio * 0.33; const gradSpot = `radial-gradient( circle at left ${pctLeftClamped}% top ${pctTopClamped}%, rgba(255, 255, 255, 0.73) 0%, rgba(255, 255, 255, ${1.0 - glintOpacityFalloff}) 1%, rgba(255, 255, 255, ${1.0 - glintOpacityFalloff}) ${1 + (glintDistRatio * 2)}%, rgba(255, 255, 255, 0.53) ${2 + (glintDistRatio * 2)}%, transparent ${5 + (glintDistRatio * 13)}% )`; const gradSpotInv = `radial-gradient( circle at left ${100 - pctLeftClamped}% top ${100 - pctTopClamped}%, #fff2 0%, #fff2 ${10 + (glintDistRatio * 2)}%, transparent ${20 + (glintDistRatio * 5)}% )`; const gradEdge = `linear-gradient( ${-rotX + rotY}rad, var(--rgb-card-glint--edge) 0%, transparent 4%, transparent 96%, var(--rgb-card-glint--edge) 100% )`; return { glintBackground: `${gradSpot}, ${gradSpotInv}, ${gradEdge}`, }; } }