Skip to content

Commit

Permalink
🖍️ improve outline stability
Browse files Browse the repository at this point in the history
  • Loading branch information
naltatis committed Jun 10, 2024
1 parent 7603b9f commit 852ce57
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 49 deletions.
145 changes: 114 additions & 31 deletions public/cdn/js/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ const config = {
explore: {
fill: "rgba(255, 90, 84, 0.1)",
stroke: "rgba(255, 90, 84, 1)",
hachureAngle: 30,
},
decide: {
fill: "rgba(84, 255, 144, 0.1)",
stroke: "rgba(84, 255, 144, 1)",
hachureAngle: 60,
},
checkout: {
fill: "rgba(255, 222, 84, 0.1)",
stroke: "rgba(255, 222, 84, 1)",
hachureAngle: 90,
},
};

Expand All @@ -27,12 +24,14 @@ function setBasicStyles() {
style.innerHTML = `
@import url('https://fonts.googleapis.com/css2?family=Pangolin&display=swap');
[data-boundary] {
[data-boundary],
[data-boundary-page] {
position: relative;
background-size: 100% 100%;
background-repeat: no-repeat;
}
[data-boundary]::after {
[data-boundary]::after,
[data-boundary-page]::after {
display: block;
content: attr(data-boundary);
position: absolute;
Expand All @@ -47,21 +46,40 @@ function setBasicStyles() {
font-weight: 400;
font-style: normal;
}
[data-boundary$="-page"]::after {
[data-boundary-page]::after {
top: 250px;
left: 0rem;
bottom: auto;
right: auto;
transform: rotate(-90deg);
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=decide]::after,
[data-boundary-page=decide]::after {
background-color: ${config.decide.stroke};
}
[data-boundary=checkout]::after,
[data-boundary-page=checkout]::after {
background: ${config.checkout.stroke};
}
[data-boundary^="explore-"]::after { background-color: ${config.explore.stroke}; color: white }
[data-boundary^="decide-"]::after { background-color: ${config.decide.stroke}; }
[data-boundary^="checkout-"]::after { background: ${config.checkout.stroke}; }
html:not(.showBoundaries) [data-boundary] { background-image: none !important;}
html:not(.showBoundaries) [data-boundary]:after { display: none; }
html.showBoundaries img { mix-blend-mode: multiply; }
html:not(.showBoundaries) [data-boundary],
html:not(.showBoundaries) [data-boundary-page] {
background-image: none !important;
}
html:not(.showBoundaries) [data-boundary]:after,
html:not(.showBoundaries) [data-boundary-page]:after{
display: none;
}
html.showBoundaries img {
mix-blend-mode: multiply;
}
`;
document.head.appendChild(style);
}
Expand Down Expand Up @@ -158,26 +176,26 @@ function generateRoundedRectangle({
/**
* 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} boundary - The boundary identifier.
* @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, boundary, width, height) {
function writeBoundaryToCache(svgNode, pathId, width, height) {
const serializer = new XMLSerializer();
const svgStr = serializer.serializeToString(svgNode);
const entry = { width, height, svg: svgStr };
window.sessionStorage.setItem(`boundary-${boundary}`, JSON.stringify(entry));
window.sessionStorage.setItem(pathId, JSON.stringify(entry));
}

/**
* Reads the SVG string from the cache for the given boundary, width, and height.
* @param {string} boundary - The boundary identifier.
* @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(boundary, width, height) {
const svgStr = window.sessionStorage.getItem(`boundary-${boundary}`);
function readBoundaryFromCache(pathId, width, height) {
const svgStr = window.sessionStorage.getItem(pathId);
if (!svgStr) {
return null;
}
Expand All @@ -195,23 +213,23 @@ function readBoundaryFromCache(boundary, width, height) {

/**
* Sets the CSS background for the given boundary using the SVG node.
* @param {string} boundary - The boundary identifier.
* @param {SVGElement} svgNode - The SVG node.
* @param {string} path - The CSS path.
* @param {string} pathId - The boundary identifier.
*/
function setCssBackground(boundary, svgNode) {
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}")`;

const id = `${boundary}-style`;
let style = document.getElementById(id);
let style = document.getElementById(pathId);
if (!style) {
style = document.createElement("style");
style.id = id;
style.id = pathId;
document.head.appendChild(style);
}
style.innerHTML = `[data-boundary="${boundary}"] { background-image: ${url}; }`;
style.innerHTML = `${path} { background-image: ${url}; }`;
}

/**
Expand All @@ -226,6 +244,30 @@ function generateWhiteBackground(rectangle) {
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.
Expand All @@ -252,6 +294,42 @@ function generateBoundary(svg, rectangle, team, isPage) {
});
}

/**
* 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.
Expand All @@ -261,9 +339,11 @@ function generateRoughBoundary(el) {
const width = Math.round(clientRect.width);
const height = Math.round(clientRect.height);

const boundary = el.dataset.boundary;
const team = boundary.split("-")[0];
const isPage = boundary.endsWith("-page");
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;
Expand All @@ -286,22 +366,25 @@ function generateRoughBoundary(el) {
svg.appendChild(generateWhiteBackground(rectangle));

// rough rectangle
let node = readBoundaryFromCache(boundary, width, height);
let pathId = generateHash(path);
let node = readBoundaryFromCache(pathId, width, height);
if (!node) {
node = generateBoundary(svg, rectangle, team, isPage);
writeBoundaryToCache(node, boundary, width, height);
writeBoundaryToCache(node, pathId, width, height);
}
svg.appendChild(node);

// apply to DOM
setCssBackground(boundary, svg);
setCssBackground(svg, path, pathId);
}

/**
* Generate rough boundaries for all elements with the data-boundary attribute.
*/
function generateRoughBoundaries() {
const boundaries = document.querySelectorAll("[data-boundary]");
const boundaries = document.querySelectorAll(
"[data-boundary], [data-boundary-page]",
);
[...boundaries].forEach(generateRoughBoundary);
}

Expand Down
2 changes: 1 addition & 1 deletion src/checkout/components/AddToCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default ({ sku }) => {
action="/checkout/cart/add"
method="POST"
class="c_AddToCart"
data-boundary="checkout-button"
data-boundary="checkout"
>
<input type="hidden" name="sku" value="${sku}" />
<div class="c_AddToCart__information">
Expand Down
2 changes: 1 addition & 1 deletion src/checkout/components/MiniCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Button from "./Button.js";
export default ({ c }) => {
const lineItems = readFromCookie(c);
const quantity = lineItems.reduce((t, { quantity }) => t + quantity, 0);
return html`<div class="c_MiniCart" data-boundary="checkout-minicart">
return html`<div class="c_MiniCart" data-boundary="checkout">
${Button({
variant: "secondary",
rounded: true,
Expand Down
2 changes: 1 addition & 1 deletion src/checkout/components/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default ({ content }) => {
<link rel="stylesheet" href="/checkout/static/styles.css" />
${Meta()}
</head>
<body data-boundary="checkout-page">
<body data-boundary-page="checkout">
${content}
<script src="/explore/static/scripts.js" type="module"></script>
<script src="/decide/static/scripts.js" type="module"></script>
Expand Down
2 changes: 1 addition & 1 deletion src/decide/pages/ProductPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default ({ id, sku, c }) => {
<link rel="stylesheet" href="/checkout/static/styles.css" />
${Meta()}
</head>
<body data-boundary="decide-page">
<body data-boundary-page="decide">
${Header({ c })}
<main class="d_ProductPage">
<div class="d_ProductPage__details">
Expand Down
2 changes: 1 addition & 1 deletion src/explore/components/Footer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { html, IMAGE_SERVER } from "../utils.js";

export default () => {
return html`<footer class="e_Footer" data-boundary="explore-footer">
return html`<footer class="e_Footer" data-boundary="explore">
<div class="e_Footer__cutter">
<div class="e_Footer__inner">
<div class="e_Footer__initiative">
Expand Down
2 changes: 1 addition & 1 deletion src/explore/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { html, IMAGE_SERVER } from "../utils.js";
* @returns {string} The Header component markup.
*/
export default ({ c }) => {
return html`<header class="e_Header" data-boundary="explore-header">
return html`<header class="e_Header" data-boundary="explore">
<div class="e_Header__cutter">
<div class="e_Header__inner">
<a class="e_Header__link" href="/">
Expand Down
5 changes: 1 addition & 4 deletions src/explore/components/Recommendations.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,7 @@ function recosForSkus(skus, length = 4) {
export default ({ skus }) => {
const recos = recosForSkus(skus);
return recos.length
? html`<div
class="e_Recommendations"
data-boundary="explore-recommendations"
>
? html`<div class="e_Recommendations" data-boundary="explore">
<h2>Recommendations</h2>
<ul class="e_Recommendations_list">
${recos.map(Recommendation).join("")}
Expand Down
7 changes: 2 additions & 5 deletions src/explore/components/StorePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ import Button from "./Button.js";

export default () => {
return html`<div class="e_StorePicker">
<div class="e_StorePicker_control" data-boundary="explore-storepicker">
<div class="e_StorePicker_control" data-boundary="explore">
<div class="e_StorePicker_selected"></div>
${Button({
className: "e_StorePicker_choose",
type: "button",
children: "choose a store",
})}
</div>
<dialog
class="e_StorePicker_dialog"
data-boundary="explore-storepicker (dialog)"
>
<dialog class="e_StorePicker_dialog" data-boundary="explore">
<div class="e_StorePicker_wrapper">
<h2>Stores</h2>
<ul class="e_StorePicker_list">
Expand Down
2 changes: 1 addition & 1 deletion src/explore/pages/CategoryPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default ({ category, c }) => {
<link rel="stylesheet" href="/checkout/static/styles.css" />
${Meta()}
</head>
<body data-boundary="explore-page">
<body data-boundary-page="explore">
${Header({ c })}
<main class="e_CategoryPage">
<h2>${title}</h2>
Expand Down
2 changes: 1 addition & 1 deletion src/explore/pages/HomePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default ({ c }) => {
<link rel="stylesheet" href="/checkout/static/styles.css" />
${Meta()}
</head>
<body data-boundary="explore-page">
<body data-boundary-page="explore">
${Header({ c })}
<main class="e_HomePage">
${data.teaser
Expand Down
2 changes: 1 addition & 1 deletion src/explore/pages/StoresPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default ({ c }) => {
<link rel="stylesheet" href="/checkout/static/styles.css" />
${Meta()}
</head>
<body data-boundary="explore-page">
<body data-boundary-page="explore">
${Header({ c })}
<main class="e_StoresPage">
<h2>Our Stores</h2>
Expand Down

0 comments on commit 852ce57

Please sign in to comment.