diff --git a/public/cdn/js/helper.js b/public/cdn/js/helper.js index 9aec381..e5fd728 100644 --- a/public/cdn/js/helper.js +++ b/public/cdn/js/helper.js @@ -1,19 +1,7 @@ -import roughjs from "https://cdn.jsdelivr.net/npm/roughjs@4.6.6/+esm"; - -// team specific styles -const config = { - explore: { - fill: "rgba(255, 90, 84, 0.1)", - stroke: "rgba(255, 90, 84, 1)", - }, - decide: { - fill: "rgba(84, 255, 144, 0.1)", - stroke: "rgba(84, 255, 144, 1)", - }, - checkout: { - fill: "rgba(255, 222, 84, 0.1)", - stroke: "rgba(255, 222, 84, 1)", - }, +const teamColors = { + explore: { stroke: "rgba(255, 90, 84, 1)", text: "#fff" }, + decide: { stroke: "rgba(84, 255, 144, 1)", text: "#000" }, + checkout: { stroke: "rgba(255, 222, 84, 1)", text: "#000" }, }; /** @@ -21,30 +9,26 @@ const config = { */ function setBasicStyles() { const style = document.createElement("style"); + style.id = "boundaries"; style.innerHTML = ` -@import url('https://fonts.googleapis.com/css2?family=Pangolin&display=swap'); - [data-boundary], [data-boundary-page] { position: relative; - background-size: 100% 100%; - background-repeat: no-repeat; } [data-boundary]::after, [data-boundary-page]::after { display: block; content: attr(data-boundary); position: absolute; - bottom: -0.8rem; - right: 50%; - transform: translateX(50%); + bottom: -1.4em; + right: 0.75rem; padding: 0 0.5rem; line-height: 1.5; font-weight: bold; pointer-events: none; - font-family: "Pangolin", cursive; font-weight: 400; font-style: normal; + border-radius: 0 0 0.5rem 0.5rem; } [data-boundary-page]::after { top: 250px; @@ -55,339 +39,58 @@ function setBasicStyles() { transform-origin: 0 0; content: attr(data-boundary-page); } -[data-boundary=explore]::after, -[data-boundary-page=explore]::after { - background-color: ${config.explore.stroke}; - color: white; + +[data-boundary]::before, +[data-boundary-page]::before { + inset: 0; + position: absolute; + content: ""; + border: 4px solid transparent; + border-radius: 1rem; + pointer-events: none; } -[data-boundary=decide]::after, -[data-boundary-page=decide]::after { - background-color: ${config.decide.stroke}; +[data-boundary-page]::before { + --visible-width: 0.5rem; + --inner-radius: 1rem; + --inset: -2rem; + --outer-radius: calc(var(--visible-width) + var(--inner-radius) - var(--inset)); + --border-width: calc(var(--visible-width) - var(--inset)); + border-width: var(--border-width); + border-radius: var(--outer-radius) var(--outer-radius) 0 0; + border-bottom-width: var(--visible-width); + top: calc(-1rem + var(--inset)); + bottom: var(--inset); + left: var(--inset); + right: var(--inset); } -[data-boundary=checkout]::after, -[data-boundary-page=checkout]::after { - background: ${config.checkout.stroke}; +${["explore", "decide", "checkout"] + .map( + (team) => ` +[data-boundary=${team}]::before, +[data-boundary-page=${team}]::before { + border-color: ${teamColors[team].stroke}; } - -html:not(.showBoundaries) [data-boundary], -html:not(.showBoundaries) [data-boundary-page] { - background-image: none !important; +[data-boundary=${team}]::after, +[data-boundary-page=${team}]::after { + background-color: ${teamColors[team].stroke}; + color: ${teamColors[team].text}; } -html:not(.showBoundaries) [data-boundary]:after, -html:not(.showBoundaries) [data-boundary-page]:after{ +`, + ) + .join("")} + +html:not(.showBoundaries) [data-boundary]::before, +html:not(.showBoundaries) [data-boundary-page]::before { display: none; } -html.showBoundaries img { - mix-blend-mode: multiply; +html:not(.showBoundaries) [data-boundary]::after, +html:not(.showBoundaries) [data-boundary-page]::after{ + display: none; } `; document.head.appendChild(style); } -/** - * Generates a rounded rectangle SVG path. - * @param {object} options - The options for generating the rounded rectangle. - * @param {number} options.x - The x-coordinate of the top-left corner of the rectangle. - * @param {number} options.y - The y-coordinate of the top-left corner of the rectangle. - * @param {number} options.width - The width of the rectangle. - * @param {number} options.height - The height of the rectangle. - * @param {number} options.borderRadius - The border radius of the rectangle. - * @param {number} options.segmentLength - The length of each line segment. - * @returns {string} The SVG path representing the rounded rectangle. - */ -function generateRoundedRectangle({ - x, - y, - width, - height, - borderRadius, - segmentLength, -}) { - const maxRadius = Math.min(width / 2, height / 2); - borderRadius = Math.min(borderRadius, maxRadius); - - /** - * Generates line segments between two points. - * @param {number} startX - The x-coordinate of the starting point. - * @param {number} startY - The y-coordinate of the starting point. - * @param {number} endX - The x-coordinate of the ending point. - * @param {number} endY - The y-coordinate of the ending point. - * @param {number} segmentLength - The length of each line segment. - * @returns {string} The points representing the line segments. - */ - function generateLineSegments(startX, startY, endX, endY, segmentLength) { - let points = ""; - const dx = endX - startX; - const dy = endY - startY; - const distance = Math.sqrt(dx * dx + dy * dy); - const steps = Math.floor(distance / segmentLength); - const stepX = dx / steps; - const stepY = dy / steps; - - for (let i = 1; i <= steps; i++) { - const nextX = startX + stepX * i; - const nextY = startY + stepY * i; - points += `L${nextX},${nextY} `; - } - - return points; - } - - const pathData = [ - `M${x + borderRadius},${y}`, - generateLineSegments( - x + borderRadius, - y, - x + width - borderRadius, - y, - segmentLength, - ), - `Q${x + width},${y} ${x + width},${y + borderRadius}`, - generateLineSegments( - x + width, - y + borderRadius, - x + width, - y + height - borderRadius, - segmentLength, - ), - `Q${x + width},${y + height} ${x + width - borderRadius},${y + height}`, - generateLineSegments( - x + width - borderRadius, - y + height, - x + borderRadius, - y + height, - segmentLength, - ), - `Q${x},${y + height} ${x},${y + height - borderRadius}`, - generateLineSegments( - x, - y + height - borderRadius, - x, - y + borderRadius, - segmentLength, - ), - `Q${x},${y} ${x + borderRadius},${y}`, - "Z", - ]; - - return pathData.join(" "); -} - -/** - * Writes the SVG node to the cache for the given boundary, width, and height. - * @param {SVGElement} svgNode - The SVG node to be cached. - * @param {string} pathId - The boundary identifier. - * @param {number} width - The width of the boundary. - * @param {number} height - The height of the boundary. - */ -function writeBoundaryToCache(svgNode, pathId, width, height) { - const serializer = new XMLSerializer(); - const svgStr = serializer.serializeToString(svgNode); - const entry = { width, height, svg: svgStr }; - window.sessionStorage.setItem(pathId, JSON.stringify(entry)); -} - -/** - * Reads the SVG string from the cache for the given boundary, width, and height. - * @param {string} pathId - The boundary identifier. - * @param {number} width - The width of the boundary. - * @param {number} height - The height of the boundary. - * @returns {SVGElement|null} - The parsed SVG element or null if not found or dimensions don't match. - */ -function readBoundaryFromCache(pathId, width, height) { - const svgStr = window.sessionStorage.getItem(pathId); - if (!svgStr) { - return null; - } - const entry = JSON.parse(svgStr); - const tolerance = 30; - if ( - Math.abs(entry.width - width) >= tolerance || - Math.abs(entry.height - height) >= tolerance - ) { - return null; - } - const parser = new window.DOMParser(); - return parser.parseFromString(entry.svg, "image/svg+xml").firstChild; -} - -/** - * Sets the CSS background for the given boundary using the SVG node. - * @param {SVGElement} svgNode - The SVG node. - * @param {string} path - The CSS path. - * @param {string} pathId - The boundary identifier. - */ -function setCssBackground(svgNode, path, pathId) { - const serializer = new XMLSerializer(); - const svgStr = serializer.serializeToString(svgNode); - const encodedSvg = encodeURIComponent(svgStr); - const url = `url("data:image/svg+xml,${encodedSvg}")`; - - let style = document.getElementById(pathId); - if (!style) { - style = document.createElement("style"); - style.id = pathId; - document.head.appendChild(style); - } - style.innerHTML = `${path} { background-image: ${url}; }`; -} - -/** - * Generates a white background for the given rectangle. - * @param {string} rectangle - The rectangle coordinates. - * @returns {SVGElement} The generated white background SVG element. - */ -function generateWhiteBackground(rectangle) { - const bgNode = document.createElementNS("http://www.w3.org/2000/svg", "path"); - bgNode.setAttribute("d", rectangle); - bgNode.setAttribute("fill", "white"); - return bgNode; -} - -/** - * Generates a short hash for the given selector. - * @param {string} selector - The selector to generate the hash for. - * @returns {string} The generated hash. - */ -function generateHash(selector) { - // eslint-disable-next-line jsdoc/require-jsdoc - function simpleHash(str) { - let hash = 0; - for (let i = 0; i < str.length; i++) { - hash = (hash << 5) - hash + str.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - return hash; - } - - // eslint-disable-next-line jsdoc/require-jsdoc - function toHex(hash) { - return ("00000000" + (hash >>> 0).toString(16)).slice(-8); - } - - return "id-" + toHex(simpleHash(selector)); -} - -/** - * Generates a boundary for the given SVG element. - * @param {SVGElement} svg - The SVG element. - * @param {string} rectangle - The rectangle coordinates. - * @param {string} team - The team name. - * @param {boolean} isPage - Indicates if it's a page boundary. - * @returns {string} The generated boundary. - */ -function generateBoundary(svg, rectangle, team, isPage) { - const rc = roughjs.svg(svg); - return rc.path(rectangle, { - bowing: 0.5, - disableMultiStroke: true, - //fill: config[team].fill, - //fillStyle: "hachure", - //fillWeight: 1.5, - //hachureAngle: config[team].hachureAngle, - //hachureGap: 12, - preserveVertices: true, - roughness: isPage ? 5 : 3, - stroke: config[team].stroke, - strokeLineDash: null, - strokeWidth: isPage ? 20 : 3, - }); -} - -/** - * Generate a unique CSS selector path for a given element - * @param {Element} element - The element to generate the path for - * @returns {string} - The unique CSS path - */ -function generateUniqueCSSPath(element) { - if (!(element instanceof Element)) return ""; - - let path = []; - - while (element) { - let selector = element.nodeName.toLowerCase(); - - if (element.id) { - selector += `#${element.id}`; - path.unshift(selector); - break; - } else { - let sibling = element; - let nth = 1; - - while (sibling.previousElementSibling) { - sibling = sibling.previousElementSibling; - if (sibling.nodeName.toLowerCase() === selector) nth++; - } - - if (nth !== 1) selector += `:nth-of-type(${nth})`; - } - - path.unshift(selector); - element = element.parentElement; - } - - return path.join(" > "); -} - -/** - * Generates a rough boundary for the given element. - * @param {HTMLElement} el - The element to generate the boundary for. - */ -function generateRoughBoundary(el) { - const clientRect = el.getBoundingClientRect(); - const width = Math.round(clientRect.width); - const height = Math.round(clientRect.height); - - const path = generateUniqueCSSPath(el); - const isPage = el.hasAttribute("data-boundary-page"); - const team = isPage - ? el.getAttribute("data-boundary-page") - : el.getAttribute("data-boundary"); - - // basic shape and position of the boundary - const inset = isPage ? -2 : 10; - const rectangle = generateRoundedRectangle({ - x: inset, - y: inset, - width: width - 2 * inset, - height: height - 2 * inset, - borderRadius: 10, - segmentLength: 150, - }); - - // svg document - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - svg.setAttribute("width", width); - svg.setAttribute("height", height); - svg.setAttribute("preserveAspectRatio", "none"); - - // white background - svg.appendChild(generateWhiteBackground(rectangle)); - - // rough rectangle - let pathId = generateHash(path); - let node = readBoundaryFromCache(pathId, width, height); - if (!node) { - node = generateBoundary(svg, rectangle, team, isPage); - writeBoundaryToCache(node, pathId, width, height); - } - svg.appendChild(node); - - // apply to DOM - setCssBackground(svg, path, pathId); -} - -/** - * Generate rough boundaries for all elements with the data-boundary attribute. - */ -function generateRoughBoundaries() { - const boundaries = document.querySelectorAll( - "[data-boundary], [data-boundary-page]", - ); - [...boundaries].forEach(generateRoughBoundary); -} - /** * Toggle the boundaries based on the active state. * @param {boolean} active - The active state of the boundaries. @@ -395,11 +98,6 @@ function generateRoughBoundaries() { function toggleBoundaries(active) { document.documentElement.classList.toggle("showBoundaries", active); window.localStorage.setItem("showBoundaries", active); - - if (!active) { - return; - } - generateRoughBoundaries(); } /** @@ -516,12 +214,59 @@ function showToggleButton() { } /** - * initialize + * Function to copy styles to shadow root. + * @param {ShadowRoot} shadowRoot - The shadow root. */ +function copyStylesToShadow(shadowRoot) { + const style = document.getElementById("boundaries"); + if (style && shadowRoot && shadowRoot.getElementById("boundaries") === null) { + shadowRoot.appendChild(style.cloneNode(true)); + } +} +/** + * Function to handle existing shadow roots. + */ +function handleExistingShadowRoots() { + document.querySelectorAll("*").forEach((el) => { + if (el.shadowRoot) { + copyStylesToShadow(el.shadowRoot); + } + }); +} + +/** + * Function to watch for shadow roots. + */ +function watchForShadowRoots() { + // Observe new elements with shadow DOM + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.shadowRoot) { + copyStylesToShadow(node.shadowRoot); + } + // Check for custom elements added to the DOM tree + if (node.querySelectorAll) { + node.querySelectorAll("*").forEach((el) => { + if (el.shadowRoot) { + copyStylesToShadow(el.shadowRoot); + } + }); + } + }); + }); + }); + observer.observe(document.documentElement, { + childList: true, + subtree: true, + }); +} + +/** + * initialize + */ setBasicStyles(); showToggleButton(); -window.addEventListener("resize", () => { - window.requestAnimationFrame(generateRoughBoundaries); -}); -window.addEventListener("click", generateRoughBoundaries); +handleExistingShadowRoots(); +watchForShadowRoots(); diff --git a/src/checkout/components/AddToCart.css b/src/checkout/components/AddToCart.css index d109a23..b646e6e 100644 --- a/src/checkout/components/AddToCart.css +++ b/src/checkout/components/AddToCart.css @@ -1,6 +1,6 @@ .c_AddToCart { padding: 1rem; - margin: 0 -1rem; + margin: 0 -1rem 1rem; } .c_AddToCart__information {