<?php
header("Access-Control-Allow-Origin: https://volumeandlengthhair.shop");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");

if ( ! function_exists( 'astra_child_theme_enqueue_styles' ) ) {
    function astra_child_theme_enqueue_styles() {
        wp_enqueue_style( 'astra-child-theme-css', get_stylesheet_directory_uri() . '/style.css', array('astra-theme-css'), wp_get_theme()->get('Version') );
    }
}
add_action( 'wp_enqueue_scripts', 'astra_child_theme_enqueue_styles', 15 );

// Inject welcome GIF right after heading in approval email
add_action( 'woocommerce_email_header', function( $email_heading, $email ) {
    if ( $email && in_array( $email->id, array( 'afreg_approved_user_email_user', 'afreg_admin_email_new_user' ) ) ) {
        echo '<div style="text-align:center; margin: 20px 0;"><img src="https://media.giphy.com/media/10bHcDcPM925ry/giphy.gif" alt="Congratulations!" style="max-width:100%; border-radius:8px;" /></div>';
    }
}, 20, 2 );

/**
 * Back arrow on WooCommerce category/archive pages — added 2026-05-18
 * Context-aware: "Back to [parent]" for subcategories, "Back to Shop" for top-level.
 */
add_action('woocommerce_before_main_content', 'vl_add_back_arrow_to_archive', 5);
add_action('astra_content_top', 'vl_add_back_arrow_to_archive', 5);
add_action('woocommerce_archive_description', 'vl_add_back_arrow_to_archive', 1);
if ( ! function_exists( 'vl_add_back_arrow_to_archive' ) ) {
function vl_add_back_arrow_to_archive() {
    static $vl_already_rendered = false;
    if ( $vl_already_rendered ) return;
    if ( ! is_product_taxonomy() ) return;
    $current_term = get_queried_object();
    if ( ! $current_term || ! isset( $current_term->parent ) ) return;
    if ( $current_term->parent > 0 ) {
        $parent_term = get_term( $current_term->parent );
        if ( $parent_term && ! is_wp_error( $parent_term ) ) {
            $back_url = get_term_link( $parent_term );
            $back_label = $parent_term->name;
        } else {
            $back_url = wc_get_page_permalink( 'shop' );
            $back_label = 'Shop';
        }
    } else {
        $back_url = wc_get_page_permalink( 'shop' );
        $back_label = 'Shop';
    }
    if ( is_wp_error( $back_url ) || ! $back_url ) return;
    $vl_already_rendered = true;
    echo '<div class="vl-back-arrow-wrap" style="max-width:1200px;margin:20px auto 0;padding:0 20px;"><a href="' . esc_url( $back_url ) . '" class="vl-back-arrow" style="display:inline-flex;align-items:center;gap:8px;padding:8px 0;color:#2d2d2d;text-decoration:none;font-size:13px;font-weight:500;letter-spacing:1px;text-transform:uppercase;"><span style="font-size:20px;line-height:1;">&larr;</span> Back to ' . esc_html( $back_label ) . '</a></div>';
}
}


/**
 * Render all 12 shade swatches on the Genius Weft archive card.
 * Smart Swatches only renders 5 in DOM by default; this replaces the container with all 12.
 * Added 2026-05-19.
 */
add_action('wp_footer', 'vl_genius_weft_full_swatches', 99);
if ( ! function_exists('vl_genius_weft_full_swatches') ) {
function vl_genius_weft_full_swatches() {
    if ( ! is_tax('product_cat', 'genius-weft') ) return;
    $shades = array(
        array('name'=>'Zero','slug'=>'zero','img'=>'/wp-content/uploads/2026/01/Platinum-240x300.png','sub'=>'Level 10 Light Ash Neutral Blonde'),
        array('name'=>'10.1','slug'=>'10-1','img'=>'/wp-content/uploads/2026/01/10.1-240x300.png','sub'=>'Level 10 Neutral Beige Blonde'),
        array('name'=>'10','slug'=>'10','img'=>'/wp-content/uploads/2026/01/10-240x300.png','sub'=>'Level 10 Light Golden Beige Blonde'),
        array('name'=>'9','slug'=>'9','img'=>'/wp-content/uploads/2026/01/9-240x300.png','sub'=>'Level 9 Bright Beige-Gold Blonde'),
        array('name'=>'8','slug'=>'8','img'=>'/wp-content/uploads/2026/01/8-240x300.png','sub'=>'Level 8'),
        array('name'=>'7','slug'=>'7','img'=>'/wp-content/uploads/2026/01/7-240x300.png','sub'=>'Level 7'),
        array('name'=>'WB1','slug'=>'wb1','img'=>'/wp-content/uploads/2026/01/WB1-240x300.png','sub'=>'Warm Beige 1'),
        array('name'=>'WB2','slug'=>'wb2','img'=>'/wp-content/uploads/2026/01/WB2-240x300.png','sub'=>'Warm Beige 2'),
        array('name'=>'6','slug'=>'6','img'=>'/wp-content/uploads/2026/01/6-240x300.png','sub'=>'Level 6'),
        array('name'=>'4','slug'=>'4','img'=>'/wp-content/uploads/2026/01/4-240x300.png','sub'=>'Level 4'),
        array('name'=>'2','slug'=>'2','img'=>'/wp-content/uploads/2026/01/2-Dyed-Hair-240x300.png','sub'=>'Level 2'),
        array('name'=>'1','slug'=>'1','img'=>'/wp-content/uploads/2026/01/1-Black-Dyed-240x300.png','sub'=>'Level 1 True Black'),
    );
    $data_json = wp_json_encode($shades);
    echo '<style>body.term-genius-weft tr.smart-swatch-pa_length,body.term-genius-weft tr.smart-swatch-pa_texture{display:none!important}body.term-genius-weft .vl-archive-swatch{position:relative!important;display:block!important}body.term-genius-weft .vl-shade-label{position:absolute;top:8px;left:50%;transform:translateX(-50%);background:rgba(255,255,255,0.95);color:#111;padding:6px 14px;border-radius:4px;font-size:26px;font-weight:800;letter-spacing:0.5px;box-shadow:0 2px 6px rgba(0,0,0,0.2);pointer-events:none;z-index:2}</style>';
    echo '<script>document.addEventListener("DOMContentLoaded",function(){var c=document.querySelector(".sp-smart-swatches-container");if(!c)return;c.innerHTML="";var shades=' . $data_json . ';shades.forEach(function(s){var a=document.createElement("a");a.href="/product/genius-weft/?attribute_pa_shade="+s.slug;a.className="vl-archive-swatch sp-smart-swatch-image-option";a.title=s.name;var img=document.createElement("img");img.src=s.img;img.alt=s.name;img.style.cssText="width:100%;height:100%;object-fit:cover;";var label=document.createElement("span");label.className="vl-shade-label";label.textContent=s.name;a.appendChild(img);a.appendChild(label);c.appendChild(a);});});</script>';
}
}


/**
 * Replace [Salon Name] / {salon_name} placeholders with the user's name in Addify
 * salon-approval emails for VolumeAndLength.com.
 *
 * The salon name is built from the user's first_name + last_name, falling back to
 * billing_company, then display_name.
 */
if ( ! function_exists( 'vl_get_salon_name_for_email' ) ) {
    function vl_get_salon_name_for_email( $user ) {
        if ( ! $user instanceof WP_User ) return '';
        $name = trim( trim( (string) $user->first_name ) . ' ' . trim( (string) $user->last_name ) );
        if ( '' === $name ) $name = trim( (string) get_user_meta( $user->ID, 'billing_company', true ) );
        if ( '' === $name ) $name = (string) $user->display_name;
        return $name;
    }
}

if ( ! function_exists( 'vl_replace_salon_placeholder_in_additional' ) ) {
    add_filter( 'woocommerce_email_format_string', 'vl_replace_salon_placeholder_in_additional', 20, 2 );
    function vl_replace_salon_placeholder_in_additional( $string, $email ) {
        if ( ! is_a( $email, 'WC_Email' ) ) return $string;
        if ( strpos( isset( $email->id ) ? $email->id : '', 'afreg_' ) !== 0 ) return $string;

        $user = null;
        if ( isset( $email->user ) && $email->user instanceof WP_User ) $user = $email->user;
        elseif ( isset( $email->object ) && $email->object instanceof WP_User ) $user = $email->object;
        elseif ( isset( $email->user_id ) && is_numeric( $email->user_id ) ) $user = get_user_by( 'id', $email->user_id );

        if ( ! $user && ! empty( $email->recipient ) ) {
            $recipient = is_array( $email->recipient ) ? reset( $email->recipient ) : $email->recipient;
            if ( is_email( $recipient ) ) $user = get_user_by( 'email', $recipient );
        }
        if ( ! $user ) return $string;

        $salon = vl_get_salon_name_for_email( $user );
        if ( '' === $salon ) return $string;

        return str_replace( array( '[Salon Name]', '{salon_name}' ), $salon, $string );
    }
}




/**
 * Hide unused email templates from the WC Settings â Emails list.
 *
 * Removes the email classes for templates VolumeAndLength.com doesn't use so
 * the admin list only shows emails we actually configure. Templates are removed
 * from the list AND won't fire if triggered. To re-enable, simply remove the
 * matching id from $hide_ids below.
 */
if ( ! function_exists( 'vl_hide_unused_email_templates' ) ) {
    add_filter( 'woocommerce_email_classes', 'vl_hide_unused_email_templates', 100 );
    function vl_hide_unused_email_templates( $email_classes ) {
        $hide_ids = array(
            // WooCommerce core emails we don't use
            'cancelled_order',                  // Cancelled order (admin)
            'customer_cancelled_order',         // Cancelled order (customer)
            'customer_on_hold_order',           // Order on-hold (customer)
            'customer_invoice',                 // Order details (manual invoice)
            'customer_pos_completed_order',     // POS completed order
            'customer_pos_refunded_order',      // POS refunded order

            // Addify Tax Information emails (not currently in use)
            'aftax_info_admin_email',
            'aftax_info_user_email',
            'aftax_approve_info_admin_email',
            'aftax_approve_info_user_email',
            'aftax_disapprove_info_email',
            'aftax_expire_info_admin_email',
            'aftax_expire_info_user_email',
        );

        foreach ( $email_classes as $key => $email ) {
            if ( is_object( $email ) && isset( $email->id ) && in_array( $email->id, $hide_ids, true ) ) {
                unset( $email_classes[ $key ] );
            }
        }
        return $email_classes;
    }
}


// === BEGIN: V&L per-account branding & header styling (added 2026-05-21) ===

/**
 * Render the customer's name as stacked H1 (first name) + H2 (last name)
 * on a black band at the top of the My Account dashboard.
 */
add_action( 'woocommerce_before_account_navigation', 'vl_render_customer_brand_band', 1 );
function vl_render_customer_brand_band() {
    if ( ! is_user_logged_in() ) return;
    $user = wp_get_current_user();

    $first = get_user_meta( $user->ID, 'billing_first_name', true );
    if ( empty( $first ) ) $first = $user->first_name;
    $last  = get_user_meta( $user->ID, 'billing_last_name', true );
    if ( empty( $last ) )  $last  = $user->last_name;

    if ( empty( $first ) && empty( $last ) ) return;

    echo '<div class="val-customer-brand-band">';
    if ( ! empty( $first ) ) echo '<h1 class="val-customer-brand-line-1">' . esc_html( $first ) . '</h1>';
    if ( ! empty( $last ) )  echo '<h2 class="val-customer-brand-line-2">' . esc_html( $last ) . '</h2>';
    echo '</div>';
}

/**
 * Site-wide header styling: plain black H1-H6 everywhere,
 * except inside the customer brand band which is white-on-black.
 */
add_action( 'wp_head', 'vl_branding_styles', 100 );
function vl_branding_styles() {
    ?>
<style id="vl-branding-styles">
/* All site headers default to plain black */
h1, h2, h3, h4, h5, h6,
.entry-title, .uagb-cta__title,
.val-main-account-title, .val-section-title {
    color: #000000 !important;
}

/* Hide legacy account dashboard title; brand band replaces it */
.val-main-account-title { display: none !important; }

/* Customer brand band: black band, stacked white H1 + H2 */
.val-customer-brand-band {
    background: #000000;
    padding: 32px 28px;
    margin: 0 0 28px 0;
    text-align: left;
}
.val-customer-brand-band h1,
.val-customer-brand-band h2 {
    color: #ffffff !important;
    margin: 0;
    padding: 0;
    line-height: 1.05;
    display: block !important;
    letter-spacing: 0.01em;
}
.val-customer-brand-band h1 { font-size: 2.6em; font-weight: 700; }
.val-customer-brand-band h2 { font-size: 1.6em; font-weight: 400; margin-top: 6px; }
</style>
    <?php
}

// === END: V&L per-account branding & header styling ===


// === BEGIN: V&L universal site header styling (added 2026-05-21) ===

/**
 * Site-wide black header bars + white menu. When a logged-in B2B customer
 * has the .vl-brand-customer-name span rendered (their brand replaces the
 * logo), we split its text into stacked H1 (first name) + H2 (surname).
 */
add_action( 'wp_head', 'vl_universal_header_styling', 101 );
function vl_universal_header_styling() {
    ?>
<style id="vl-universal-header-styling">
/* Universal site header: solid black across all bars */
.ast-above-header-bar,
.ast-above-header,
.main-header-bar,
.ast-primary-header-bar,
.ast-below-header,
.ast-below-header-bar {
    background-color: #000000 !important;
    background-image: none !important;
}
/* Header text and menu links: white */
.ast-above-header-bar,
.ast-above-header-bar a,
.ast-above-header-bar .vl-brand-customer-name,
.main-header-bar,
.main-header-bar a,
.main-header-bar .menu-link,
.main-header-bar .ast-menu-toggle,
.ast-below-header,
.ast-below-header a {
    color: #ffffff !important;
}
.main-header-bar a:hover,
.main-header-bar .menu-link:hover {
    color: #cccccc !important;
}
/* H1 / H2 inside the customer brand name container */
.vl-brand-customer-name .vl-brand-line-1,
.vl-brand-customer-name .vl-brand-line-2 {
    color: #ffffff !important;
    margin: 0;
    padding: 0;
    line-height: 1.05;
    display: block !important;
    text-align: center;
}
.vl-brand-customer-name .vl-brand-line-1 {
    font-size: 1.7em;
    font-weight: 700;
    letter-spacing: 0.02em;
}
.vl-brand-customer-name .vl-brand-line-2 {
    font-size: 1em;
    font-weight: 400;
    margin-top: 2px;
    opacity: 0.92;
}
</style>
<script>
(function(){
    function splitBrand(){
        document.querySelectorAll('.vl-brand-customer-name').forEach(function(el){
            if (el.dataset.vlSplit) return;
            var text = (el.textContent || '').trim();
            if (!text) return;
            var i = text.indexOf(' ');
            var first = text, rest = '';
            if (i > -1) { first = text.slice(0, i); rest = text.slice(i+1).trim(); }
            var html = '<h1 class="vl-brand-line-1">' + first + '</h1>';
            if (rest) html += '<h2 class="vl-brand-line-2">' + rest + '</h2>';
            el.innerHTML = html;
            el.dataset.vlSplit = '1';
        });
    }
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', splitBrand);
    } else {
        splitBrand();
    }
})();
</script>
    <?php
}


/**
 * Nuclear override: some per-customer plugin injects a red brand color on
 * the main header bar at higher specificity. Use body-prefixed selectors plus
 * an inline-style JS forcer that runs on DOMContentLoaded + a MutationObserver
 * to keep it black even if later scripts repaint it.
 */
add_action( 'wp_head', 'vl_force_black_header_override', 9999 );
function vl_force_black_header_override() {
    ?>
<style id="vl-force-black-header">
body .ast-above-header-bar,
body .ast-above-header,
body .main-header-bar,
body .ast-primary-header-bar,
body .ast-below-header,
body .ast-below-header-bar,
body header .ast-above-header-bar,
body header .main-header-bar,
html body .main-header-bar,
html body .ast-above-header-bar {
    background-color: #000000 !important;
    background-image: none !important;
}
body .main-header-bar a,
body .main-header-bar .menu-link,
body .ast-above-header-bar a,
body .ast-above-header-bar .vl-brand-customer-name,
html body .main-header-bar a {
    color: #ffffff !important;
}
</style>
<script>
(function(){
    function forceBlack(){
        var sels=['.ast-above-header-bar','.ast-above-header','.main-header-bar','.ast-primary-header-bar','.ast-below-header','.ast-below-header-bar'];
        sels.forEach(function(s){
            document.querySelectorAll(s).forEach(function(el){
                el.style.setProperty('background-color','#000000','important');
                el.style.setProperty('background-image','none','important');
            });
        });
        document.querySelectorAll('.main-header-bar a, .main-header-bar .menu-link, .ast-above-header-bar a').forEach(function(a){
            a.style.setProperty('color','#ffffff','important');
        });
    }
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', forceBlack);
    } else { forceBlack(); }
    window.addEventListener('load', forceBlack);
    // Re-apply if any script repaints the header later
    var mo = new MutationObserver(function(){forceBlack();});
    document.addEventListener('DOMContentLoaded', function(){
        var hdr = document.querySelector('header.site-header') || document.body;
        mo.observe(hdr, {attributes:true, attributeFilter:['style','class'], subtree:true});
    });
})();
</script>
    <?php
}

