const { computePosition, shift, flip, offset, autoUpdate } = window.FloatingUIDOM;
async function clearForMeGetIngredients(sku) {
const url = `https://clickable-api.clearforme.com/api/app/products/details/v2?clientname=credo&sku=${sku}`;
const headers = {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YzAxMjk1ODhmN2M0MjJiYThmZDlkYjAiLCJzdG9yZVVybCI6Imh0dHBzOi8vY3JlZG9iZWF1dHkuY29tL3Byb2R1Y3RzLyIsImlhdCI6MTY5MTE2NzgxN30.Sn94-NFiRt1A_YKPc7_-7tD_HOWu0sNKQKr8sO-UfVg'
};
const response = await fetch(url, {headers});
if (response.status !== 200) return null;
return await response.json();
}
async function clearForMeGetIngredientDetails(cfmIngredientId, productIngredientId) {
const url = `https://clickable-api.clearforme.com/api/app/ingredients/definition/v2?clientname=credo&productIngredientId=${productIngredientId}&cfmIngredientId=${cfmIngredientId}`;
const headers = {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YzAxMjk1ODhmN2M0MjJiYThmZDlkYjAiLCJzdG9yZVVybCI6Imh0dHBzOi8vY3JlZG9iZWF1dHkuY29tL3Byb2R1Y3RzLyIsImlhdCI6MTY5MTE2NzgxN30.Sn94-NFiRt1A_YKPc7_-7tD_HOWu0sNKQKr8sO-UfVg'
};
const response = await fetch(url, { headers });
if (response.status !== 200) return null;
return await response.json();
}
function trackButtonForTooltip(button, tooltipSelector, elementName) {
const tooltip = document.querySelector(tooltipSelector);
if (!button || !tooltip) {
return
}
const offsetAmount = 6.5;
const updatePosition = () => {
computePosition(button, tooltip, {
placement: 'bottom',
middleware: [
flip(),
shift(),
offset(offsetAmount)
]
}).then(({ x, y, placement, middlewareData }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
});
document.addEventListener(`modal-event-${elementName}-close`, (e) => {
tooltip.style.display = 'none';
tooltip.style.visibility = 'hidden';
});
document.addEventListener(`modal-event-${elementName}-open`, (e) => {
tooltip.style.display = 'block';
tooltip.style.visibility = 'visible';
});
};
let updateCleanup;
// Calculate position initially so that it can be displayed in the right position programatically from other events
updateCleanup = autoUpdate(button, tooltip, updatePosition)
button.addEventListener('click', (e) => {
updateCleanup = autoUpdate(button, tooltip, updatePosition);
requestAnimationFrame(async () => {
// Tooltip logic
const toolTipIsDisabled = tooltip.classList.contains('disabled');
if (!toolTipIsDisabled){
let closeList = ['#support-tooltip', '#chat-unavailable-tooltip', '#chat-unavailable-tooltip', '#account-login-tooltip', 'cart-drawer'];
let fCloseList = closeList.filter(m => m !== elementName);
for (var i = fCloseList.length - 1; i >= 0; i--) {
document.querySelector(fCloseList[i]).style.display = 'none';
document.querySelector(fCloseList[i]).style.visibility = 'hidden';
}
tooltip.style.display = 'block';
tooltip.style.visibility = 'visible';
}
// Force loading state
tooltip.classList.add('tooltip-is-loading');
const detailsDiv = tooltip.querySelector('.ingredient-details');
const title = tooltip.querySelector('.ingredient-title');
title.textContent = '';
detailsDiv.innerHTML = "
";
// Fetch details
const cfmIngredientId = button.getAttribute('data-cfm');
const productIngredientId = button.getAttribute('data-product');
const details = await clearForMeGetIngredientDetails(cfmIngredientId, productIngredientId);
// Remove loading state
tooltip.classList.remove('tooltip-is-loading');
// Update title
title.textContent = details.ingredientName || '';
// Remove loading state
detailsDiv.innerHTML = '';
if (details.definition) {
// Insert 'What it is' and its description
const whatIsItEyebrow = document.createElement('p');
whatIsItEyebrow.className = 'ingredient-eyebrow';
whatIsItEyebrow.textContent = 'What it is';
detailsDiv.appendChild(whatIsItEyebrow);
const whatIsItText = document.createElement('p');
whatIsItText.className = 'ingredient-details-text';
whatIsItText.textContent = details.definition;
detailsDiv.appendChild(whatIsItText);
}
if (details.functions && Array.isArray(details.functions) && details.functions.length > 0) {
const functionEyebrow = document.createElement('p');
functionEyebrow.className = 'ingredient-eyebrow';
functionEyebrow.textContent = 'Purpose';
detailsDiv.appendChild(functionEyebrow);
const functionText = document.createElement('p');
functionText.className = 'ingredient-details-text';
functionText.textContent = details.functions.map(f => f.attribute).join(', ');
detailsDiv.appendChild(functionText);
}
if (details.commonNames && Array.isArray(details.commonNames) && details.commonNames.length > 0) {
const commonNameEyebrow = document.createElement('p');
commonNameEyebrow.className = 'ingredient-eyebrow';
commonNameEyebrow.textContent = 'Commonly Called';
detailsDiv.appendChild(commonNameEyebrow);
const commonNameText = document.createElement('p');
commonNameText.className = 'ingredient-details-text';
commonNameText.textContent = details.commonNames.join(', ');
detailsDiv.appendChild(commonNameText);
}
document.addEventListener('mousemove', trackMouse);
});
});
const trackMouse = (e) => {
const buttonRect = button.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
const isWithinExtendedRect = e.clientX > tooltipRect.left && e.clientX < tooltipRect.right && e.clientY > buttonRect.top && e.clientY < tooltipRect.bottom;
if (!isWithinExtendedRect) {
tooltip.style.display = 'none';
tooltip.style.visibility = 'hidden';
document.removeEventListener('mousemove', trackMouse);
if (updateCleanup) {
updateCleanup();
updateCleanup = null;
}
}
};
}
class ProductIngredients extends HTMLElement {
constructor() {
super();
}
async connectedCallback() {
const sku = this.getAttribute('product-sku');
const data = await clearForMeGetIngredients(sku);
if (data && data['productIngredients'] && data['productIngredients'][0]['ingredientGroup']) {
this.renderIngredients(data['productIngredients'], data['productDetails']['additionalDetails']);
} else {
// if CFM failed check if we have backup data and try rendering that
const backupData = document.querySelector('#productIngredientBackupValue')
if (backupData) {
this.innerHTML = backupData.innerHTML;
// remove the "click to learn more" text since it doesn't work
const clickForMore = document.querySelector('#ingredientsClickForMoreText')
if (clickForMore) {
clickForMore.style.display = 'none';
}
} else {
// bail
this.innerHTML = 'Error loading ingredients.';
}
}
}
async renderIngredients(productIngredients, additionalDetails) {
let ingredientDivs = '';
for (let i = 0; i < productIngredients.length; i++) {
if (!Array.isArray(productIngredients[i]['ingredientGroup']) || !productIngredients[i]['ingredientGroup'].length) {
continue;
}
const ingredientGroupDivs = productIngredients[i]['ingredientGroup'].map(ingredientGroup => {
const toTitleCase = (str) => {
return str.toLowerCase().replace(/(^|\s|-)(.)/g, (match, separator, letter) => {
return separator + letter.toUpperCase();
});
};
// Using ingredientGroupName as the title
console.log(ingredientGroup);
const titleCaseGroupName = toTitleCase(ingredientGroup.ingredientGroupName);
const ingredientHeader = ``;
const ingredientSpans = ingredientGroup.ingredients.map(item => {
const name = item.specialCharacter ? `${item.ingredientName}${item.specialCharacter}` : item.ingredientName;
return `
${name}`;
}).join(', ');
return `
${ingredientHeader}${ingredientSpans}
`;
}).join('');
ingredientDivs += ingredientGroupDivs;
}
const additionalInfo = additionalDetails ? `
${additionalDetails}` : '';
this.innerHTML = `${ingredientDivs}${additionalInfo}`;
// Attach click listeners
this.querySelectorAll('.clickable-ingredient').forEach(span => {
trackButtonForTooltip(span, '#ingredient-tooltip');
});
}
}
customElements.define('product-ingredients', ProductIngredients);
https://credobeauty.com/products/sun-fragrance-body-mist
6854676349041
SUN Hair and Body Fragrance Mist
48.00
//credobeauty.com/cdn/shop/files/SUN_Fragrance_Body_Mist_01.png?v=1713583869
//credobeauty.com/cdn/shop/files/SUN_Fragrance_Body_Mist_01_large.png?v=1713583869
USD
OutOfStock
fragrance, fragrances, body sprays
278478979185
AAPI-Founded Beauty
283618082929
All brands (minus osea and gift cards)
99648700529
All Clean Beauty
275862061169
All products (no rewards)
282180485233
All products except for Deep Thirst Moisturizer
282200309873
All products except fragrance
161582841969
BIPOC Brands
273801871473
Body Sprays
266896769137
Citrus Perfume
440347404
Clean Fragrances & Perfumes
242266054
Ellis Brooklyn
436490892
Fragrances
100484808817
Vegan Beauty Products
277132050545
Women Founded Brands
SUN Fragrance Body Mist
This product is: a solar, fruity, flirty fragrance body mist.
Why we love it: Ellis Brooklyn SEA Fragrance Body Mist is the life of the beach party captured in a scent as joyful and playful as an afternoon in the sunshine.
"Everything good, everything magical happens between the months of June and August." — Jenny Han, The Summer I Turned Pretty
Fragrance Notes:
Top: Italian Mandarin, Sweet Clementine, Lemon Sfuma
Mid: Orange Flower Water, Williams Pear Blossom, Vanilla
Dry: Cedarwood, Upcycled Musk, Ambrox Super
Ellis Brooklyn
AAPI-Founded Beauty
allproducts
body sprays
citrus
FOC in-store haul
fragrance
Shop By Ingredient_Vegan
Shop By Scent_Citrus
Shop By Skin Type_All
Vegan
women founded
add-to-cart
//credobeauty.com/cdn/shop/files/SUN_Fragrance_Body_Mist_Lifestyle_02.jpg?v=1713677582
//credobeauty.com/cdn/shop/files/SUN_Fragrance_Body_Mist_Lifestyle_03.jpg?v=1713677112
//credobeauty.com/cdn/shop/files/SUN_Fragrance_Body_Mist_Lifestyle_04.jpg?v=1713676703
//credobeauty.com/cdn/shop/files/SUN_Fragrance_Body_Mist_Lifestyle_05.jpg?v=1713677653
40170115203185
100 ml
48.00
//credobeauty.com/cdn/shop/files/SUN_Fragrance_Body_Mist_01.png?v=1713583869
https://credobeauty.com/products/sun-fragrance-body-mist?variant=40170115203185
OutOfStock
100 ml