diff --git a/languages/de.json b/languages/de.json index 08391f4..15dc5b6 100644 --- a/languages/de.json +++ b/languages/de.json @@ -19,14 +19,34 @@ "ShowCommunityHint": "Sichtbarkeit der Charaktergemeinschaft.", "Choices": { "CharTop": "Charakter-Name Oben (Spieler Unten)", + "PlayerTop": "Spieler-Name Oben (Charakter Unten)", + "NoPlayer": "Spieler-Name ausblenden", "MenuLabel": "Stream-Overlay", - "PlayerNameDisplay": "Spielernamen-Anzeige" + "PlayerNameDisplay": "Spielernamen-Anzeige", + "Standard": "Standard (Breitbild)", + "Vertical": "Vertikal (Sammelkarte)", + "RefFade": "Ausblenden (Fade)", + "RefSlide": "Gleiten (Slide)", + "RefZoom": "Zoomen (Zoom)", + "RefCycle": "Zyklus (In Rotation)", + "RefStatic": "Statisch (Immer sichtbar)", + "RefHidden": "Versteckt (Im Karussell)" }, "PlayerNameDisplayHint": "Wo der Spielername relativ zum Charakternamen angezeigt werden soll.", "HideFearLabel": "Fear-Label verbergen", "HideFearLabelHint": "Verbirgt die Textbeschriftung 'FEAR' neben den Fear-Tokens.", "ShowResourceLabels": "Ressourcen-Bezeichnungen anzeigen", - "ShowResourceLabelsHint": "Text-Bezeichnungen (HP, Stress, Hoffnung) neben Icons anzeigen." + "ShowResourceLabelsHint": "Text-Bezeichnungen (HP, Stress, Hoffnung) neben Icons anzeigen.", + "CardLayout": "Karten-Layout Modus", + "CardLayoutHint": "Wähle zwischen der Standard-Breitbild-Karte oder einem vertikalen Sammelkarten-Layout.", + "CarouselEnabled": "Karussell-Modus aktivieren", + "CarouselEnabledHint": "Wechsle automatisch nacheinander durch Spieler.", + "CarouselSpeed": "Karussell-Geschwindigkeit (s)", + "CarouselSpeedHint": "Sekunden, die jede Karte angezeigt wird, bevor gewechselt wird.", + "CarouselEffect": "Übergangseffekt", + "CarouselEffectHint": "Animationseffekt beim Wechseln der Karten.", + "CarouselGMMode": "GM Karussell-Verhalten", + "CarouselGMModeHint": "Wie der GM im Karussell behandelt werden soll." }, "OpenStream": "Stream-Ansicht öffnen", "ModeNumeric": "Numerisch (3/6)", diff --git a/languages/en.json b/languages/en.json index cb63cc1..f0c4367 100644 --- a/languages/en.json +++ b/languages/en.json @@ -24,7 +24,15 @@ "Choices": { "CharTop": "Character Name Top (Player Bottom)", "PlayerTop": "Player Name Top (Character Bottom)", - "NoPlayer": "Hide Player Name" + "NoPlayer": "Hide Player Name", + "Standard": "Standard (Horizontal Card)", + "Vertical": "Vertical (Trading Card)", + "RefFade": "Fade", + "RefSlide": "Slide", + "RefZoom": "Zoom", + "RefCycle": "Cycle (In Rotation)", + "RefStatic": "Static (Always Visible)", + "RefHidden": "Hidden (In Carousel)" }, "MenuLabel": "Stream Overlay", "PlayerNameDisplay": "Player Name Display", @@ -32,7 +40,21 @@ "HideFearLabel": "Hide Fear Label", "HideFearLabelHint": "Hide the text label 'FEAR' next to the fear tokens.", "ShowResourceLabels": "Show Resource Labels", - "ShowResourceLabelsHint": "Show text labels (HP, Stress, Hope) next to icons." + "ShowResourceLabelsHint": "Show text labels (HP, Stress, Hope) next to icons.", + "AutoExpandChat": "Auto-Expand Chat Messages", + "AutoExpandChatHint": "Automatically expand all chat messages to show full content (rolls, descriptions) without needing to click.", + "HideChatActions": "Hide Chat Action Buttons", + "HideChatActionsHint": "Hides buttons like 'Apply Damage', 'Place Template', or other interactive elements from chat cards to keep the view clean.", + "CardLayout": "Card Layout Mode", + "CardLayoutHint": "Choose between the standard wide card or a vertical trading-card style.", + "CarouselEnabled": "Enable Carousel Mode", + "CarouselEnabledHint": "Cycle through players one by one automatically.", + "CarouselSpeed": "Carousel Speed (s)", + "CarouselSpeedHint": "Seconds to show each player card before switching.", + "CarouselEffect": "Transition Effect", + "CarouselEffectHint": "Animation effect when switching cards.", + "CarouselGMMode": "GM Carousel Behavior", + "CarouselGMModeHint": "How the GM should constitute in the carousel." }, "OpenStream": "Open Stream View", "ModeNumeric": "Numeric (3/6)", diff --git a/module.json b/module.json index dc4241a..11b8a18 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "id": "dh-stream-overlay", "title": "Daggerheart Stream Overlay", - "version": "1.0.0", + "version": "2.0.0", "compatibility": { "minimum": "13", "verified": "13" @@ -44,7 +44,7 @@ ], "packFolders": [], "url": "https://github.com/cptn-cosmo/dh-stream-overlay", - "manifest": "https://github.com/cptn-cosmo/dh-stream-overlay/releases/latest/download/module.json", - "download": "https://github.com/cptn-cosmo/dh-stream-overlay/releases/download/1.0.0/dh-stream-overlay.zip", + "manifest": "https://git.geeks.gay/cosmo/dh-stream-overlay/raw/branch/main/module.json", + "download": "https://git.geeks.gay/cosmo/dh-stream-overlay/releases/download/2.0.0/dh-stream-overlay.zip", "description": "A stream overlay module for Daggerheart that displays chat and a linked party actor sheet." } \ No newline at end of file diff --git a/scripts/module.js b/scripts/module.js index 07db141..96c0080 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -191,7 +191,149 @@ Hooks.once("init", () => { scope: "world", config: false, type: Boolean, default: true }); + game.settings.register("dh-stream-overlay", "autoExpandChat", { + name: "DH_STREAM_OVERLAY.Settings.AutoExpandChat", + hint: "DH_STREAM_OVERLAY.Settings.AutoExpandChatHint", + scope: "world", + config: true, + type: Boolean, + default: true, + onChange: () => { if (document.body.classList.contains("stream")) location.reload(); } + }); + + game.settings.register("dh-stream-overlay", "hideChatActions", { + name: "DH_STREAM_OVERLAY.Settings.HideChatActions", + hint: "DH_STREAM_OVERLAY.Settings.HideChatActionsHint", + scope: "world", + config: true, + type: Boolean, + default: true, + onChange: () => { if (document.body.classList.contains("stream")) location.reload(); } + }); + + + game.settings.register("dh-stream-overlay", "cardLayout", { + name: "DH_STREAM_OVERLAY.Settings.CardLayout", + hint: "DH_STREAM_OVERLAY.Settings.CardLayoutHint", + scope: "world", + config: true, + type: String, + choices: { + "standard": "DH_STREAM_OVERLAY.Settings.Choices.Standard", + "vertical": "DH_STREAM_OVERLAY.Settings.Choices.Vertical" + }, + default: "standard", + onChange: () => { if (document.body.classList.contains("stream")) location.reload(); } + }); + + + game.settings.register("dh-stream-overlay", "carouselEnabled", { + name: "DH_STREAM_OVERLAY.Settings.CarouselEnabled", + hint: "DH_STREAM_OVERLAY.Settings.CarouselEnabledHint", + scope: "world", + config: true, + type: Boolean, + default: false, + onChange: () => { if (document.body.classList.contains("stream")) location.reload(); } + }); + + game.settings.register("dh-stream-overlay", "carouselSpeed", { + name: "DH_STREAM_OVERLAY.Settings.CarouselSpeed", + hint: "DH_STREAM_OVERLAY.Settings.CarouselSpeedHint", + scope: "world", + config: true, + type: Number, + default: 5, + range: { min: 2, max: 60, step: 1 }, + onChange: () => { if (document.body.classList.contains("stream")) location.reload(); } + }); + + game.settings.register("dh-stream-overlay", "carouselEffect", { + name: "DH_STREAM_OVERLAY.Settings.CarouselEffect", + hint: "DH_STREAM_OVERLAY.Settings.CarouselEffectHint", + scope: "world", + config: true, + type: String, + choices: { + "fade": "DH_STREAM_OVERLAY.Settings.Choices.RefFade", + "slide": "DH_STREAM_OVERLAY.Settings.Choices.RefSlide", + "zoom": "DH_STREAM_OVERLAY.Settings.Choices.RefZoom" + }, + default: "fade", + onChange: () => { if (document.body.classList.contains("stream")) location.reload(); } + }); + + game.settings.register("dh-stream-overlay", "carouselGMMode", { + name: "DH_STREAM_OVERLAY.Settings.CarouselGMMode", + hint: "DH_STREAM_OVERLAY.Settings.CarouselGMModeHint", + scope: "world", + config: true, + type: String, + choices: { + "cycle": "DH_STREAM_OVERLAY.Settings.Choices.RefCycle", + "static": "DH_STREAM_OVERLAY.Settings.Choices.RefStatic", + "hidden": "DH_STREAM_OVERLAY.Settings.Choices.RefHidden" + }, + default: "cycle", + onChange: () => { if (document.body.classList.contains("stream")) location.reload(); } + }); + }); + +// ============================================================================= +// CAROUSEL LOGIC +// ============================================================================= +let carouselIndex = 0; +let carouselTimer = null; + +function startCarousel() { + if (carouselTimer) clearInterval(carouselTimer); + const speed = game.settings.get("dh-stream-overlay", "carouselSpeed") || 5; + carouselTimer = setInterval(() => { + cycleCarousel(); + }, speed * 1000); +} + +function stopCarousel() { + if (carouselTimer) { + clearInterval(carouselTimer); + carouselTimer = null; + } +} + +function cycleCarousel() { + const container = document.querySelector(".dh-party-grid.carousel-mode"); + if (!container) return; + + const cards = container.querySelectorAll(".dh-party-card"); + if (cards.length === 0) return; + + // Increment + carouselIndex++; + if (carouselIndex >= cards.length) carouselIndex = 0; + + updateCarouselClasses(container); +} + +function updateCarouselClasses(container) { + if (!container) return; + const cards = container.querySelectorAll(".dh-party-card"); + + // Safety: Ensure index is valid + if (carouselIndex >= cards.length) carouselIndex = 0; + + cards.forEach((card, idx) => { + // Reset classes + card.classList.remove("active-slide", "exit-slide"); + + if (idx === carouselIndex) { + card.classList.add("active-slide"); + } else { + // Optional: Mark previous slide for exit animation if needed + // For simple fade, just lacking 'active-slide' hides it + } + }); +} // ... // ... inside renderPartyOverlay function ... @@ -353,24 +495,45 @@ function renderPartyOverlay(container) { const gms = game.users.filter(u => u.isGM); if (players.length === 0 && gms.length === 0) { - container.innerHTML = `
No Active Players or GM Found
`; return; } - let html = `
`; + let layoutMode = "standard"; + try { layoutMode = game.settings.get("dh-stream-overlay", "cardLayout"); } catch (e) { } - // 1. Render GM(s) + let carouselEnabled = false; + let carouselEffect = "fade"; + let carouselGMMode = "cycle"; + try { + carouselEnabled = game.settings.get("dh-stream-overlay", "carouselEnabled"); + carouselEffect = game.settings.get("dh-stream-overlay", "carouselEffect"); + carouselGMMode = game.settings.get("dh-stream-overlay", "carouselGMMode"); + } catch (e) { } + + const renderPips = (val, max, type) => { + let h = `
`; + for (let i = 1; i <= max; i++) { + h += `
`; + } + h += `
`; + return h; + }; + + // --- HTML GENERATORS --- + + // GM HTML Generator + let gmHtml = ""; gms.forEach(gm => { const img = gm.avatar || "icons/svg/mystery-man.svg"; - const name = gm.name; // GM just uses their user name usually + const name = gm.name; - html += ` -
-
+ gmHtml += ` +
+
-
+
Game Master
${name}
@@ -379,6 +542,9 @@ function renderPartyOverlay(container) { `; }); + // Player HTML Generator + let playerHtml = ""; + // 2. Render Players // Check setting availability let nameMode = "char-top"; @@ -407,14 +573,7 @@ function renderPartyOverlay(container) { showCommunity = game.settings.get("dh-stream-overlay", "showCommunity"); } catch (e) { console.warn("DH OVERLAY: Settings not ready yet, using default"); } - const renderPips = (val, max, type) => { - let h = `
`; - for (let i = 1; i <= max; i++) { - h += `
`; - } - h += `
`; - return h; - }; + players.forEach(user => { const actor = user.character; @@ -546,7 +705,7 @@ function renderPartyOverlay(container) { const spotlightHtml = isSpotlightRequesting ? `
` : ""; - html += ` + playerHtml += `
${spotlightHtml}
@@ -594,8 +753,45 @@ function renderPartyOverlay(container) { }); - html += `
`; - container.innerHTML = html; + // --- ASSEMBLY LOGIC --- + if (carouselEnabled) { + let finalHtml = ""; + let carouselClass = `carousel-mode effect-${carouselEffect}`; + + // GM Logic + if (carouselGMMode === "static") { + // Static grid for GM, Carousel for Players + // Wrap in a flex container + container.innerHTML = ` + `; + + } else if (carouselGMMode === "hidden") { + // Only Players in Carousel + container.innerHTML = `
${playerHtml}
`; + } else { + // "cycle" or default - Mixed + container.innerHTML = `
${gmHtml}${playerHtml}
`; + } + + } else { + // Standard Mode + container.innerHTML = `
${gmHtml}${playerHtml}
`; + } + + // Post-Render: Apply Carousel State immediately (if applicable) + if (carouselEnabled) { + // Find the carousel grid (could be the main one or secondary) + const grid = container.querySelector(".dh-party-grid.carousel-mode"); + if (grid) { + updateCarouselClasses(grid); + if (!carouselTimer) startCarousel(); + } + } else { + stopCarousel(); + } } // ============================================================================= @@ -612,17 +808,27 @@ Hooks.once("ready", async () => { }); // Fallback Init Trigger -document.addEventListener("DOMContentLoaded", () => { - if (window.location.pathname.includes("/stream")) { - setTimeout(async () => { - if (!overlayInitialized && document.body.classList.contains("stream")) { - console.warn("DH Stream Overlay | Fallback Init Triggered"); +// Fallback Init Trigger +if (window.location.pathname.includes("/stream")) { + const attemptInit = async (retries = 0) => { + if (overlayInitialized) return; + + // If game is ready, or purely maximizing retries (10 seconds), force init + if (game.ready || retries > 20) { + if (!overlayInitialized) { + console.warn(`DH Stream Overlay | Fallback Init Triggered (Ready: ${game.ready}, Retries: ${retries})`); overlayInitialized = true; await initStreamOverlay(); } - }, 1000); - } -}); + } else { + // Check again in 500ms + setTimeout(() => attemptInit(retries + 1), 500); + } + }; + + // Start the fallback poll + setTimeout(() => attemptInit(), 1000); +} // Menu Button Hook Hooks.on("renderDaggerheartMenu", (_app, html) => { @@ -633,7 +839,10 @@ Hooks.on("renderDaggerheartMenu", (_app, html) => { const button = document.createElement("button"); button.innerHTML = ` ${game.i18n.localize("DH_STREAM_OVERLAY.OpenStream")}`; button.style.width = "100%"; - button.onclick = () => window.open("/stream", "streamOverlay", "width=1280,height=720,menubar=no,toolbar=no,status=no"); + button.onclick = () => { + const path = window.location.pathname.replace(/\/game\/?$/, "/stream"); + window.open(path, "streamOverlay", "width=1280,height=720,menubar=no,toolbar=no,status=no"); + }; section.appendChild(button); container.appendChild(section); }); @@ -645,12 +854,20 @@ Hooks.on("renderDaggerheartMenu", (_app, html) => { async function initStreamOverlay() { console.log("DH Stream Overlay | Initializing Layout..."); + // 0a. WAIT FOR SETTINGS + // Ensure "init" hook has run so settings are registered. + // If not, we wait a moment. + if (!game.settings.settings.has("dh-stream-overlay.autoExpandChat")) { + console.warn("DH Stream Overlay | Settings not yet registered, waiting for init..."); + await new Promise(r => setTimeout(r, 500)); + } + // 0. WAIT FOR GAME DATA // The stream view might load faster than Foundry data. let dataRetries = 0; - while ((!game.users || !game.users.size) && dataRetries < 20) { - console.log(`DH Stream Overlay | Waiting for game.users... (${dataRetries}/20)`); - await new Promise(r => setTimeout(r, 500)); + while ((!game.users || !game.users.size) && dataRetries < 10) { + console.log(`DH Stream Overlay | Waiting for game.users... (${dataRetries}/10)`); + await new Promise(r => setTimeout(r, 200)); dataRetries++; } @@ -697,10 +914,11 @@ async function initStreamOverlay() { // Appending is safer for overlay z-index usually. body.appendChild(wrapper); + // 3. Move Chat Log // 3. Move Chat Log const retryChat = async () => { let attempts = 0; - while (attempts < 50) { // Try for longer (approx 10s) + while (attempts < 20) { // Reduced retries to avoid long load times // Try Standard DOM let chatLog = document.getElementById("chat-log"); @@ -723,9 +941,22 @@ async function initStreamOverlay() { // Force scroll to bottom chatLog.scrollTop = chatLog.scrollHeight; console.log("DH Stream Overlay | Chat Log Moved Successfully"); + + // --- FIX: EXISTING MESSAGES --- + // Manually strip inline styles from existing messages if auto-expand is OFF + const autoExpand = game.settings.get("dh-stream-overlay", "autoExpandChat"); + if (!autoExpand) { + const $log = $(chatLog); + // Remove .expanded class first + $log.find(".dice-roll.expanded, .damage-section.expanded, .target-section.expanded").removeClass("expanded"); + + // Strip inline styles + $log.find(".dice-tooltip, .dice-formula, .daggerheart-target-list, .evaluation-selection").css("display", ""); + $log.find(".dice-roll .dice-tooltip").css("display", ""); // Specificity check + } return; } - await new Promise(r => setTimeout(r, 200)); + await new Promise(r => setTimeout(r, 250)); attempts++; } console.warn("DH Stream Overlay | Chat Log NOT FOUND after retries"); @@ -749,9 +980,37 @@ async function initStreamOverlay() { }); // Scroll chat to bottom on new message - Hooks.on("renderChatMessageHTML", () => { + // Scroll chat to bottom on new message & Handle Auto-Expand Logic + Hooks.on("renderChatMessage", (message, html) => { + // Scroll const log = document.getElementById("chat-log"); if (log) log.scrollTop = log.scrollHeight; + + // Auto-Expand Logic (Override System Defaults if necessary) + // If Auto-Expand is DISABLED, we want to ensure tooltips don't have inline 'display: block' + // which the system might be adding by default. + try { + const autoExpand = game.settings.get("dh-stream-overlay", "autoExpandChat"); + if (!autoExpand) { + // WRAPPED IN TIMEOUT to beat system race conditions + setTimeout(() => { + // 1. Remove .expanded class to force collapse state, allowing manual toggle primarily + html.find(".dice-roll.expanded, .damage-section.expanded, .target-section.expanded").removeClass("expanded"); + + // 2. Remove inline display styles that force expansion (failsafe) + html.find(".dice-tooltip, .dice-formula, .daggerheart-target-list, .evaluation-selection").css("display", ""); + + // Also legacy support for non-jquery if appropriate (but standard hook passes jQuery) + if (html instanceof HTMLElement) { + html.querySelectorAll(".expanded").forEach(el => el.classList.remove("expanded")); + const nodes = html.querySelectorAll(".dice-tooltip, .dice-formula, .daggerheart-target-list, .evaluation-selection"); + nodes.forEach(n => n.style.display = ""); + } + }, 10); + } + } catch (e) { + console.warn("DH Stream Overlay | Error in renderChatMessage hook", e); + } }); // 6. Combat Tracker Handling @@ -817,6 +1076,8 @@ async function initStreamOverlay() { function injectStyles() { const useGreen = game.settings.get("dh-stream-overlay", "useGreenScreen"); + const autoExpand = game.settings.get("dh-stream-overlay", "autoExpandChat"); + const hideActions = game.settings.get("dh-stream-overlay", "hideChatActions"); const bgColor = useGreen ? "#00ff00" : "rgba(0, 0, 0, 0)"; const style = document.createElement("style"); @@ -938,12 +1199,22 @@ function injectStyles() { height: 100vh; background: transparent !important; /* Fully transparent for Chroma Key */ /* border-right: 2px solid rgba(255, 255, 255, 0.1) !important; Optional separator */ - pointer-events: auto; + pointer-events: auto !important; + z-index: 10000 !important; /* Force on top of everything */ position: relative; display: flex; flex-direction: column; overflow: hidden; } + /* GLOBAL INTERACTIVITY FORCE - ensuring chat is always clickable */ + #stream-chat-container #chat-log, + #stream-chat-container .chat-message, + #stream-chat-container .message-content, + #stream-chat-container .dice-roll, + #stream-chat-container button { + pointer-events: auto !important; + } + #stream-right-col { grid-column: 2; height: 100vh; @@ -1077,6 +1348,51 @@ function injectStyles() { /* Hide standard timestamps to save space if needed, or style them */ .message-timestamp { color: #888; font-size: 0.8em; } + /* Auto-Expand Chat Logic */ + ${autoExpand ? ` + .chat-message .message-content, + .chat-message .dice-roll, + .chat-message .dice-result, + .chat-message .dice-tooltip, + .chat-message .dice-formula { + display: block !important; + visibility: visible !important; + height: auto !important; + opacity: 1 !important; + } + + .dice-tooltip { display: block !important; } + ` : ` + /* Force collapse default state (allow JS toggling) via HIGH SPECIFICITY */ + #stream-chat-container .chat-message .dice-tooltip, + #stream-chat-container .chat-message .dice-formula, + #stream-chat-container .chat-message .daggerheart-target-list, + #stream-chat-container .chat-message .evaluation-selection, + #stream-chat-container .chat-message .dice-roll .dice-tooltip, + #stream-chat-container .chat-message .dice-result .dice-tooltip { + display: none !important; + } + + /* Allow manual interaction if the .expanded class is present */ + #stream-chat-container .chat-message .dice-roll.expanded .dice-tooltip, + #stream-chat-container .chat-message .dice-roll.expanded .dice-formula, + #stream-chat-container .chat-message .target-section.expanded .daggerheart-target-list, + #stream-chat-container .chat-message .damage-section.expanded .dice-formula, + #stream-chat-container .chat-message .dice-roll.expanded .dice-result .dice-tooltip { + display: block !important; + } + + /* FORCE INTERACTIVITY removed from here to be global */ + `} + + ${hideActions ? ` + .chat-message button, + .chat-message .chat-card-buttons, + .chat-message .card-buttons { + display: none !important; + } + ` : ''} + #stream-combat-container { position: absolute; top: 20px; right: 20px; @@ -1091,14 +1407,16 @@ function injectStyles() { padding: 5px; } + /* Party Overlay Cards - GRID RESTORED */ .dh-party-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(520px, 1fr)); align-items: start; /* Prevents cards from stretching vertically to match tallest */ - gap: 15px; + gap: 40px; width: 100%; - padding-right: 10px; + padding: 20px; + padding-right: 30px; } .dh-party-card { @@ -1260,7 +1578,120 @@ function injectStyles() { color: #333; } `; + + + style.innerHTML += ` + /* Vertical Layout */ + .dh-party-grid.layout-vertical { + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + align-items: start; + } + + .dh-party-grid.layout-vertical .dh-party-card { + flex-direction: column; + min-height: 400px; + } + + .dh-party-grid.layout-vertical .dh-card-img-wrapper { + width: 100%; + height: 250px; + border-right: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + .dh-party-grid.layout-vertical .dh-card-img { + object-position: top center; + } + + .dh-party-grid.layout-vertical .dh-card-content { + padding: 15px; + gap: 10px; + } + `; + + /* Carousel Mode */ + style.innerHTML += ` + /* Carousel Mode */ + .dh-party-grid.carousel-mode { + display: grid !important; + grid-template-columns: 1fr !important; + grid-template-rows: 1fr; + justify-items: center; + align-items: center; + } + + .dh-party-grid.carousel-mode .dh-party-card { + grid-area: 1 / 1 / -1 / -1; /* Stack on top of each other */ + opacity: 0; + visibility: hidden; + /* TRANSITION FIX: Delay visibility hide so it fades out first */ + transition: opacity 0.8s ease-in-out, transform 0.8s ease-in-out, visibility 0s linear 0.8s; + pointer-events: none; + width: 100%; + max-width: 600px; /* Constrain width in carousel */ + min-height: 120px; + } + + /* Active Slide */ + .dh-party-grid.carousel-mode .dh-party-card.active-slide { + opacity: 1; + visibility: visible; + /* Show immediately */ + transition: opacity 0.8s ease-in-out, transform 0.8s ease-in-out, visibility 0s linear; + pointer-events: auto; + z-index: 10; + transform: none; + } + + /* Effects */ + + /* Fade (Default behavior above) */ + + /* Slide */ + .dh-party-grid.carousel-mode.effect-slide .dh-party-card { + transform: translateX(50px); + } + .dh-party-grid.carousel-mode.effect-slide .dh-party-card.active-slide { + transform: translateX(0); + } + + /* Zoom */ + .dh-party-grid.carousel-mode.effect-zoom .dh-party-card { + transform: scale(0.9); + } + .dh-party-grid.carousel-mode.effect-zoom .dh-party-card.active-slide { + transform: scale(1); + } + + `; + + style.innerHTML += ` + /* Width Constraint for Vertical Carousel */ + .dh-party-grid.carousel-mode.layout-vertical .dh-party-card { + max-width: 350px !important; + } + + /* Static GM Wrapper */ + .dh-carousel-wrapper { + display: flex; + align-items: center; + width: 100%; + gap: 20px; + justify-content: center; + } + + .dh-static-gm { + flex: 0 0 auto; + /* Matches vertical card layout styling */ + width: 350px; + } + + .dh-static-gm .dh-party-card { + height: 100%; + max-width: 350px; + } + + `; + document.head.appendChild(style); } - -