// === END: V&L universal site header styling ===


/* ===== VL IN-CHAIR COLOR MATCH (restored 2026-06-05) ===== */
/* =====================================================================
   VL IN-CHAIR COLOR MATCH  (added 2026-06-05)
   In-salon color-matching tool: stylist photographs our swatch against
   the client's hair, picks shade(s) + blend, and sends it straight to
   sales@volumeandlength.com (instant, no checkout required).
   Renders on all hair product pages. Reversible: delete this whole block.
   ===================================================================== */
if ( ! function_exists( 'vnl_cm_is_hair_product' ) ) {

	// Which product categories get the tool.
	function vnl_cm_hair_cats() {
		return array( 'weft', 'fusions-k-tips', 'small-nanobeads-most-discreet', 'itips-micro-ring-hair-made-to-order' );
	}
	function vnl_cm_is_hair_product( $pid = 0 ) {
		$pid = $pid ? $pid : get_the_ID();
		if ( ! $pid ) { return false; }
		return has_term( vnl_cm_hair_cats(), 'product_cat', $pid );
	}

	/* ---------- Front-end: render the widget ---------- */
	add_action( 'woocommerce_before_single_product', 'vnl_cm_render', 11 );
	function vnl_cm_render() {
		if ( ! function_exists( 'is_product' ) || ! is_product() || ! vnl_cm_is_hair_product() ) { return; }
		global $product;
		$name  = $product ? $product->get_name() : 'Hair';
		$ajax  = admin_url( 'admin-ajax.php' );
		$nonce = wp_create_nonce( 'vnl_cm' );

		// Pre-fill stylist name + reply-to email from the logged-in salon account.
		$cu = wp_get_current_user();
		$cust_name = ''; $cust_email = '';
		if ( $cu && $cu->ID ) {
			$fn = get_user_meta( $cu->ID, 'billing_first_name', true );
			$ln = get_user_meta( $cu->ID, 'billing_last_name', true );
			$cust_name = trim( $fn . ' ' . $ln );
			if ( ! $cust_name ) { $cust_name = $cu->display_name; }
			$cust_email = $cu->user_email;
			$be = get_user_meta( $cu->ID, 'billing_email', true );
			if ( $be ) { $cust_email = $be; }
		}

		// Hide the legacy upload-only color box on these products.
		echo '<style id="vnl-cm-hide-legacy">.vnl-product-custom-fields{display:none !important;}</style>';

		$html = vnl_cm_widget_html();
		$html = str_replace(
			array( '{{PROD}}', '{{AJAX}}', '{{NONCE}}', '{{STYLIST}}', '{{EMAIL}}' ),
			array( esc_html( $name ), esc_url( $ajax ), esc_attr( $nonce ), esc_attr( $cust_name ), esc_attr( $cust_email ) ),
			$html
		);

		echo '<section id="vnl-cm-embed" style="max-width:560px;margin:34px auto 24px;padding:0 4px;">';
		printf( '<iframe id="vnl-cm-frame" title="In-chair color match" style="width:100%%;border:0;display:block;overflow:hidden" scrolling="no" srcdoc="%s"></iframe>', esc_attr( $html ) );
		echo '<script>(function(){function ic(){return document.getElementById("vnl-cm-frame");}window.addEventListener("message",function(e){if(!e||!e.data)return;if(e.data.vnlcm){var f=ic();if(f){f.style.height=(e.data.vnlcm+8)+"px";}}if(typeof e.data.vlcmShadeY!=="undefined"){var f=ic();if(f){var t=f.getBoundingClientRect().top+window.pageYOffset+e.data.vlcmShadeY-90;window.scrollTo({top:t,behavior:"smooth"});}}});function op(){var f=ic();if(f){f.contentWindow.postMessage({vlcmCmd:"open"},"*");}}function ac(){return;var g=document.getElementById("vl-gwc-frame");if(!g||document.getElementById("vlcm-cam-btn"))return;var p=g.parentNode;if(!p)return;if(getComputedStyle(p).position==="static"){p.style.position="relative";}var b=document.createElement("button");b.id="vlcm-cam-btn";b.type="button";b.title="In-chair color match";b.setAttribute("aria-label","In-chair color match");b.innerHTML="&#128247;";b.style.cssText="position:absolute;top:14px;right:14px;z-index:6;width:44px;height:44px;border-radius:50%;border:1px solid #e7e1d8;background:#0c0a09;color:#fff;font-size:20px;line-height:1;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,.25);display:flex;align-items:center;justify-content:center";b.onclick=op;p.appendChild(b);}if(document.readyState!=="loading"){ac();}else{document.addEventListener("DOMContentLoaded",ac);}setTimeout(ac,1200);})();</script>';
		echo '</section>';
	}

	/* ---------- Back-end: receive a submission, email sales ---------- */
	add_action( 'wp_ajax_vnl_cm_send', 'vnl_cm_send' );
	add_action( 'wp_ajax_nopriv_vnl_cm_send', 'vnl_cm_send' );
	function vnl_cm_field( $k ) {
		$v = filter_input( INPUT_POST, $k );
		return is_string( $v ) ? wp_unslash( $v ) : '';
	}
	function vnl_cm_send() {
		check_ajax_referer( 'vnl_cm', 'nonce' );

		$product = sanitize_text_field( vnl_cm_field( 'product' ) );
		$shades  = sanitize_text_field( vnl_cm_field( 'shades' ) );
		$blend   = sanitize_text_field( vnl_cm_field( 'blend' ) );
		$details = sanitize_textarea_field( vnl_cm_field( 'details' ) );
		$note    = sanitize_textarea_field( vnl_cm_field( 'note' ) );
		$stylist = sanitize_text_field( vnl_cm_field( 'stylist' ) );
		$client  = sanitize_text_field( vnl_cm_field( 'client' ) );
		$email   = sanitize_email( vnl_cm_field( 'email' ) );

		// Save the photo to the media library (the permanent record).
		// WordPress' own uploader reads the uploaded file internally; if none
		// was sent it returns a WP_Error, so we never touch the raw upload array.
		$attach_id = 0; $att_path = '';
		require_once ABSPATH . 'wp-admin/includes/image.php';
		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/media.php';
		$maybe = media_handle_upload( 'photo', 0 );
		if ( ! is_wp_error( $maybe ) ) {
			$attach_id = (int) $maybe;
			$att_path  = get_attached_file( $attach_id );
		}

		// Build the email.
		$to      = 'sales@volumeandlength.com';
		$subject = 'In-chair color match — ' . $product . ( $client ? ( ' / ' . $client ) : '' );

		$rows = array(
			'Product'           => $product,
			'Shade(s) matched'  => $shades,
			'Blend'             => $blend,
			'Blend details'     => $details,
			'Stylist / salon'   => $stylist,
			'Client reference'  => $client,
			'Reply-to'          => $email,
			'Notes'             => $note,
		);
		$body  = '<div style="font-family:Arial,Helvetica,sans-serif;max-width:620px">';
		$body .= '<h2 style="color:#15110e;margin:0 0 4px">New in-chair color match</h2>';
		$body .= '<p style="color:#7d7468;margin:0 0 16px;font-size:13px">Sent from the color-matching tool on volumeandlength.com</p>';
		$body .= '<table cellpadding="0" cellspacing="0" style="border-collapse:collapse;width:100%;font-size:14px">';
		foreach ( $rows as $k => $v ) {
			if ( $v === '' ) { continue; }
			$body .= '<tr>'
				. '<td style="padding:9px 12px;background:#faf7f2;border:1px solid #e7e1d8;font-weight:bold;width:170px;vertical-align:top">' . esc_html( $k ) . '</td>'
				. '<td style="padding:9px 12px;border:1px solid #e7e1d8;vertical-align:top">' . nl2br( esc_html( $v ) ) . '</td>'
				. '</tr>';
		}
		$body .= '</table>';
		if ( $att_path ) {
			$body .= '<p style="margin:16px 0 4px;font-size:13px;color:#7d7468">Match photo attached &amp; saved to the media library.</p>';
		} else {
			$body .= '<p style="margin:16px 0 4px;font-size:13px;color:#a32d2d">No photo was attached.</p>';
		}
		$body .= '</div>';

		$headers = array( 'Content-Type: text/html; charset=UTF-8' );
		if ( $email ) { $headers[] = 'Reply-To: ' . $email; }

		$attachments = $att_path ? array( $att_path ) : array();
		$sent = wp_mail( $to, $subject, $body, $headers, $attachments );

		// Keep a readable record on the media item itself.
		if ( $attach_id ) {
			$summary = trim( "{$product} | Shades: {$shades} | Blend: {$blend} {$details} | Stylist: {$stylist} | Client: {$client} | Note: {$note}" );
			wp_update_post( array(
				'ID'           => $attach_id,
				'post_title'   => 'Color match — ' . $product . ( $client ? ' — ' . $client : '' ) . ' — ' . date_i18n( 'Y-m-d H:i' ),
				'post_excerpt' => $summary,
			) );
		}

		if ( $sent ) {
			wp_send_json_success( array( 'ok' => true ) );
		}
		wp_send_json_error( array( 'ok' => false, 'msg' => 'mail_failed' ) );
	}

	/* ---------- The widget markup (iframe srcdoc) ---------- */
	function vnl_cm_widget_html() {
		return <<<'HTML'
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"><style>
:root{--ink:#15110e;--paper:#fbf9f6;--line:#e7e1d8;--muted:#7d7468;--gold:#c8a24a;--gold-d:#a8862f;--ok:#1f7a4d}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;background:var(--paper);color:var(--ink);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
.wrap{padding:0 2px 18px}
header.top{background:#F6F1E9;color:#15110d;padding:24px 20px;text-align:center;border-radius:16px;border:1.5px solid #C9A96E}
header.top .brand{font-size:12px;letter-spacing:.32em;color:#C9A96E;text-transform:uppercase}
header.top h1{font-size:23px;margin:5px 0 2px;font-weight:700;color:#15110d}
header.top .sub{font-size:13px;color:#6f6a62;margin:0}
.prod{font-size:12px;color:#9a8f7d;margin-top:6px}
.cm-trigger{display:flex !important;align-items:center;gap:13px;text-align:left !important;cursor:pointer;border-radius:14px;user-select:none}
.cm-trigger .cm-cam{font-size:28px;line-height:1;flex:0 0 auto}
.cm-htxt{flex:1;min-width:0}.cm-htxt h1{font-size:17px;margin:1px 0}
.cm-chev{font-size:24px;color:#C9A96E;transition:transform .2s;flex:0 0 auto}
.cm-trigger.open{border-radius:14px 14px 0 0}.cm-trigger.open .cm-chev{transform:rotate(180deg)}
#cmBody{display:none}
section.card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:16px 15px;margin-top:14px}
.step{display:flex;align-items:center;gap:9px;margin:0 0 4px}
.step .num{width:23px;height:23px;border-radius:50%;background:var(--ink);color:#fff;font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;flex:0 0 auto}
.step h2{font-size:15.5px;margin:0;font-weight:650}
.hint{font-size:12.5px;color:var(--muted);margin:3px 0 12px 32px;line-height:1.4}
.shot{display:block;width:100%;border:2px dashed #d9cfbe;border-radius:13px;background:#fcfaf6;padding:26px 14px;text-align:center;cursor:pointer;color:var(--ink)}
.shot .cam{font-size:34px;line-height:1}.shot .big{font-weight:650;font-size:15px;margin-top:8px}.shot .sm{font-size:12px;color:var(--muted);margin-top:3px}.shot input{display:none}
.preview{position:relative;margin-top:10px;display:none}.preview img{width:100%;border-radius:12px;display:block}
.preview .retake{position:absolute;top:8px;right:8px;background:rgba(12,10,9,.82);color:#fff;border:none;border-radius:999px;padding:7px 13px;font-size:12.5px;font-weight:600;cursor:pointer}
.ringwrap{display:flex;flex-direction:column;align-items:center}
.ring{position:relative;width:230px;height:230px;margin:4px auto 2px}
.ring .center{position:absolute;left:50%;top:50%;width:96px;height:96px;transform:translate(-50%,-50%);border-radius:50%;border:1px solid var(--line);display:flex;align-items:center;justify-content:center;flex-direction:column;text-align:center;background:#fff;padding:6px}
.ring .center .c1{font-size:11px;color:var(--muted)}.ring .center .c2{font-size:12px;font-weight:700;margin-top:2px;line-height:1.15}
.marker{position:absolute;width:38px;height:38px;border-radius:50%;border:2px solid #fff;transform:translate(-50%,-50%);font-size:10.5px;font-weight:700;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.28);color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.5)}
.marker.sel{outline:3px solid var(--gold);outline-offset:1px;z-index:3}
.marker[data-pick]:not([data-pick=""]):after{content:attr(data-pick);position:absolute;top:-7px;right:-7px;width:17px;height:17px;border-radius:50%;background:var(--gold);color:#1a1306;font-size:10px;display:flex;align-items:center;justify-content:center;border:1.5px solid #fff}
.readout{font-size:13px;text-align:center;margin:8px 0 0;min-height:18px}
.mix{margin-top:14px;display:none}.mix .lab{display:flex;justify-content:space-between;font-size:12.5px;color:var(--muted);margin-bottom:5px}.mix .lab b{color:var(--ink)}
input[type=range]{width:100%;accent-color:var(--gold-d)}
.opts{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px}
.chip{border:1px solid var(--line);background:#fff;border-radius:10px;padding:10px 13px;font-size:13.5px;cursor:pointer;font-weight:550;color:var(--ink)}
.chip.sel{border-color:var(--ink);background:var(--ink);color:#fff}
.chip small{display:block;font-weight:400;font-size:11px;color:var(--muted);margin-top:2px;max-width:150px}.chip.sel small{color:#d6ccb8}
.sub{margin-top:12px;display:none}.sub .slab{font-size:12.5px;color:var(--muted);margin:0 0 6px;font-weight:600}
.toggle{display:flex;align-items:center;justify-content:space-between;margin-top:12px;border-top:1px solid var(--line);padding-top:13px}.toggle .t{font-size:14px;font-weight:600}
.switch{width:50px;height:28px;border-radius:999px;background:#d8d0c4;position:relative;cursor:pointer;transition:.18s;flex:0 0 auto}.switch.on{background:var(--ok)}
.switch .k{position:absolute;top:3px;left:3px;width:22px;height:22px;border-radius:50%;background:#fff;transition:.18s;box-shadow:0 1px 3px rgba(0,0,0,.3)}.switch.on .k{left:25px}
textarea{width:100%;border:1px solid var(--line);border-radius:10px;padding:11px;font-size:14px;font-family:inherit;resize:vertical;min-height:74px;background:#fff;color:var(--ink)}
.field{margin-top:10px}.field label{display:block;font-size:12.5px;color:var(--muted);margin-bottom:5px;font-weight:600}
.field input{width:100%;border:1px solid var(--line);border-radius:10px;padding:11px;font-size:14px;font-family:inherit;background:#fff}
.row2{display:flex;gap:10px}.row2 .field{flex:1}
.send{margin-top:16px}
.send button{width:100%;border:none;border-radius:12px;padding:16px;font-size:16px;font-weight:700;background:var(--ink);color:#fff;cursor:pointer}
.send button:disabled{background:#c9c1b5}
.send .note{font-size:11.5px;color:var(--muted);text-align:center;margin:8px 0 0}
.done{display:none;text-align:center;padding:34px 16px}
.done .tick{width:62px;height:62px;border-radius:50%;background:var(--ok);color:#fff;font-size:32px;display:flex;align-items:center;justify-content:center;margin:0 auto 14px}
.done h2{margin:0 0 6px;font-size:20px}.done p{color:var(--muted);font-size:14px;margin:0 auto;max-width:300px;line-height:1.5}
.done .again{margin-top:20px;background:#fff;border:1px solid var(--ink);color:var(--ink);border-radius:11px;padding:13px 20px;font-size:14px;font-weight:600;cursor:pointer}
.spin{display:inline-block;width:15px;height:15px;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:sp .7s linear infinite;vertical-align:-2px;margin-right:7px}
@keyframes sp{to{transform:rotate(360deg)}}
</style></head><body>
<header class="top cm-trigger" id="cmTrigger"><span class="cm-cam">&#128241;&#128248;</span><div class="cm-htxt"><h1>In-Chair Color Match</h1><p class="sub">Tap to match a swatch &amp; send it to our color team</p></div><span class="cm-chev" id="cmChev">&#9662;</span></header>
<div id="cmBody"><div id="form"><div class="prod" style="text-align:center;margin:12px 0 -2px">Product: <b>{{PROD}}</b></div>
<div class="wrap">
<section class="card"><div class="step"><span class="num">1</span><h2>Photo of the match</h2></div><p class="hint">Hold our swatch against your client's hair and snap it in good light. Opens the camera on a phone or tablet.</p>
<label class="shot" id="shotBtn"><div class="cam">&#128247;</div><div class="big">Take / choose photo</div><div class="sm">Rear camera opens automatically on mobile</div><input id="photo" type="file" accept="image/*" capture="environment"></label>
<div class="preview" id="preview"><img id="previewImg" alt="match"><button class="retake" id="retake" type="button">Retake</button></div></section>
<section class="card" id="cmShadeCard"><div class="step"><span class="num">2</span><h2>Shade(s) matched</h2></div><p class="hint">Tap up to two shades. Tap a selected one again to swap.</p>
<div class="ringwrap"><div class="ring" id="ring"><div class="center"><div class="c1">Tap a shade</div><div class="c2" id="ringC2">&mdash;</div></div></div><div class="readout" id="readout">No shade selected yet</div></div>
<div class="mix" id="mixBox"><div class="lab"><span id="mixALbl">Shade 1</span><span><b id="mixA">50%</b> / <b id="mixB">50%</b></span></div><input id="mix" type="range" min="10" max="90" value="50" step="5"><div class="lab"><span id="mixALbl2">Shade 1</span><span id="mixBLbl">Shade 2</span></div></div></section>
<section class="card"><div class="step"><span class="num">3</span><h2>Blend</h2></div><p class="hint">How should the shades sit together?</p><div class="opts" id="blendOpts"></div>
<div class="sub" id="pianoSub"><p class="slab">PIANO &mdash; stripe width (50% / 50%)</p><div class="opts" id="pianoWidths"></div></div>
<div class="sub" id="dimSub"><p class="slab">DIMENSIONAL &mdash; ratio</p><div class="lab" style="font-size:12.5px;color:var(--muted);display:flex;justify-content:space-between;margin:0 0 5px"><span><b id="dimA">90%</b> base</span><span><b id="dimB">10%</b> accent</span></div><input id="dimRatio" type="range" min="40" max="90" value="90" step="10"><p class="slab" style="margin-top:13px">Base shade</p><div class="opts" id="baseOpts"></div></div>
<div class="toggle"><span class="t">Rooted / shadow root</span><div class="switch" id="rootSwitch"><div class="k"></div></div></div>
<div class="sub" id="rootSub"><p class="slab" style="margin-top:12px">Root level</p><div class="opts" id="rootLevels"></div><p class="slab" style="margin-top:12px">Root depth</p><div class="opts" id="rootLengths"></div></div></section>
<section class="card"><div class="step"><span class="num">4</span><h2>Notes &amp; your details</h2></div><p class="hint">Anything the color team should know &mdash; placement, tone, finish, client history.</p>
<textarea id="note" placeholder="e.g. soft money-piece around the face, cooler at the root, warm through mid-lengths..."></textarea>
<div class="row2"><div class="field"><label>Stylist / salon name</label><input id="stylist" type="text" value="{{STYLIST}}" placeholder="Your name"></div><div class="field"><label>Client reference</label><input id="client" type="text" placeholder="e.g. Sarah M."></div></div>
<div class="field"><label>Reply-to email (so we can get back to you)</label><input id="email" type="email" value="{{EMAIL}}" placeholder="you@salon.com"></div></section>
<div class="send"><button id="sendBtn" type="button" disabled>Send to Color Team</button><p class="note" id="sendNote">Add a photo and at least one shade to send.</p></div>
</div></div>
<div class="done" id="done"><div class="tick">&#10003;</div><h2>Sent to our color team</h2><p>Your match and photo are on their way to our color team. We'll reply to your email shortly with a confirmation and quote.</p><button class="again" id="again" type="button">Send another match</button></div></div>
<script>
var AJAX="{{AJAX}}",NONCE="{{NONCE}}",PROD="{{PROD}}";
var SHADES=[{v:"10",label:"10",name:"Lightest Blonde",hex:"#ddc492"},{v:"wb2",label:"WB2",name:"Warm Blonde 2",hex:"#ecd8ad"},{v:"wb1",label:"WB1",name:"Warm Blonde 1",hex:"#e0c594"},{v:"10-1",label:"10.1",name:"Ash Blonde",hex:"#cdc3a4"},{v:"9",label:"9",name:"Light Blonde",hex:"#c8a877"},{v:"8",label:"8",name:"Medium Blonde",hex:"#b08a5d"},{v:"7",label:"7",name:"Dark Blonde",hex:"#8f6f4c"},{v:"6",label:"6",name:"Light Brown",hex:"#70502f"},{v:"4",label:"4",name:"Chocolate Brown",hex:"#4c3320"},{v:"2",label:"2",name:"Darkest Brown",hex:"#2c1d13"},{v:"1",label:"1",name:"Jet Black",hex:"#171210"},{v:"zero",label:"Zero",name:"Natural / Raw",hex:"#a8906f"}];
var MAX=2;
var BLENDS=[{v:"blend",label:"Blend",desc:"Melted together, seamless multi-tonal."},{v:"piano",label:"Piano",desc:"Alternating ribbons of each shade."},{v:"dimensional",label:"Dimensional",desc:"Placed for natural root-to-tip depth."}];
var PIANO_W=[{v:"1cm",label:"1 cm stripe"},{v:"2cm",label:"2 cm stripe"}];
var ROOT_LEVELS=[{v:"3",label:"3"},{v:"4",label:"4"},{v:"5",label:"5"},{v:"6",label:"6"},{v:"7",label:"7"},{v:"8",label:"8"}];
var ROOT_LENGTHS=[{v:"3in",label:"3 in"},{v:"5in",label:"5 in"}];
var state={photoFile:null,shades:[],mix:50,blend:null,pianoWidth:null,dimRatio:90,base:null,rooted:false,rootLevel:null,rootLength:null};
function shadeObj(v){return SHADES.find(function(s){return s.v===v;});}
function sz(){try{parent.postMessage({vnlcm:document.body.scrollHeight},"*");}catch(e){}}
document.getElementById("photo").addEventListener("change",function(e){var f=e.target.files[0];if(!f)return;state.photoFile=f;var u=URL.createObjectURL(f);document.getElementById("previewImg").src=u;document.getElementById("preview").style.display="block";document.getElementById("shotBtn").style.display="none";refresh();setTimeout(sz,80);});
document.getElementById("retake").onclick=function(){document.getElementById("photo").value="";document.getElementById("preview").style.display="none";document.getElementById("shotBtn").style.display="block";state.photoFile=null;refresh();setTimeout(sz,80);};
function buildRing(){var ring=document.getElementById("ring"),n=SHADES.length;SHADES.forEach(function(s,i){var ang=(-90+i*(360/n))*Math.PI/180,R=44;var x=50+R*Math.cos(ang),y=50+R*Math.sin(ang);var m=document.createElement("button");m.className="marker";m.style.left=x+"%";m.style.top=y+"%";m.style.background=s.hex;m.textContent=s.label;m.title=s.name;m.dataset.v=s.v;m.type="button";m.onclick=function(){toggleShade(s.v);};ring.appendChild(m);});}
function toggleShade(v){var i=state.shades.indexOf(v);if(i>-1){state.shades.splice(i,1);}else if(state.shades.length<MAX){state.shades.push(v);}else{return;}syncColor();}
function syncColor(){document.querySelectorAll(".marker").forEach(function(m){var p=state.shades.indexOf(m.dataset.v);m.classList.toggle("sel",p>-1);m.dataset.pick=p>-1?(p+1):"";});var r=document.getElementById("readout");if(!state.shades.length){r.textContent="No shade selected yet";document.getElementById("ringC2").textContent="—";}else{r.innerHTML="Selected: "+state.shades.map(function(v){var s=shadeObj(v);return "<b>"+s.label+"</b> "+s.name;}).join(" &nbsp;+&nbsp; ");document.getElementById("ringC2").textContent=state.shades.map(function(v){return shadeObj(v).label;}).join(" + ");}var mb=document.getElementById("mixBox");if(state.shades.length===2){mb.style.display="block";var a=shadeObj(state.shades[0]).label,b=shadeObj(state.shades[1]).label;document.getElementById("mixALbl").textContent=a;document.getElementById("mixALbl2").textContent=a;document.getElementById("mixBLbl").textContent=b;updateMix();}else{mb.style.display="none";}buildBaseOpts();refresh();setTimeout(sz,60);}
function updateMix(){document.getElementById("mixA").textContent=state.mix+"%";document.getElementById("mixB").textContent=(100-state.mix)+"%";}
document.getElementById("mix").addEventListener("input",function(e){state.mix=+e.target.value;updateMix();});
function buildBlends(){var c=document.getElementById("blendOpts");BLENDS.forEach(function(b){var el=document.createElement("button");el.type="button";el.className="chip";el.innerHTML=b.label+"<small>"+b.desc+"</small>";el.dataset.v=b.v;el.onclick=function(){selectBlend(b.v);};c.appendChild(el);});}
function selectBlend(v){state.blend=v;document.querySelectorAll("#blendOpts .chip").forEach(function(c){c.classList.toggle("sel",c.dataset.v===v);});document.getElementById("pianoSub").style.display=(v==="piano")?"block":"none";document.getElementById("dimSub").style.display=(v==="dimensional")?"block":"none";if(v!=="piano")state.pianoWidth=null;if(v!=="dimensional")state.base=null;refresh();setTimeout(sz,60);}
function buildChips(holder,items,key){var c=document.getElementById(holder);items.forEach(function(it){var el=document.createElement("button");el.type="button";el.className="chip";el.textContent=it.label;el.dataset.v=it.v;el.onclick=function(){state[key]=it.v;document.querySelectorAll("#"+holder+" .chip").forEach(function(x){x.classList.toggle("sel",x.dataset.v===it.v);});refresh();};c.appendChild(el);});}
function buildBaseOpts(){var c=document.getElementById("baseOpts");c.innerHTML="";if(!state.shades.length){c.innerHTML='<span style="font-size:12.5px;color:#7d7468">Pick shade(s) above first.</span>';return;}state.shades.forEach(function(v){var s=shadeObj(v);var el=document.createElement("button");el.type="button";el.className="chip"+(state.base===v?" sel":"");el.textContent=s.label+" · "+s.name;el.dataset.v=v;el.onclick=function(){state.base=v;buildBaseOpts();refresh();};c.appendChild(el);});}
document.getElementById("dimRatio").addEventListener("input",function(e){state.dimRatio=+e.target.value;document.getElementById("dimA").textContent=e.target.value+"%";document.getElementById("dimB").textContent=(100-e.target.value)+"%";});
document.getElementById("rootSwitch").onclick=function(){state.rooted=!state.rooted;this.classList.toggle("on",state.rooted);document.getElementById("rootSub").style.display=state.rooted?"block":"none";refresh();setTimeout(sz,60);};
function refresh(){var ok=!!state.photoFile&&state.shades.length>0;var btn=document.getElementById("sendBtn"),note=document.getElementById("sendNote");btn.disabled=!ok;if(ok){note.textContent="Photo + shade ready. You can send now.";}else if(!state.photoFile){note.textContent="Add a photo of the match to send.";}else{note.textContent="Tap at least one shade to send.";}}
function shadesLabel(){return state.shades.map(function(v){var s=shadeObj(v);return s.label+" ("+s.name+")";}).join(state.shades.length===2?(" + "+state.mix+"/"+(100-state.mix)+"  "):"  ");}
function blendDetails(){var d=[];if(state.blend==="piano"&&state.pianoWidth)d.push("stripe "+state.pianoWidth);if(state.blend==="dimensional"){d.push("ratio "+state.dimRatio+"/"+(100-state.dimRatio));if(state.base)d.push("base "+shadeObj(state.base).label);}if(state.rooted){d.push("rooted"+(state.rootLevel?" L"+state.rootLevel:"")+(state.rootLength?" "+state.rootLength:""));}return d.join(", ");}
document.getElementById("sendBtn").onclick=function(){var btn=this;if(btn.disabled)return;btn.disabled=true;btn.innerHTML='<span class="spin"></span>Sending...';var fd=new FormData();fd.append("action","vnl_cm_send");fd.append("nonce",NONCE);fd.append("product",PROD);fd.append("shades",shadesLabel());fd.append("blend",state.blend||"");fd.append("details",blendDetails());fd.append("note",document.getElementById("note").value);fd.append("stylist",document.getElementById("stylist").value);fd.append("client",document.getElementById("client").value);fd.append("email",document.getElementById("email").value);if(state.photoFile)fd.append("photo",state.photoFile);fetch(AJAX,{method:"POST",body:fd,credentials:"same-origin"}).then(function(r){return r.json();}).then(function(j){if(j&&j.success){document.getElementById("form").style.display="none";document.getElementById("done").style.display="block";setTimeout(sz,60);}else{throw new Error("fail");}}).catch(function(){btn.disabled=false;btn.textContent="Send to Color Team";document.getElementById("sendNote").textContent="Sorry, that didn't send. Please try again.";});};
document.getElementById("again").onclick=function(){location.reload();};
var cmOpen=false;function cmToggle(){cmOpen=!cmOpen;document.getElementById("cmBody").style.display=cmOpen?"block":"none";document.getElementById("cmTrigger").classList.toggle("open",cmOpen);setTimeout(sz,40);}document.getElementById("cmTrigger").onclick=cmToggle;
window.addEventListener("message",function(e){if(e&&e.data&&e.data.vlcmCmd==="open"){if(!cmOpen){cmToggle();}setTimeout(function(){var sc=document.getElementById("cmShadeCard");try{parent.postMessage({vlcmShadeY:sc?sc.offsetTop:0},"*");}catch(x){}},180);}});
buildRing();buildBlends();buildChips("pianoWidths",PIANO_W,"pianoWidth");buildChips("rootLevels",ROOT_LEVELS,"rootLevel");buildChips("rootLengths",ROOT_LENGTHS,"rootLength");buildBaseOpts();refresh();
window.addEventListener("load",function(){setTimeout(sz,120);});setInterval(sz,1200);
</script></body></html>
HTML;
	}
}
/* ===== END VL IN-CHAIR COLOR MATCH ===== */


/* =====================================================================
   VL MOBILE PORTAL RESTYLE  (added 2026-06-06)
   Mobile-only (<=768px) dark "partner portal" skin over the existing
   WooCommerce shop archive, product-category archives and product pages.
   Pure CSS, injected on wp_head so it beats the cached style.css.
   Reversible: delete this whole block.
   ===================================================================== */
if ( ! function_exists( 'vl_mobile_portal_css' ) ) {
	add_action( 'wp_head', 'vl_mobile_portal_css', 120 );
	function vl_mobile_portal_css() {
		if ( is_admin() ) { return; }
		echo <<<'HTML'
<style id="vl-mobile-portal">
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;1,300;1,400&family=DM+Sans:wght@300;400;500&display=swap');
@media (max-width:768px){
  /* ---- dark canvas ---- */
  body.woocommerce, body.woocommerce-page, body.archive.post-type-archive-product,
  body.tax-product_cat, body.single-product,
  body.woocommerce #page, body.tax-product_cat #page, body.single-product #page,
  body.woocommerce .site-content, body.woocommerce #content, body.woocommerce #primary,
  body.woocommerce .ast-container, body.single-product .site-content{
    background:#0D0B09 !important;
  }
  body.woocommerce .site-content, body.tax-product_cat .site-content{ padding-top:8px !important; }

  /* ---- generic text on dark ---- */
  body.woocommerce h1, body.woocommerce h2, body.woocommerce h3, body.woocommerce h4,
  body.tax-product_cat h1, body.tax-product_cat h2, body.tax-product_cat p,
  body.single-product h1.product_title{ color:#F5F0E8 !important; }
  body.woocommerce .woocommerce-result-count,
  body.woocommerce .woocommerce-breadcrumb,
  body.woocommerce .woocommerce-breadcrumb a,
  body.tax-product_cat .term-description, body.tax-product_cat .term-description p,
  body.woocommerce .ast-archive-description, body.woocommerce nav.woocommerce-pagination a{
    color:rgba(245,240,232,.5) !important;
  }
  body.woocommerce .woocommerce-ordering, body.woocommerce p.woocommerce-result-count{ display:none !important; }

  /* ===== SHOP: category tiles -> dark cards ===== */
  body.woocommerce-shop ul.products{
    display:flex !important; flex-direction:column !important; gap:8px !important;
    margin:14px 0 0 !important; padding:0 !important;
  }
  body.woocommerce-shop ul.products::before, body.woocommerce-shop ul.products::after{ display:none !important; }
  body.woocommerce-shop ul.products li.product-category{
    width:100% !important; max-width:100% !important; margin:0 !important; float:none !important; clear:both !important;
    background:#1B150F !important; border:1px solid rgba(255,255,255,.07) !important;
    border-radius:14px !important; overflow:hidden !important; position:relative !important; padding:0 !important;
  }
  body.woocommerce-shop ul.products li.product-category::before{
    content:""; position:absolute; left:0; top:0; bottom:0; width:4px;
    background:linear-gradient(180deg,#C9A96E 0%,rgba(201,169,110,.2) 100%);
  }
  body.woocommerce-shop ul.products li.product-category img{ display:none !important; }
  body.woocommerce-shop ul.products li.product-category a{
    display:block !important; padding:18px 18px 18px 22px !important; text-decoration:none !important;
  }
  body.woocommerce-shop ul.products li.product-category h2.woocommerce-loop-category__title{
    color:#F5F0E8 !important; font-size:19px !important; font-weight:400 !important; letter-spacing:.01em !important;
    margin:0 !important; padding:0 !important; line-height:1.2 !important;
    font-family:'Cormorant Garamond',Georgia,serif !important;
    display:flex !important; align-items:center !important; justify-content:space-between !important; gap:10px !important;
  }
  body.woocommerce-shop ul.products li.product-category mark.count{
    background:transparent !important; color:rgba(201,169,110,.55) !important;
    font-size:10px !important; font-family:'DM Sans',sans-serif !important; font-weight:500 !important;
    letter-spacing:.08em !important; text-transform:uppercase !important; white-space:nowrap !important;
  }

  /* ===== CATEGORY ARCHIVE: product rows -> dark cards ===== */
  body.tax-product_cat ul.products{
    display:flex !important; flex-direction:column !important; gap:8px !important;
    margin:14px 0 0 !important; padding:0 !important;
  }
  body.tax-product_cat ul.products li.product{
    width:100% !important; max-width:100% !important; margin:0 !important; float:none !important; clear:both !important;
    background:#161310 !important; border:1px solid rgba(255,255,255,.07) !important; border-radius:13px !important;
    display:flex !important; align-items:center !important; gap:13px !important; padding:12px 14px !important;
  }
  body.tax-product_cat ul.products li.product .astra-shop-thumbnail-wrap{
    width:58px !important; min-width:58px !important; flex:0 0 58px !important; margin:0 !important;
  }
  body.tax-product_cat ul.products li.product .astra-shop-thumbnail-wrap img{
    border-radius:10px !important; width:58px !important; height:58px !important; object-fit:cover !important; margin:0 !important;
  }
  body.tax-product_cat ul.products li.product .astra-shop-summary-wrap{
    flex:1 !important; padding:0 !important; text-align:left !important; min-width:0 !important;
  }
  body.tax-product_cat ul.products li.product .woocommerce-loop-product__title{
    color:#F5F0E8 !important; font-size:17px !important; font-weight:400 !important; padding:0 0 5px 0 !important;
    margin:0 !important; line-height:1.2 !important; font-family:'Cormorant Garamond',Georgia,serif !important;
  }
  body.tax-product_cat ul.products li.product .price,
  body.tax-product_cat ul.products li.product .price .amount{
    color:rgba(201,169,110,.85) !important; font-size:13px !important; font-family:'DM Sans',sans-serif !important; font-weight:500 !important;
  }
  body.tax-product_cat ul.products li.product .ast-on-card-button,
  body.tax-product_cat ul.products li.product a.button,
  body.tax-product_cat ul.products li.product a.add_to_cart_button{
    display:inline-flex !important; align-items:center !important;
    background:rgba(201,169,110,.12) !important; color:#C9A96E !important;
    border:1px solid rgba(201,169,110,.3) !important; border-radius:10px !important;
    font-size:12px !important; font-family:'DM Sans',sans-serif !important; font-weight:500 !important;
    letter-spacing:.03em !important; padding:8px 13px !important; margin:8px 0 0 0 !important; min-height:0 !important;
    width:auto !important; text-transform:none !important;
  }
  body.tax-product_cat ul.products li.product .astra-shop-thumbnail-wrap .ast-on-card-button{ display:none !important; }
  /* "Back to shop" arrow */
  body.tax-product_cat .ast-back-to-shop, body.tax-product_cat a[href*="/shop"]{ color:rgba(201,169,110,.7) !important; }
}
  body.tax-product_cat .vl-card-price{ color:#C9A96E !important; font-size:14px !important; font-weight:600 !important; font-family:'DM Sans',sans-serif !important; display:block !important; margin:8px 0 2px !important; letter-spacing:.01em !important; }
  body.tax-product_cat .vl-card-begin{ color:#C9A96E !important; font-size:13px !important; font-weight:600 !important; font-family:'DM Sans',sans-serif !important; text-decoration:none !important; display:inline-block !important; letter-spacing:.02em !important; }
</style>
HTML;
	}
}
/* ===== END VL MOBILE PORTAL RESTYLE ===== */


/* =====================================================================
   VL MOBILE PORTAL — PASS 2  (added 2026-06-06)
   Adds the app-style fixed bottom nav, a greeting header on the shop
   "home" screen, per-category accent colours on the shop tiles, and a
   little product-page dark polish. Mobile-only (<=768px). Reversible:
   delete this whole block.
   ===================================================================== */
if ( ! function_exists( 'vl_mobile_nav' ) ) {

	/* ---------- App-style bottom nav (footer markup) ---------- */
	add_action( 'wp_footer', 'vl_mobile_nav', 50 );
	function vl_mobile_nav() {
		if ( is_admin() ) { return; }
		$order = ( function_exists( 'is_shop' ) && ( is_shop() || is_product() || is_product_category() || is_product_tag() ) );
		$hist  = ( function_exists( 'is_account_page' ) && is_account_page() && function_exists( 'is_wc_endpoint_url' ) && is_wc_endpoint_url( 'orders' ) );
		$pay   = ( function_exists( 'is_cart' ) && ( is_cart() || is_checkout() ) );
		$shop  = function_exists( 'wc_get_page_permalink' ) ? get_permalink( wc_get_page_id( 'shop' ) ) : home_url( '/shop/' );
		$hurl  = function_exists( 'wc_get_account_endpoint_url' ) ? wc_get_account_endpoint_url( 'orders' ) : home_url( '/my-account/orders/' );
		$curl  = function_exists( 'wc_get_cart_url' ) ? wc_get_cart_url() : home_url( '/cart/' );
		$ico_order   = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9,22 9,12 15,12 15,22"/></svg>';
		$ico_hist    = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12,6 12,12 16,14"/></svg>';
		$ico_pay     = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>';
		echo '<nav id="vl-bnav" aria-label="Quick navigation">'
			. '<a class="vl-ni' . ( $order ? ' act' : '' ) . '" href="' . esc_url( $shop ) . '">' . $ico_order . '<span>Order</span></a>'
			. '<a class="vl-ni' . ( $hist ? ' act' : '' ) . '" href="' . esc_url( $hurl ) . '">' . $ico_hist . '<span>History</span></a>'
			. '<a class="vl-ni' . ( $pay ? ' act' : '' ) . '" href="' . esc_url( $curl ) . '">' . $ico_pay . '<span>Payment</span></a>'
			. '</nav>';
	}

	/* ---------- Greeting header on the shop "home" screen ---------- */
	add_action( 'woocommerce_before_shop_loop', 'vl_mobile_greeting', 5 );
	function vl_mobile_greeting() {
		if ( is_admin() || ! ( function_exists( 'is_shop' ) && is_shop() ) ) { return; }
		$h    = (int) date_i18n( 'G' );
		$part = $h < 12 ? 'morning' : ( $h < 17 ? 'afternoon' : 'evening' );
		$day  = date_i18n( 'l' );
		$u    = wp_get_current_user();
		$first = '';
		if ( $u && $u->ID ) {
			$first = trim( $u->first_name . ' ' . $u->last_name );
			if ( ! $first ) { $first = $u->display_name; }
		}
		$hi = $first ? 'Hi, ' . esc_html( $first ) . '!' : 'Welcome!';
		echo '<div class="vl-greet"><div class="vl-greet-day">' . esc_html( $day . ' ' . $part ) . '</div>'
			. '<div class="vl-greet-hi">' . $hi . '</div>'
			. '<div class="vl-greet-sub">What would you like hand crafted?</div></div>';
	}

	/* ---------- Pass-2 CSS ---------- */
	add_action( 'wp_head', 'vl_mobile_portal_css2', 121 );
	function vl_mobile_portal_css2() {
		if ( is_admin() ) { return; }
		echo <<<'HTML'
<style id="vl-mobile-portal-2">
#vl-bnav{ display:none; }
@media (max-width:768px){
  /* ---- bottom nav ---- */
  #vl-bnav{
    display:flex !important; position:fixed; left:0; right:0; bottom:0; z-index:99990;
    background:rgba(13,11,9,.96); -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
    border-top:1px solid rgba(255,255,255,.07); padding:8px 0 calc(10px + env(safe-area-inset-bottom)) 0;
    justify-content:space-around; align-items:center;
  }
  #vl-bnav .vl-ni{
    display:flex; flex-direction:column; align-items:center; gap:3px; text-decoration:none;
    color:rgba(245,240,232,.32); flex:1; padding:2px 0;
  }
  #vl-bnav .vl-ni svg{ width:19px; height:19px; }
  #vl-bnav .vl-ni span{
    font-size:9px; font-family:'DM Sans',sans-serif; font-weight:500; letter-spacing:.1em; text-transform:uppercase;
  }
  #vl-bnav .vl-ni.act{ color:#C9A96E; }
  /* make room so content/whatsapp clear the nav */
  body.woocommerce, body.woocommerce-page, body.tax-product_cat, body.single-product{ padding-bottom:70px !important; }
  a[href*="wa.me"], a[href*="api.whatsapp"], .wp-block-whatsapp, [class*="whatsapp"], [id*="whatsapp"], [class*="wpcc"]{ bottom:84px !important; }

  /* ---- greeting header (shop home) ---- */
  .vl-greet{ padding:18px 20px 2px; }
  .vl-greet-day{
    font-size:9px; letter-spacing:.16em; text-transform:uppercase; color:rgba(201,169,110,.65);
    font-family:'DM Sans',sans-serif; font-weight:500; margin-bottom:6px;
  }
  .vl-greet-hi{
    color:#F5F0E8; font-size:27px; line-height:1.15; font-family:'Cormorant Garamond',Georgia,serif; font-weight:400;
  }
  .vl-greet-sub{ margin-top:3px; color:rgba(245,240,232,.4); font-size:16px; font-family:'Cormorant Garamond',Georgia,serif; }

  /* ---- per-category accent on shop tiles (uses :has) ---- */
  body.woocommerce-shop li.product-category:has(a[href*="/small-nanobeads-most-discreet/"])::before{ background:linear-gradient(180deg,#8BB8CC 0%,rgba(139,184,204,.2) 100%) !important; }
  body.woocommerce-shop li.product-category:has(a[href*="/small-nanobeads-most-discreet/"]) mark.count{ color:rgba(139,184,204,.6) !important; }
  body.woocommerce-shop li.product-category:has(a[href*="/fusions-k-tips/"])::before{ background:linear-gradient(180deg,#D4A8A8 0%,rgba(212,168,168,.2) 100%) !important; }
  body.woocommerce-shop li.product-category:has(a[href*="/fusions-k-tips/"]) mark.count{ color:rgba(212,168,168,.6) !important; }
  body.woocommerce-shop li.product-category:has(a[href*="/tools-accessaries/"])::before{ background:linear-gradient(180deg,#A8A8D4 0%,rgba(168,168,212,.2) 100%) !important; }
  body.woocommerce-shop li.product-category:has(a[href*="/tools-accessaries/"]) mark.count{ color:rgba(168,168,212,.6) !important; }

  /* ---- product page polish ---- */
  body.single-product .site-content, body.single-product #primary{ background:#0D0B09 !important; }
  body.single-product .woocommerce-breadcrumb, body.single-product .woocommerce-breadcrumb a{ color:rgba(245,240,232,.4) !important; }
  body.single-product .ast-single-post-order, body.single-product .entry-title{ color:#F5F0E8 !important; }

  /* ---- fixes: hide shop "Shop / Explore..." header + make tile titles reliably visible ---- */
  body.woocommerce-shop .ast-archive-description,
  body.woocommerce-shop header.woocommerce-products-header,
  body.woocommerce-shop .woocommerce-products-header{ display:none !important; }
  body.woocommerce-shop ul.products li.product-category{ min-height:62px !important; }
  body.woocommerce-shop ul.products li.product-category a{
    display:flex !important; align-items:center !important; justify-content:space-between !important; gap:12px !important;
    padding:15px 16px 15px 22px !important;
  }
  body.woocommerce-shop ul.products li.product-category h2.woocommerce-loop-category__title{
    display:block !important; flex:1 1 auto !important; min-width:0 !important; min-height:auto !important;
    color:#F5F0E8 !important; font-size:19px !important; line-height:1.25 !important; text-align:left !important;
  }
  body.woocommerce-shop ul.products li.product-category h2.woocommerce-loop-category__title mark.count{
    display:block !important; margin-top:3px !important; float:none !important;
  }
  body.woocommerce-shop ul.products li.product-category a::after{
    content:"\203A"; flex:0 0 auto; color:rgba(245,240,232,.4); font-size:20px; font-family:'DM Sans',sans-serif; line-height:1;
  }

  /* ---- slim footer (hide the bulky menu/widget block, keep a thin copyright line) ---- */
  footer.site-footer .site-primary-footer-wrap{ display:none !important; }
  footer.site-footer .site-below-footer-wrap{ padding:12px 16px calc(12px + env(safe-area-inset-bottom)) !important; min-height:0 !important; }
  footer.site-footer .site-below-footer-wrap .ast-builder-grid-row{ grid-template-columns:1fr !important; gap:2px !important; }
  body.woocommerce footer.site-footer, body.tax-product_cat footer.site-footer, body.single-product footer.site-footer{ background:#0D0B09 !important; border-top:1px solid rgba(255,255,255,.06) !important; }
  body.woocommerce footer.site-footer .site-below-footer-wrap, body.woocommerce footer.site-footer .site-below-footer-wrap *,
  body.tax-product_cat footer.site-footer .site-below-footer-wrap *, body.single-product footer.site-footer .site-below-footer-wrap *{
    color:rgba(245,240,232,.25) !important; font-size:10px !important; line-height:1.5 !important;
  }
}
</style>
HTML;
	}
}
/* ===== END VL MOBILE PORTAL — PASS 2 ===== */


/* =====================================================================
   VL MOBILE PORTAL — PASS 3  (added 2026-06-06, image cards 2026-06-07)
   Replaces the native WooCommerce category tiles (which the per-account
   branding keeps breaking) with our OWN big image cards on mobile, so the
   shop "home" matches the mockup. Each card uses the category's WooCommerce
   image as a full-bleed background with a dark gradient for legibility.
   Uses <span> markup that the "all headings black" branding rule can't
   touch. Mobile-only. Reversible: delete this whole block.
   ===================================================================== */
if ( ! function_exists( 'vl_mobile_catlist' ) ) {

	add_action( 'woocommerce_before_shop_loop', 'vl_mobile_catlist', 6 );
	function vl_mobile_catlist() {
		if ( is_admin() || ! ( function_exists( 'is_shop' ) && is_shop() ) ) { return; }
		$cats = array(
			array( 'slug' => 'weft',                          'name' => 'Wefts',                  'meta' => 'Machine &amp; Genius Weft',  'ac' => '#C9A96E', 'cm' => true ),
			array( 'slug' => 'small-nanobeads-most-discreet', 'name' => 'Nano &amp; Ultra Tips',             'meta' => 'Micro-bond application',     'ac' => '#8BB8CC', 'cm' => true ),
			array( 'slug' => 'fusions-k-tips',                'name' => 'Keratin Tips &amp; Strips', 'meta' => 'Fusion bonding',         'ac' => '#D4A8A8', 'cm' => true ),
			array( 'slug' => 'tools-accessaries',             'name' => 'Accessories',           'meta' => 'Rings, tools &amp; more',    'ac' => '#A8A8D4', 'cm' => false ),
			array( 'slug' => 'the-brand-bar',                 'name' => 'Brand Bar',         'meta' => 'White-label range',          'ac' => '#C9A96E', 'cm' => false ),
		);
		echo '<div class="vl-catlist"><div class="vl-catlist-lbl">Select a category</div>';
		foreach ( $cats as $c ) {
			$term = get_term_by( 'slug', $c['slug'], 'product_cat' );
			if ( ! $term || is_wp_error( $term ) ) { continue; }
			$link  = get_term_link( $term );
			if ( is_wp_error( $link ) ) { continue; }
			$count = (int) $term->count;
			$cm    = $c['cm'] ? '<span class="vl-cm-tag">&#10022; color match</span>' : '';
			$pl    = ( 1 === $count ) ? ' product' : ' products';
			$tid   = get_term_meta( $term->term_id, 'thumbnail_id', true );
			$img   = $tid ? wp_get_attachment_image_url( $tid, 'large' ) : '';
			$bg    = $img ? '<span class="vl-cat-bg" style="background-image:url(' . esc_url( $img ) . ')"></span>' : '';
			$cls   = 'vl-cat-card' . ( $img ? ' has-img' : '' );
			echo '<a class="' . $cls . '" href="' . esc_url( $link ) . '" style="--ac:' . esc_attr( $c['ac'] ) . '">'
				. $bg
				. '<span class="vl-cat-strip"></span>'
				. '<span class="vl-cat-body">'
					. '<span class="vl-cat-t">' . $c['name'] . $cm . '</span>'
					. '<span class="vl-cat-m">' . $c['meta'] . ' &middot; ' . $count . $pl . '</span>'
				. '</span>'
				. '<span class="vl-cat-arr">&#8250;</span>'
				. '</a>';
		}
		echo '</div>';
	}

	add_action( 'wp_head', 'vl_mobile_catlist_css', 122 );
	function vl_mobile_catlist_css() {
		if ( is_admin() ) { return; }
		echo <<<'HTML'
<style id="vl-mobile-portal-3">
.vl-catlist{ display:none; }
@media (max-width:768px){
  /* hide the native tiles + header + site footer on the shop home, show our own list */
  body.woocommerce-shop ul.products{ display:none !important; }
  body.woocommerce-shop .ast-archive-description, body.woocommerce-shop header.woocommerce-products-header{ display:none !important; }
  footer.site-footer{ display:none !important; }

  .vl-catlist{ display:flex !important; flex-direction:column; gap:12px; padding:6px 16px 0; }
  .vl-catlist-lbl{ color:rgba(245,240,232,.28); font-size:9px; font-family:'DM Sans',sans-serif; font-weight:600; letter-spacing:.18em; text-transform:uppercase; margin:6px 0 2px; }

  /* big image cards */
  .vl-cat-card{ position:relative; display:flex; align-items:center; text-decoration:none; min-height:150px; background:#1B150F; border:1px solid rgba(255,255,255,.08); border-radius:18px; overflow:hidden; }
  .vl-cat-card:active{ opacity:.92; }
  .vl-cat-bg{ position:absolute; inset:0; z-index:0; background-size:cover; background-position:center; }
  .vl-cat-card.has-img::after{ content:""; position:absolute; inset:0; z-index:1; background:rgba(13,11,9,0.22); pointer-events:none; }
  .vl-cat-strip{ position:absolute; left:0; top:0; bottom:0; width:4px; z-index:2; background:var(--ac,#C9A96E); }
  .vl-cat-body{ position:relative; z-index:2; flex:1 1 auto; padding:0 82px 0 20px; min-width:0; display:flex; flex-direction:column; justify-content:center; text-align:center; }
  .vl-cat-t{ display:block; color:#F5F0E8 !important; font-size:24px; font-family:'Cormorant Garamond',Georgia,serif; line-height:1.15; text-shadow:0 1px 10px rgba(0,0,0,.65); }
  .vl-cm-tag{ color:var(--ac,#C9A96E); font-size:11px; font-family:'DM Sans',sans-serif; font-weight:500; margin-left:8px; opacity:.95; white-space:nowrap; text-shadow:0 1px 6px rgba(0,0,0,.8); }
  .vl-cat-m{ display:block; color:rgba(245,240,232,.72); font-size:12px; font-family:'DM Sans',sans-serif; font-weight:300; margin-top:4px; text-shadow:0 1px 6px rgba(0,0,0,.8); }
  .vl-cat-arr{ position:absolute; top:50%; transform:translateY(-50%); right:16px; bottom:auto; z-index:2; width:66px; height:66px; border-radius:50%; background:rgba(13,11,9,.45); -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px); border:1px solid rgba(255,255,255,.22); display:flex; align-items:center; justify-content:center; color:#F5F0E8; font-size:58px; font-family:'DM Sans',sans-serif; line-height:0; padding-bottom:6px; }

  /* move the WhatsApp (Join.chat) button to top-right, just under the header */
  .joinchat{ top:72px !important; bottom:auto !important; right:12px !important; left:auto !important; }
  .joinchat__button{ top:0 !important; bottom:auto !important; right:0 !important; }
  .joinchat__chatbox{ top:74px !important; bottom:auto !important; }
}
</style>
HTML;
	}
}
/* ===== END VL MOBILE PORTAL — PASS 3 ===== */





/* ===== VL CONFIGURATOR (rebuilt 2026-06-08: multi-product + weighted pricing) ===== */
/* Make the product Description/tabs legible on the dark mobile portal. */
add_action('wp_head','vl_dark_product_tabs_css',130);
function vl_dark_product_tabs_css(){
    if(!function_exists('is_product') || !is_product()) return;
    echo '<style id="vl-dark-tabs">@media (max-width:768px){'
       .'body.single-product .woocommerce-tabs .panel,'
       .'body.single-product .woocommerce-Tabs-panel,'
       .'body.single-product .woocommerce-Tabs-panel p,'
       .'body.single-product .woocommerce-Tabs-panel li,'
       .'body.single-product .wc-tab,'
       .'body.single-product #tab-description,'
       .'body.single-product #tab-description p{color:#F5F0E8 !important;}'
       .'body.single-product .wc-tabs li a,body.single-product ul.tabs li a{color:rgba(245,240,232,.65) !important;}'
       .'body.single-product .wc-tabs li.active a,body.single-product ul.tabs li.active a{color:#C9A96E !important;}'
       .'}</style>';
}

/* "Gallery" heading + breathing room above the product gallery on configurator products. */
add_action('woocommerce_before_single_product_summary','vl_gallery_heading',19);
function vl_gallery_heading(){
    global $post; if(!$post) return;
    $vl_cfg = array('genius-weft','machine-weft','nanos-hair','ultra-tips','k-tips-hair','k-strip-hair');
    if(!in_array($post->post_name,$vl_cfg,true)) return;
    echo '<style>.vl-gallery-heading{font-family:\'Cormorant Garamond\',Georgia,serif;font-size:30px;font-weight:600;color:#15110d;margin:52px 0 16px;padding:0 4px;line-height:1.1}@media (max-width:768px){.vl-gallery-heading{color:#F5F0E8 !important}}</style>';
    echo '<div class="vl-gallery-heading">Gallery</div>';
}

/* Make WooCommerce cart/checkout/account legible on the dark mobile portal. */
add_action('wp_head','vl_dark_woo_pages_css',131);
function vl_dark_woo_pages_css(){
    if(!function_exists('is_cart')) return;
    if(!( is_cart() || is_checkout() || is_account_page() )) return;
    ?>
<style id="vl-dark-woo">
@media (max-width:768px){
  body.woocommerce-cart, body.woocommerce-checkout, body.woocommerce-account,
  body.woocommerce-cart p, body.woocommerce-checkout p, body.woocommerce-account p,
  body.woocommerce-cart li, body.woocommerce-checkout li, body.woocommerce-account li,
  body.woocommerce-cart td, body.woocommerce-checkout td, body.woocommerce-account td,
  body.woocommerce-cart th, body.woocommerce-checkout th, body.woocommerce-account th,
  body.woocommerce-cart label, body.woocommerce-checkout label, body.woocommerce-account label,
  body.woocommerce-cart legend, body.woocommerce-checkout legend, body.woocommerce-account legend,
  body.woocommerce-cart h1, body.woocommerce-cart h2, body.woocommerce-cart h3, body.woocommerce-cart h4,
  body.woocommerce-checkout h1, body.woocommerce-checkout h2, body.woocommerce-checkout h3, body.woocommerce-checkout h4,
  body.woocommerce-account h1, body.woocommerce-account h2, body.woocommerce-account h3, body.woocommerce-account h4{
    color:#F5F0E8 !important;
  }
  body.woocommerce-cart a:not(.button):not(.checkout-button), body.woocommerce-checkout a:not(.button):not(.checkout-button), body.woocommerce-account a:not(.button):not(.checkout-button){ color:#C9A96E !important; }
  body.woocommerce-cart input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]),
  body.woocommerce-cart select, body.woocommerce-cart textarea,
  body.woocommerce-checkout input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]),
  body.woocommerce-checkout select, body.woocommerce-checkout textarea,
  body.woocommerce-account input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]),
  body.woocommerce-account select, body.woocommerce-account textarea{
    background:#fff !important; color:#15110d !important; border:1px solid #c9bfa8 !important;
  }
  body.woocommerce-checkout .select2-selection, body.woocommerce-checkout .select2-selection__rendered,
  body.woocommerce-account .select2-selection, body.woocommerce-account .select2-selection__rendered{ background:#fff !important; color:#15110d !important; }
  body.woocommerce-cart table td, body.woocommerce-cart table th,
  body.woocommerce-checkout table td, body.woocommerce-checkout table th,
  body.woocommerce-account table td, body.woocommerce-account table th{ border-color:rgba(245,240,232,.2) !important; }
}
</style>
    <?php
}

/* Remove the "Additional information" product tab. */
add_filter('woocommerce_product_tabs','vl_remove_additional_info_tab',98);
function vl_remove_additional_info_tab($tabs){ unset($tabs['additional_information']); return $tabs; }

/* Remove "You may also like" (upsells) and "Related products" on all front-end product pages. */
add_action('wp','vl_remove_upsell_related_sections');
function vl_remove_upsell_related_sections(){
    remove_action('woocommerce_after_single_product_summary','woocommerce_upsell_display',15);
    remove_action('woocommerce_after_single_product_summary','woocommerce_output_related_products',20);
}
add_filter('woocommerce_related_products','vl_empty_if_frontend',99);
add_filter('woocommerce_product_get_upsell_ids','vl_empty_if_frontend',99);
function vl_empty_if_frontend($val){ return is_admin() ? $val : array(); }

/* Hide the native WooCommerce order summary on configurator products — the configurator replaces it. */
add_action('woocommerce_before_single_product_summary','vl_hide_native_summary',99);
function vl_hide_native_summary(){
    global $post; if(!$post) return;
    $vl_cfg = array('genius-weft','machine-weft','nanos-hair','ultra-tips','k-tips-hair','k-strip-hair');
    if(!in_array($post->post_name,$vl_cfg,true)) return;
    remove_all_actions('woocommerce_single_product_summary');
}

add_action('woocommerce_before_single_product','vl_gweft_configurator_embed');
function vl_gweft_configurator_embed(){
    if(!function_exists('is_product') || !is_product()) return;
    global $post; if(!$post) return;
    $vl_map = array('genius-weft'=>'gweft','machine-weft'=>'machineweft','nanos-hair'=>'nanotips','ultra-tips'=>'nanotips','k-tips-hair'=>'ktips','k-strip-hair'=>'kstrip');
    if(!isset($vl_map[$post->post_name])) return;
    $vl_key=$vl_map[$post->post_name];
    $vl_name=get_the_title($post);
    $vl_img=get_the_post_thumbnail_url($post,'large'); if(!$vl_img){ $vl_img='https://volumeandlength.com/wp-content/uploads/2026/06/G-weft-light-brown.jpg'; }
    $vl_price='$89.00';
    if(function_exists('wc_get_product')){ $vl_pr=wc_get_product($post->ID); if($vl_pr){ $vl_pp=$vl_pr->get_price(); if($vl_pp!=='' && $vl_pp!==null){ $vl_price='$'.number_format((float)$vl_pp,2); } } }
    $vl_desc = $post->post_excerpt ? wp_strip_all_tags($post->post_excerpt) : 'Premium, ethically-sourced human hair, made to order.';
    $vl_html = <<<'VLGWCDOC'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>G-Weft Configurator — Mockup</title>
<style>
  :root{
    --ink:#15110d;
    --muted:#6f6a62;
    --line:#e6e0d6;
    --bg:#ffffff;
    --paper:#faf7f2;
    --gold:#b5915f;
    --gold-deep:#8a6b45;
    --olive:#5b5530;
    --radius:14px;
  }
  *{box-sizing:border-box;margin:0;padding:0}
  body{
    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    color:var(--ink);background:var(--paper);line-height:1.55;
  }
  .serif{font-family:Georgia,"Times New Roman",serif}
  .topbar{background:#0c0a08;color:#fff;text-align:center;padding:18px 16px}
  .wordmark{font-family:Georgia,serif;font-size:24px;letter-spacing:.5px;line-height:1}
  .wordmark small{display:block;font-size:9px;letter-spacing:3px;color:#cdbfa6;margin-top:6px;font-family:-apple-system,sans-serif}
  .shell{max-width:1040px;margin:0 auto;padding:26px 18px 24px}
  .steps{display:flex;gap:4px;justify-content:space-between;flex-wrap:nowrap;flex:1 1 auto;min-width:0;margin:0}
  .steps .pip{display:flex;flex-direction:column;align-items:center;gap:5px;font-size:14.4px;color:var(--muted);text-align:center;flex:1 1 0;max-width:none}
  .steps .dot{width:24px;height:24px;border-radius:50%;border:1.5px solid var(--line);display:grid;place-items:center;font-size:13.2px;background:#fff;color:var(--muted)}
  .steps .pip.active .dot{background:var(--ink);border-color:var(--ink);color:#fff}
  .steps .pip.done .dot{background:var(--olive);border-color:var(--olive);color:#fff}
  .steps .pip.active span,.steps .pip.done span{color:var(--ink)}
  .steps .bar{display:none}
  .vl-tophead{display:flex;align-items:flex-start;gap:14px;margin:6px 0 26px;padding:0 4px}
  .vl-topright{flex:0 0 auto;display:flex;flex-direction:column;align-items:flex-end;gap:7px}
  .vl-topright #vlTopContinue{white-space:nowrap}
  .vl-price{font-family:'DM Sans',sans-serif;font-weight:700;font-size:19px;color:var(--ink)}
  #vlTopContinue[disabled]{opacity:.45;cursor:not-allowed}
  .panel .nav .btn-primary{display:none}
  .panel{display:none;animation:fade .35s ease}
  .panel.active{display:block}
  @keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
  .eyebrow{font-size:12px;letter-spacing:2.5px;text-transform:uppercase;color:var(--gold-deep);margin-bottom:10px}
  h1.step-title{font-family:Georgia,serif;font-size:34px;font-weight:500;margin-bottom:6px}
  .lead{color:var(--muted);max-width:560px;margin-bottom:26px}
  .vl-rm{color:#C9A96E;cursor:pointer;text-decoration:underline;font-weight:600;white-space:nowrap}
  .hero{display:grid;grid-template-columns:1fr 1fr;gap:40px;align-items:center}
  .hero .photo{border-radius:var(--radius);overflow:hidden;background:#efe9e0;aspect-ratio:1/1}
  .hero .photo img{width:100%;height:100%;object-fit:cover;display:block}
  .hero h1{font-family:Georgia,serif;font-size:46px;font-weight:500;margin-bottom:14px}
  .hero p{color:var(--muted);margin-bottom:18px}
  .price{font-size:20px;font-weight:600}
  .price small{font-weight:400;color:var(--muted);font-size:13px}
  .ring-wrap{display:flex;flex-direction:column;align-items:center}
  .ring{position:relative;width:min(560px,92vw);aspect-ratio:1/1;margin:0 auto}
  .ring>img{width:100%;height:100%;object-fit:contain;display:block;border-radius:50%}
  .marker{position:absolute;transform:translate(-50%,-50%);width:46px;height:46px;border-radius:50%;
    border:2px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,.28);cursor:pointer;display:grid;place-items:center;
    font-size:12px;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.45);transition:transform .15s, box-shadow .15s;}
  .marker:hover{transform:translate(-50%,-50%) scale(1.14)}
  .marker.sel{outline:3px solid var(--olive);outline-offset:2px;transform:translate(-50%,-50%) scale(1.18)}
  .marker.sel::after{content:attr(data-pick);position:absolute;top:-9px;right:-9px;background:var(--olive);color:#fff;width:18px;height:18px;border-radius:50%;font-size:10px;display:grid;place-items:center;box-shadow:0 1px 3px rgba(0,0,0,.3)}
  .ring-readout{margin-top:18px;text-align:center;min-height:24px;font-size:15px}
  .ring-readout b{font-family:Georgia,serif;font-size:18px}
  .ring-hint{font-size:12px;color:var(--muted);margin-top:6px;text-align:center}
  .styles{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;max-width:620px;margin:0 0 24px}
  .stylecard{border:1.5px solid var(--line);background:#fff;border-radius:10px;padding:14px 16px;cursor:pointer;transition:.15s}
  .stylecard:hover{border-color:var(--gold)}
  .stylecard.sel{border-color:var(--ink);box-shadow:inset 0 0 0 1px var(--ink)}
  .stylecard .t{font-weight:600;margin-bottom:4px}
  .stylecard.sel .t::after{content:" ✓";color:var(--olive)}
  .stylecard .d{font-size:12px;color:var(--muted);line-height:1.4}
  @media(max-width:620px){.styles{grid-template-columns:1fr}}
  .mix{max-width:620px;margin:0 0 8px}
  .mixhead{display:flex;justify-content:space-between;font-size:14px;margin-bottom:6px}
  .mixhead b{font-family:Georgia,serif;font-size:17px}
  .mixslider{width:100%}
  .mixbar{height:14px;border-radius:7px;overflow:hidden;display:flex;margin-top:12px;border:1px solid var(--line)}
  .mixbar i{display:block;height:100%}
  .mixnote{font-size:12px;color:var(--muted);margin-top:8px}
  .baserow{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--muted);margin-bottom:14px}
  .basebtn{border:1.5px solid var(--line);background:#fff;border-radius:20px;padding:6px 16px;cursor:pointer;font-size:13px;color:var(--ink)}
  .basebtn:hover{border-color:var(--gold)}
  .basebtn.sel{border-color:var(--ink);background:var(--ink);color:#fff}
  .basetag{font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--gold-deep);margin-left:5px}
  .mixsingle{max-width:620px;font-size:14px;color:var(--muted);margin:0 0 8px}
  .mixslider:disabled{opacity:.6;cursor:not-allowed}
  .widths{display:flex;gap:10px;max-width:620px;margin:0 0 24px}
  .widthchip{border:1.5px solid var(--line);background:#fff;border-radius:10px;padding:12px 24px;cursor:pointer;font-size:15px;transition:.15s}
  .widthchip:hover{border-color:var(--gold)}
  .widthchip.sel{border-color:var(--ink);background:var(--ink);color:#fff}
  .field{margin-bottom:22px;max-width:620px}
  .field label{display:block;font-weight:600;margin-bottom:8px}
  textarea,.upload{width:100%;border:1.5px solid var(--line);border-radius:10px;padding:14px;font:inherit;background:#fff}
  textarea{min-height:120px;resize:vertical}
  .upload{display:flex;align-items:center;gap:12px;cursor:pointer;color:var(--muted)}
  .upload b{color:var(--ink)}
  .blendpick{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:6px}
  .blendpick .bd{display:inline-flex;align-items:center;gap:8px;border:1.5px solid var(--line);border-radius:30px;padding:7px 14px;font-size:14px}
  .swatchdot{display:inline-block;width:14px;height:14px;border-radius:50%;border:1px solid rgba(0,0,0,.15);vertical-align:-2px;margin-right:7px}
  .review{display:grid;grid-template-columns:200px 1fr;gap:28px;align-items:start;border:1px solid var(--line);border-radius:var(--radius);padding:24px;background:#fff}
  .review .thumb{border-radius:10px;overflow:hidden;aspect-ratio:1/1;background:#efe9e0}
  .review .thumb img{width:100%;height:100%;object-fit:cover}
  .sumrow{display:flex;justify-content:space-between;padding:11px 0;border-bottom:1px solid var(--line);gap:16px}
  .sumrow:last-of-type{border-bottom:0}
  .sumrow .k{color:var(--muted);font-size:13px;letter-spacing:.5px;text-transform:uppercase}
  .sumrow .v{font-weight:600;text-align:right}
  .qty{display:inline-flex;align-items:center;border:1.5px solid var(--line);border-radius:8px;overflow:hidden}
  .qty button{border:0;background:#f3efe8;width:34px;height:34px;font-size:18px;cursor:pointer}
  .qty input{width:42px;text-align:center;border:0;height:34px;font:inherit}
  .nav{display:flex;justify-content:space-between;align-items:center;margin-top:34px;gap:14px}
  .btn{border:0;border-radius:10px;padding:14px 30px;font-size:15px;font-weight:600;cursor:pointer;letter-spacing:.4px}
  .btn-primary{background:var(--olive);color:#fff}
  .btn-primary:disabled{background:#cfc8bc;cursor:not-allowed}
  .btn-ghost{background:transparent;color:var(--muted);border:1.5px solid var(--line)}
  .btn-cart{background:var(--olive);color:#fff;width:100%;margin-top:18px;padding:16px}
  .mock-flag{position:fixed;left:12px;bottom:12px;background:#15110d;color:#cdbfa6;font-size:11px;letter-spacing:1px;padding:7px 12px;border-radius:20px;opacity:.85}
  @media(max-width:760px){
    .hero{grid-template-columns:1fr;gap:24px}
    .hero h1{font-size:34px}
    .review{grid-template-columns:1fr}
    .review .thumb{aspect-ratio:2/1}
    h1.step-title{font-size:26px}
  }
  .vlpct{display:flex;flex-direction:column;gap:10px;margin-top:6px}
  .vlpct-row{display:flex;align-items:center;gap:10px}
  .vlpct-sw{width:22px;height:22px;border-radius:50%;flex:0 0 auto;border:1px solid rgba(0,0,0,.1)}
  .vlpct-name{flex:1;font-weight:600;font-size:14px}
  .vlpct-btn{width:32px;height:32px;border:1px solid var(--line);background:#fff;border-radius:8px;font-size:17px;line-height:1;cursor:pointer}
  .vlpct-btn:disabled{opacity:.35;cursor:default}
  .vlpct-val{min-width:48px;text-align:center;font-weight:700}
  .vlpct-lock{width:32px;height:32px;border:1px solid var(--line);background:#fff;border-radius:8px;cursor:pointer;font-size:14px}
  .vlpct-lock.on{background:var(--olive);border-color:var(--olive)}
</style>
</head>
<body>
  

  <div class="shell">
    <div class="vl-tophead">
      <div class="steps" id="steps"></div>
      <div class="vl-topright"><button class="btn btn-primary" id="vlTopContinue" onclick="goNext()">Continue →</button><span class="vl-price" id="vlRunPrice"></span></div>
    </div>

    <!-- STEP 1: COLOUR (up to 2) -->
    <section class="panel" data-key="color" data-step="0">
      <div class="eyebrow">Step 1 — Color</div>
      <p class="lead">Tap up to two colors <span class="vl-rm-x" style="display:none">on the digital color swatch, which is an exact replica of the physical color swatch in your salon. Pick one for a single color, or two if you'd like them blended. </span><a class="vl-rm" onclick="vlToggleLead(this)">Read more</a></p>
      <div class="ring-wrap">
        <div class="ring" id="ring">
          <img src="https://volumeandlength.com/wp-content/uploads/2026/06/bamboo-color-ring-1.png" alt="Color ring">
        </div>
        <div class="ring-readout" id="ringReadout">No color selected yet</div>
        <div class="ring-hint"></div>
      </div>
      <div class="nav">
        <span></span>
        <button class="btn btn-primary" id="colourNext" disabled onclick="goNext()">Continue →</button>
      </div>
    </section>

    <!-- STEP 2: BLEND -->
    <section class="panel" data-key="blend" data-step="1">
      <div class="eyebrow">Step 2 — Blend</div>
      <div id="blendChosen" class="blendpick"></div>
      <div id="blendMix" style="margin-top:14px"></div>
      <label style="display:block;font-weight:600;margin:18px 0 10px">Blend style</label>
      <div class="styles" id="blendStyles"></div>
      <div id="pianoWidthWrap" style="display:none">
        <label style="display:block;font-weight:600;margin:4px 0 10px">Stripe width</label>
        <div class="widths" id="pianoWidths"></div>
      </div>
      <div style="margin-top:6px">
        <label style="display:block;font-weight:600;margin:0 0 10px">Rooted</label>
        <div class="widths" id="rootedToggle"></div>
        <div id="rootedOpts" style="display:none;margin-top:18px"></div>
      </div>
      <div class="nav">
        <button class="btn btn-ghost" onclick="goBack()">← Back</button>
        <button class="btn btn-primary" id="blendNext" disabled onclick="goNext()">Continue →</button>
      </div>
    </section>

    <!-- STEP 3: WIDTH (weft only) -->
    <section class="panel" data-key="width" data-step="2">
      <div class="eyebrow">Step 3 — Width</div>
      <h1 class="step-title serif">Choose your weft width</h1>
      <p class="lead">Pick the width that suits the install.</p>
      <div class="widths" id="weftWidths"></div>
      <div class="nav">
        <button class="btn btn-ghost" onclick="goBack()">← Back</button>
        <button class="btn btn-primary" id="widthNext" disabled onclick="goNext()">Continue →</button>
      </div>
    </section>

    <!-- STEP: LENGTH & TEXTURE -->
    <section class="panel" data-key="lentex" data-step="3">
      <div class="eyebrow">Length &amp; Texture</div>
      <label style="display:block;font-weight:600;margin:0 0 10px">Length</label>
      <div class="widths" id="lengthChips"></div>
      <div class="vl-weight-note" id="vlWeightNote" style="display:none;margin-top:10px;font-size:13px;color:var(--muted)"></div>
      <label style="display:block;font-weight:600;margin:22px 0 10px">Texture</label>
      <div class="widths" id="textureChips"></div>
      <div class="nav">
        <button class="btn btn-ghost" onclick="goBack()">← Back</button>
        <button class="btn btn-primary" id="ltNext" disabled onclick="goNext()">Review order →</button>
      </div>
    </section>

    <!-- STEP: REVIEW -->
    <section class="panel" data-key="review" data-step="4">
      <div class="eyebrow">Step 5 — Review</div>
      <p class="lead">Check everything looks right, then add to your order.</p>
      <div class="review">
        <div class="thumb"><img src="{{IMG}}" alt="G-Weft"></div>
        <div>
          <div class="sumrow"><span class="k">Product</span><span class="v">{{NAME}}</span></div>
          <div class="sumrow"><span class="k">Color(s)</span><span class="v" id="revShade">—</span></div>
          <div class="sumrow"><span class="k">Blend style</span><span class="v" id="revBlend">—</span></div>
          <div class="sumrow"><span class="k">Rooted</span><span class="v" id="revRooted">No</span></div>
          <div class="sumrow" id="revWidthRow"><span class="k">Width</span><span class="v" id="revWidth">—</span></div>
          <div class="sumrow"><span class="k">Length</span><span class="v" id="revLength">—</span></div>
          <div class="sumrow"><span class="k">Texture</span><span class="v" id="revTexture">—</span></div>
          <div class="sumrow" id="revWeightRow" style="display:none"><span class="k">Weight</span><span class="v" id="revWeight">—</span></div>
          <div class="sumrow"><span class="k">Price</span><span class="v" id="revPrice">{{PRICE}} <small style="color:var(--muted);font-weight:400">excl. GST</small></span></div>
          <div class="sumrow" style="border-bottom:0;align-items:center">
            <span class="k" id="revQtyLabel">Quantity</span>
            <span class="v"><span class="qty"><button onclick="qty(-1)">−</button><input id="qty" value="1" readonly><button onclick="qty(1)">+</button></span></span>
          </div>
          <button class="btn btn-cart" onclick="vlAddToCart()">ADD TO CART</button>
        </div>
      </div>
      <div class="nav">
        <button class="btn btn-ghost" onclick="goBack()">← Back</button>
        <span></span>
      </div>
    </section>
  </div>

  

<script>
  var SHADES = [
    {v:"10",  label:"10",   name:"Lightest Blonde", hex:"#ddc492"},
    {v:"wb2", label:"WB2",  name:"Warm Blonde 2",   hex:"#ecd8ad"},
    {v:"wb1", label:"WB1",  name:"Warm Blonde 1",   hex:"#e0c594"},
    {v:"10-1",label:"10.1", name:"Ash Blonde",      hex:"#cdc3a4"},
    {v:"9",   label:"9",    name:"Light Blonde",    hex:"#c8a877"},
    {v:"8",   label:"8",    name:"Medium Blonde",   hex:"#b08a5d"},
    {v:"7",   label:"7",    name:"Dark Blonde",     hex:"#8f6f4c"},
    {v:"6",   label:"6",    name:"Light Brown",     hex:"#70502f"},
    {v:"4",   label:"4",    name:"Chocolate Brown", hex:"#4c3320"},
    {v:"2",   label:"2",    name:"Darkest Brown",   hex:"#2c1d13"},
    {v:"1",   label:"1",    name:"Jet Black",       hex:"#171210"},
    {v:"zero",label:"Zero", name:"Natural / Raw",   hex:"#a8906f"}
  ];
  var VL_KEY='{{ADDKEY}}'; var MAX_SHADES=(VL_KEY==='nanotips'||VL_KEY==='ktips'||VL_KEY==='kstrip')?3:2;
  var VL_BASE_PRICE='{{PRICE}}';
  var BLENDS = [
    {v:"blend",       label:"Blend",       desc:"Shades melted together for a seamless, multi-tonal finish."},
    {v:"piano",       label:"Piano",       desc:"Alternating ribbons of each shade, like piano keys."},
    {v:"dimensional", label:"Dimensional", desc:"Placed for natural depth and root-to-tip movement."}
  ];
  var WIDTHS = [{v:"1cm",label:"1 cm"},{v:"2cm",label:"2 cm"}];
  var WEFT_WIDTHS = [{v:"36",label:"36 in"},{v:"12",label:"12 in"}];
  var LENGTHS = [{v:"14",label:'14"'},{v:"16",label:'16"'},{v:"18",label:'18"'},{v:"20",label:'20"'},{v:"22",label:'22"'},{v:"24",label:'24"'}];
  var VL_TIPS = (VL_KEY==='nanotips'||VL_KEY==='ktips'||VL_KEY==='kstrip');
  var TEXTURES = [{v:"straight",label:"Straight"},{v:"straightbody",label:"Straight with body"}];
  var VL_UNIT_WEIGHT = {'14':15,'16':20,'18':20,'20':25,'22':25,'24':30};
  var ROOT_LEVELS = [{v:"3",label:"3"},{v:"4",label:"4"},{v:"5",label:"5"},{v:"6",label:"6"},{v:"7",label:"7"},{v:"8",label:"8"}];
  var ROOT_LENGTHS = [{v:"1in",label:"1 in"},{v:"3in",label:"3 in"},{v:"5in",label:"5 in"}];
  var state = {step:0, shades:[], mix:50, base:0, blend:null, pianoWidth:null, rooted:false, rootLevel:null, rootLength:null, width:null, length:null, texture:null, qty:1};
  var VL_LABELS = {color:'Color',blend:'Blend',width:'Width',lentex:'Length & Texture',review:'Review'};
  var VL_STEPS = VL_TIPS ? ['color','blend','lentex','review'] : ['color','blend','lentex','width','review'];

  function renderSteps(){
    var c = document.getElementById('steps'); c.innerHTML='';
    VL_STEPS.forEach(function(key,i){
      var pip = document.createElement('div');
      pip.className='pip'+(i===state.step?' active':'')+(i<state.step?' done':'');
      pip.innerHTML='<span class="dot">'+(i<state.step?'✓':(i+1))+'</span><span>'+VL_LABELS[key]+'</span>';
      c.appendChild(pip);
      if(i<VL_STEPS.length-1){var b=document.createElement('div');b.className='bar';c.appendChild(b);}
    });
  }
  function go(n){
    state.step=n;
    var key=VL_STEPS[n];
    document.querySelectorAll('.panel').forEach(function(p){p.classList.toggle('active', p.dataset.key===key);});
    var ap=document.querySelector('.panel[data-key="'+key+'"] .eyebrow');
    if(ap){ ap.textContent='Step '+(n+1)+' — '+VL_LABELS[key]; }
    var apPanel=document.querySelector('.panel[data-key="'+key+'"]');
    if(apPanel && key!=='review'){ var pb=apPanel.querySelector('.btn-primary'); if(pb){ pb.textContent=(VL_STEPS[n+1]==='review')?'Review order →':'Continue →'; } }
    if(key==='blend'){ renderBlendChosen(); renderMix(); updateBlendNext(); }
    if(key==='review'){ if(typeof vlUpdatePrice==='function'){ vlUpdatePrice(); } if(typeof vlUpdateWeight==='function'){ vlUpdateWeight(); } }
    renderSteps();
    if(typeof vlRefresh==='function')vlRefresh();
    window.scrollTo({top:0,behavior:'smooth'});
  }
  function goNext(){ if(state.step<VL_STEPS.length-1) go(state.step+1); }
  function goBack(){ if(state.step>0) go(state.step-1); }
  function vlToggleLead(a){var p=a.parentNode;var x=p.querySelector('.vl-rm-x');if(!x)return;var open=x.style.display!=='none';x.style.display=open?'none':'inline';a.textContent=open?'Read more':'Read less';}

  function buildRing(){
    var ring=document.getElementById('ring');
    var n=SHADES.length;
    SHADES.forEach(function(s,i){
      var ang=(-90 + i*(360/n)) * Math.PI/180;
      var R=44;
      var x=50+R*Math.cos(ang), y=50+R*Math.sin(ang);
      var m=document.createElement('button');
      m.className='marker'; m.style.left=x+'%'; m.style.top=y+'%';
      m.style.background=s.hex; m.textContent=s.label; m.title=s.name; m.dataset.v=s.v;
      m.onclick=function(){toggleShade(s.v);};
      ring.appendChild(m);
    });
  }
  function toggleShade(v){
    var idx=state.shades.indexOf(v);
    if(idx>-1){ state.shades.splice(idx,1); }
    else if(state.shades.length<MAX_SHADES){ state.shades.push(v); }
    else { flashHint(); return; }
    syncColor();
  }
  function flashHint(){
    var h=document.querySelector('.ring-hint');
    h.style.color='#a32d2d'; h.textContent='You can choose up to two colors — tap a selected one to swap.';
    setTimeout(function(){h.style.color='';h.textContent='';},1800);
  }
  function shadeObj(v){return SHADES.find(function(x){return x.v===v;});}
  function syncColor(){
    document.querySelectorAll('.marker').forEach(function(m){
      var p=state.shades.indexOf(m.dataset.v);
      m.classList.toggle('sel', p>-1);
      m.dataset.pick = p>-1 ? (p+1) : '';
    });
    var r=document.getElementById('ringReadout');
    if(state.shades.length===0){ r.textContent='No color selected yet'; }
    else { r.innerHTML='Selected: '+state.shades.map(function(v){var s=shadeObj(v);return '<b>'+s.label+'</b> '+s.name;}).join(' &nbsp;+&nbsp; '); }
    document.getElementById('colourNext').disabled = state.shades.length===0;
    if(typeof MAX_SHADES!=='undefined'&&MAX_SHADES>=3){ state.pcts=null; state.locks=null; }
    updateReviewShade();
  }
  function updateReviewShade(){
    var rev=document.getElementById('revShade');
    if(!state.shades.length){ rev.innerHTML='—'; return; }
    var _pc=(typeof vlPcts==='function')?vlPcts():null;
    rev.innerHTML = state.shades.map(function(v,i){
      var s=shadeObj(v);
      var pct = (state.shades.length>1 && _pc) ? ' '+_pc[i]+'%' : '';
      return '<span class="swatchdot" style="background:'+s.hex+'"></span>'+s.label+pct;
    }).join(' + ');
  }
  function renderMix(){
    var c=document.getElementById('blendMix'); c.innerHTML='';
    { var _bc=document.getElementById('blendChosen'); if(_bc)_bc.style.display='none'; vlRenderPercents(); return; }
    if(state.shades.length===0){ return; }
    if(state.shades.length===1){
      var s=shadeObj(state.shades[0]);
      c.innerHTML='<div class="mixsingle"><span class="swatchdot" style="background:'+s.hex+'"></span><b>'+s.label+'</b> · '+s.name+' at 100%</div>';
      return;
    }
    var a=shadeObj(state.shades[0]), b=shadeObj(state.shades[1]);
    var piano=(state.blend==='piano');
    var r=mixRange();
    var p=piano?50:state.mix;
    var baseName = shadeObj(state.shades[state.base]).label;
    var note = piano ? 'Piano is a fixed 50 / 50 split — the two shades alternate in even stripes.'
      : state.blend==='dimensional' ? 'Dimensional ranges from 90 / 10 to 40 / 60 — your base shade ('+baseName+') stays between 40% and 90%.'
      : 'Slide to set the split — each shade stays between 10% and 90%.';
    var dim = state.blend==='dimensional';
    var tagA = (dim && state.base===0) ? '<span class="basetag">base</span>' : '';
    var tagB = (dim && state.base===1) ? '<span class="basetag">base</span>' : '';
    var baseRow = !dim ? '' : (''
      +'<div class="baserow">Base shade:'
      +  '<button class="basebtn'+(state.base===0?' sel':'')+'" onclick="setBase(0)">'+a.label+'</button>'
      +  '<button class="basebtn'+(state.base===1?' sel':'')+'" onclick="setBase(1)">'+b.label+'</button>'
      +'</div>');
    c.innerHTML=''
      +'<label style="display:block;font-weight:600;margin:0 0 10px">Color mix</label>'
      +'<div class="mix">'
      +  baseRow
      +  '<div class="mixhead">'
      +    '<span><span class="swatchdot" style="background:'+a.hex+'"></span>'+a.label+tagA+' <b id="mixA">'+p+'%</b></span>'
      +    '<span><b id="mixB">'+(100-p)+'%</b> '+b.label+tagB+' <span class="swatchdot" style="background:'+b.hex+'"></span></span>'
      +  '</div>'
      +  '<input type="range" class="mixslider" min="'+r.min+'" max="'+r.max+'" step="5" value="'+p+'"'+(piano?' disabled':'')+' oninput="setMix(this.value)">'
      +  '<div class="mixbar"><i id="fillA" style="width:'+p+'%;background:'+a.hex+'"></i><i id="fillB" style="width:'+(100-p)+'%;background:'+b.hex+'"></i></div>'
      +  '<div class="mixnote">'+note+'</div>'
      +'</div>';
  }
  function mixRange(){
    if(state.blend!=='dimensional') return {min:10,max:90};
    return state.base===0 ? {min:40,max:90} : {min:10,max:60};
  }
  function setBase(i){
    state.base=i; var r=mixRange();
    state.mix=Math.min(r.max,Math.max(r.min,state.mix));
    renderMix(); updateReviewShade();
  }
  function setMix(v){
    var r=mixRange(); v=Math.min(r.max,Math.max(r.min,+v)); state.mix=v;
    document.getElementById('mixA').textContent=v+'%';
    document.getElementById('mixB').textContent=(100-v)+'%';
    document.getElementById('fillA').style.width=v+'%';
    document.getElementById('fillB').style.width=(100-v)+'%';
    updateReviewShade();
  }
  function renderBlendChosen(){
    var c=document.getElementById('blendChosen'); c.innerHTML='';
    if(!state.shades.length){ c.innerHTML='<span style="color:var(--muted);font-size:14px">No shade chosen yet — go back to add one.</span>'; return; }
    state.shades.forEach(function(v){var s=shadeObj(v);
      var d=document.createElement('span'); d.className='bd';
      d.innerHTML='<span class="swatchdot" style="background:'+s.hex+'"></span>'+s.label+' · '+s.name;
      c.appendChild(d);
    });
  }
  function buildBlendStyles(){
    var c=document.getElementById('blendStyles'); c.innerHTML='';
    BLENDS.forEach(function(b){
      var card=document.createElement('div');
      card.className='stylecard'+(state.blend===b.v?' sel':'');
      card.innerHTML='<div class="t">'+b.label+'</div><div class="d">'+b.desc+'</div>';
      card.onclick=function(){ selectBlend(b.v); };
      c.appendChild(card);
    });
  }
  function selectBlend(v){
    state.blend=v;
    buildBlendStyles();
    var wrap=document.getElementById('pianoWidthWrap');
    if(v==='piano'){ state.mix=50; wrap.style.display=''; }
    else { wrap.style.display='none'; state.pianoWidth=null; var r=mixRange(); state.mix=Math.min(r.max,Math.max(r.min,state.mix)); }
    renderMix();
    updateBlendNext();
    updateReviewBlend();
  }
  function widthLabel(v){ var w=WIDTHS.find(function(x){return x.v===v;}); return w?w.label:''; }
  function buildPianoWidths(){
    var c=document.getElementById('pianoWidths'); c.innerHTML='';
    WIDTHS.forEach(function(w){
      var b=document.createElement('button');
      b.className='widthchip'+(state.pianoWidth===w.v?' sel':'');
      b.textContent=w.label;
      b.onclick=function(){ selectPianoWidth(w.v); };
      c.appendChild(b);
    });
  }
  function selectPianoWidth(v){ state.pianoWidth=v; buildPianoWidths(); updateBlendNext(); updateReviewBlend(); }
  function updateBlendNext(){
    var _single=state.shades.length<2;
    var _bs=document.getElementById('blendStyles');
    if(_bs){ _bs.style.display=_single?'none':''; if(_bs.previousElementSibling)_bs.previousElementSibling.style.display=_single?'none':''; }
    if(_single){ state.blend=null; }
    var ok = (_single || !!state.blend)
      && (state.blend!=='piano' || !!state.pianoWidth)
      && (!state.rooted || (!!state.rootLevel && !!state.rootLength));
    document.getElementById('blendNext').disabled = !ok;
  }
  function renderRooted(){
    var t=document.getElementById('rootedToggle'); t.innerHTML='';
    [[false,'Off'],[true,'On']].forEach(function(o){
      var b=document.createElement('button');
      b.className='widthchip'+(state.rooted===o[0]?' sel':'');
      b.textContent=o[1];
      b.onclick=function(){ toggleRooted(o[0]); };
      t.appendChild(b);
    });
    var wrap=document.getElementById('rootedOpts');
    if(!state.rooted){ wrap.style.display='none'; wrap.innerHTML=''; return; }
    wrap.style.display='';
    wrap.innerHTML=''
      +'<label style="display:block;font-weight:600;margin:0 0 10px">Root level</label>'
      +'<div class="widths" id="rootLevelChips"></div>'
      +'<label style="display:block;font-weight:600;margin:18px 0 10px">Root length</label>'
      +'<div class="widths" id="rootLengthChips"></div>';
    buildRootLevels(); buildRootLengths();
  }
  function toggleRooted(on){
    state.rooted=on;
    if(!on){ state.rootLevel=null; state.rootLength=null; }
    renderRooted(); updateBlendNext(); updateReviewRooted();
  }
  function buildRootLevels(){
    var c=document.getElementById('rootLevelChips'); if(!c) return; c.innerHTML='';
    ROOT_LEVELS.forEach(function(l){
      var b=document.createElement('button');
      b.className='widthchip'+(state.rootLevel===l.v?' sel':'');
      b.textContent=l.label;
      b.onclick=function(){ state.rootLevel=l.v; buildRootLevels(); updateBlendNext(); updateReviewRooted(); };
      c.appendChild(b);
    });
  }
  function buildRootLengths(){
    var c=document.getElementById('rootLengthChips'); if(!c) return; c.innerHTML='';
    ROOT_LENGTHS.forEach(function(l){
      var b=document.createElement('button');
      b.className='widthchip'+(state.rootLength===l.v?' sel':'');
      b.textContent=l.label;
      b.onclick=function(){ state.rootLength=l.v; buildRootLengths(); updateBlendNext(); updateReviewRooted(); };
      c.appendChild(b);
    });
  }
  function updateReviewRooted(){
    var rev=document.getElementById('revRooted');
    if(!state.rooted){ rev.textContent='No'; return; }
    var parts=[];
    if(state.rootLevel){ parts.push('Level '+state.rootLevel); }
    if(state.rootLength){ var ln=ROOT_LENGTHS.find(function(x){return x.v===state.rootLength;}); parts.push(ln.label); }
    rev.textContent = parts.length? parts.join(' · ') : 'On';
  }
  function updateReviewBlend(){
    var rev=document.getElementById('revBlend');
    if(!state.blend){ rev.textContent='—'; return; }
    var b=BLENDS.find(function(x){return x.v===state.blend;});
    rev.textContent = b.label + (state.blend==='piano'&&state.pianoWidth?' · '+widthLabel(state.pianoWidth):'');
  }
  function buildWidths(){
    var c=document.getElementById('weftWidths'); c.innerHTML='';
    WEFT_WIDTHS.forEach(function(w){
      var b=document.createElement('button');
      b.className='widthchip'+(state.width===w.v?' sel':'');
      b.textContent=w.label;
      b.onclick=function(){ selectWidth(w.v); };
      c.appendChild(b);
    });
  }
  function selectWidth(v){
    state.width=v; buildWidths();
    document.getElementById('widthNext').disabled=false;
    var w=WEFT_WIDTHS.find(function(x){return x.v===v;});
    document.getElementById('revWidth').textContent=w.label;
  }
  function buildLengths(){
    var c=document.getElementById('lengthChips'); c.innerHTML='';
    LENGTHS.forEach(function(l){
      var b=document.createElement('button');
      b.className='widthchip'+(state.length===l.v?' sel':'');
      b.textContent=l.label;
      b.onclick=function(){ selectLength(l.v); };
      c.appendChild(b);
    });
  }
  function selectLength(v){
    state.length=v; buildLengths();
    updateLTNext();
    var l=LENGTHS.find(function(x){return x.v===v;});
    document.getElementById('revLength').textContent=l.label;
    if(typeof vlUpdateWeight==='function') vlUpdateWeight();
  }
  function updateLTNext(){ var b=document.getElementById('ltNext'); if(b) b.disabled=!(!!state.length && !!state.texture); }
  function vlUnitWeight(){ var len=(''+(state.length||'')).replace(/[^0-9]/g,''); return VL_UNIT_WEIGHT[len]||0; }
  function vlUpdateWeight(){
    if(!VL_TIPS) return;
    var uw=vlUnitWeight();
    var note=document.getElementById('vlWeightNote');
    if(note){ if(uw>0 && state.length){ note.style.display=''; note.textContent='≈ '+uw+' g per unit (25 pcs)'; } else { note.style.display='none'; } }
    var row=document.getElementById('revWeightRow'); var rw=document.getElementById('revWeight');
    if(row && rw){ if(uw>0){ var q=state.qty||1; var tot=uw*q; row.style.display=''; rw.textContent=tot+' g'+(q>1?' ('+q+' units × '+uw+' g)':' (1 unit · 25 pcs)'); } else { row.style.display='none'; } }
  }
  function buildTextures(){
    var c=document.getElementById('textureChips'); c.innerHTML='';
    TEXTURES.forEach(function(t){
      var b=document.createElement('button');
      b.className='widthchip'+(state.texture===t.v?' sel':'');
      b.textContent=t.label;
      b.onclick=function(){ selectTexture(t.v); };
      c.appendChild(b);
    });
  }
  function selectTexture(v){
    state.texture=v; buildTextures();
    updateLTNext();
    var t=TEXTURES.find(function(x){return x.v===v;});
    document.getElementById('revTexture').textContent=t.label;
  }
  function qty(d){ state.qty=Math.max(1,state.qty+d); document.getElementById('qty').value=state.qty; if(typeof vlUpdatePrice==='function')vlUpdatePrice(); if(typeof vlUpdateWeight==='function')vlUpdateWeight(); }

  function vlLabel(arr,val){var x=arr.find(function(o){return o.v===val;});return x?x.label:'';}
  var VL_KEY='{{ADDKEY}}';
  var VL_TIER={'1':1,'2':2,'4':2,'6':3,'7':3,'wb1':3,'wb2':3,'3a':3,'8c':3,'8':4,'7m':4,'9':5,'10':5,'10-1':6,'zero':6};
  var VL_PRICE={{VLPRICE}};
  var VL_LABOR={{VLLABOR}};
  var VL_CUR_SYM={{VLCURSYM}};var VL_CUR_RATE={{VLCURRATE}};
  function vlMoney(v){return VL_CUR_SYM+(Number(v)*VL_CUR_RATE).toFixed(2);}
  function vlPcts(){var n=state.shades.length;if(state.pcts&&state.pcts.length===n)return state.pcts.slice();if(n<=1)return[100];if(n===2)return[state.mix,100-state.mix];var e=Math.floor(100/n),a=[];for(var i=0;i<n;i++)a.push(e);a[0]+=100-e*n;return a;}
  function vlBlendUnit(){var len=(''+(state.length||'')).replace(/[^0-9]/g,'');var sh=state.shades||[];if(!sh.length)return 0;var pc=vlPcts();var sum=0;pc.forEach(function(p){sum+=p;});if(sum<=0)return 0;var mat=0,ok=true;for(var i=0;i<sh.length;i++){var t=VL_TIER[(''+sh[i]).toLowerCase()];if(t===undefined||!VL_PRICE[t]||VL_PRICE[t][len]===undefined){ok=false;break;}mat+=(pc[i]/sum)*VL_PRICE[t][len];}if(!ok)return 0;return (mat+VL_LABOR[VL_KEY])*25;}
  function vlUpdatePrice(){var el=document.getElementById('revPrice');if(!el)return;if(typeof VL_LABOR[VL_KEY]==='undefined')return;var u=vlBlendUnit();if(!u||u<=0)return;var q=state.qty||1,tot=u*q;el.innerHTML=vlMoney(tot)+' <small style="color:var(--muted);font-weight:400">excl. GST'+(q>1?' &middot; '+vlMoney(u)+'/unit':'')+'</small>';}
  function vlUnitForLen(len){var sh=state.shades||[];if(!sh.length)return 0;if(typeof VL_LABOR[VL_KEY]==='undefined')return 0;var pc=vlPcts();var sum=0;pc.forEach(function(p){sum+=p;});if(sum<=0)return 0;var mat=0,ok=true;for(var i=0;i<sh.length;i++){var t=VL_TIER[(''+sh[i]).toLowerCase()];if(t===undefined||!VL_PRICE[t]||VL_PRICE[t][len]===undefined){ok=false;break;}mat+=(pc[i]/sum)*VL_PRICE[t][len];}if(!ok)return 0;return (mat+VL_LABOR[VL_KEY])*25;}
  function vlUpdateRunPrice(){var el=document.getElementById('vlRunPrice');if(!el)return;if(typeof VL_LABOR[VL_KEY]==='undefined'){el.textContent=VL_BASE_PRICE;return;}var len=state.length||'14';var u=vlUnitForLen(len);var q=state.qty||1;el.textContent=u>0?vlMoney(u*q):'';}
  function vlSyncTop(){var top=document.getElementById('vlTopContinue');if(!top)return;var key=VL_STEPS[state.step];if(key==='review'){top.style.display='none';return;}top.style.display='';var panel=document.querySelector('.panel[data-key="'+key+'"]');var pb=panel?panel.querySelector('.nav .btn-primary'):null;if(pb){top.disabled=pb.disabled;top.textContent=pb.textContent;}}
  function vlRefresh(){vlSyncTop();vlUpdateRunPrice();}
  function vlEvenSplit(n){ if(n<=0)return []; var b=Math.floor(100/n),a=[],i; for(i=0;i<n;i++)a.push(b); var r=100-b*n; for(i=0;i<r;i++)a[i]++; return a; }
  function vlInitPcts(){ var n=state.shades.length; state.pcts=vlEvenSplit(n); state.locks=[]; for(var i=0;i<n;i++)state.locks.push(false); }
  function vlAdjust(i,delta){ if(!state.pcts||state.locks[i])return; var others=[]; for(var j=0;j<state.pcts.length;j++){ if(j!==i && !state.locks[j])others.push(j); } if(!others.length)return; if(delta>0){ var canTake=0; others.forEach(function(j){canTake+=state.pcts[j];}); var take=Math.min(Math.abs(delta),canTake); if(take<=0)return; state.pcts[i]+=take; others.sort(function(a,b){return state.pcts[b]-state.pcts[a];}); var rem=take; for(var k=0;k<others.length && rem>0;k++){ var d=Math.min(rem,state.pcts[others[k]]); state.pcts[others[k]]-=d; rem-=d; } } else { var give=Math.min(Math.abs(delta),state.pcts[i]); if(give<=0)return; state.pcts[i]-=give; others.sort(function(a,b){return state.pcts[a]-state.pcts[b];}); state.pcts[others[0]]+=give; } vlRenderPercents(); }
  function vlToggleLock(i){ if(!state.locks)return; state.locks[i]=!state.locks[i]; vlRenderPercents(); }
  function vlRenderPercents(){ var c=document.getElementById('blendMix'); if(!c)return; var n=state.shades.length; if(n===0){ c.innerHTML=''; return; } if(!state.pcts||state.pcts.length!==n){ vlInitPcts(); } var h='<label style="display:block;font-weight:600;margin:0 0 10px">Adjust the percentage of the colors to suit your client</label><div class="vlpct">'; for(var i=0;i<n;i++){ var s=shadeObj(state.shades[i]); var lk=state.locks[i]; h+='<div class="vlpct-row"><span class="vlpct-sw" style="background:'+s.hex+'"></span><span class="vlpct-name">'+s.label+' · '+s.name+'</span><button type="button" class="vlpct-btn" onclick="vlAdjust('+i+',-1)"'+(lk?' disabled':'')+'>&minus;</button><span class="vlpct-val">'+state.pcts[i]+'%</span><button type="button" class="vlpct-btn" onclick="vlAdjust('+i+',1)"'+(lk?' disabled':'')+'>+</button><button type="button" class="vlpct-lock'+(lk?' on':'')+'" onclick="vlToggleLock('+i+')" title="Lock this percentage">'+(lk?'&#128274;':'&#128275;')+'</button></div>'; } h+='</div>'; c.innerHTML=h; if(typeof updateReviewShade==='function')updateReviewShade(); }
  function vlAddToCart(){if(!state.shades.length){alert('Please choose a shade first.');return;}var sh=state.shades,pc=vlPcts();var s=sh.map(function(v,i){var o=shadeObj(v);return o.label+(o.name?' ('+o.name+')':'');}).join(' + ');var blend=sh.length>1?pc.join('/'):'No';var rooted=state.rooted?('Yes'+(state.rootLevel?' L'+state.rootLevel:'')+(state.rootLength?' '+state.rootLength:'')):'No';var p=new URLSearchParams();p.set('vl_add',VL_KEY);p.set('shade',sh[0]);p.set('qty',state.qty);p.set('vlshades',sh.join(','));p.set('vlpcts',pc.join(','));p.set('vllen',state.length||'');var vlRV=function(id){var e=document.getElementById(id);var t=e?(e.textContent||'').replace(/\s+/g,' ').trim():'';return (t==='—'||t==='-')?'':t;};p.set('shades_label',vlRV('revShade')||s);p.set('blend',vlRV('revBlend')||blend);p.set('rooted',vlRV('revRooted')||rooted);p.set('width',vlRV('revWidth'));p.set('length',vlRV('revLength')||vlLabel(LENGTHS,state.length));p.set('texture',vlRV('revTexture')||vlLabel(TEXTURES,state.texture));p.set('weight',vlRV('revWeight'));try{window.top.location.href='/?'+p.toString();}catch(e){window.location.href='/?'+p.toString();}}

  buildRing();
  buildBlendStyles();
  buildPianoWidths();
  buildWidths();
  buildLengths();
  buildTextures();
  renderRooted();
  if(VL_TIPS){ var _rw=document.getElementById('revWidthRow'); if(_rw)_rw.style.display='none'; var _ql=document.getElementById('revQtyLabel'); if(_ql)_ql.textContent='Units'; }
  go(0);
  if(typeof vlRefresh==='function'){ vlRefresh(); setInterval(vlRefresh,250); }
  if(MAX_SHADES>=3){var _l=document.querySelector('.panel[data-key="color"] .lead');if(_l)_l.innerHTML='Tap up to three colors <span class="vl-rm-x" style="display:none">on the digital color swatch, an exact replica of the physical color swatch in your salon — one for a single color, or blend up to three. </span><a class="vl-rm" onclick="vlToggleLead(this)">Read more</a>';var _h=document.querySelector('.panel[data-key="color"] .ring-hint');if(_h)_h.textContent='';}
</script>
<script>
function vlPostH(){try{var el=document.querySelector('.shell');var h=el?(el.offsetTop+el.offsetHeight):document.body.scrollHeight;parent.postMessage({vlgwc:1,h:h},'*');}catch(e){}}
window.addEventListener('load',vlPostH);
document.addEventListener('click',function(){setTimeout(vlPostH,400);});
setInterval(vlPostH,1200);
</script>
</body>
</html>

VLGWCDOC;
    list($vl_tier_tbl,$vl_price_tbl,$vl_labor_tbl,$vl_pcs_tbl) = vl_blend_price_tables();
    $vl_cur_sym = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol() : '$';
    $vl_cur_rate = 1.0;
    if ( class_exists('\\WCPay\\MultiCurrency\\MultiCurrency') ) {
        try {
            $vl_mc = \WCPay\MultiCurrency\MultiCurrency::instance();
            if ( $vl_mc && method_exists($vl_mc,'get_selected_currency') ) {
                $vl_sel = $vl_mc->get_selected_currency();
                if ( $vl_sel && method_exists($vl_sel,'get_rate') ) { $vl_cur_rate = (float) $vl_sel->get_rate(); }
                if ( $vl_sel && method_exists($vl_sel,'get_code') ) { $vl_cur_sym = get_woocommerce_currency_symbol( $vl_sel->get_code() ); }
            }
        } catch ( \Throwable $e ) { $vl_cur_rate = 1.0; }
    }
    if ( $vl_cur_rate <= 0 ) { $vl_cur_rate = 1.0; }
    $vl_cur_sym = html_entity_decode( (string) $vl_cur_sym, ENT_QUOTES, 'UTF-8' ); // &#36; -> $ so textContent shows it correctly
    $vl_html = str_replace(
        array('{{NAME}}','{{IMG}}','{{PRICE}}','{{ADDKEY}}','{{DESC}}','{{VLPRICE}}','{{VLLABOR}}','{{VLCURSYM}}','{{VLCURRATE}}'),
        array(esc_html($vl_name),esc_url($vl_img),$vl_price,$vl_key,esc_html($vl_desc),wp_json_encode($vl_price_tbl),wp_json_encode($vl_labor_tbl),wp_json_encode($vl_cur_sym),wp_json_encode($vl_cur_rate)),
        $vl_html);
    echo '<div class="vl-gwc-embed" style="max-width:1100px;margin:0 auto 36px;">';
    echo '<iframe id="vl-gwc-frame" title="Configurator" scrolling="no" style="width:100%;height:1250px;border:0;overflow:hidden;background:#faf7f2;border-radius:14px;" srcdoc="'.htmlspecialchars($vl_html, ENT_QUOTES).'"></iframe>';
    echo '</div>';
    echo '<script>window.addEventListener("message",function(e){if(e.data&&e.data.vlgwc){var f=document.getElementById("vl-gwc-frame"); if(f){f.style.height=(e.data.h+24)+"px";}}});</script>';
}
add_filter('woocommerce_cart_item_name','vl_gweft_item_name',10,3);
function vl_gweft_item_name($name,$cart_item,$cart_item_key){
    if(empty($cart_item['vl_config']) || empty($cart_item['product_id'])) return $name;
    $vl_t=get_the_title($cart_item['product_id']);
    $vl_u=get_permalink($cart_item['product_id']);
    return $vl_u ? '<a href="'.esc_url($vl_u).'">'.esc_html($vl_t).'</a>' : esc_html($vl_t);
}
add_filter('woocommerce_get_item_data','vl_gweft_item_data',10,2);
function vl_gweft_item_data($data,$cart_item){
    if(!empty($cart_item['vl_config']) && is_array($cart_item['vl_config'])){
        foreach($cart_item['vl_config'] as $k=>$v){ $data[]=array('key'=>$k,'value'=>$v); }
    }
    return $data;
}
add_action('template_redirect','vl_gweft_handle_add');
function vl_gweft_handle_add(){
    if(empty($_GET['vl_add']) || is_admin()) return;
    $vl_keymap=array('gweft'=>34176,'machineweft'=>1715,'nanotips'=>17148,'ktips'=>17519,'kstrip'=>35345);
    $vl_key=sanitize_key(wp_unslash($_GET['vl_add']));
    if(!isset($vl_keymap[$vl_key])) return;
    if(!function_exists('WC') || !WC()->cart) return;
    $product_id=$vl_keymap[$vl_key];
    $product=wc_get_product($product_id);
    if(!$product){ wp_safe_redirect(home_url('/')); exit; }
    $qty=max(1,intval(isset($_GET['qty'])?$_GET['qty']:1));
    $variation_id=0; $variation=array();
    if($product->is_type('variable')){
        $vl_match=array();
        if(!empty($_GET['length'])){ $vl_ln=preg_replace('/[^0-9]/','',sanitize_text_field(wp_unslash($_GET['length']))); if($vl_ln!=='') $vl_match['attribute_pa_length']=$vl_ln; }
        if(!empty($_GET['texture'])){ $vl_tx=sanitize_text_field(wp_unslash($_GET['texture'])); $vl_tt=get_term_by('name',$vl_tx,'pa_texture'); $vl_match['attribute_pa_texture']=$vl_tt?$vl_tt->slug:sanitize_title($vl_tx); }
        $vl_vid=$vl_match?$product->get_data_store()->find_matching_product_variation($product,$vl_match):0;
        if($vl_vid){ $variation_id=$vl_vid; $variation=$vl_match; }
        else { $av=$product->get_available_variations(); if(!empty($av)){ $variation_id=$av[0]['variation_id']; $variation=$av[0]['attributes']; } }
    }
    $cfg=array();
    foreach(array('shades_label'=>'Color(s)','blend'=>'Blend','rooted'=>'Rooted','width'=>'Width','length'=>'Length','texture'=>'Texture','weight'=>'Weight') as $k=>$lab){
        if(!empty($_GET[$k])) $cfg[$lab]=sanitize_text_field(wp_unslash($_GET[$k]));
    }
    WC()->cart->add_to_cart($product_id,$qty,$variation_id,$variation,array('vl_config'=>$cfg));
    wp_safe_redirect(wc_get_cart_url()); exit;
}
function vl_blend_price_tables(){
    $tier=array('1'=>1,'2'=>2,'4'=>2,'6'=>3,'7'=>3,'wb1'=>3,'wb2'=>3,'3a'=>3,'8c'=>3,'8'=>4,'7m'=>4,'9'=>5,'10'=>5,'10-1'=>6,'zero'=>6);
    $price=array(
      1=>array('14'=>0.85,'16'=>1.60,'18'=>1.85,'20'=>2.55,'22'=>2.75,'24'=>3.00),
      2=>array('14'=>1.00,'16'=>1.68,'18'=>1.88,'20'=>2.80,'22'=>3.00,'24'=>3.20),
      3=>array('14'=>1.05,'16'=>1.70,'18'=>1.90,'20'=>2.95,'22'=>3.20,'24'=>3.40),
      4=>array('14'=>1.10,'16'=>1.75,'18'=>1.90,'20'=>3.05,'22'=>3.35,'24'=>3.55),
      5=>array('14'=>1.15,'16'=>1.90,'18'=>2.05,'20'=>3.20,'22'=>3.55,'24'=>3.70),
      6=>array('14'=>1.25,'16'=>1.95,'18'=>2.15,'20'=>3.35,'22'=>3.75,'24'=>4.10)
    );
    $labor=array('nanotips'=>0.15,'ktips'=>0.20);
    $pcs=25;
    // Single source of truth: VNL Blend Pricing admin table (WooCommerce > VNL Blend Pricing).
    $opt = get_option('vnl_bp_settings');
    if ( is_array($opt) && !empty($opt['strand']) && !empty($opt['lengths']) ) {
        $lens = array_values($opt['lengths']);
        $np = array();
        foreach ( $opt['strand'] as $t => $arr ) {
            $row = array();
            foreach ( $lens as $i => $L ) { if ( isset($arr[$i]) ) { $row[(string)$L] = (float) $arr[$i]; } }
            if ( $row ) { $np[(int)$t] = $row; }
        }
        if ( $np ) { $price = $np; }
        if ( isset($opt['labour']['nano']) && $opt['labour']['nano'] !== '' ) { $labor['nanotips'] = (float) $opt['labour']['nano']; }
        if ( isset($opt['labour']['ktip']) && $opt['labour']['ktip'] !== '' ) { $labor['ktips'] = (float) $opt['labour']['ktip']; }
        if ( !empty($opt['unit_pcs']) ) { $pcs = (int) $opt['unit_pcs']; }
    }
    return array($tier,$price,$labor,$pcs);
}
function vl_blend_unit_price($key,$shades_csv,$pcts_csv,$len){
    list($tier,$price,$labor,$pcs)=vl_blend_price_tables();
    if(!isset($labor[$key])) return 0;
    $len=preg_replace('/[^0-9]/','',$len);
    $shades=array_values(array_filter(array_map('trim',explode(',',strtolower($shades_csv))),'strlen'));
    if(empty($shades)) return 0;
    $pcts=array_map('floatval',explode(',',$pcts_csv));
    $sum=array_sum($pcts);
    if($sum<=0){ $n=count($shades); $pcts=array_fill(0,$n,100/$n); $sum=100; }
    $material=0;
    foreach($shades as $idx=>$slug){
        if(!isset($tier[$slug])) return 0;
        $t=$tier[$slug];
        if(!isset($price[$t][$len])) return 0;
        $pct=isset($pcts[$idx])?$pcts[$idx]:0;
        $material += ($pct/$sum)*$price[$t][$len];
    }
    return round(($material+$labor[$key])*$pcs, 2);
}
add_filter('woocommerce_add_cart_item_data','vl_blend_add_price_data',20,1);
function vl_blend_add_price_data($data){
    if(empty($_GET['vl_add']) || empty($_GET['vlshades']) || empty($_GET['vllen'])) return $data;
    $key=sanitize_key(wp_unslash($_GET['vl_add']));
    $unit=vl_blend_unit_price($key, sanitize_text_field(wp_unslash($_GET['vlshades'])), isset($_GET['vlpcts'])?sanitize_text_field(wp_unslash($_GET['vlpcts'])):'', sanitize_text_field(wp_unslash($_GET['vllen'])));
    if($unit>0){ $data['vl_unit_price']=$unit; }
    return $data;
}
add_action('woocommerce_before_calculate_totals','vl_blend_apply_price',20,1);
function vl_blend_apply_price($cart){
    if(empty($cart) || !is_object($cart)) return;
    foreach($cart->get_cart() as $ci){
        if(!empty($ci['vl_unit_price']) && isset($ci['data']) && is_object($ci['data'])){
            $ci['data']->set_price(floatval($ci['vl_unit_price']));
        }
    }
}
/* ===== end VL CONFIGURATOR ===== */

/* ===== VL currency: display Australian dollars as "A$" (store-wide) ===== */
add_filter('woocommerce_currency_symbol','vl_aud_currency_symbol',99,2);
function vl_aud_currency_symbol($symbol,$currency){
    if ( $currency === 'AUD' ) { return 'A$'; }
    return $symbol;
}

/* ===== VL product breadcrumb (rebuilt) ===== */
add_action('astra_content_top','vl_product_breadcrumb',5);
function vl_product_breadcrumb(){
    if(!function_exists('is_product') || !is_product()) return;
    global $post; if(!$post) return;
    $sep=' <span class="vl-bc-sep">&raquo;</span> ';
    $vl_home = function_exists('wc_get_page_permalink') ? wc_get_page_permalink('shop') : ''; if(!$vl_home){ $vl_home = home_url('/shop/'); }
    $out='<a class="vl-bc-link" href="'.esc_url($vl_home).'">Home</a>';
    $terms=get_the_terms($post->ID,'product_cat');
    if($terms && !is_wp_error($terms)){
        $cat=null; foreach($terms as $t){ if((int)$t->parent===0){ $cat=$t; break; } }
        if(!$cat){ $cat=$terms[0]; }
        if($cat){ $out.=$sep.'<a class="vl-bc-link" href="'.esc_url(get_term_link($cat)).'">'.esc_html($cat->name).'</a>'; }
    }
    $out.=$sep.'<span class="vl-bc-cur">'.esc_html(get_the_title($post->ID)).'</span>';
    echo '<div class="vl-product-bc">'.$out.'</div>';
}
/* ===== VL weft card descriptions (rebuilt) ===== */
add_action('woocommerce_after_shop_loop_item','vl_weft_card_desc',20);
function vl_weft_card_desc(){
    if(!function_exists('is_product_category') || !is_product_category()) return;
    global $product; if(!$product) return;
    $id=$product->get_id();
    $map=array(
      34176=>'Our newest weft method — stitched then glued for a thin, lay-flat profile with zero return. Fully cuttable to size.',
      1715=>'Classic machine-made weft — full, durable, and reliable for sew-in and tape-weft installs.',
      17148=>'Individual strands set with a small nano-bead — no heat or glue, so they stay gentle on the hair, reusable, and easy to reposition.',
      36050=>'Our smallest, most discreet nano-bead tip — near-invisible at the hairline and ideal for fine hair and close-to-scalp placement.',
      17519=>'Pre-bonded keratin tips for a seamless heat-fusion install — secure, long-wearing, and natural through the root.',
      35345=>'Keratin-bonded strips for a fast, flat, comfortable install — full volume with a smooth, lay-flat finish.'
    );
    if(!isset($map[$id])) return;
    $vl_min = $product->is_type('variable') ? $product->get_variation_price('min', true) : $product->get_price();
    $vl_price = ($vl_min!=='' && $vl_min!==null) ? 'From $'.number_format((float)$vl_min, 2) : '';
    echo '<div class="vl-card-desc"><p>'.esc_html($map[$id]).'</p>'.($vl_price?'<span class="vl-card-price">'.esc_html($vl_price).'</span>':'').'<a class="vl-card-begin" href="'.esc_url(get_permalink($id)).'">Begin your order &rarr;</a></div>';
}
