Currently, I have a cleaning cost calculator that estimates the price based on the home’s size, number of bedrooms, bathrooms, and other parameters. After entering all the details, the client receives a cost estimate. However, in most cases, clients also want to know how long the cleaning will take. I would like to add a feature that allows displaying two results in the results section: one for the price estimate and another for the time estimate.
Greetings, @Harry_Vonavi ![]()
I see that currently both results (price and time) are displayed in the Results section:
Does it look the way you need, or maybe you wanted to display both results in 1 line?
I didn’t find calculator where I can add time field in normal format like minutes and hours without dot between numbers and it’s pretty complicated to find right formula to get even this result that I have right now.
Thanks for clarification!
I completely understand your point and agree that the built-in time calculation feature would make things much simpler.
I’ve moved your idea back to the Wishlist. If more users support this idea, it might be considered in the future.
In the meantime, our devs confirmed that it’s possible to add hours/minutes captions right after the numbers with a custom code.
Your request is in the hands of our devs now. I’ll update you once the solution is ready ![]()
Thanks
Thank you for waiting!
To add the “hours” caption, please add this script to the Custom JS field on the Settings tab of your widget’s settings:
(() => {
const RESULT_SELECTOR = '[class*="result-primary__Value-sc"]';
const ANIMATED_NUMBER_SELECTOR = '[class*="animated-number__Content-sc"]';
const transformText = (text) => (text ? text.replace(/\./g, ' hours ') : text);
function createOverlay(targetElement) {
const parentElement = targetElement.parentElement;
if (!parentElement) return null;
const parentStyles = window.getComputedStyle(parentElement);
if (parentStyles.position === 'static') parentElement.style.position = 'relative';
const overlayElement = document.createElement('span');
overlayElement.className = 'user-overlay-numeric';
overlayElement.style.position = 'absolute';
overlayElement.style.inset = '0';
overlayElement.style.display = 'inline-flex';
overlayElement.style.alignItems = 'center';
overlayElement.style.justifyContent = 'left';
overlayElement.style.pointerEvents = 'none';
overlayElement.style.whiteSpace = 'nowrap';
overlayElement.style.overflow = 'hidden';
overlayElement.style.textOverflow = 'ellipsis';
overlayElement.style.zIndex = '2147483647';
const targetStyles = window.getComputedStyle(targetElement);
['font', 'fontSize', 'fontFamily', 'fontWeight', 'lineHeight', 'letterSpacing', 'textAlign', 'padding', 'margin', 'boxSizing'].forEach(key => {
overlayElement.style[key] = targetStyles[key];
});
overlayElement.style.color = targetStyles.color;
overlayElement.style.backgroundColor = targetStyles.backgroundColor !== 'rgba(0, 0, 0, 0)' ? targetStyles.backgroundColor : '';
parentElement.appendChild(overlayElement);
targetElement.style.visibility = 'hidden';
return overlayElement;
}
function observeAnimatedElement(animatedElement) {
if (!animatedElement) return;
const overlay = createOverlay(animatedElement);
if (!overlay) return;
const observer = new MutationObserver(() => {
const updatedText = transformText(animatedElement.textContent || '');
overlay.textContent = updatedText;
});
observer.observe(animatedElement, {
childList: true,
subtree: true,
characterData: true
});
}
const waitForElement = (selector, root = document) =>
new Promise(resolve => {
const found = root.querySelector(selector);
if (found) return resolve(found);
const observer = new MutationObserver(() => {
const element = root.querySelector(selector);
if (element) {
resolve(element);
observer.disconnect();
}
});
observer.observe(root, {
childList: true,
subtree: true
});
});
waitForElement(RESULT_SELECTOR).then(() => {
const resultElements = Array.from(document.querySelectorAll(RESULT_SELECTOR));
const resultElement = resultElements[1];
if (!resultElement) return console.warn('result not found');
const animatedNumberElement = resultElement.querySelector(ANIMATED_NUMBER_SELECTOR) || resultElement;
observeAnimatedElement(animatedNumberElement);
});
})();
Custom JS scripts don’t function in the preview mode. So, you’ll be able to see it in action only on your website or through the Share Link
To add a “minutes” caption, just open the Format setting in your calculations and paste “minutes” into the Suffix field:
Please try it out and let me know if you like the result ![]()
it looks great, but only for regular type of cleaning. how I can apply js code to each type of cleaning?
Ah, sorry about that!
Our devs will review the code, and I’ll update you once it’s done ![]()
We’ve slightly adjusted the script in your widget:
(() => {
const RESULT_SELECTOR = '[class*="result-primary__Value-sc"]';
const ANIMATED_NUMBER_SELECTOR = '[class*="animated-number__Content-sc"]';
const transformText = (text) =>
text ? text.replace(/\./g, " hours ") : text;
function createOverlay(targetElement) {
const parentElement = targetElement.parentElement;
if (!parentElement) return null;
const parentStyles = window.getComputedStyle(parentElement);
if (parentStyles.position === "static")
parentElement.style.position = "relative";
const overlayElement = document.createElement("span");
overlayElement.className = "user-overlay-numeric";
overlayElement.style.position = "absolute";
overlayElement.style.inset = "0";
overlayElement.style.display = "inline-flex";
overlayElement.style.alignItems = "center";
overlayElement.style.justifyContent = "left";
overlayElement.style.pointerEvents = "none";
overlayElement.style.whiteSpace = "nowrap";
overlayElement.style.overflow = "hidden";
overlayElement.style.textOverflow = "ellipsis";
overlayElement.style.zIndex = "2147483647";
const targetStyles = window.getComputedStyle(targetElement);
[
"font",
"fontSize",
"fontFamily",
"fontWeight",
"lineHeight",
"letterSpacing",
"textAlign",
"padding",
"margin",
"boxSizing",
].forEach((key) => {
overlayElement.style[key] = targetStyles[key];
});
overlayElement.style.color = targetStyles.color;
overlayElement.style.backgroundColor =
targetStyles.backgroundColor !== "rgba(0, 0, 0, 0)" ? targetStyles.backgroundColor : "";
parentElement.appendChild(overlayElement);
targetElement.style.visibility = "hidden";
return overlayElement;
}
function observeAnimatedElement(animatedElement) {
if (!animatedElement) return;
const overlay = createOverlay(animatedElement);
if (!overlay) return;
const observer = new MutationObserver(() => {
const updatedText = transformText(animatedElement.textContent || "");
overlay.textContent = updatedText;
});
observer.observe(animatedElement, {
childList: true,
subtree: true,
characterData: true,
});
}
const waitForElement = (selector, root = document) =>
new Promise((res) => {
const observer = new MutationObserver(() => {
const element = root.querySelector(selector);
if (element) {
res(element);
observer.disconnect();
}
});
observer.observe(root, { childList: true, subtree: true });
});
const waitForResults = () => {
waitForElement(RESULT_SELECTOR).then(() => {
const resultElements = Array.from(
document.querySelectorAll(RESULT_SELECTOR),
);
const resultElement = resultElements[1];
if (!resultElement) return console.warn("result not found");
const animatedNumberElement =
resultElement.querySelector(ANIMATED_NUMBER_SELECTOR) || resultElement;
observeAnimatedElement(animatedNumberElement);
waitForResults();
});
};
waitForResults();
})();
Please check it out and let me know if it’s fine now ![]()
Okay! I’ll let you know once the devs share the final solution ![]()
We’ve added this code to the Custom JS field:
const isPrimary = true;
const PRIMARY = 'result-primary__Value-sc';
const SECONDARY = 'result-secondary__Value-sc';
(() => {
const RESULT_SELECTOR = `[class*="${isPrimary ? PRIMARY : SECONDARY}"]`;
const ANIMATED_NUMBER_SELECTOR = '[class*="animated-number__Content-sc"]';
const transformText = (text) =>
text ? text.replace(/\./g, " hrs ") : text;
function createOverlay(targetElement) {
if (!targetElement || targetElement.dataset.overlayCreated) return null; // <-- добавили проверку
const parentElement = targetElement.parentElement;
if (!parentElement) return null;
const parentStyles = window.getComputedStyle(parentElement);
if (parentStyles.position === "static")
parentElement.style.position = "relative";
const overlayElement = document.createElement("span");
overlayElement.className = "user-overlay-numeric";
overlayElement.style.position = "absolute";
overlayElement.style.inset = "0";
overlayElement.style.display = "inline-flex";
overlayElement.style.alignItems = "center";
overlayElement.style.justifyContent = "left";
overlayElement.style.pointerEvents = "none";
overlayElement.style.whiteSpace = "nowrap";
overlayElement.style.overflow = "hidden";
overlayElement.style.textOverflow = "ellipsis";
overlayElement.style.zIndex = "2147483647";
const targetStyles = window.getComputedStyle(targetElement);
[
"font",
"fontSize",
"fontFamily",
"fontWeight",
"lineHeight",
"letterSpacing",
"textAlign",
"padding",
"margin",
"boxSizing",
].forEach((key) => {
overlayElement.style[key] = targetStyles[key];
});
overlayElement.style.color = targetStyles.color;
overlayElement.style.backgroundColor =
targetStyles.backgroundColor !== "rgba(0, 0, 0, 0)" ? targetStyles.backgroundColor : "";
parentElement.appendChild(overlayElement);
targetElement.style.visibility = "hidden";
targetElement.dataset.overlayCreated = "true"; // <-- помечаем
return overlayElement;
}
function observeAnimatedElement(animatedElement) {
if (!animatedElement) return;
const overlay = createOverlay(animatedElement);
if (!overlay) return;
const observer = new MutationObserver(() => {
const updatedText = transformText(animatedElement.textContent || "");
overlay.textContent = updatedText;
});
observer.observe(animatedElement, {
childList: true,
subtree: true,
characterData: true,
});
}
const waitForElement = (selector, root = document) =>
new Promise((res) => {
const observer = new MutationObserver(() => {
const element = root.querySelector(selector);
if (element) {
res(element);
observer.disconnect();
}
});
observer.observe(root, {
childList: true,
subtree: true
});
});
const waitForResults = () => {
waitForElement(RESULT_SELECTOR).then(() => {
const resultElements = Array.from(
document.querySelectorAll(RESULT_SELECTOR),
);
const idx = isPrimary ? 1 : 0;
const resultElement = resultElements[idx];
if (!resultElement) return console.warn("result not found");
const animatedNumberElement =
resultElement.querySelector(ANIMATED_NUMBER_SELECTOR) || resultElement;
if (!animatedNumberElement.dataset.overlayCreated) {
observeAnimatedElement(animatedNumberElement);
}
waitForResults();
});
};
waitForResults();
})();
Please test it out and let me know if you like the result ![]()
Just to clear things up, at the moment you have only primary results in your widget. Would you like to change all of them to secondary? Or only some of them?
I’m not decided yet, just want to be sure that I have right JC code, if I decide to change it to secondary option
Got it!
If you decide to make all results secondary, this code should work for you ![]()
const isPrimary = false;
const PRIMARY = 'result-primary__Value-sc';
const SECONDARY = 'result-secondary__Value-sc';
(() => {
const RESULT_SELECTOR = `[class*="${isPrimary ? PRIMARY : SECONDARY}"]`;
const ANIMATED_NUMBER_SELECTOR = '[class*="animated-number__Content-sc"]';
const transformText = (text) =>
text ? text.replace(/\./g, " hrs ") : text;
function createOverlay(targetElement) {
if (!targetElement || targetElement.dataset.overlayCreated) return null; // <-- добавили проверку
const parentElement = targetElement.parentElement;
if (!parentElement) return null;
const parentStyles = window.getComputedStyle(parentElement);
if (parentStyles.position === "static")
parentElement.style.position = "relative";
const overlayElement = document.createElement("span");
overlayElement.className = "user-overlay-numeric";
overlayElement.style.position = "absolute";
overlayElement.style.inset = "0";
overlayElement.style.display = "inline-flex";
overlayElement.style.alignItems = "center";
overlayElement.style.justifyContent = "left";
overlayElement.style.pointerEvents = "none";
overlayElement.style.whiteSpace = "nowrap";
overlayElement.style.overflow = "hidden";
overlayElement.style.textOverflow = "ellipsis";
overlayElement.style.zIndex = "2147483647";
const targetStyles = window.getComputedStyle(targetElement);
[
"font",
"fontSize",
"fontFamily",
"fontWeight",
"lineHeight",
"letterSpacing",
"textAlign",
"padding",
"margin",
"boxSizing",
].forEach((key) => {
overlayElement.style[key] = targetStyles[key];
});
overlayElement.style.color = targetStyles.color;
overlayElement.style.backgroundColor =
targetStyles.backgroundColor !== "rgba(0, 0, 0, 0)" ? targetStyles.backgroundColor : "";
parentElement.appendChild(overlayElement);
targetElement.style.visibility = "hidden";
targetElement.dataset.overlayCreated = "true"; // <-- помечаем
return overlayElement;
}
function observeAnimatedElement(animatedElement) {
if (!animatedElement) return;
const overlay = createOverlay(animatedElement);
if (!overlay) return;
const observer = new MutationObserver(() => {
const updatedText = transformText(animatedElement.textContent || "");
overlay.textContent = updatedText;
});
observer.observe(animatedElement, {
childList: true,
subtree: true,
characterData: true,
});
}
const waitForElement = (selector, root = document) =>
new Promise((res) => {
const observer = new MutationObserver(() => {
const element = root.querySelector(selector);
if (element) {
res(element);
observer.disconnect();
}
});
observer.observe(root, {
childList: true,
subtree: true
});
});
const waitForResults = () => {
waitForElement(RESULT_SELECTOR).then(() => {
const resultElements = Array.from(
document.querySelectorAll(RESULT_SELECTOR),
);
const idx = isPrimary ? 1 : 0;
const resultElement = resultElements[idx];
if (!resultElement) return console.warn("result not found");
const animatedNumberElement =
resultElement.querySelector(ANIMATED_NUMBER_SELECTOR) || resultElement;
if (!animatedNumberElement.dataset.overlayCreated) {
observeAnimatedElement(animatedNumberElement);
}
waitForResults();
});
};
waitForResults();
})();
oh, so two different JC codes for two different settings
ok, got it, thanks for your help!
It’s my pleasure ![]()
2 posts were split to a new topic: Sync results on different pages




