PageSourceSearch

https://vanillasquad.com/assets/themes/apex/js/shop-enhancer.js?v1.2.13

js vanillasquad.com collected 2026-10-05 03:41:00 UTC 106,143 bytes, 2,601 lines download raw bytes

1// Shop Enhancer - Non-blocking individual features
2
3// Translation helper - reads from window.APEX_LANG.shop
4function _shopLang(key, fallback) {
5    var T = (window.APEX_LANG && window.APEX_LANG.shop) || {};
6    return T[key] !== undefined ? T[key] : fallback;
7}
8
9document.addEventListener("DOMContentLoaded", function () {
10    // Compute RGB CSS variables from hex/rgb theme colors (live update)
11    try {
12        function parseColorToRgb(cssVar) {
13            const val = getComputedStyle(document.documentElement)
14                .getPropertyValue(cssVar)
15                .trim();
16            if (!val) return null;
17
18            // Try hex
19            const hexMatch = val.match(/^#([0-9a-f]{3,8})$/i);
20            if (hexMatch) {
21                let hex = hexMatch[1];
22                if (hex.length === 3)
23                    hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
24                return [
25                    parseInt(hex.substring(0, 2), 16),
26                    parseInt(hex.substring(2, 4), 16),
27                    parseInt(hex.substring(4, 6), 16),
28                ];
29            }
30
31            // Try rgb/rgba
32            const rgbMatch = val.match(
33                /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/,
34            );
35            if (rgbMatch)
36                return [
37                    parseInt(rgbMatch[1]),
38                    parseInt(rgbMatch[2]),
39                    parseInt(rgbMatch[3]),
40                ];
41
42            return null;
43        }
44
45        function updateRgbVariables() {
46            const color5Rgb = parseColorToRgb("--color5");
47            const color2Rgb = parseColorToRgb("--color2");
48            if (color5Rgb)
49                document.documentElement.style.setProperty(
50                    "--color5-rgb",
51                    color5Rgb.join(", "),
52                );
53            if (color2Rgb)
54                document.documentElement.style.setProperty(
55                    "--color2-rgb",
56                    color2Rgb.join(", "),
57                );
58        }
59
60        // Initial computation
61        updateRgbVariables();
62
63        // Watch for style changes on <html> and all <style> tags for live editor updates
64        const styleObserver = new MutationObserver(updateRgbVariables);
65        styleObserver.observe(document.documentElement, {
66            attributes: true,
67            attributeFilter: ["style"],
68        });
69        document.querySelectorAll("style").forEach(function (s) {
70            styleObserver.observe(s, {
71                childList: true,
72                characterData: true,
73                subtree: true,
74            });
75        });
76        // Also watch for new <style> tags added to <head>
77        const headObserver = new MutationObserver(function (mutations) {
78            mutations.forEach(function (m) {
79                m.addedNodes.forEach(function (node) {
80                    if (node.nodeName === "STYLE") {
81                        styleObserver.observe(node, {
82                            childList: true,
83                            characterData: true,
84                            subtree: true,
85                        });
86                        updateRgbVariables();
87                    }
88                });
89            });
90        });
91        headObserver.observe(document.head, { childList: true });
92    } catch (error) {
93        console.warn("Shop Enhancer - RGB Variables: ", error);
94    }
95
96    // Add global animations at startup
97    if (!document.getElementById("shine-animation")) {
98        const style = document.createElement("style");
99        style.id = "shine-animation";
100        style.textContent = `
101      @keyframes shine {
102        0% { transform: translateX(-100%); }
103        30% { transform: translateX(100%); }
104        100% { transform: translateX(100%); }
105      }
106    `;
107        document.head.appendChild(style);
108    }
109
110    // Feature 1: Category Description Card Enhancer
111    try {
112        const categoryDescriptionCards =
113            document.querySelectorAll(".card-body.pb-1");
114
115        categoryDescriptionCards.forEach(function (cardBody) {
116            // Remove pb-1 class
117            cardBody.classList.remove("pb-1");
118
119            // Add mb-0 to all <p> elements inside this card-body
120            const paragraphs = cardBody.querySelectorAll("p");
121            paragraphs.forEach(function (p) {
122                p.classList.add("mb-0");
123            });
124        });
125    } catch (error) {
126        console.warn("Shop Enhancer - Category Description: ", error);
127    }
128
129    // Feature 2: Transform d-grid to card shop-profile-card
130    try {
131        const profileGrids = document.querySelectorAll(".d-grid.gap-2.mb-4");
132
133        profileGrids.forEach(function (grid) {
134            // Check if it contains the profile content (has d-flex justify-content-center)
135            const hasProfileContent = grid.querySelector(
136                ".d-flex.justify-content-center.mb-3",
137            );
138
139            if (hasProfileContent) {
140                grid.classList.remove("d-grid");
141                grid.classList.add("card", "shop-profile-card");
142            }
143        });
144    } catch (error) {
145        console.warn("Shop Enhancer - Profile Card Transform: ", error);
146    }
147
148    // Feature 2b: Move profile card above categories list
149    try {
150        const profileCard = document.querySelector(".shop-profile-card");
151        const categoryList = document.querySelector(".list-group.mb-4");
152
153        if (
154            profileCard &&
155            categoryList &&
156            categoryList.parentNode === profileCard.parentNode
157        ) {
158            // Insert profile card before the category list
159            categoryList.parentNode.insertBefore(profileCard, categoryList);
160        }
161    } catch (error) {
162        console.warn("Shop Enhancer - Profile Card Position: ", error);
163    }
164
165    // Feature 3: Enhance shop-profile-card design
166    try {
167        const profileCards = document.querySelectorAll(".shop-profile-card");
168
169        profileCards.forEach(function (card) {
170            // Remove gap-2 and add proper structure
171            card.classList.remove("gap-2");
172            card.classList.add("p-0", "overflow-hidden");
173
174            // Get user info section
175            const userInfoSection = card.querySelector(
176                ".d-flex.justify-content-center.mb-3",
177            );
178            if (userInfoSection) {
179                // Create card header for user info
180                userInfoSection.classList.remove(
181                    "justify-content-center",
182                    "mb-3",
183                );
184                userInfoSection.classList.add("card-header", "p-3");
185
186                // Style the avatar
187                const avatar = userInfoSection.querySelector("img");
188                if (avatar) {
189                    avatar.classList.add(
190                        "rounded-circle",
191                        "border",
192                        "border-2",
193                        "border-light",
194                    );
195                    avatar.classList.remove("rounded", "me-3");
196                    avatar.classList.add("me-2");
197                    avatar.style.width = "56px";
198                    avatar.style.height = "56px";
199                    avatar.style.objectFit = "cover";
200                }
201
202                // Style the username container
203                const userTextContainer =
204                    userInfoSection.querySelector(".align-self-center");
205                if (userTextContainer) {
206                    userTextContainer.classList.remove("align-self-center");
207                    userTextContainer.classList.add(
208                        "d-flex",
209                        "flex-column",
210                        "gap-1",
211                        "justify-content-center",
212                    );
213
214                    // Style the username
215                    const username = userTextContainer.querySelector("h3");
216                    if (username) {
217                        username.classList.add("fw-bold");
218                        username.style.fontSize = "1.1rem";
219                    }
220
221                    // Transform money text to badge with gaming coin style
222                    const moneyParagraph = userTextContainer.querySelector("p");
223                    if (moneyParagraph) {
224                        const moneyText = moneyParagraph.textContent.trim();
225
226                        // Extract only the amount (remove "Money: " or any "Label: " pattern)
227                        const amountOnly = moneyText
228                            .replace(/^[^:]+:\s*/, "")
229                            .trim();
230
231                        // Create badge element
232                        const badge = document.createElement("span");
233                        badge.className = "badge rounded-pill px-2 py-1";
234                        badge.style.cssText = `
235              background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
236              color: #000;
237              font-weight: 700;
238              font-size: 0.75rem;
239              text-shadow: 0 1px 2px rgba(255, 255, 255, 0.3);
240              box-shadow: 
241                0 2px 8px rgba(255, 215, 0, 0.3),
242                inset 0 1px 0 rgba(255, 255, 255, 0.4),
243                inset 0 -1px 0 rgba(0, 0, 0, 0.2);
244              border: 1.5px solid rgba(255, 255, 255, 0.3);
245              position: relative;
246              overflow: hidden;
247            `;
248                        badge.innerHTML = `<i class="bi bi-coin me-1" style="filter: drop-shadow(0 1px 1px rgba(0,0,0,0.3)); font-size: 0.85rem;"></i>${amountOnly}`;
249
250                        // Add shine animation
251                        const shine = document.createElement("span");
252                        shine.style.cssText = `
253              position: absolute;
254              top: 0;
255              left: 0;
256              width: 100%;
257              height: 100%;
258              background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.4) 50%, transparent 100%);
259              animation: shine 3s infinite;
260              pointer-events: none;
261            `;
262                        badge.appendChild(shine);
263
264                        // Replace the <p> with the badge
265                        moneyParagraph.replaceWith(badge);
266                    }
267                }
268            }
269
270            // Create card body for buttons with row grid
271            const cardBody = document.createElement("div");
272            cardBody.className = "card-body p-3";
273
274            // Create row for buttons
275            const buttonRow = document.createElement("div");
276            buttonRow.className = "row g-2";
277
278            // Move all buttons and form to card body
279            const buttons = card.querySelectorAll(":scope > .btn");
280            const form = card.querySelector(":scope > form");
281
282            buttons.forEach(function (btn) {
283                // Create col wrapper
284                const col = document.createElement("div");
285                col.className = "col";
286
287                // Keep btn-primary style, just add flex for icon alignment
288                btn.classList.add("w-100");
289
290                col.appendChild(btn);
291                buttonRow.appendChild(col);
292            });
293
294            cardBody.appendChild(buttonRow);
295
296            // Style and move logout button to the same row
297            if (form) {
298                const logoutBtn = form.querySelector("button");
299                if (logoutBtn) {
300                    // Create col wrapper for logout button
301                    const col = document.createElement("div");
302                    col.className = "col";
303
304                    // Style with red gradient matching other buttons
305                    logoutBtn.classList.remove("btn-secondary", "w-100");
306                    logoutBtn.classList.add("w-100");
307                    logoutBtn.style.cssText = `
308            background: linear-gradient(135deg, #ff6b6b 0%, #dc3545 100%) !important;
309            color: #fff !important;
310            font-weight: 800;
311            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
312            box-shadow: 
313              0 4px 15px rgba(220, 53, 69, 0.4),
314              inset 0 2px 0 rgba(255, 255, 255, 0.2),
315              inset 0 -2px 0 rgba(0, 0, 0, 0.2);
316            border: 2px solid rgba(255, 255, 255, 0.2) !important;
317            position: relative;
318            overflow: hidden;
319          `;
320
321                    // Style the icon
322                    const icon = logoutBtn.querySelector("i");
323                    if (icon) {
324                        icon.style.cssText =
325                            "filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3)); position: relative; z-index: 2;";
326                    }
327
328                    // Add shine animation
329                    const shine = document.createElement("span");
330                    shine.style.cssText = `
331            position: absolute;
332            top: 0;
333            left: 0;
334            width: 100%;
335            height: 100%;
336            background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%);
337            animation: shine 3s infinite;
338            pointer-events: none;
339            z-index: 1;
340          `;
341                    logoutBtn.appendChild(shine);
342
343                    // Wrap text content to add z-index
344                    logoutBtn.childNodes.forEach((node) => {
345                        if (
346                            node.nodeType === Node.TEXT_NODE &&
347                            node.textContent.trim()
348                        ) {
349                            const span = document.createElement("span");
350                            span.style.cssText =
351                                "position: relative; z-index: 2;";
352                            span.textContent = node.textContent;
353                            node.replaceWith(span);
354                        }
355                    });
356
357                    // Move form into the col (keep form for CSRF)
358                    form.classList.remove("text-center");
359                    form.style.cssText = "margin: 0; display: contents;";
360                    col.appendChild(form);
361                    buttonRow.appendChild(col);
362                }
363            }
364
365            // Append card body to card
366            card.appendChild(cardBody);
367        });
368    } catch (error) {
369        console.warn("Shop Enhancer - Profile Card Design: ", error);
370    }
371
372    // Feature 4: Style Login Button with gold coin design
373    try {
374        // Find login button by looking for the login icon
375        const loginButton = Array.from(document.querySelectorAll("a.btn")).find(
376            (btn) => btn.querySelector("i.bi-box-arrow-in-right"),
377        );
378
379        if (loginButton) {
380            loginButton.style.cssText = `
381        background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%) !important;
382        color: #000 !important;
383        font-weight: 800;
384        text-shadow: 0 1px 2px rgba(255, 255, 255, 0.3);
385        box-shadow: 
386          0 4px 15px rgba(255, 215, 0, 0.4),
387          inset 0 2px 0 rgba(255, 255, 255, 0.4),
388          inset 0 -2px 0 rgba(0, 0, 0, 0.2);
389        border: 2px solid rgba(255, 255, 255, 0.3) !important;
390        position: relative;
391        overflow: hidden;
392      `;
393
394            // Style the icon
395            const icon = loginButton.querySelector("i");
396            if (icon) {
397                icon.style.cssText =
398                    "filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3)); position: relative; z-index: 2;";
399            }
400
401            // Add shine animation
402            const shine = document.createElement("span");
403            shine.style.cssText = `
404        position: absolute;
405        top: 0;
406        left: 0;
407        width: 100%;
408        height: 100%;
409        background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.4) 50%, transparent 100%);
410        animation: shine 3s infinite;
411        pointer-events: none;
412        z-index: 1;
413      `;
414            loginButton.appendChild(shine);
415
416            // Wrap text content to add z-index
417            loginButton.childNodes.forEach((node) => {
418                if (
419                    node.nodeType === Node.TEXT_NODE &&
420                    node.textContent.trim()
421                ) {
422                    const span = document.createElement("span");
423                    span.style.cssText = "position: relative; z-index: 2;";
424                    span.textContent = node.textContent;
425                    node.replaceWith(span);
426                }
427            });
428        }
429    } catch (error) {
430        console.warn("Shop Enhancer - Login Button Style: ", error);
431    }
432
433    // Feature 4b: Style Credit Button with gold coin design
434    try {
435        // Find credit button by looking for the credit-card icon
436        const creditButton = Array.from(
437            document.querySelectorAll("a.btn"),
438        ).find((btn) => btn.querySelector("i.bi-credit-card"));
439
440        if (creditButton) {
441            creditButton.style.cssText = `
442        background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%) !important;
443        color: #000 !important;
444        font-weight: 800;
445        text-shadow: 0 1px 2px rgba(255, 255, 255, 0.3);
446        box-shadow: 
447          0 4px 15px rgba(255, 215, 0, 0.4),
448          inset 0 2px 0 rgba(255, 255, 255, 0.4),
449          inset 0 -2px 0 rgba(0, 0, 0, 0.2);
450        border: 2px solid rgba(255, 255, 255, 0.3) !important;
451        position: relative;
452        overflow: hidden;
453      `;
454
455            // Style the icon
456            const icon = creditButton.querySelector("i");
457            if (icon) {
458                icon.style.cssText =
459                    "filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3)); position: relative; z-index: 2;";
460            }
461
462            // Add shine animation
463            const shine = document.createElement("span");
464            shine.style.cssText = `
465        position: absolute;
466        top: 0;
467        left: 0;
468        width: 100%;
469        height: 100%;
470        background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.4) 50%, transparent 100%);
471        animation: shine 3s infinite;
472        pointer-events: none;
473        z-index: 1;
474      `;
475            creditButton.appendChild(shine);
476
477            // Wrap text content to add z-index
478            creditButton.childNodes.forEach((node) => {
479                if (
480                    node.nodeType === Node.TEXT_NODE &&
481                    node.textContent.trim()
482                ) {
483                    const span = document.createElement("span");
484                    span.style.cssText = "position: relative; z-index: 2;";
485                    span.textContent = node.textContent;
486                    node.replaceWith(span);
487                }
488            });
489        }
490    } catch (error) {
491        console.warn("Shop Enhancer - Credit Button Style: ", error);
492    }
493
494    // Feature 4c: Style Cart Button with same effects but primary colors
495    try {
496        // Find cart button by looking for the cart icon
497        const cartButton = Array.from(document.querySelectorAll("a.btn")).find(
498            (btn) => btn.querySelector("i.bi-cart"),
499        );
500
501        if (cartButton) {
502            cartButton.style.cssText = `
503        background: linear-gradient(135deg, var(--color6) 0%, var(--color5) 100%) !important;
504        color: var(--color2) !important;
505        font-weight: 800;
506        text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
507        box-shadow: 
508          0 4px 15px rgba(var(--color5-rgb, 138, 43, 226), 0.4),
509          inset 0 2px 0 rgba(255, 255, 255, 0.2),
510          inset 0 -2px 0 rgba(0, 0, 0, 0.2);
511        border: 2px solid rgba(255, 255, 255, 0.2) !important;
512        position: relative;
513        overflow: hidden;
514      `;
515
516            // Style the icon
517            const icon = cartButton.querySelector("i");
518            if (icon) {
519                icon.style.cssText =
520                    "filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3)); position: relative; z-index: 2;";
521            }
522
523            // Add shine animation
524            const shine = document.createElement("span");
525            shine.style.cssText = `
526        position: absolute;
527        top: 0;
528        left: 0;
529        width: 100%;
530        height: 100%;
531        background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%);
532        animation: shine 3s infinite;
533        pointer-events: none;
534        z-index: 1;
535      `;
536            cartButton.appendChild(shine);
537
538            // Wrap text content to add z-index
539            cartButton.childNodes.forEach((node) => {
540                if (
541                    node.nodeType === Node.TEXT_NODE &&
542                    node.textContent.trim()
543                ) {
544                    const span = document.createElement("span");
545                    span.style.cssText = "position: relative; z-index: 2;";
546                    span.textContent = node.textContent;
547                    node.replaceWith(span);
548                }
549            });
550        }
551    } catch (error) {
552        console.warn("Shop Enhancer - Cart Button Style: ", error);
553    }
554
555    // Feature 5: Enhance Goal Progress Bar with gaming style
556    try {
557        // Find goal card by looking for bi-graph-up icon in card-header
558        const goalCard = Array.from(document.querySelectorAll(".card")).find(
559            (card) => card.querySelector(".card-header i.bi-graph-up"),
560        );
561
562        if (goalCard) {
563            const progressContainer = goalCard.querySelector(".progress");
564            const progressBar = goalCard.querySelector(".progress-bar");
565            const goalText = goalCard.querySelector(".card-text");
566
567            if (progressContainer && progressBar) {
568                // Cap the visual progress at 100% so over-completed goals still fill the bar cleanly.
569                const rawProgressValue =
570                    parseFloat(progressBar.getAttribute("aria-valuenow")) || 0;
571                const progressValue = Math.max(
572                    0,
573                    Math.min(rawProgressValue, 100),
574                );
575
576                // Style progress container - taller for text inside
577                progressContainer.style.cssText = `
578          height: 32px;
579          background: rgba(0, 0, 0, 0.3);
580          border-radius: 16px;
581          border: 2px solid rgba(255, 255, 255, 0.1);
582          overflow: hidden;
583          position: relative;
584          box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
585        `;
586
587                // Style progress bar with theme gradient
588                progressBar.classList.remove(
589                    "progress-bar-striped",
590                    "progress-bar-animated",
591                );
592                progressBar.style.cssText = `
593          width: ${progressValue}%;
594          background: linear-gradient(90deg, var(--color6) 0%, var(--color5) 100%);
595          border-radius: 14px;
596          position: relative;
597          overflow: hidden;
598          height: 100%;
599          min-width: 50px;
600          display: flex;
601          align-items: center;
602          justify-content: center;
603          box-shadow: 
604            0 0 20px var(--color5),
605            0 0 40px rgba(var(--color5-rgb, 138, 43, 226), 0.3);
606          transition: width 1s ease-in-out;
607        `;
608                progressBar.setAttribute("aria-valuenow", progressValue);
609
610                // Add animated stripes using background-size for seamless loop
611                const stripes = document.createElement("div");
612                stripes.style.cssText = `
613          position: absolute;
614          top: 0;
615          left: 0;
616          right: 0;
617          bottom: 0;
618          background-image: linear-gradient(
619            -45deg,
620            transparent 25%,
621            rgba(255, 255, 255, 0.1) 25%,
622            rgba(255, 255, 255, 0.1) 50%,
623            transparent 50%,
624            transparent 75%,
625            rgba(255, 255, 255, 0.1) 75%,
626            rgba(255, 255, 255, 0.1) 100%
627          );
628          background-size: 28px 28px;
629          animation: moveStripes 0.7s linear infinite;
630        `;
631                progressBar.appendChild(stripes);
632
633                // Add shine effect
634                const shine = document.createElement("div");
635                shine.style.cssText = `
636          position: absolute;
637          top: 0;
638          left: 0;
639          right: 0;
640          height: 50%;
641          background: linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, transparent 100%);
642          border-radius: 14px 14px 0 0;
643          pointer-events: none;
644        `;
645                progressBar.appendChild(shine);
646
647                // Remove goal text
648                if (goalText) {
649                    goalText.remove();
650                }
651
652                // Add percentage text inside the progress bar
653                const percentText = document.createElement("span");
654                percentText.style.cssText = `
655          position: relative;
656          z-index: 2;
657          color: #fff;
658          font-size: 0.85rem;
659          font-weight: 800;
660          text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
661          letter-spacing: 0.5px;
662        `;
663                percentText.textContent = `${progressValue}%`;
664                progressBar.appendChild(percentText);
665
666                // Add CSS animations if not already added
667                if (!document.getElementById("goal-animations")) {
668                    const style = document.createElement("style");
669                    style.id = "goal-animations";
670                    style.textContent = `
671            @keyframes moveStripes {
672              from { background-position: 0 0; }
673              to { background-position: 28px 0; }
674            }
675          `;
676                    document.head.appendChild(style);
677                }
678            }
679        }
680    } catch (error) {
681        console.warn("Shop Enhancer - Goal Progress Bar: ", error);
682    }
683
684    // Feature 6: Enhance Shop Login Page Layout
685    try {
686        // Check if we're on the shop login page (has #shop and login form)
687        const shopContainer = document.querySelector("#shop");
688        const loginForm = document.querySelector(
689            'form[action*="shop"][action*="login"]',
690        );
691
692        if (shopContainer && loginForm) {
693            const loginCard = loginForm.closest(".card");
694            const contentCol = loginCard?.closest(".col-lg-9");
695
696            if (loginCard && contentCol) {
697                // Remove text-center from col and add centering
698                contentCol.classList.remove("text-center");
699                contentCol.classList.add(
700                    "d-flex",
701                    "align-items-center",
702                    "justify-content-center",
703                );
704
705                // Style the card with max-width for better centering
706                loginCard.style.maxWidth = "450px";
707                loginCard.style.width = "100%";
708
709                // Create styled card header with icon
710                const title = shopContainer.querySelector("h1");
711                if (title) {
712                    const cardHeader = document.createElement("div");
713                    cardHeader.className = "card-header text-center py-4";
714
715                    // Add large icon
716                    const iconDiv = document.createElement("div");
717                    iconDiv.className = "mb-3";
718                    iconDiv.innerHTML =
719                        '<i class="bi bi-person-circle" style="font-size: 3rem; opacity: 0.8;"></i>';
720                    cardHeader.appendChild(iconDiv);
721
722                    // Move and style title
723                    title.classList.add("mb-0", "h4");
724                    cardHeader.appendChild(title);
725
726                    loginCard.insertBefore(cardHeader, loginCard.firstChild);
727                }
728
729                // Style card body
730                const cardBody = loginCard.querySelector(".card-body");
731                if (cardBody) {
732                    cardBody.classList.add("p-4");
733                }
734
735                // Make form full width
736                loginForm.classList.remove("col-md-6", "mx-auto");
737                loginForm.style.maxWidth = "none";
738
739                // Style input group with icon
740                const inputGroup = loginForm.querySelector(".mb-3");
741                const input = loginForm.querySelector("input.form-control");
742                const label = loginForm.querySelector(".form-label");
743
744                if (inputGroup && input) {
745                    inputGroup.classList.remove("mb-3");
746                    inputGroup.classList.add("mb-4");
747
748                    // Create input group with icon
749                    const inputWrapper = document.createElement("div");
750                    inputWrapper.className = "input-group";
751
752                    const inputIcon = document.createElement("span");
753                    inputIcon.className = "input-group-text";
754                    inputIcon.innerHTML = '<i class="bi bi-person"></i>';
755
756                    // Style input
757                    input.classList.add("py-2");
758
759                    // Wrap input
760                    input.parentNode.insertBefore(inputWrapper, input);
761                    inputWrapper.appendChild(inputIcon);
762                    inputWrapper.appendChild(input);
763
764                    // Move error message outside of input-group
765                    const errorSpan =
766                        loginForm.querySelector(".invalid-feedback");
767                    if (errorSpan) {
768                        inputWrapper.parentNode.insertBefore(
769                            errorSpan,
770                            inputWrapper.nextSibling,
771                        );
772                    }
773                }
774
775                // Style button
776                const submitBtn = loginForm.querySelector(
777                    'button[type="submit"]',
778                );
779                if (submitBtn) {
780                    submitBtn.classList.remove("mx-auto");
781                    submitBtn.classList.add("w-100", "py-2", "mt-2");
782                }
783            }
784        }
785    } catch (error) {
786        console.warn("Shop Enhancer - Login Page Layout: ", error);
787    }
788
789    // Feature 7: Enhance Shop Modal Footer Design
790    try {
791        // Use MutationObserver to catch modals when they appear
792        const observer = new MutationObserver(function (mutations) {
793            mutations.forEach(function (mutation) {
794                mutation.addedNodes.forEach(function (node) {
795                    if (node.nodeType === Node.ELEMENT_NODE) {
796                        const modal =
797                            node.closest?.(".modal") ||
798                            (node.classList?.contains("modal") ? node : null);
799                        if (modal) {
800                            enhanceModalFooter(modal);
801                        }
802                    }
803                });
804            });
805        });
806
807        observer.observe(document.body, { childList: true, subtree: true });
808
809        // Also enhance any already existing modals
810        document.querySelectorAll(".modal").forEach(enhanceModalFooter);
811
812        function enhanceModalFooter(modal) {
813            const footer = modal.querySelector(".modal-footer");
814            if (!footer || footer.dataset.enhanced) return;
815            footer.dataset.enhanced = "true";
816
817            // Style the footer - wrap-friendly layout
818            footer.style.cssText = `
819        background: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.15) 100%);
820        border-top: 1px solid rgba(255, 255, 255, 0.08);
821        padding: 1.25rem 1.75rem;
822        display: flex;
823        align-items: center;
824        justify-content: space-between;
825        flex-wrap: wrap;
826        gap: 0.75rem;
827      `;
828
829            // Style the price display
830            const priceSpan = footer.querySelector("span.flex-md-fill");
831            if (priceSpan) {
832                priceSpan.classList.remove("flex-md-fill", "font-weight-bold");
833                priceSpan.style.cssText = `
834          display: inline-flex;
835          align-items: center;
836          gap: 0.5rem;
837          background: linear-gradient(135deg, rgba(var(--color5-rgb, 138, 43, 226), 0.15) 0%, rgba(var(--color5-rgb, 138, 43, 226), 0.05) 100%);
838          border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.3);
839          border-radius: 10px;
840          padding: 0.5rem 1rem;
841          font-weight: 800;
842          font-size: 1.15rem;
843          font-family: 'Lexend', sans-serif;
844          color: var(--color2);
845          position: relative;
846          overflow: hidden;
847        `;
848
849                // Add coin icon before price
850                const priceIcon = document.createElement("i");
851                priceIcon.className = "bi bi-coin";
852                priceIcon.style.cssText = `
853          color: var(--color6);
854          font-size: 1.2rem;
855          filter: drop-shadow(0 0 4px rgba(var(--color5-rgb, 138, 43, 226), 0.5));
856        `;
857                priceSpan.insertBefore(priceIcon, priceSpan.firstChild);
858
859                // Style the discount (del) element if present
860                const discount = priceSpan.querySelector("del");
861                if (discount) {
862                    discount.style.cssText = `
863            color: rgba(var(--color2), 0.4);
864            opacity: 0.5;
865            font-size: 0.85rem;
866            font-weight: 400;
867          `;
868                }
869
870                // Add subtle shimmer on the price tag
871                const priceShine = document.createElement("span");
872                priceShine.style.cssText = `
873          position: absolute;
874          top: 0;
875          left: 0;
876          width: 100%;
877          height: 100%;
878          background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.08) 50%, transparent 100%);
879          animation: shine 4s infinite;
880          pointer-events: none;
881        `;
882                priceSpan.appendChild(priceShine);
883            }
884
885            // Style all buttons in the footer
886            const buttons = footer.querySelectorAll(".btn");
887            buttons.forEach(function (btn) {
888                // Determine button type
889                const isBuy = btn.querySelector("i.bi-cart-plus");
890                const isRemove = btn.querySelector("i.bi-cart-x");
891
892                if (isBuy) {
893                    // Buy/Add to cart button - themed gradient
894                    btn.style.cssText = `
895            background: linear-gradient(135deg, var(--color6) 0%, var(--color5) 100%) !important;
896            color: var(--color2) !important;
897            font-weight: 800;
898            font-size: 0.95rem;
899            padding: 0.625rem 1.5rem;
900            border-radius: 10px;
901            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
902            box-shadow: 
903              0 4px 15px rgba(var(--color5-rgb, 138, 43, 226), 0.4),
904              inset 0 2px 0 rgba(var(--color2-rgb, 255, 255, 255), 0.2),
905              inset 0 -2px 0 rgba(0, 0, 0, 0.2);
906            border: 2px solid rgba(var(--color2-rgb, 255, 255, 255), 0.15) !important;
907            position: relative;
908            overflow: hidden;
909            transition: all 0.3s ease;
910            text-transform: uppercase;
911            font-family: 'Lexend', sans-serif;
912            display: inline-flex;
913            align-items: center;
914            gap: 0.5rem;
915          `;
916
917                    // Add hover
918                    btn.addEventListener("mouseenter", function () {
919                        this.style.transform = "translateY(-2px) scale(1.03)";
920                        this.style.boxShadow = `
921              0 6px 25px rgba(var(--color5-rgb, 138, 43, 226), 0.5),
922              inset 0 2px 0 rgba(var(--color2-rgb, 255, 255, 255), 0.2),
923              inset 0 -2px 0 rgba(0, 0, 0, 0.2)
924            `;
925                    });
926                    btn.addEventListener("mouseleave", function () {
927                        this.style.transform = "";
928                        this.style.boxShadow = `
929              0 4px 15px rgba(var(--color5-rgb, 138, 43, 226), 0.4),
930              inset 0 2px 0 rgba(var(--color2-rgb, 255, 255, 255), 0.2),
931              inset 0 -2px 0 rgba(0, 0, 0, 0.2)
932            `;
933                    });
934
935                    // Add shine
936                    const btnShine = document.createElement("span");
937                    btnShine.style.cssText = `
938            position: absolute;
939            top: 0;
940            left: 0;
941            width: 100%;
942            height: 100%;
943            background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.25) 50%, transparent 100%);
944            animation: shine 3s infinite;
945            pointer-events: none;
946            z-index: 1;
947          `;
948                    btn.appendChild(btnShine);
949
950                    // Z-index text/icon
951                    const icon = btn.querySelector("i");
952                    if (icon)
953                        icon.style.cssText =
954                            "position: relative; z-index: 2; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));";
955                    btn.childNodes.forEach((n) => {
956                        if (
957                            n.nodeType === Node.TEXT_NODE &&
958                            n.textContent.trim()
959                        ) {
960                            const s = document.createElement("span");
961                            s.style.cssText = "position: relative; z-index: 2;";
962                            s.textContent = n.textContent;
963                            n.replaceWith(s);
964                        }
965                    });
966                } else if (isRemove) {
967                    // Remove from cart - red style
968                    btn.style.cssText = `
969            background: linear-gradient(135deg, var(--color6) 0%, var(--color5) 100%) !important;
970            color: var(--color2) !important;
971            font-weight: 800;
972            font-size: 0.95rem;
973            padding: 0.625rem 1.5rem;
974            border-radius: 10px;
975            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
976            box-shadow: 
977              0 4px 15px rgba(var(--color5-rgb, 138, 43, 226), 0.4),
978              inset 0 2px 0 rgba(var(--color2-rgb, 255, 255, 255), 0.2),
979              inset 0 -2px 0 rgba(0, 0, 0, 0.2);
980            border: 2px solid rgba(var(--color2-rgb, 255, 255, 255), 0.15) !important;
981            position: relative;
982            overflow: hidden;
983            transition: all 0.3s ease;
984            text-transform: uppercase;
985            font-family: 'Lexend', sans-serif;
986            display: inline-flex;
987            align-items: center;
988            gap: 0.5rem;
989          `;
990
991                    btn.addEventListener("mouseenter", function () {
992                        this.style.transform = "translateY(-2px) scale(1.03)";
993                    });
994                    btn.addEventListener("mouseleave", function () {
995                        this.style.transform = "";
996                    });
997                } else {
998                    // Other buttons (manage, subscribe, login)
999                    btn.style.cssText = `
1000            background: linear-gradient(135deg, var(--color6) 0%, var(--color5) 100%) !important;
1001            color: var(--color2) !important;
1002            font-weight: 800;
1003            padding: 0.625rem 1.5rem;
1004            border-radius: 10px;
1005            border: 2px solid rgba(var(--color2-rgb, 255, 255, 255), 0.15) !important;
1006            transition: all 0.3s ease;
1007            text-transform: uppercase;
1008            font-family: 'Lexend', sans-serif;
1009          `;
1010                    btn.addEventListener("mouseenter", function () {
1011                        this.style.transform = "translateY(-2px) scale(1.03)";
1012                    });
1013                    btn.addEventListener("mouseleave", function () {
1014                        this.style.transform = "";
1015                    });
1016                }
1017            });
1018
1019            // Restyle the buy form layout
1020            const buyForm = footer.querySelector("form.row");
1021            if (buyForm) {
1022                buyForm.classList.remove(
1023                    "row",
1024                    "row-cols-lg-auto",
1025                    "g-0",
1026                    "gy-2",
1027                );
1028                buyForm.style.cssText = `
1029          display: flex;
1030          align-items: center;
1031          gap: 0.75rem;
1032          flex-wrap: wrap;
1033        `;
1034            }
1035
1036            // Transform quantity input into styled +/- counter
1037            const quantityInput = footer.querySelector("input#quantity");
1038            if (quantityInput) {
1039                const min = parseInt(quantityInput.min) || 1;
1040                const max = parseInt(quantityInput.max) || 99;
1041                const parentDiv =
1042                    quantityInput.closest(".mx-3") ||
1043                    quantityInput.parentElement;
1044                const label = footer.querySelector('label[for="quantity"]');
1045
1046                // Create the counter container
1047                const counter = document.createElement("div");
1048                counter.style.cssText = `
1049          display: inline-flex;
1050          align-items: center;
1051          background: rgba(0, 0, 0, 0.25);
1052          border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.3);
1053          border-radius: 10px;
1054          overflow: hidden;
1055          height: 40px;
1056        `;
1057
1058                // Minus button
1059                const minusBtn = document.createElement("button");
1060                minusBtn.type = "button";
1061                minusBtn.innerHTML =
1062                    '<i class="bi bi-dash" style="margin:0;"></i>';
1063                minusBtn.style.cssText = `
1064          width: 36px;
1065          height: 100%;
1066          border: none;
1067          background: rgba(var(--color5-rgb, 138, 43, 226), 0.2);
1068          color: var(--color2);
1069          font-size: 1.1rem;
1070          cursor: pointer;
1071          display: flex;
1072          align-items: center;
1073          justify-content: center;
1074          transition: background 0.2s;
1075          padding: 0;
1076        `;
1077                minusBtn.addEventListener("mouseenter", function () {
1078                    this.style.background =
1079                        "rgba(var(--color5-rgb, 138, 43, 226), 0.4)";
1080                });
1081                minusBtn.addEventListener("mouseleave", function () {
1082                    this.style.background =
1083                        "rgba(var(--color5-rgb, 138, 43, 226), 0.2)";
1084                });
1085
1086                // Display value
1087                const display = document.createElement("span");
1088                display.textContent = quantityInput.value || "1";
1089                display.style.cssText = `
1090          min-width: 40px;
1091          text-align: center;
1092          font-weight: 800;
1093          font-size: 1rem;
1094          font-family: 'Lexend', sans-serif;
1095          color: var(--color2);
1096          padding: 0 4px;
1097          user-select: none;
1098        `;
1099
1100                // Plus button
1101                const plusBtn = document.createElement("button");
1102                plusBtn.type = "button";
1103                plusBtn.innerHTML =
1104                    '<i class="bi bi-plus" style="margin:0;"></i>';
1105                plusBtn.style.cssText = `
1106          width: 36px;
1107          height: 100%;
1108          border: none;
1109          background: rgba(var(--color5-rgb, 138, 43, 226), 0.2);
1110          color: var(--color2);
1111          font-size: 1.1rem;
1112          cursor: pointer;
1113          display: flex;
1114          align-items: center;
1115          justify-content: center;
1116          transition: background 0.2s;
1117          padding: 0;
1118        `;
1119                plusBtn.addEventListener("mouseenter", function () {
1120                    this.style.background =
1121                        "rgba(var(--color5-rgb, 138, 43, 226), 0.4)";
1122                });
1123                plusBtn.addEventListener("mouseleave", function () {
1124                    this.style.background =
1125                        "rgba(var(--color5-rgb, 138, 43, 226), 0.2)";
1126                });
1127
1128                // Wire up logic
1129                minusBtn.addEventListener("click", function () {
1130                    let val = parseInt(quantityInput.value) || 1;
1131                    if (val > min) {
1132                        val--;
1133                        quantityInput.value = val;
1134                        display.textContent = val;
1135                    }
1136                });
1137                plusBtn.addEventListener("click", function () {
1138                    let val = parseInt(quantityInput.value) || 1;
1139                    if (val < max) {
1140                        val++;
1141                        quantityInput.value = val;
1142                        display.textContent = val;
1143                    }
1144                });
1145
1146                // Hide original input but keep it in DOM for form submission
1147                quantityInput.style.cssText =
1148                    "position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;";
1149
1150                // Build counter
1151                counter.appendChild(minusBtn);
1152                counter.appendChild(display);
1153                counter.appendChild(plusBtn);
1154
1155                // Create wrapper with label
1156                const wrapper = document.createElement("div");
1157                wrapper.style.cssText =
1158                    "display: flex; align-items: center; gap: 0.5rem;";
1159                if (label) {
1160                    label.style.cssText = `
1161            font-weight: 700;
1162            font-size: 0.75rem;
1163            text-transform: uppercase;
1164            letter-spacing: 0.5px;
1165            opacity: 0.7;
1166            margin: 0;
1167            white-space: nowrap;
1168          `;
1169                    wrapper.appendChild(label);
1170                }
1171                wrapper.appendChild(counter);
1172                // Keep the hidden input inside
1173                wrapper.appendChild(quantityInput);
1174
1175                // Replace original parent
1176                if (parentDiv) {
1177                    parentDiv.replaceWith(wrapper);
1178                }
1179            }
1180
1181            // Style price input
1182            const priceInput = footer.querySelector("input#price");
1183            if (priceInput) {
1184                const parentDiv =
1185                    priceInput.closest(".mx-3") || priceInput.parentElement;
1186                const label = footer.querySelector('label[for="price"]');
1187
1188                priceInput.style.cssText = `
1189          background: rgba(0, 0, 0, 0.25);
1190          border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.3);
1191          border-radius: 10px;
1192          color: var(--color2);
1193          font-weight: 700;
1194          font-family: 'Lexend', sans-serif;
1195          text-align: center;
1196          padding: 0.5rem 0.75rem;
1197          width: 100px;
1198          height: 40px;
1199          transition: all 0.3s ease;
1200        `;
1201                priceInput.addEventListener("focus", function () {
1202                    this.style.borderColor = "var(--color5)";
1203                    this.style.boxShadow =
1204                        "0 0 12px rgba(var(--color5-rgb, 138, 43, 226), 0.3)";
1205                    this.style.outline = "none";
1206                });
1207                priceInput.addEventListener("blur", function () {
1208                    this.style.borderColor =
1209                        "rgba(var(--color5-rgb, 138, 43, 226), 0.3)";
1210                    this.style.boxShadow = "none";
1211                });
1212
1213                // Create wrapper with label
1214                const wrapper = document.createElement("div");
1215                wrapper.style.cssText =
1216                    "display: flex; align-items: center; gap: 0.5rem;";
1217                if (label) {
1218                    label.style.cssText = `
1219            font-weight: 700;
1220            font-size: 0.75rem;
1221            text-transform: uppercase;
1222            letter-spacing: 0.5px;
1223            opacity: 0.7;
1224            margin: 0;
1225            white-space: nowrap;
1226          `;
1227                    wrapper.appendChild(label);
1228                }
1229                wrapper.appendChild(priceInput);
1230
1231                if (parentDiv) {
1232                    parentDiv.replaceWith(wrapper);
1233                }
1234            }
1235        }
1236    } catch (error) {
1237        console.warn("Shop Enhancer - Modal Footer Design: ", error);
1238    }
1239
1240    const packageImageStorageKey = "apexShopPackageImages";
1241    const packageImageMemory = {};
1242
1243    function getPackageIdFromUrl(url) {
1244        if (!url) return null;
1245
1246        try {
1247            const path = new URL(url, window.location.origin).pathname;
1248            const match = path.match(
1249                /\/(?:[^/]+\/)?(?:packages|cart\/remove)\/(\d+)(?:\/|$)/,
1250            );
1251
1252            return match ? match[1] : null;
1253        } catch (error) {
1254            return null;
1255        }
1256    }
1257
1258    function getPackageImageMap() {
1259        try {
1260            return Object.assign(
1261                {},
1262                packageImageMemory,
1263                JSON.parse(
1264                    localStorage.getItem(packageImageStorageKey) || "{}",
1265                ),
1266            );
1267        } catch (error) {
1268            return packageImageMemory;
1269        }
1270    }
1271
1272    function rememberPackageImage(id, src, alt) {
1273        if (!id || !src || src.endsWith("#")) return;
1274
1275        const images = getPackageImageMap();
1276        images[id] = { src, alt: alt || "" };
1277        packageImageMemory[id] = images[id];
1278
1279        try {
1280            localStorage.setItem(
1281                packageImageStorageKey,
1282                JSON.stringify(images),
1283            );
1284        } catch (error) {
1285            // Ignore storage quota/private-mode failures; the cart can still use its fallback icon.
1286        }
1287    }
1288
1289    function renderCartItemImage(media, imageData, name) {
1290        if (!media || !imageData || !imageData.src) return false;
1291
1292        media.innerHTML = "";
1293
1294        const img = document.createElement("img");
1295        img.src = imageData.src;
1296        img.alt = imageData.alt || name || "";
1297        img.loading = "lazy";
1298        img.style.cssText = `
1299            width: 100%;
1300            height: 100%;
1301            object-fit: contain;
1302            padding: 0.35rem;
1303            filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.35));
1304        `;
1305        img.addEventListener("error", function () {
1306            media.innerHTML =
1307                '<i class="bi bi-box-seam" style="font-size: 1.25rem; color: var(--color6);"></i>';
1308        });
1309
1310        media.appendChild(img);
1311        return true;
1312    }
1313
1314    function rememberPackageImagesFromDocument(doc) {
1315        doc.querySelectorAll("a[data-package-url]").forEach(function (link) {
1316            const packageId = getPackageIdFromUrl(link.dataset.packageUrl);
1317            const img = link.querySelector("img");
1318
1319            if (packageId && img) {
1320                rememberPackageImage(packageId, img.src, img.alt);
1321            }
1322        });
1323    }
1324
1325    function getShopBasePath() {
1326        const path = window.location.pathname.replace(/\/+$/, "");
1327        const cartMatch = path.match(/^(.*)\/cart(?:\/|$)/);
1328
1329        if (cartMatch) return cartMatch[1] || "/shop";
1330
1331        return "/shop";
1332    }
1333
1334    async function hydrateCartImages(packageIds) {
1335        const missingIds = Array.from(new Set(packageIds)).filter(function (id) {
1336            return id && !getPackageImageMap()[id];
1337        });
1338
1339        if (missingIds.length === 0) return;
1340
1341        try {
1342            const parser = new DOMParser();
1343            const shopBase = getShopBasePath();
1344            const shopResponse = await fetch(shopBase, {
1345                credentials: "same-origin",
1346            });
1347
1348            if (!shopResponse.ok) return;
1349
1350            const shopDoc = parser.parseFromString(
1351                await shopResponse.text(),
1352                "text/html",
1353            );
1354            rememberPackageImagesFromDocument(shopDoc);
1355
1356            const categoryUrls = Array.from(
1357                shopDoc.querySelectorAll('a[href*="/categories/"]'),
1358            )
1359                .map(function (link) {
1360                    return new URL(link.href, window.location.origin).href;
1361                })
1362                .filter(function (href, index, urls) {
1363                    return (
1364                        href.startsWith(window.location.origin) &&
1365                        urls.indexOf(href) === index
1366                    );
1367                });
1368
1369            for (const url of categoryUrls) {
1370                const currentMap = getPackageImageMap();
1371                if (missingIds.every((id) => currentMap[id])) break;
1372
1373                const response = await fetch(url, {
1374                    credentials: "same-origin",
1375                });
1376                if (!response.ok) continue;
1377
1378                const doc = parser.parseFromString(
1379                    await response.text(),
1380                    "text/html",
1381                );
1382                rememberPackageImagesFromDocument(doc);
1383            }
1384        } catch (error) {
1385            console.warn("Shop Enhancer - Cart Images: ", error);
1386        }
1387
1388        const images = getPackageImageMap();
1389        document
1390            .querySelectorAll("[data-cart-package-image]")
1391            .forEach(function (media) {
1392                const id = media.dataset.cartPackageImage;
1393                renderCartItemImage(media, images[id], media.dataset.name);
1394            });
1395    }
1396
1397    function createCartItemMedia(packageId, name) {
1398        const media = document.createElement("div");
1399        media.dataset.cartPackageImage = packageId || "";
1400        media.dataset.name = name || "";
1401        media.style.cssText = `
1402            width: 58px;
1403            height: 58px;
1404            border-radius: 12px;
1405            background: rgba(var(--color5-rgb, 138, 43, 226), 0.12);
1406            border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.18);
1407            display: flex;
1408            align-items: center;
1409            justify-content: center;
1410            flex-shrink: 0;
1411            overflow: hidden;
1412            position: relative;
1413        `;
1414
1415        const imageData = packageId ? getPackageImageMap()[packageId] : null;
1416
1417        if (!renderCartItemImage(media, imageData, name)) {
1418            media.innerHTML =
1419                '<i class="bi bi-box-seam" style="font-size: 1.25rem; color: var(--color6);"></i>';
1420        }
1421
1422        return media;
1423    }
1424
1425    function extractAmountText(text) {
1426        const clean = (text || "").replace(/\s+/g, " ").trim();
1427        const colonAmount = clean.match(/:\s*(.+)$/);
1428
1429        if (colonAmount) return colonAmount[1].trim();
1430
1431        const amountMatch = clean.match(/[\d.,]+\s*(?:[^\s:]+)?$/);
1432
1433        return amountMatch ? amountMatch[0].trim() : clean;
1434    }
1435
1436    function escapeHtml(value) {
1437        return String(value || "").replace(/[&<>"']/g, function (char) {
1438            return {
1439                "&": "&amp;",
1440                "<": "&lt;",
1441                ">": "&gt;",
1442                '"': "&quot;",
1443                "'": "&#039;",
1444            }[char];
1445        });
1446    }
1447
1448    function restylePromotionTable(table, iconClass) {
1449        const rows = Array.from(table.querySelectorAll("tbody tr"));
1450        const list = document.createElement("div");
1451        list.style.cssText = `
1452            display: flex;
1453            flex-direction: column;
1454            gap: 0.5rem;
1455            margin-bottom: 0.6rem;
1456        `;
1457
1458        rows.forEach(function (row) {
1459            const cells = row.querySelectorAll("th, td");
1460            if (cells.length < 2) return;
1461
1462            const code = cells[0].textContent.trim();
1463            const discount = cells[1].textContent.trim();
1464            const actionForm = cells[2] ? cells[2].querySelector("form") : null;
1465            const item = document.createElement("div");
1466            item.style.cssText = `
1467                display: grid;
1468                grid-template-columns: 28px minmax(0, 1fr) auto;
1469                gap: 0.65rem;
1470                align-items: center;
1471                padding: 0.55rem 0.65rem;
1472                border-radius: 10px;
1473                background: rgba(0, 0, 0, 0.18);
1474                border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.16);
1475            `;
1476
1477            const icon = document.createElement("div");
1478            icon.style.cssText = `
1479                width: 28px;
1480                height: 28px;
1481                border-radius: 8px;
1482                display: flex;
1483                align-items: center;
1484                justify-content: center;
1485                background: rgba(var(--color5-rgb, 138, 43, 226), 0.16);
1486                color: var(--color6);
1487                flex-shrink: 0;
1488            `;
1489            icon.innerHTML = `<i class="${iconClass}" style="margin:0; font-size:0.85rem;"></i>`;
1490
1491            const info = document.createElement("div");
1492            info.style.cssText = "min-width: 0;";
1493            info.innerHTML = `
1494                <div style="font-family: 'Lexend', sans-serif; font-weight: 800; font-size: 0.85rem; color: var(--color2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(code)}</div>
1495                <div style="font-size: 0.78rem; color: rgba(var(--color2-rgb, 255, 255, 255), 0.58);
1495 margin-top: 1px;">${escapeHtml(discount)}</div>
1496            `;
1497
1498            item.appendChild(icon);
1499            item.appendChild(info);
1500
1501            if (actionForm) {
1502                actionForm.classList.remove("d-inline-block");
1503                actionForm.style.cssText =
1504                    "margin: 0; display: flex; align-items: center; justify-content: center;";
1505                const btn = actionForm.querySelector(".btn-danger");
1506                if (btn) {
1507                    btn.classList.remove("btn", "btn-sm", "btn-danger");
1508                    btn.style.cssText = `
1509                        width: 28px;
1510                        height: 28px;
1511                        padding: 0;
1512                        display: grid;
1513                        place-items: center;
1514                        background: rgba(220, 53, 69, 0.14);
1515                        border: 1px solid rgba(220, 53, 69, 0.28);
1516                        color: #ff6b6b;
1517                        border-radius: 8px;
1518                        font-size: 0.72rem;
1519                        line-height: 1;
1520                        transition: all 0.2s;
1521                    `;
1522                    const icon = btn.querySelector("i");
1523                    if (icon) {
1524                        icon.style.cssText =
1525                            "display: block; margin: 0; line-height: 1; font-size: 0.72rem;";
1526                    }
1527                }
1528                item.appendChild(actionForm);
1529            }
1530
1531            list.appendChild(item);
1532        });
1533
1534        table.replaceWith(list);
1535    }
1536
1537    // Feature 8: Enhance Product Cards with gaming design
1538    try {
1539        const shopContainer = document.querySelector("#shop");
1540        if (shopContainer) {
1541            const productCards = shopContainer.querySelectorAll(
1542                ".col-md-4 > .card.h-100",
1543            );
1544
1545            productCards.forEach(function (card) {
1546                // === Card container styling ===
1547                card.style.cssText = `
1548          border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.15);
1549          border-radius: 14px;
1550          overflow: hidden;
1551          transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
1552          cursor: pointer;
1553          position: relative;
1554        `;
1555
1556                // Hover effects on card
1557                card.addEventListener("mouseenter", function () {
1558                    this.style.transform = "translateY(-8px)";
1559                    this.style.boxShadow = `
1560            0 15px 40px rgba(0, 0, 0, 0.5),
1561            0 0 30px rgba(var(--color5-rgb, 138, 43, 226), 0.2),
1562            0 0 60px rgba(var(--color5-rgb, 138, 43, 226), 0.1)
1563          `;
1564                    this.style.borderColor =
1565                        "rgba(var(--color5-rgb, 138, 43, 226), 0.5)";
1566                });
1567                card.addEventListener("mouseleave", function () {
1568                    this.style.transform = "";
1569                    this.style.boxShadow = "";
1570                    this.style.borderColor =
1571                        "rgba(var(--color5-rgb, 138, 43, 226), 0.15)";
1572                });
1573
1574                // === Image section ===
1575                const imgLink = card.querySelector("a[data-package-url]");
1576                const img = card.querySelector("img.card-img-top");
1577                const packageId = getPackageIdFromUrl(
1578                    imgLink ? imgLink.dataset.packageUrl : "",
1579                );
1580
1581                if (img) {
1582                    rememberPackageImage(packageId, img.src, img.alt);
1583
1584                    // Create image wrapper
1585                    const imgWrapper = document.createElement("div");
1586                    imgWrapper.style.cssText = `
1587            position: relative;
1588            overflow: hidden;
1589            background: radial-gradient(circle at center, rgba(var(--color5-rgb, 138, 43, 226), 0.1) 0%, transparent 70%);
1590            display: flex;
1591            align-items: center;
1592            justify-content: center;
1593            padding: 1.5rem;
1594            min-height: 180px;
1595          `;
1596
1597                    // Style image
1598                    img.style.cssText = `
1599            max-height: 160px;
1600            width: auto;
1601            max-width: 100%;
1602            object-fit: contain;
1603            filter: drop-shadow(0 5px 15px rgba(0, 0, 0, 0.4));
1604            transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
1605            position: relative;
1606            z-index: 2;
1607          `;
1608                    img.classList.remove("card-img-top");
1609
1610                    // Hover zoom on image
1611                    card.addEventListener("mouseenter", function () {
1612                        img.style.transform = "scale(1.08)";
1613                        img.style.filter =
1614                            "drop-shadow(0 8px 25px rgba(var(--color5-rgb, 138, 43, 226), 0.4))";
1615                    });
1616                    card.addEventListener("mouseleave", function () {
1617                        img.style.transform = "";
1618                        img.style.filter =
1619                            "drop-shadow(0 5px 15px rgba(0, 0, 0, 0.4))";
1620                    });
1621
1622                    // Add subtle glow behind image
1623                    const glow = document.createElement("div");
1624                    glow.style.cssText = `
1625            position: absolute;
1626            top: 50%;
1627            left: 50%;
1628            transform: translate(-50%, -50%);
1629            width: 60%;
1630            height: 60%;
1631            background: radial-gradient(circle, rgba(var(--color5-rgb, 138, 43, 226), 0.25) 0%, transparent 70%);
1632            filter: blur(20px);
1633            z-index: 1;
1634            pointer-events: none;
1635            transition: opacity 0.4s;
1636          `;
1637                    imgWrapper.appendChild(glow);
1638
1639                    // Bottom gradient fade
1640                    const imgFade = document.createElement("div");
1641                    imgFade.style.cssText = `
1642            position: absolute;
1643            bottom: 0;
1644            left: 0;
1645            right: 0;
1646            height: 40%;
1647            background: linear-gradient(180deg, transparent 0%, var(--color4, #0d0c17) 100%);
1648            z-index: 3;
1649            pointer-events: none;
1650          `;
1651                    imgWrapper.appendChild(imgFade);
1652
1653                    // Replace original image placement
1654                    if (imgLink) {
1655                        imgLink.style.cssText =
1656                            "text-decoration: none; display: block;";
1657                        imgLink.innerHTML = "";
1658                        imgLink.appendChild(imgWrapper);
1659                        imgWrapper.appendChild(img);
1660                    } else {
1661                        const parent = img.parentElement;
1662                        parent.insertBefore(imgWrapper, img);
1663                        imgWrapper.appendChild(img);
1664                    }
1665                }
1666
1667                // === Card body ===
1668                const cardBody = card.querySelector(".card-body");
1669                if (cardBody) {
1670                    cardBody.style.cssText = `
1671            padding: 1.25rem 1.25rem 1.5rem;
1672            display: flex;
1673            flex-direction: column;
1674            gap: 0.75rem;
1675            position: relative;
1676            z-index: 4;
1677          `;
1678
1679                    // Style title
1680                    const title = cardBody.querySelector(".card-title");
1681                    if (title) {
1682                        title.style.cssText = `
1683              font-family: 'Lexend', sans-serif;
1684              font-weight: 800;
1685              font-size: 1.05rem;
1686              margin: 0;
1687              color: var(--color2);
1688              text-transform: uppercase;
1689              letter-spacing: 0.5px;
1690            `;
1691                    }
1692
1693                    // Style price + buy button in a row
1694                    const priceEl = cardBody.querySelector(".card-subtitle");
1695                    const buyBtn = cardBody.querySelector(".btn");
1696
1697                    if (priceEl || buyBtn) {
1698                        // Create row container for price + buy button
1699                        const actionRow = document.createElement("div");
1700                        actionRow.style.cssText = `
1701              display: flex;
1702              gap: 0.5rem;
1703              margin-top: auto;
1704              align-items: stretch;
1705            `;
1706
1707                        if (priceEl) {
1708                            priceEl.classList.remove("card-subtitle", "mb-3");
1709                            priceEl.style.cssText = `
1710                display: inline-flex;
1711                align-items: center;
1712                justify-content: center;
1713                gap: 0.4rem;
1714                background: linear-gradient(135deg, rgba(var(--color5-rgb, 138, 43, 226), 0.2) 0%, rgba(var(--color5-rgb, 138, 43, 226), 0.08) 100%);
1715                border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.25);
1716                border-radius: 10px;
1717                padding: 0.6rem 0.85rem;
1718                font-weight: 800;
1719                font-size: 0.85rem;
1720                font-family: 'Lexend', sans-serif;
1721                color: var(--color2);
1722                margin: 0;
1723                white-space: nowrap;
1724                flex: 1 1 0;
1725              `;
1726
1727                            // Add coin icon
1728                            const coinIcon = document.createElement("i");
1729                            coinIcon.className = "bi bi-coin";
1730                            coinIcon.style.cssText = `
1731                color: var(--color6);
1732                font-size: 0.85rem;
1733                margin: 0;
1734                filter: drop-shadow(0 0 3px rgba(var(--color5-rgb, 138, 43, 226), 0.4));
1735              `;
1736                            priceEl.insertBefore(coinIcon, priceEl.firstChild);
1737
1738                            // Style discount
1739                            const discount = priceEl.querySelector("del");
1740                            if (discount) {
1741                                discount.style.cssText = `
1742                  opacity: 0.5;
1743                  font-size: 0.75rem;
1744                  font-weight: 400;
1745                  margin-right: 0.25rem;
1746                `;
1747                            }
1748
1749                            actionRow.appendChild(priceEl);
1750                        }
1751
1752                        if (buyBtn) {
1753                            buyBtn.style.cssText = `
1754                background: linear-gradient(135deg, var(--color6) 0%, var(--color5) 100%) !important;
1755                color: var(--color2) !important;
1756                font-weight: 800;
1757                font-size: 0.85rem;
1758                padding: 0.6rem 1.25rem;
1759                border-radius: 10px;
1760                border: 1px solid rgba(var(--color2-rgb, 255, 255, 255), 0.15) !important;
1761                position: relative;
1762                overflow: hidden;
1763                transition: all 0.3s ease;
1764                text-transform: uppercase;
1765                font-family: 'Lexend', sans-serif;
1766                box-shadow: 0 4px 15px rgba(var(--color5-rgb, 138, 43, 226), 0.3);
1767                flex: 1 1 0;
1768                display: flex;
1769                align-items: center;
1770                justify-content: center;
1771                margin: 0;
1772              `;
1773
1774                            buyBtn.addEventListener("mouseenter", function () {
1775                                this.style.boxShadow =
1776                                    "0 6px 25px rgba(var(--color5-rgb, 138, 43, 226), 0.5)";
1777                            });
1778                            buyBtn.addEventListener("mouseleave", function () {
1779                                this.style.boxShadow =
1780                                    "0 4px 15px rgba(var(--color5-rgb, 138, 43, 226), 0.3)";
1781                            });
1782
1783                            // Add shine
1784                            const btnShine = document.createElement("span");
1785                            btnShine.style.cssText = `
1786                position: absolute;
1787                top: 0;
1788                left: 0;
1789                width: 100%;
1790                height: 100%;
1791                background: linear-gradient(90deg, transparent 0%, rgba(var(--color2-rgb, 255, 255, 255), 0.2) 50%, transparent 100%);
1792                animation: shine 3s infinite;
1793                pointer-events: none;
1794                z-index: 1;
1795              `;
1796                            buyBtn.appendChild(btnShine);
1797
1798                            // Z-index on content
1799                            const icon = buyBtn.querySelector("i");
1800                            if (icon)
1801                                icon.style.cssText =
1802                                    "position: relative; z-index: 2; margin: 0 0.4rem 0 0;";
1803                            buyBtn.childNodes.forEach((n) => {
1804                                if (
1805                                    n.nodeType === Node.TEXT_NODE &&
1806                                    n.textContent.trim()
1807                                ) {
1808                                    const s = document.createElement("span");
1809                                    s.style.cssText =
1810                                        "position: relative; z-index: 2;";
1811                                    s.textContent = n.textContent;
1812                                    n.replaceWith(s);
1813                                }
1814                            });
1815
1816                            actionRow.appendChild(buyBtn);
1817                        }
1818
1819                        cardBody.appendChild(actionRow);
1820                    }
1821                }
1822
1823                // === Top-right decorative glow corner ===
1824                const cornerGlow = document.createElement("div");
1825                cornerGlow.style.cssText = `
1826          position: absolute;
1827          top: -30px;
1828          right: -30px;
1829          width: 80px;
1830          height: 80px;
1831          background: radial-gradient(circle, rgba(var(--color5-rgb, 138, 43, 226), 0.3) 0%, transparent 70%);
1832          filter: blur(15px);
1833          pointer-events: none;
1834          z-index: 0;
1835        `;
1836                card.appendChild(cornerGlow);
1837            });
1838        }
1839    } catch (error) {
1840        console.warn("Shop Enhancer - Product Cards: ", error);
1841    }
1842
1843    // Feature 9: Redesign Cart Page with gaming layout
1844    try {
1845        // Detect cart page: look for cart-items table OR the alert-warning inside a card on a cart page
1846        const cartTable = document.querySelector("table.cart-items");
1847        let cartCard = cartTable ? cartTable.closest(".card") : null;
1848
1849        // Fallback: detect empty cart page by URL or alert content
1850        if (!cartCard && window.location.pathname.includes("/cart")) {
1851            // Find the main card on the page (should be the one after h1)
1852            const allCards = document.querySelectorAll(".card");
1853            allCards.forEach(function (c) {
1854                if (
1855                    !cartCard &&
1856                    c.querySelector(".alert-warning, .input-group")
1857                ) {
1858                    cartCard = c;
1859                }
1860            });
1861        }
1862
1863        if (cartCard) {
1864            const cardBody = cartCard.querySelector(".card-body");
1865            if (!cardBody) throw new Error("No card-body found");
1866
1867            // === Hide original <h1> and restyle ===
1868            const pageTitle =
1869                cardBody.parentElement.parentElement.querySelector(
1870                    ":scope > h1",
1871                );
1872            if (pageTitle) {
1873                pageTitle.style.cssText = `
1874          font-family: 'Lexend', sans-serif;
1875          font-weight: 900;
1876          font-size: 1.8rem;
1877          text-transform: uppercase;
1878          letter-spacing: 1px;
1879          margin-bottom: 1.25rem;
1880          display: flex;
1881          align-items: center;
1882          gap: 0.75rem;
1883        `;
1884                const cartIcon = document.createElement("i");
1885                cartIcon.className = "bi bi-cart3";
1886                cartIcon.style.cssText = `
1887          color: var(--color6);
1888          font-size: 1.6rem;
1889          filter: drop-shadow(0 0 6px rgba(var(--color5-rgb, 138, 43, 226), 0.4));
1890        `;
1891                pageTitle.insertBefore(cartIcon, pageTitle.firstChild);
1892            }
1893
1894            // === Remove the wrapping card - we'll make our own layout ===
1895            cardBody.style.cssText =
1896                "padding: 0; border: none; background: none;";
1897            cartCard.style.cssText =
1898                "border: none; background: none; box-shadow: none;";
1899            // Remove top glow line from card via ::before
1900            cartCard.classList.add("cart-redesigned");
1901
1902            // Create main 2-column layout
1903            const mainRow = document.createElement("div");
1904            mainRow.style.cssText =
1905                "display: grid; grid-template-columns: 1fr 380px; gap: 1.5rem;";
1906            mainRow.id = "cart-gaming-layout";
1907
1908            // === LEFT COLUMN: items ===
1909            const leftCol = document.createElement("div");
1910            leftCol.style.cssText = "min-width: 0;";
1911
1912            // === RIGHT COLUMN: summary sidebar ===
1913            const rightCol = document.createElement("div");
1914            rightCol.style.cssText = "min-width: 0;";
1915
1916            // ===============================
1917            // LEFT: Transform table rows into item cards
1918            // ===============================
1919            // Find update form as the form containing the cart-items table
1920            const updateForm = cartTable ? cartTable.closest("form") : null;
1921            // Find clear form (form with btn-danger right after the update form)
1922            const clearForm = updateForm
1923                ? updateForm.nextElementSibling &&
1924                  updateForm.nextElementSibling.tagName === "FORM"
1925                    ? updateForm.nextElementSibling
1926                    : cardBody.querySelector("form.text-end.mb-4")
1927                : null;
1928            const tbody = cartTable ? cartTable.querySelector("tbody") : null;
1929            const rows = tbody ? tbody.querySelectorAll("tr") : [];
1930
1931            if (updateForm && rows.length > 0) {
1932                // Hide original table
1933                cartTable.style.display = "none";
1934                const cartPackageIds = [];
1935
1936                // Create items container
1937                const itemsContainer = document.createElement("div");
1938                itemsContainer.style.cssText =
1939                    "display: flex; flex-direction: column; gap: 0.75rem;";
1940
1941                // Create hidden form for CSRF + quantities
1942                const hiddenFormData = document.createElement("div");
1943                hiddenFormData.style.display = "none";
1944                if (!updateForm.id) {
1945                    updateForm.id = "apex-cart-update-form";
1946                }
1947                // Move CSRF token
1948                const csrf = updateForm.querySelector('input[name="_token"]');
1949                if (csrf) hiddenFormData.appendChild(csrf.cloneNode());
1950
1951                rows.forEach(function (row) {
1952                    const cells = row.querySelectorAll("td, th");
1953                    if (cells.length < 5) return;
1954
1955                    const name = cells[0].textContent.trim();
1956                    const price = cells[1].textContent.trim();
1957                    const total = cells[2].textContent.trim();
1958                    const qtyInput = cells[3].querySelector("input");
1959                    const removeLink = cells[4].querySelector("a");
1960                    const packageId = getPackageIdFromUrl(
1961                        removeLink ? removeLink.href : "",
1962                    );
1963                    if (packageId) cartPackageIds.push(packageId);
1964
1965                    // Item card
1966                    const itemCard = document.createElement("div");
1967                    itemCard.style.cssText = `
1968            background: linear-gradient(135deg, var(--color3) 0%, var(--color4) 100%);
1969            border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.12);
1970            border-radius: 12px;
1971            padding: 1rem 1.25rem;
1972            display: flex;
1973            align-items: center;
1974            gap: 1rem;
1975            transition: all 0.3s ease;
1976            position: relative;
1977            overflow: hidden;
1978          `;
1979
1980                    itemCard.addEventListener("mouseenter", function () {
1981                        this.style.borderColor =
1982                            "rgba(var(--color5-rgb, 138, 43, 226), 0.35)";
1983                        this.style.boxShadow =
1984                            "0 4px 20px rgba(var(--color5-rgb, 138, 43, 226), 0.12)";
1985                    });
1986                    itemCard.addEventListener("mouseleave", function () {
1987                        this.style.borderColor =
1988                            "rgba(var(--color5-rgb, 138, 43, 226), 0.12)";
1989                        this.style.boxShadow = "none";
1990                    });
1991
1992                    // Item image, using the product image cached from shop cards when available.
1993                    const itemIcon = createCartItemMedia(packageId, name);
1994
1995                    // Item info (name + unit price)
1996                    const itemInfo = document.createElement("div");
1997                    itemInfo.style.cssText = "flex: 1; min-width: 0;";
1998                    itemInfo.innerHTML = `
1999            <div style="font-family: 'Lexend', sans-serif; font-weight: 700; font-size: 0.95rem; color: var(--color2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
2000              ${escapeHtml(name)}
2001            </div>
2002            <div style="font-size: 0.8rem; color: rgba(var(--color2-rgb, 255, 255, 255), 0.5); margin-top: 2px;">
2003              ${escapeHtml(price)} ${escapeHtml(_shopLang("per_unit", "/ unit"))}
2004            </div>
2005          `;
2006
2007                    // Quantity controls
2008                    const qtyContainer = document.createElement("div");
2009                    qtyContainer.style.cssText = `
2010            display: flex;
2011            align-items: center;
2012            background: rgba(0, 0, 0, 0.25);
2013            border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.2);
2014            border-radius: 8px;
2015            overflow: hidden;
2016            height: 34px;
2017            flex-shrink: 0;
2018          `;
2019
2020                    if (qtyInput) {
2021                        const min = parseInt(qtyInput.min) || 0;
2022                        const max = parseInt(qtyInput.max) || 99;
2023                        const isReadonly = qtyInput.hasAttribute("readonly");
2024
2025                        // Clone input for form submission (hidden)
2026                        const hiddenQty = qtyInput.cloneNode();
2027                        hiddenQty.style.cssText =
2028                            "position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;";
2029                        hiddenFormData.appendChild(hiddenQty);
2030
2031                        const minusBtn = document.createElement("button");
2032                        minusBtn.type = "button";
2033                        minusBtn.innerHTML =
2034                            '<i class="bi bi-dash" style="margin:0;"></i>';
2035                        minusBtn.style.cssText = `
2036              width: 30px; height: 100%; border: none;
2037              background: rgba(var(--color5-rgb, 138, 43, 226), 0.15);
2038              color: var(--color2); cursor: pointer;
2039              display: flex; align-items: center; justify-content: center;
2040              transition: background 0.2s; padding: 0; font-size: 1rem;
2041            `;
2042                        if (isReadonly) minusBtn.disabled = true;
2043
2044                        const display = document.createElement("span");
2045                        display.textContent = qtyInput.value || "1";
2046                        display.style.cssText = `
2047              min-width: 32px; text-align: center; font-weight: 800;
2048              font-size: 0.85rem; font-family: 'Lexend', sans-serif;
2049              color: var(--color2); user-select: none;
2050            `;
2051
2052                        const plusBtn = document.createElement("button");
2053                        plusBtn.type = "button";
2054                        plusBtn.innerHTML =
2055                            '<i class="bi bi-plus" style="margin:0;"></i>';
2056                        plusBtn.style.cssText = minusBtn.style.cssText;
2057                        if (isReadonly) plusBtn.disabled = true;
2058
2059                        minusBtn.addEventListener("mouseenter", function () {
2060                            this.style.background =
2061                                "rgba(var(--color5-rgb, 138, 43, 226), 0.35)";
2062                        });
2063                        minusBtn.addEventListener("mouseleave", function () {
2064                            this.style.background =
2065                                "rgba(var(--color5-rgb, 138, 43, 226), 0.15)";
2066                        });
2067                        plusBtn.addEventListener("mouseenter", function () {
2068                            this.style.background =
2069                                "rgba(var(--color5-rgb, 138, 43, 226), 0.35)";
2070                        });
2071                        plusBtn.addEventListener("mouseleave", function () {
2072                            this.style.background =
2073                                "rgba(var(--color5-rgb, 138, 43, 226), 0.15)";
2074                        });
2075
2076                        minusBtn.addEventListener("click", function () {
2077                            let val = parseInt(hiddenQty.value) || 0;
2078                            if (val > min) {
2079                                val--;
2080                                hiddenQty.value = val;
2081                                display.textContent = val;
2082                            }
2083                        });
2084                        plusBtn.addEventListener("click", function () {
2085                            let val = parseInt(hiddenQty.value) || 0;
2086                            if (val < max) {
2087                                val++;
2088                                hiddenQty.value = val;
2089                                display.textContent = val;
2090                            }
2091                        });
2092
2093                        qtyContainer.appendChild(minusBtn);
2094                        qtyContainer.appendChild(display);
2095                        qtyContainer.appendChild(plusBtn);
2096                    }
2097
2098                    // Total price
2099                    const totalBadge = document.createElement("div");
2100                    totalBadge.style.cssText = `
2101            font-family: 'Lexend', sans-serif;
2102            font-weight: 800;
2103            font-size: 0.9rem;
2104            color: var(--color2);
2105            white-space: nowrap;
2106            min-width: 70px;
2107            text-align: right;
2108            flex-shrink: 0;
2109          `;
2110                    totalBadge.textContent = total;
2111
2112                    // Remove button
2113                    const removeBtn = document.createElement("a");
2114                    removeBtn.href = removeLink ? removeLink.href : "#";
2115                    removeBtn.style.cssText = `
2116            width: 32px; height: 32px; border-radius: 8px;
2117            background: rgba(220, 53, 69, 0.15);
2118            border: 1px solid rgba(220, 53, 69, 0.25);
2119            display: flex; align-items: center; justify-content: center;
2120            color: #ff6b6b; transition: all 0.2s; flex-shrink: 0;
2121            text-decoration: none; font-size: 0.8rem;
2122          `;
2123                    removeBtn.innerHTML =
2124                        '<i class="bi bi-trash3" style="margin:0;"></i>';
2125                    removeBtn.addEventListener("mouseenter", function () {
2126                        this.style.background = "rgba(220, 53, 69, 0.35)";
2127                        this.style.borderColor = "rgba(220, 53, 69, 0.5)";
2128                    });
2129                    removeBtn.addEventListener("mouseleave", function () {
2130                        this.style.background = "rgba(220, 53, 69, 0.15)";
2131                        this.style.borderColor = "rgba(220, 53, 69, 0.25)";
2132                    });
2133
2134                    itemCard.appendChild(itemIcon);
2135                    itemCard.appendChild(itemInfo);
2136                    itemCard.appendChild(qtyContainer);
2137                    itemCard.appendChild(totalBadge);
2138                    itemCard.appendChild(removeBtn);
2139                    itemsContainer.appendChild(itemCard);
2140                });
2141
2142                // Action bar under items (update + clear)
2143                const actionBar = document.createElement("div");
2144                actionBar.style.cssText = `
2145          display: flex;
2146          justify-content: space-between;
2147          align-items: center;
2148          margin-top: 0.75rem;
2149          gap: 0.75rem;
2150        `;
2151
2152                // Re-wire update form
2153                updateForm.style.cssText = "display: contents;";
2154                const updateBtn = updateForm.querySelector(
2155                    'button[type="submit"]',
2156                );
2157                if (updateBtn) {
2158                    updateBtn.className = "";
2159                    updateBtn.setAttribute("form", updateForm.id);
2160                    updateBtn.style.cssText = `
2161            background: rgba(var(--color5-rgb, 138, 43, 226), 0.15);
2162            border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.3);
2163            color: var(--color2);
2164            font-family: 'Lexend', sans-serif;
2165            font-weight: 700;
2166            font-size: 0.8rem;
2167            padding: 0.5rem 1rem;
2168            border-radius: 8px;
2169            cursor: pointer;
2170            transition: all 0.2s;
2171            text-transform: uppercase;
2172            display: flex;
2173            align-items: center;
2174            gap: 0.4rem;
2175          `;
2176                    updateBtn.addEventListener("mouseenter", function () {
2177                        this.style.background =
2178                            "rgba(var(--color5-rgb, 138, 43, 226), 0.3)";
2179                    });
2180                    updateBtn.addEventListener("mouseleave", function () {
2181                        this.style.background =
2182                            "rgba(var(--color5-rgb, 138, 43, 226), 0.15)";
2183                    });
2184                }
2185                // Hide the old <p> wrapper
2186                const updateP = updateForm.querySelector("p.text-end");
2187                if (updateP) updateP.style.display = "none";
2188
2189                // Clear button
2190                if (clearForm) {
2191                    clearForm.style.cssText = "display: contents;";
2192                    const clearBtn = clearForm.querySelector("button");
2193                    if (clearBtn) {
2194                        clearBtn.className = "";
2195                        clearBtn.style.cssText = `
2196              background: rgba(220, 53, 69, 0.12);
2197              border: 1px solid rgba(220, 53, 69, 0.25);
2198              color: #ff6b6b;
2199              font-family: 'Lexend', sans-serif;
2200              font-weight: 700;
2201              font-size: 0.8rem;
2202              padding: 0.5rem 1rem;
2203              border-radius: 8px;
2204              cursor: pointer;
2205              transition: all 0.2s;
2206              text-transform: uppercase;
2207              display: flex;
2208              align-items: center;
2209              gap: 0.4rem;
2210            `;
2211                        clearBtn.addEventListener("mouseenter", function () {
2212                            this.style.background = "rgba(220, 53, 69, 0.25)";
2213                        });
2214                        clearBtn.addEventListener("mouseleave", function () {
2215                            this.style.background = "rgba(220, 53, 69, 0.12)";
2216                        });
2217                    }
2218                    actionBar.appendChild(clearForm);
2219                }
2220
2221                if (updateBtn) {
2222                    actionBar.appendChild(updateBtn);
2223                }
2224
2225                leftCol.appendChild(itemsContainer);
2226                leftCol.appendChild(actionBar);
2227
2228                // Move hidden form data to update form
2229                updateForm.appendChild(hiddenFormData);
2230
2231                hydrateCartImages(cartPackageIds);
2232            }
2233
2234            // Handle empty cart
2235            const emptyAlert = cardBody.querySelector(".alert-warning");
2236            if (emptyAlert) {
2237                emptyAlert.style.cssText = `
2238          background: rgba(var(--color5-rgb, 138, 43, 226), 0.08);
2239          border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.2);
2240          border-radius: 12px;
2241          color: var(--color2);
2242          font-family: 'Lexend', sans-serif;
2243          text-align: center;
2244          padding: 2rem;
2245        `;
2246                leftCol.appendChild(emptyAlert);
2247            }
2248
2249            // ===============================
2250            // RIGHT: Summary sidebar
2251            // ===============================
2252            const summaryCard = document.createElement("div");
2253            summaryCard.style.cssText = `
2254        background: linear-gradient(135deg, var(--color3) 0%, var(--color4) 100%);
2255        border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.15);
2256        border-radius: 14px;
2257        padding: 0;
2258        overflow: hidden;
2259        position: sticky;
2260        top: 100px;
2261      `;
2262
2263            // Summary header
2264            const summaryHeader = document.createElement("div");
2265            summaryHeader.style.cssText = `
2266        padding: 1rem 1.25rem;
2267        border-bottom: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.12);
2268        display: flex;
2269        align-items: center;
2270        gap: 0.6rem;
2271      `;
2272            summaryHeader.innerHTML = `
2273        <i class="bi bi-receipt" style="color: var(--color6); font-size: 1.1rem;"></i>
2274        <span style="font-family: 'Lexend', sans-serif; font-weight: 800; font-size: 1rem; text-transform: uppercase; color: var(--color2);">
2275          ${_shopLang("summary", "Summary")}
2276        </span>
2277      `;
2278            summaryCard.appendChild(summaryHeader);
2279
2280            const summaryBody = document.createElement("div");
2281            summaryBody.style.cssText = "padding: 1.25rem;";
2282
2283            const payableTotalH5 = Array.from(
2284                cardBody.querySelectorAll(":scope > .row h5.text-end"),
2285            ).pop();
2286
2287            // Move coupons/gift cards section
2288            const couponRows = cardBody.querySelectorAll(":scope > .row");
2289            couponRows.forEach(function (row) {
2290                const h5 = row.querySelector("h5");
2291                if (!h5) return;
2292                const text = h5.textContent.trim().toLowerCase();
2293                const isGiftcardSection =
2294                    !!row.querySelector(
2295                        'input[name="giftcard"], form[action*="giftcards"]',
2296                    ) ||
2297                    text.includes("gift") ||
2298                    text.includes("cadeau");
2299
2300                // Style coupon/giftcard forms
2301                const inputGroups = row.querySelectorAll(".input-group");
2302                inputGroups.forEach(function (ig) {
2303                    ig.style.cssText = "margin-bottom: 0.75rem !important;";
2304                    const input = ig.querySelector("input.form-control");
2305                    if (input) {
2306                        input.style.cssText = `
2307              background: rgba(0, 0, 0, 0.2);
2308              border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.2);
2309              color: var(--color2);
2310              border-radius: 8px 0 0 8px;
2311              font-size: 0.85rem;
2312              padding: 0.5rem 0.75rem;
2313            `;
2314                    }
2315                    const btn = ig.querySelector(".btn");
2316                    if (btn) {
2317                        btn.style.cssText = `
2318              background: rgba(var(--color5-rgb, 138, 43, 226), 0.2);
2319              border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.3);
2320              color: var(--color2);
2321              font-size: 0.85rem;
2322              font-weight: 700;
2323              border-radius: 0 8px 8px 0;
2324              padding: 0.5rem 0.75rem;
2325              transition: all 0.2s;
2326            `;
2327                        btn.addEventListener("mouseenter", function () {
2328                            this.style.background =
2329                                "rgba(var(--color5-rgb, 138, 43, 226), 0.35)";
2330                        });
2331                        btn.addEventListener("mouseleave", function () {
2332                            this.style.background =
2333                                "rgba(var(--color5-rgb, 138, 43, 226), 0.2)";
2334                        });
2335                    }
2336                });
2337
2338                // Style section titles
2339                row.querySelectorAll("h5").forEach(function (title) {
2340                    title.style.cssText = `
2341            font-family: 'Lexend', sans-serif;
2342            font-weight: 700;
2343            font-size: 0.85rem;
2344            text-transform: uppercase;
2345            color: rgba(var(--color2-rgb, 255, 255, 255), 0.6);
2346            letter-spacing: 0.5px;
2347            margin-bottom: 0.5rem;
2348          `;
2349                });
2350
2351                // Replace coupon/gift card tables with compact applied-code rows.
2352                const tables = row.querySelectorAll("table");
2353                tables.forEach(function (table) {
2354                    restylePromotionTable(
2355                        table,
2356                        isGiftcardSection
2357                            ? "bi bi-gift"
2358                            : "bi bi-ticket-perforated",
2359                    );
2360                });
2361
2362                if (payableTotalH5 && row.contains(payableTotalH5)) {
2363                    payableTotalH5.style.display = "none";
2364                }
2365
2366                // Fix column layout — remove offset, make full width
2367                row.querySelectorAll(".col-md-4, .col-md-6").forEach(
2368                    function (col) {
2369                        col.className = "mb-3";
2370                        col.style.cssText = "";
2371                    },
2372                );
2373                row.querySelectorAll('[class*="offset-"]').forEach(
2374                    function (col) {
2375                        col.className = "mb-3";
2376                    },
2377                );
2378
2379                row.style.cssText = "";
2380                row.className = "";
2381
2382                // Create a section wrapper
2383                const section = document.createElement("div");
2384                section.style.cssText = `
2385          border-bottom: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.1);
2386          padding-bottom: 0.75rem;
2387          margin-bottom: 0.75rem;
2388        `;
2389                section.appendChild(row);
2390                summaryBody.appendChild(section);
2391            });
2392
2393            // Total line. When a gift card is applied, the plugin renders the correct
2394            // payable total inside the gift card block; use that value and hide the
2395            // pre-gift-card total to avoid showing two conflicting totals.
2396            const totalH5 = cardBody.querySelector(":scope > h5.text-end");
2397            const totalSource = payableTotalH5 || totalH5;
2398            if (totalSource) {
2399                const totalSection = document.createElement("div");
2400                totalSection.style.cssText = `
2401          display: flex;
2402          justify-content: space-between;
2403          align-items: center;
2404          padding: 0.75rem 0;
2405          border-bottom: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.1);
2406          margin-bottom: 0.75rem;
2407        `;
2408
2409                const totalLabel = document.createElement("span");
2410                totalLabel.style.cssText = `
2411          font-family: 'Lexend', sans-serif;
2412          font-weight: 700;
2413          font-size: 0.85rem;
2414          text-transform: uppercase;
2415          color: rgba(var(--color2-rgb, 255, 255, 255), 0.6);
2416        `;
2417                totalLabel.textContent = _shopLang("total", "Total");
2418
2419                const totalValue = document.createElement("span");
2420                totalValue.style.cssText = `
2421          font-family: 'Lexend', sans-serif;
2422          font-weight: 800;
2423          font-size: 1.2rem;
2424          color: var(--color2);
2425          display: flex;
2426          align-items: center;
2427          gap: 0.4rem;
2428        `;
2429                const amount = extractAmountText(totalSource.textContent);
2430                totalValue.innerHTML = `<i class="bi bi-coin" style="color: var(--color6); font-size: 1rem;"></i> ${escapeHtml(amount)}`;
2431
2432                if (totalH5) totalH5.style.display = "none";
2433                if (payableTotalH5) payableTotalH5.style.display = "none";
2434
2435                totalSection.appendChild(totalLabel);
2436                totalSection.appendChild(totalValue);
2437                summaryBody.appendChild(totalSection);
2438            }
2439
2440            // Move checkout form — find the form with back/checkout buttons
2441            const allForms = cardBody.querySelectorAll(":scope > form");
2442            let checkoutForm = null;
2443            allForms.forEach(function (f) {
2444                if (
2445                    f.querySelector(".btn-secondary, .btn-primary") &&
2446                    !f.querySelector("table") &&
2447                    f !== updateForm &&
2448                    f !== clearForm
2449                ) {
2450                    checkoutForm = f;
2451                }
2452            });
2453            if (checkoutForm) {
2454                const checkoutSection = document.createElement("div");
2455                checkoutSection.style.cssText = "margin-top: 0.5rem;";
2456
2457                // Style back button
2458                const backBtn = checkoutForm.querySelector(".btn-secondary");
2459                if (backBtn) {
2460                    backBtn.style.cssText = `
2461            background: rgba(var(--color2-rgb, 255, 255, 255), 0.08);
2462            border: 1px solid rgba(var(--color2-rgb, 255, 255, 255), 0.12);
2463            color: var(--color2);
2464            font-family: 'Lexend', sans-serif;
2465            font-weight: 700;
2466            font-size: 0.8rem;
2467            padding: 0.6rem 1rem;
2468            border-radius: 10px;
2469            width: 100%;
2470            text-align: center;
2471            display: block;
2472            transition: all 0.2s;
2473            text-decoration: none;
2474            margin-bottom: 0.5rem;
2475          `;
2476                    backBtn.addEventListener("mouseenter", function () {
2477                        this.style.background =
2478                            "rgba(var(--color2-rgb, 255, 255, 255), 0.15)";
2479                    });
2480                    backBtn.addEventListener("mouseleave", function () {
2481                        this.style.background =
2482                            "rgba(var(--color2-rgb, 255, 255, 255), 0.08)";
2483                    });
2484                }
2485
2486                // Style checkout/buy button
2487                const primaryBtn = checkoutForm.querySelector(".btn-primary");
2488                if (primaryBtn) {
2489                    primaryBtn.style.cssText = `
2490            background: linear-gradient(135deg, var(--color6) 0%, var(--color5) 100%) !important;
2491            border: 1px solid rgba(var(--color2-rgb, 255, 255, 255), 0.15) !important;
2492            color: var(--color2) !important;
2493            font-family: 'Lexend', sans-serif;
2494            font-weight: 800;
2495            font-size: 0.9rem;
2496            padding: 0.75rem 1.25rem;
2497            border-radius: 10px;
2498            width: 100%;
2499            text-align: center;
2500            cursor: pointer;
2501            transition: all 0.3s;
2502            text-transform: uppercase;
2503            position: relative;
2504            overflow: hidden;
2505            box-shadow: 0 4px 20px rgba(var(--color5-rgb, 138, 43, 226), 0.3);
2506            display: block;
2507            margin: 0;
2508          `;
2509                    primaryBtn.classList.remove("ms-auto");
2510
2511                    primaryBtn.addEventListener("mouseenter", function () {
2512                        this.style.boxShadow =
2513                            "0 6px 30px rgba(var(--color5-rgb, 138, 43, 226), 0.5)";
2514                        this.style.transform = "translateY(-2px)";
2515                    });
2516                    primaryBtn.addEventListener("mouseleave", function () {
2517                        this.style.boxShadow =
2518                            "0 4px 20px rgba(var(--color5-rgb, 138, 43, 226), 0.3)";
2519                        this.style.transform = "";
2520                    });
2521
2522                    // Add shine
2523                    const btnShine = document.createElement("span");
2524                    btnShine.style.cssText = `
2525            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
2526            background: linear-gradient(90deg, transparent 0%, rgba(var(--color2-rgb, 255, 255, 255), 0.2) 50%, transparent 100%);
2527            animation: shine 3s infinite; pointer-events: none; z-index: 1;
2528          `;
2529                    primaryBtn.appendChild(btnShine);
2530                }
2531
2532                // Change flex layout to vertical stack
2533                const flexDiv = checkoutForm.querySelector(".d-flex");
2534                if (flexDiv) {
2535                    flexDiv.style.cssText =
2536                        "display: flex; flex-direction: column; gap: 0.5rem;";
2537                }
2538
2539                // Terms
2540                const termsDiv = checkoutForm.querySelector(
2541                    ".d-flex.justify-content-end",
2542                );
2543                if (termsDiv) {
2544                    termsDiv.style.cssText =
2545                        "display: flex; justify-content: center; margin-bottom: 0.5rem;";
2546                    termsDiv
2547                        .querySelectorAll("label, a")
2548                        .forEach(function (el) {
2549                            el.style.fontSize = "0.8rem";
2550                        });
2551                }
2552
2553                // Email input if present
2554                const emailInput = checkoutForm.querySelector("#email");
2555                if (emailInput) {
2556                    emailInput.style.cssText = `
2557            background: rgba(0, 0, 0, 0.2);
2558            border: 1px solid rgba(var(--color5-rgb, 138, 43, 226), 0.2);
2559            color: var(--color2);
2560            border-radius: 8px;
2561            font-size: 0.85rem;
2562            padding: 0.6rem 0.75rem;
2563            width: 100%;
2564            margin-bottom: 0.5rem;
2565          `;
2566                    emailInput.classList.remove("form-group");
2567                }
2568
2569                checkoutSection.appendChild(checkoutForm);
2570                summaryBody.appendChild(checkoutSection);
2571            }
2572
2573            summaryCard.appendChild(summaryBody);
2574            rightCol.appendChild(summaryCard);
2575
2576            // === Assemble layout ===
2577            // Insert mainRow before cardBody content
2578            mainRow.appendChild(leftCol);
2579            mainRow.appendChild(rightCol);
2580            cardBody.appendChild(mainRow);
2581
2582            // Hide original table and move remaining original content away
2583            if (cartTable) cartTable.style.display = "none";
2584
2585            // === Add responsive style ===
2586            const responsiveStyle = document.createElement("style");
2587            responsiveStyle.textContent = `
2588        .cart-redesigned::before { display: none !important; }
2589        .cart-redesigned { background: none !important; }
2590        @media (max-width: 991px) {
2591          #cart-gaming-layout {
2592            grid-template-columns: 1fr !important;
2593          }
2594        }
2595      `;
2596            document.head.appendChild(responsiveStyle);
2597        }
2598    } catch (error) {
2599        console.warn("Shop Enhancer - Cart Page Redesign: ", error);
2600    }
2601});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.