Refer a Friend

Share the ritual.

Give your friends $15 off their first HERSCULPT order. Earn 200 points for every friend who joins the ritual.

01

Share your link

Found in your account dashboard. One link, infinite friends.

02

They shop with $%s off

The discount applies automatically at checkout. No code, no friction.

03

You earn your reward

200 points lands in your account when their order completes.

(function (window, document) { 'use strict'; var HS = window.HS = window.HS || {}; var R = window.HERSCULPT || {}; /* ----------------------------------------------------------------- * Tiny event bus pub sub for cross snippet communication * --------------------------------------------------------------- */ HS.bus = (function () { var listeners = {}; return { on: function (event, cb) { (listeners[event] = listeners[event] || []).push(cb); }, off: function (event, cb) { if (!listeners[event]) return; listeners[event] = listeners[event].filter(function (f) { return f !== cb; }); }, emit: function (event, payload) { (listeners[event] || []).forEach(function (cb) { try { cb(payload); } catch (e) { console.error(e); } }); } }; })(); /* ----------------------------------------------------------------- * Selectors and DOM helpers * --------------------------------------------------------------- */ HS.$ = function (sel, ctx) { return (ctx || document).querySelector(sel); }; HS.$$ = function (sel, ctx) { return Array.prototype.slice.call((ctx || document).querySelectorAll(sel)); }; HS.on = function (target, event, handler, options) { if (typeof target === 'string') target = document.querySelectorAll(target); if (target instanceof NodeList || Array.isArray(target)) { Array.prototype.forEach.call(target, function (el) { el.addEventListener(event, handler, options); }); } else if (target && target.addEventListener) { target.addEventListener(event, handler, options); } }; HS.delegate = function (root, event, selector, handler) { root.addEventListener(event, function (e) { var target = e.target.closest(selector); if (target && root.contains(target)) handler.call(target, e, target); }); }; HS.throttle = function (fn, wait) { var last = 0, t; return function () { var now = Date.now(), args = arguments, ctx = this; if (now - last >= wait) { last = now; fn.apply(ctx, args); } else { clearTimeout(t); t = setTimeout(function () { last = Date.now(); fn.apply(ctx, args); }, wait - (now - last)); } }; }; HS.debounce = function (fn, wait) { var t; return function () { var ctx = this, args = arguments; clearTimeout(t); t = setTimeout(function () { fn.apply(ctx, args); }, wait); }; }; /* ----------------------------------------------------------------- * AJAX core posts FormData or plain object, attaches nonce, * resolves with parsed JSON * --------------------------------------------------------------- */ HS.ajax = function (action, data, options) { options = options || {}; var url = options.url || R.ajaxUrl; var fd = data instanceof FormData ? data : (function () { var f = new FormData(); Object.keys(data || {}).forEach(function (k) { f.append(k, data[k]); }); return f; })(); if (!fd.has('action')) fd.append('action', action); if (!fd.has('nonce')) fd.append('nonce', R.nonce); return fetch(url, { method: 'POST', body: fd, credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } }).then(function (res) { return res.json().then(function (json) { return { ok: res.ok && json && json.success, status: res.status, body: json }; }); }); }; HS.rest = function (path, opts) { opts = opts || {}; var url = R.restUrl + path.replace(/^\//, ''); var headers = opts.headers || {}; headers['X-WP-Nonce'] = R.restNonce; if (opts.body && typeof opts.body === 'object' && !(opts.body instanceof FormData)) { headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(opts.body); } return fetch(url, { method: opts.method || 'GET', body: opts.body || undefined, headers: headers, credentials: 'same-origin' }).then(function (res) { return res.json().then(function (json) { return { ok: res.ok, status: res.status, body: json }; }); }); }; /* ----------------------------------------------------------------- * Form submit utility shows spinner, disables form, surfaces * a result message into [data-hs-form-message] * --------------------------------------------------------------- */ HS.bindForm = function (form, action, options) { if (!form) return; options = options || {}; form.addEventListener('submit', function (e) { e.preventDefault(); var submit = form.querySelector('[type="submit"]'); var msgBox = form.querySelector('[data-hs-form-message]'); var origHTML = submit ? submit.innerHTML : ''; if (submit) { submit.disabled = true; submit.innerHTML = ' ' + (R.i18n && R.i18n.submitting || 'Submitting'); } if (msgBox) { msgBox.textContent = ''; msgBox.removeAttribute('data-state'); } HS.ajax(action, new FormData(form)).then(function (res) { if (submit) { submit.disabled = false; submit.innerHTML = origHTML; } if (res.ok) { if (msgBox) { msgBox.textContent = (res.body && res.body.data && res.body.data.message) || (R.i18n && R.i18n.success) || 'Thank you'; msgBox.setAttribute('data-state', 'success'); } if (options.onSuccess) options.onSuccess(res.body && res.body.data); if (options.resetOnSuccess !== false) form.reset(); } else { if (msgBox) { msgBox.textContent = (res.body && res.body.data && res.body.data.message) || (R.i18n && R.i18n.genericError) || 'Error'; msgBox.setAttribute('data-state', 'error'); } if (options.onError) options.onError(res.body); } }).catch(function () { if (submit) { submit.disabled = false; submit.innerHTML = origHTML; } if (msgBox) { msgBox.textContent = (R.i18n && R.i18n.genericError) || 'Error'; msgBox.setAttribute('data-state', 'error'); } }); }); }; /* ----------------------------------------------------------------- * Theme toggle sets data-hs-theme on root, persists in storage * --------------------------------------------------------------- */ HS.theme = { get: function () { return document.documentElement.getAttribute('data-hs-theme') || 'light'; }, set: function (theme) { document.documentElement.setAttribute('data-hs-theme', theme); try { localStorage.setItem(R.theme.storageKey, theme); } catch (e) {} HS.bus.emit('theme:change', theme); }, toggle: function () { this.set(this.get() === 'dark' ? 'light' : 'dark'); } }; HS.delegate(document, 'click', '[data-hs-theme-toggle]', function () { HS.theme.toggle(); }); /* ----------------------------------------------------------------- * Scroll reveal IntersectionObserver attaches data-hs-reveal=visible * --------------------------------------------------------------- */ HS.initReveal = function () { if (!('IntersectionObserver' in window)) { HS.$$('[data-hs-reveal]').forEach(function (el) { el.setAttribute('data-hs-reveal', 'visible'); }); return; } var io = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { entry.target.setAttribute('data-hs-reveal', 'visible'); io.unobserve(entry.target); } }); }, { rootMargin: '0px 0px -10% 0px', threshold: 0.05 }); HS.$$('[data-hs-reveal]').forEach(function (el) { io.observe(el); }); }; /* ----------------------------------------------------------------- * Sticky header behavior shrink on scroll, hide on scroll down * --------------------------------------------------------------- */ HS.initHeader = function () { var header = HS.$('[data-hs-header]'); if (!header) return; var lastY = 0; var update = HS.throttle(function () { var y = window.scrollY || window.pageYOffset || 0; header.classList.toggle('is-scrolled', y > 24); if (y > 200 && y > lastY + 6) { header.classList.add('is-hidden'); } else if (y < lastY - 6 || y < 200) { header.classList.remove('is-hidden'); } lastY = y; }, 80); window.addEventListener('scroll', update, { passive: true }); update(); }; /* ----------------------------------------------------------------- * Page loader remove on load * --------------------------------------------------------------- */ HS.initLoader = function () { var loader = HS.$('.hs-page-loader'); if (!loader) return; window.requestAnimationFrame(function () { setTimeout(function () { loader.classList.add('is-hidden'); }, 240); }); }; /* ----------------------------------------------------------------- * Auto init * --------------------------------------------------------------- */ function ready(fn) { if (document.readyState !== 'loading') fn(); else document.addEventListener('DOMContentLoaded', fn); } ready(function () { HS.initReveal(); HS.initHeader(); HS.initLoader(); HS.bus.emit('ready'); }); window.addEventListener('load', function () { HS.initLoader(); HS.bus.emit('load'); }); })(window, document);