https://coffee-labo.co.jp/wp-content/plugins/coffee-labo-commerce/assets/js/storefront.js?ver=0.5.45
1(function () { 2 "use strict"; 3 4 document.documentElement.classList.add("clc-js"); 5 6 var settings = window.CLCCommerce || {}; 7 var labels = settings.labels || {}; 8 var locale = document.documentElement.lang || "ja-JP"; 9 var own = Object.prototype.hasOwnProperty; 10 11 function ready(callback) { 12 if (document.readyState === "loading") { 13 document.addEventListener("DOMContentLoaded", callback, { once: true }); 14 return; 15 } 16 17 callback(); 18 } 19 20 function toArray(list) { 21 return Array.prototype.slice.call(list || []); 22 } 23 24 function allIncluding(scope, selector) { 25 var matches = toArray(scope.querySelectorAll(selector)); 26 27 if (scope.matches && scope.matches(selector)) { 28 matches.unshift(scope); 29 } 30 31 return matches; 32 } 33 34 function setHidden(element, hidden) { 35 if (!element) { 36 return; 37 } 38 39 element.hidden = Boolean(hidden); 40 } 41 42 function setText(element, value) { 43 if (element) { 44 element.textContent = value == null ? "" : String(value); 45 } 46 } 47 48 function normalize(value) { 49 return String(value == null ? "" : value).trim().toLocaleLowerCase(locale); 50 } 51 52 function renderTemplate(template, replacements) { 53 var output = String(template || ""); 54 55 Object.keys(replacements).forEach(function (key) { 56 var value = String(replacements[key]); 57 output = output.replace(new RegExp("\\{" + key + "\\}", "g"), value); 58 }); 59 60 if (own.call(replacements, "price")) { 61 output = output.replace(/%s/g, String(replacements.price)); 62 } 63 64 if (own.call(replacements, "count")) { 65 output = output.replace(/%d/g, String(replacements.count)); 66 } 67 68 return output; 69 } 70 71 function connectionNodes(connection) { 72 if (!connection) { 73 return []; 74 } 75 76 if (Array.isArray(connection)) { 77 return connection.map(function (entry) { 78 return entry && entry.node ? entry.node : entry; 79 }).filter(Boolean); 80 } 81 82 if (Array.isArray(connection.nodes)) { 83 return connection.nodes.filter(Boolean); 84 } 85 86 if (Array.isArray(connection.edges)) { 87 return connection.edges.map(function (edge) { 88 return edge && edge.node ? edge.node : edge; 89 }).filter(Boolean); 90 } 91 92 return []; 93 } 94 95 function extractProduct(payload) { 96 var product; 97 98 if (!payload || typeof payload !== "object") { 99 return null; 100 } 101 102 if (payload.data && payload.data.product) { 103 product = Object.assign({}, payload.data.product); 104 if (payload.data.variants) { 105 product.variants = payload.data.variants; 106 } 107 return product; 108 } 109 110 if (payload.product) { 111 product = Object.assign({}, payload.product); 112 if (payload.variants) { 113 product.variants = payload.variants; 114 } 115 return product; 116 } 117 118 if (payload.node && payload.node.variants) { 119 return payload.node; 120 } 121 122 return payload; 123 } 124 125 function parseProductData(productElement) { 126 var script = productElement.querySelector("script.clc-product-data[type=\"application/json\"]"); 127 128 if (!script) { 129 return null; 130 } 131 132 try { 133 return extractProduct(JSON.parse(script.textContent || "{}")); 134 } catch (error) { 135 return null; 136 } 137 } 138 139 function moneyValue(value, fallbackCurrency) { 140 var amount; 141 var currency = fallbackCurrency || settings.currency || "JPY"; 142 143 if (value == null || value === "") { 144 return null; 145 } 146 147 if (typeof value === "object") { 148 amount = value.amount; 149 currency = value.currencyCode || currency; 150 } else { 151 amount = value; 152 } 153 154 amount = Number(amount); 155 156 if (!Number.isFinite(amount)) { 157 return null; 158 } 159 160 return { 161 amount: amount, 162 currencyCode: currency 163 }; 164 } 165 166 function productCurrency(product, variants) { 167 var priceRange = product.priceRange || {}; 168 var rangePrice = priceRange.minVariantPrice || priceRange.maxVariantPrice || {}; 169 var firstPrice = variants[0] && variants[0].price; 170 171 return product.currencyCode || rangePrice.currencyCode || 172 (firstPrice && firstPrice.currencyCode) || settings.currency || "JPY"; 173 } 174 175 function formatMoney(money) { 176 if (!money) { 177 return ""; 178 } 179 180 try { 181 return new Intl.NumberFormat(locale, { 182 style: "currency", 183 currency: money.currencyCode || "JPY", 184 currencyDisplay: "symbol" 185 }).format(money.amount); 186 } catch (error) { 187 return String(money.amount) + " " + String(money.currencyCode || ""); 188 } 189 } 190 191 function optionMap(inputs) { 192 var selected = Object.create(null); 193
194 inputs.forEach(function (input) { 195 var name = input.getAttribute("data-option-name"); 196 197 if (name && input.checked) { 198 selected[name] = input.value; 199 } 200 }); 201 202 return selected; 203 } 204 205 function variantOptions(variant) { 206 var selected = Object.create(null); 207 208 toArray(variant && variant.selectedOptions).forEach(function (option) { 209 if (option && option.name != null) { 210 selected[String(option.name)] = String(option.value == null ? "" : option.value); 211 } 212 }); 213 214 return selected; 215 } 216 217 function matchesOptions(variant, selected) { 218 var options = variantOptions(variant); 219 var names = Object.keys(selected); 220 221 return names.every(function (name) { 222 return own.call(options, name) && String(options[name]) === String(selected[name]); 223 }); 224 } 225 226 function selectedOptionNames(inputs) { 227 var names = Object.create(null); 228 229 inputs.forEach(function (input) { 230 var name = input.getAttribute("data-option-name"); 231 if (name) { 232 names[name] = true; 233 } 234 }); 235 236 return Object.keys(names); 237 } 238 239 function findSelectedVariant(variants, inputs) { 240 var selected = optionMap(inputs); 241 var requiredCount = selectedOptionNames(inputs).length; 242 243 if (!requiredCount || Object.keys(selected).length < requiredCount) { 244 return null; 245 } 246 247 return variants.find(function (variant) { 248 return matchesOptions(variant, selected); 249 }) || null; 250 } 251 252 function isVariantAvailable(variant) { 253 if (!variant) { 254 return false; 255 } 256 257 if (typeof variant.availableForSale === "boolean") { 258 return variant.availableForSale; 259 } 260 261 if (typeof variant.available === "boolean") { 262 return variant.available; 263 } 264 265 return true; 266 } 267 268 function idEquals(left, right) { 269 return left != null && right != null && String(left) === String(right); 270 } 271 272 function numericVariantId(variant) { 273 var match = String(variant && variant.id || "").match(/(?:^|\/)([1-9][0-9]*)$/); 274 return match ? match[1] : ""; 275 } 276 277 function synchronizeVariantUrl(product, variant, productElement) { 278 var handle = String(product && product.handle || productElement.getAttribute("data-product-handle") || ""); 279 var variantId = numericVariantId(variant); 280 var url; 281 var pathMatch; 282 283 if (!handle || !variantId || !window.history || typeof window.history.replaceState !== "function") { 284 return; 285 } 286 287 try { 288 url = new URL(window.location.href); 289 pathMatch = url.pathname.match(/\/products\/([^/]+)\/?$/); 290 if (!pathMatch || decodeURIComponent(pathMatch[1]) !== handle) { 291 return; 292 } 293 if (url.searchParams.get("variant") === variantId) { 294 return; 295 } 296 297 // Keep campaign, discount, and any future public query parameters intact; 298 // only Shopify's selected numeric variant is replaced in-place. 299 url.searchParams.set("variant", variantId); 300 window.history.replaceState(window.history.state, "", url.toString()); 301 } catch (error) { 302 // URL synchronization is progressive enhancement. A restricted history 303 // implementation must never interrupt variant selection or purchasing. 304 } 305 } 306 307 function preferredVariant(product, variants, productElement, inputs) { 308 var selected = findSelectedVariant(variants, inputs); 309 var requestedId = productElement.getAttribute("data-selected-variant-id") || 310 product.selectedVariantId || 311 (product.selectedOrFirstAvailableVariant && product.selectedOrFirstAvailableVariant.id); 312 313 if (selected) { 314 return selected; 315 } 316 317 if (requestedId) { 318 selected = variants.find(function (variant) { 319 return idEquals(variant.id, requestedId); 320 }); 321 322 if (selected) { 323 return selected; 324 } 325 } 326 327 return variants.find(isVariantAvailable) || variants[0] || null; 328 } 329 330 function synchronizeOptionInputs(inputs, variant) { 331 var selected = variantOptions(variant); 332 333 inputs.forEach(function (input) { 334 var name = input.getAttribute("data-option-name"); 335 336 if (name && own.call(selected, name)) { 337 input.checked = String(input.value) === String(selected[name]); 338 } 339 }); 340 } 341 342 function allocationsFor(variant) { 343 var allocations = connectionNodes(variant && variant.sellingPlanAllocations); 344 345 if (allocations.length || !variant) { 346 return allocations; 347 } 348 349 // The PHP view model intentionally flattens Storefront API allocations so 350 // the browser receives only the selling-plan fields it renders. 351 return connectionNodes(variant.sellingPlans).map(function (plan) { 352 var adjustments = connectionNodes(plan.priceAdjustments); 353 354 if (!adjustments.length && (plan.price || plan.perDeliveryPrice)) { 355 adjustments = [{ 356 price: plan.price, 357 compareAtPrice: plan.compareAtPrice, 358 perDeliveryPrice: plan.perDeliveryPrice, 359 orderCount: null 360 }]; 361 } 362 363 return { 364 sellingPlan: plan, 365 priceAdjustments: adjustments 366 }; 367 }); 368 } 369 370 function allocationFor(variant, sellingPlanId) { 371 if (!sellingPlanId) { 372 return null; 373 } 374 375 return allocationsFor(variant).find(function (allocation) { 376 return allocation && allocation.sellingPlan && idEquals(allocation.sellingPlan.id, sellingPlanId); 377 }) || null; 378 } 379 380 function selectedPlanId(planInputs) { 381 var checked = planInputs.find(function (input) { 382 return input.checked && !input.disabled; 383 }); 384 385 return checked ? String(checked.value || "") : ""; 386 } 387 388 function synchronizePlanInputs(planInputs, variant, requiresSellingPlan, allowFallback) { 389 var ids = Object.create(null); 390 var selectedWasDisabled = false;
391 392 allocationsFor(variant).forEach(function (allocation) { 393 if (allocation && allocation.sellingPlan && allocation.sellingPlan.id != null) { 394 ids[String(allocation.sellingPlan.id)] = true; 395 } 396 }); 397 398 planInputs.forEach(function (input) { 399 var isOneTime = String(input.value || "") === ""; 400 var enabled = (isOneTime && !requiresSellingPlan) || (!isOneTime && Boolean(ids[String(input.value)])); 401 input.disabled = !enabled; 402 403 if (!enabled && input.checked) { 404 input.checked = false; 405 selectedWasDisabled = true; 406 } 407 }); 408 409 if (allowFallback && (selectedWasDisabled || !planInputs.some(function (input) { return input.checked && !input.disabled; }))) { 410 var fallback = planInputs.find(function (input) { 411 return !input.disabled && String(input.value || "") === ""; 412 }) || planInputs.find(function (input) { 413 return !input.disabled; 414 }); 415 416 if (fallback) { 417 fallback.checked = true; 418 } 419 } 420 421 return { 422 selectionCleared: selectedWasDisabled, 423 hasSelection: planInputs.some(function (input) { return input.checked && !input.disabled; }) 424 }; 425 } 426 427 function variantPricing(variant, sellingPlanId, currency) { 428 var allocation = allocationFor(variant, sellingPlanId); 429 var adjustments = allocation ? connectionNodes(allocation.priceAdjustments) : []; 430 var adjustment = adjustments[0]; 431 var laterAdjustment = adjustments[1] || null; 432 var regular = moneyValue(variant && variant.price, currency); 433 var current = moneyValue(adjustment && adjustment.price, currency) || regular; 434 var compare = moneyValue(adjustment && adjustment.compareAtPrice, currency) || 435 moneyValue(variant && variant.compareAtPrice, currency); 436 var perDelivery = moneyValue(adjustment && adjustment.perDeliveryPrice, currency); 437 var later = moneyValue(laterAdjustment && (laterAdjustment.perDeliveryPrice || laterAdjustment.price), currency); 438 439 return { 440 allocation: allocation, 441 adjustment: adjustment, 442 adjustments: adjustments, 443 laterAdjustment: laterAdjustment, 444 current: current, 445 compare: compare, 446 perDelivery: perDelivery, 447 later: later 448 }; 449 } 450 451 function productImage(variant) { 452 if (!variant || !variant.image) { 453 return null; 454 } 455 456 if (typeof variant.image === "string") { 457 return { url: variant.image, altText: "" }; 458 } 459 460 return { 461 url: variant.image.url || variant.image.src || "", 462 altText: variant.image.altText || variant.image.alt || "", 463 width: Number(variant.image.width) || 0, 464 height: Number(variant.image.height) || 0 465 }; 466 } 467 468 function responsiveShopifyImageUrl(url, width) { 469 var parsed; 470 471 try { 472 parsed = new URL(String(url || ""), window.location.href); 473 } catch (error) { 474 return ""; 475 } 476 477 if (parsed.protocol !== "https:" || parsed.hostname !== "cdn.shopify.com") { 478 return ""; 479 } 480 481 parsed.searchParams.set("width", String(width)); 482 return parsed.toString(); 483 } 484 485 function applyResponsiveProductImage(imageElement, url) { 486 var widths = [480, 800, 1200]; 487 var sources = widths.map(function (width) { 488 var source = responsiveShopifyImageUrl(url, width); 489 return source ? source + " " + String(width) + "w" : ""; 490 }).filter(Boolean); 491 492 imageElement.src = url; 493 if (sources.length === widths.length) { 494 imageElement.srcset = sources.join(", "); 495 imageElement.sizes = "(max-width: 900px) 100vw, 58vw"; 496 } else { 497 imageElement.removeAttribute("srcset"); 498 imageElement.removeAttribute("sizes"); 499 } 500 } 501 502 function analyticsItem(product, variant, pricing, quantity, sellingPlanId) { 503 var price = pricing && pricing.current ? pricing.current.amount : 0; 504 var item = { 505 item_id: String(product.id || product.handle || ""), 506 item_name: String(product.title || ""), 507 price: price, 508 quantity: quantity || 1 509 }; 510 511 if (variant && variant.title) { 512 item.item_variant = String(variant.title); 513 } 514 515 if (variant && variant.sku) { 516 item.item_variant_id = String(variant.sku); 517 } 518 519 if (product.vendor) { 520 item.item_brand = String(product.vendor); 521 } 522 523 if (product.productType) { 524 item.item_category = String(product.productType); 525 } 526 527 if (sellingPlanId) { 528 item.item_subscription_plan = String(sellingPlanId); 529 } 530 531 return item; 532 } 533 534 function sendAnalyticsEvent(eventName, ecommerce, onComplete) { 535 var completed = false;
536 var callbackTimer = 0; 537 var eventPayload; 538 539 if (!eventName || !ecommerce) { 540 if (typeof onComplete === "function") { 541 onComplete(); 542 } 543 return; 544 } 545 546 window.dataLayer = window.dataLayer || []; 547 if (typeof window.gtag !== "function") { 548 window.gtag = function () { 549 window.dataLayer.push(arguments); 550 }; 551 } 552 553 eventPayload = Object.assign({}, ecommerce); 554 if (typeof onComplete === "function") { 555 eventPayload.event_callback = function () { 556 if (completed) { 557 return; 558 } 559 completed = true; 560 window.clearTimeout(callbackTimer); 561 onComplete(); 562 }; 563 eventPayload.event_timeout = 1200; 564 callbackTimer = window.setTimeout(eventPayload.event_callback, 1500); 565 } 566 567 // The live site uses the Google tag directly, not a GTM container. Queue 568 // one gtag command only so events are not counted twice. Checkout waits for 569 // its callback (or a bounded fallback timer) before leaving the document. 570 window.gtag("event", eventName, eventPayload); 571 } 572 573 function navigateToPreparedCheckout(checkoutLink, fallbackUrl) { 574 // Read the live href on the next task. Google's linker decorates the real 575 // Checkout anchor while the trusted click bubbles to document; deferring 576 // this read preserves that `_gl` value even when gtag calls back 577 // synchronously. The validated Storefront API URL remains a fail-open 578 // fallback so analytics blocking can never block a purchase. 579 window.setTimeout(function () { 580 var destination = checkoutLink && checkoutLink.href ? checkoutLink.href : fallbackUrl; 581 if (destination) { 582 window.location.assign(String(destination)); 583 } 584 }, 0); 585 } 586 587 function pushEcommerce(eventName, product, variant, pricing, quantity, sellingPlanId) { 588 if (!eventName || !product || !variant || !pricing || !pricing.current) { 589 return; 590 } 591 592 var count = quantity || 1; 593 var item = analyticsItem(product, variant, pricing, count, sellingPlanId); 594 var ecommerce = { 595 currency: pricing.current.currencyCode, 596 value: Number((pricing.current.amount * count).toFixed(2)), 597 items: [item] 598 }; 599 600 sendAnalyticsEvent(eventName, ecommerce); 601 } 602 603 function pushViewItemOnce(product, variant, pricing) { 604 var key = String(product.id || product.handle || product.title || "product"); 605 var viewed = window.__CLCCommerceViewedItems; 606 607 if (!viewed || typeof viewed.has !== "function" || typeof viewed.add !== "function") { 608 viewed = new Set(); 609 window.__CLCCommerceViewedItems = viewed; 610 } 611 612 if (viewed.has(key)) { 613 return; 614 } 615 616 viewed.add(key); 617 pushEcommerce("view_item", product, variant, pricing, 1, ""); 618 } 619 620 var cartStorageKey = "clc_commerce_cart_v1"; 621 var pendingCheckoutStorageKey = "clc_commerce_pending_checkouts_v1"; 622 var checkoutReconciliationLockKey = "clc_commerce_checkout_reconciliation_lock_v1"; 623 var discountStorageKey = "clc_commerce_discount_v1"; 624 var cartMemory = []; 625 var pendingCheckoutMemory = []; 626 var cartStorageAvailable = null; 627 var pendingCheckoutStorageAvailable = null; 628 var checkoutReconciliationRunning = false; 629 630 function createClientId(prefix) { 631 var random = ""; 632 633 try { 634 if (window.crypto && typeof window.crypto.randomUUID === "function") { 635 random = window.crypto.randomUUID(); 636 } 637 } catch (error) { 638 random = ""; 639 } 640 if (!random) { 641 random = String(Date.now()) + "-" + Math.random().toString(36).slice(2); 642 } 643 return String(prefix || "clc") + "-" + random; 644 } 645 646 function normalizeDiscountCode(value) { 647 var code = String(value == null ? "" : value).trim(); 648 return /^[A-Za-z0-9_-]{1,255}$/.test(code) ? code : ""; 649 } 650 651 function activeDiscountCode() { 652 var queryCode = ""; 653 var storedCode = ""; 654 655 try { 656 queryCode = normalizeDiscountCode(new URL(window.location.href).searchParams.get("discount")); 657 } catch (error) { 658 queryCode = ""; 659 } 660 661 if (queryCode) { 662 try { 663 window.sessionStorage.setItem(discountStorageKey, queryCode); 664 } catch (error) { 665 // Strict storage settings can block sessionStorage. The current URL 666 // still carries the code for this page's Checkout preparation. 667 } 668 return queryCode; 669 } 670 671 try { 672 storedCode = normalizeDiscountCode(window.sessionStorage.getItem(discountStorageKey)); 673 } catch (error) { 674 storedCode = ""; 675 } 676 return storedCode; 677 } 678 679 function clearDiscountCode(expectedCode) { 680 var expected = normalizeDiscountCode(expectedCode); 681 var stored = ""; 682 683 try { 684 stored = normalizeDiscountCode(window.sessionStorage.getItem(discountStorageKey)); 685 if (!expected || !stored || stored === expected) { 686 window.sessionStorage.removeItem(discountStorageKey); 687 } 688 } catch (error) { 689 // There is no persistent coupon state to clear when storage is blocked. 690 } 691 } 692 693 function cartItemMaximum(item) { 694 return String(item && item.productHandle || "") === "trial-set" ? 1 : 20; 695 } 696 697 function normalizeCartItem(item) { 698 var quantity; 699 var amount; 700 701 if (!item || typeof item !== "object") { 702 return null; 703 } 704 705 if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(String(item.productHandle || ""))) { 706 return null; 707 } 708 709 if (!/^gid:\/\/shopify\/ProductVariant\/[1-9][0-9]*$/.test(String(item.variantId || ""))) { 710 return null; 711 } 712 713 if (item.sellingPlanId && !/^gid:\/\/shopify\/SellingPlan\/[1-9][0-9]*$/.test(String(item.sellingPlanId))) { 714 return null; 715 } 716 717 quantity = Math.min(cartItemMaximum(item), Math.max(1, Number.parseInt(item.quantity, 10) || 1)); 718 amount = Number(item.price && item.price.amount); 719 720 return { 721 key: String(item.variantId) + "::" + String(item.sellingPlanId || ""), 722 entryId: /^[A-Za-z0-9._:-]{1,160}$/.test(String(item.entryId || "")) ? String(item.entryId) : "", 723 productHandle: String(item.productHandle), 724 productId: String(item.productId || item.productHandle), 725 title: String(item.title || "åå"), 726 variantId: String(item.variantId),
727 variantTitle: String(item.variantTitle || ""), 728 sellingPlanId: item.sellingPlanId ? String(item.sellingPlanId) : "", 729 sellingPlanName: String(item.sellingPlanName || ""), 730 quantity: quantity, 731 price: Number.isFinite(amount) ? { 732 amount: amount, 733 currencyCode: String((item.price && item.price.currencyCode) || "JPY") 734 } : null, 735 imageUrl: String(item.imageUrl || ""), 736 imageAlt: String(item.imageAlt || item.title || "") 737 }; 738 } 739 740 function readCart() { 741 var parsed; 742 var stored; 743 var migrated = false; 744 745 if (cartStorageAvailable === false) { 746 parsed = cartMemory; 747 } else { 748 try { 749 stored = window.localStorage.getItem(cartStorageKey); 750 parsed = JSON.parse(stored || "[]"); 751 cartStorageAvailable = true; 752 } catch (error) { 753 cartStorageAvailable = false; 754 parsed = cartMemory; 755 } 756 } 757 758 if (!Array.isArray(parsed)) { 759 parsed = []; 760 } 761 762 cartMemory = parsed.slice(0, 20).map(normalizeCartItem).filter(Boolean).map(function (item) { 763 if (!item.entryId) { 764 item.entryId = createClientId("cart"); 765 migrated = true; 766 } 767 return item; 768 }); 769 if (migrated && cartStorageAvailable !== false) { 770 try { 771 window.localStorage.setItem(cartStorageKey, JSON.stringify(cartMemory)); 772 } catch (error) { 773 cartStorageAvailable = false; 774 } 775 } 776 return cartMemory.slice(); 777 } 778 779 function writeCart(items) { 780 cartMemory = items.slice(0, 20).map(normalizeCartItem).filter(Boolean).map(function (item) { 781 if (!item.entryId) { 782 item.entryId = createClientId("cart"); 783 } 784 return item; 785 }); 786 787 try { 788 window.localStorage.setItem(cartStorageKey, JSON.stringify(cartMemory)); 789 cartStorageAvailable = true; 790 } catch (error) { 791 cartStorageAvailable = false; 792 // Private browsing or strict storage settings can block localStorage. 793 } 794 795 document.dispatchEvent(new CustomEvent("clc:cart-changed", { 796 detail: { items: cartMemory.slice() } 797 })); 798 } 799 800 function normalizePendingCheckout(checkout) { 801 var cartId; 802 var cartToken; 803 var items; 804 var createdAt; 805 var checkedAt; 806 807 if (!checkout || typeof checkout !== "object") { 808 return null; 809 } 810 811 cartId = String(checkout.cartId || ""); 812 if ( 813 cartId.indexOf("gid://shopify/Cart/") !== 0 || 814 cartId.length > 4096 || 815 /[\u0000-\u0020\u007f<>"']/.test(cartId) 816 ) { 817 return null; 818 } 819 cartToken = String(checkout.cartToken || ""); 820 if (!/^[A-Za-z0-9_-]+\.[a-f0-9]{64}$/.test(cartToken) || cartToken.length > 512) { 821 return null; 822 } 823 824 items = Array.isArray(checkout.items) ? checkout.items.map(normalizeCartItem).filter(Boolean) : []; 825 if (!items.length) { 826 return null; 827 } 828 829 createdAt = Number(checkout.createdAt); 830 checkedAt = Number(checkout.checkedAt); 831 return { 832 cartId: cartId, 833 cartToken: cartToken, 834 items: items, 835 discountCode: normalizeDiscountCode(checkout.discountCode), 836 createdAt: Number.isFinite(createdAt) && createdAt > 0 ? createdAt : Date.now(), 837 checkedAt: Number.isFinite(checkedAt) && checkedAt > 0 ? checkedAt : 0 838 }; 839 } 840 841 function readPendingCheckouts() { 842 var parsed; 843 var stored; 844 845 if (pendingCheckoutStorageAvailable === false) { 846 parsed = pendingCheckoutMemory; 847 } else { 848 try { 849 stored = window.localStorage.getItem(pendingCheckoutStorageKey); 850 parsed = JSON.parse(stored || "[]"); 851 pendingCheckoutStorageAvailable = true; 852 } catch (error) { 853 pendingCheckoutStorageAvailable = false; 854 parsed = pendingCheckoutMemory; 855 } 856 } 857 858 pendingCheckoutMemory = (Array.isArray(parsed) ? parsed : []).map(normalizePendingCheckout).filter(Boolean).slice(-5); 859 return pendingCheckoutMemory.slice(); 860 } 861 862 function writePendingCheckouts(checkouts) { 863 var normalized = (Array.isArray(checkouts) ? checkouts : []).map(normalizePendingCheckout).filter(Boolean).slice(-5); 864 865 pendingCheckoutMemory = normalized.slice(); 866 867 try { 868 if (normalized.length) { 869 window.localStorage.setItem(pendingCheckoutStorageKey, JSON.stringify(normalized)); 870 } else { 871 window.localStorage.removeItem(pendingCheckoutStorageKey); 872 } 873 pendingCheckoutStorageAvailable = true; 874 } catch (error) { 875 pendingCheckoutStorageAvailable = false;
876 // The cart remains untouched when persistent storage is unavailable. 877 } 878 } 879 880 function rememberPendingCheckout(cartId, cartToken, items, discountCode) { 881 var pending = normalizePendingCheckout({ 882 cartId: cartId, 883 cartToken: cartToken, 884 items: items, 885 discountCode: discountCode, 886 createdAt: Date.now(), 887 checkedAt: 0 888 }); 889 var checkouts; 890 891 if (!pending) { 892 return; 893 } 894 895 checkouts = readPendingCheckouts().filter(function (checkout) { 896 return checkout.cartId !== pending.cartId; 897 }); 898 checkouts.push(pending); 899 writePendingCheckouts(checkouts); 900 } 901 902 function consumePendingCheckout(cartId) { 903 var checkouts = readPendingCheckouts(); 904 var claimed = checkouts.find(function (checkout) { return checkout.cartId === cartId; }); 905 906 if (!claimed) { 907 return null; 908 } 909 910 writePendingCheckouts(checkouts.filter(function (checkout) { 911 return checkout.cartId !== cartId; 912 })); 913 return claimed; 914 } 915 916 function markPendingCheckoutChecked(cartId, checkedAt) { 917 var checkouts = readPendingCheckouts(); 918 919 checkouts.forEach(function (checkout) { 920 if (checkout.cartId === cartId) { 921 checkout.checkedAt = checkedAt; 922 } 923 }); 924 writePendingCheckouts(checkouts); 925 } 926 927 function removeCheckoutItems(purchasedItems) { 928 var remaining = readCart(); 929 930 purchasedItems.forEach(function (purchased) { 931 var index = remaining.findIndex(function (item) { 932 if (purchased.entryId) { 933 return item.entryId === purchased.entryId; 934 } 935 return item.key === purchased.key; 936 }); 937 938 if (index < 0) { 939 return; 940 } 941 remaining[index].quantity -= purchased.quantity; 942 if (remaining[index].quantity <= 0) { 943 remaining.splice(index, 1); 944 } 945 }); 946 writeCart(remaining); 947 } 948 949 function withCheckoutReconciliationLock(callback) { 950 var lockName = "clc-commerce-checkout-reconciliation"; 951 var leaseToken; 952 var leaseUntil; 953 var existing; 954 955 if (window.navigator && window.navigator.locks && typeof window.navigator.locks.request === "function") { 956 return window.navigator.locks.request(lockName, { mode: "exclusive", ifAvailable: true }, function (lock) { 957 return lock ? callback() : null; 958 }); 959 } 960 961 leaseToken = createClientId("lock"); 962 leaseUntil = Date.now() + 30000; 963 try { 964 existing = JSON.parse(window.localStorage.getItem(checkoutReconciliationLockKey) || "null"); 965 if (existing && Number(existing.until) > Date.now()) { 966 return Promise.resolve(null); 967 } 968 window.localStorage.setItem(checkoutReconciliationLockKey, JSON.stringify({ token: leaseToken, until: leaseUntil })); 969 existing = JSON.parse(window.localStorage.getItem(checkoutReconciliationLockKey) || "null"); 970 if (!existing || existing.token !== leaseToken) { 971 return Promise.resolve(null); 972 } 973 } catch (error) { 974 // In storage-restricted browsers the existing per-document guard remains. 975 return Promise.resolve().then(callback); 976 } 977 978 return Promise.resolve().then(callback).finally(function () { 979 try { 980 existing = JSON.parse(window.localStorage.getItem(checkoutReconciliationLockKey) || "null"); 981 if (existing && existing.token === leaseToken) { 982 window.localStorage.removeItem(checkoutReconciliationLockKey); 983 } 984 } catch (error) { 985 // The short lease expires automatically if storage becomes unavailable. 986 } 987 }); 988 } 989 990 function reconcilePendingCheckouts(force) { 991 if (checkoutReconciliationRunning || !settings.cartStatusUrl || !settings.nonce) { 992 return; 993 } 994 995 checkoutReconciliationRunning = true; 996 withCheckoutReconciliationLock(function () { 997 var now = Date.now(); 998 var maxAge = 30 * 24 * 60 * 60 * 1000; 999 var completionWindow = 24 * 60 * 60 * 1000; 1000 var checkouts = readPendingCheckouts().filter(function (checkout) { 1001 return now - checkout.createdAt <= maxAge; 1002 }); 1003 var due = checkouts.filter(function (checkout) { 1004 return Boolean(force) || !checkout.checkedAt || now - checkout.checkedAt >= 60000; 1005 }); 1006 1007 writePendingCheckouts(checkouts); 1008 if (!due.length) { 1009 return null; 1010 } 1011 1012 return Promise.all(due.map(function (checkout) { 1013 return window.fetch(settings.cartStatusUrl, { 1014 method: "POST", 1015 credentials: "same-origin", 1016 headers: { 1017 "Content-Type": "application/json", 1018 "X-WP-Nonce": settings.nonce 1019 }, 1020 body: JSON.stringify({ cart_id: checkout.cartId, cart_token: checkout.cartToken }) 1021 }).then(function (response) { 1022 return response.json().catch(function () { return {}; }).then(function (payload) { 1023 if (!response.ok) { 1024 throw new Error(payload.message || "Cart status unavailable"); 1025 } 1026 return payload; 1027 }); 1028 }).then(function (payload) { 1029 var claimed; 1030 1031 if (payload.status === "unavailable" || payload.status === "completed_or_expired") { 1032 claimed = consumePendingCheckout(checkout.cartId); 1033 if (claimed && Date.now() - claimed.createdAt <= completionWindow) { 1034 removeCheckoutItems(claimed.items); 1035 clearDiscountCode(claimed.discountCode); 1036 } 1037 return; 1038 } 1039 if (payload.status === "active") { 1040 markPendingCheckoutChecked(checkout.cartId, Date.now()); 1041 } 1042 }).catch(function () { 1043 markPendingCheckoutChecked(checkout.cartId, Date.now()); 1044 }); 1045 })); 1046 }).finally(function () { 1047 checkoutReconciliationRunning = false;
1048 }); 1049 } 1050 1051 function addCartItem(item) { 1052 var normalized = normalizeCartItem(item); 1053 var items = readCart(); 1054 var existing; 1055 var addedQuantity; 1056 1057 if (!normalized) { 1058 return false; 1059 } 1060 1061 existing = items.find(function (candidate) { 1062 return candidate.key === normalized.key; 1063 }); 1064 1065 if (existing) { 1066 addedQuantity = Math.min(cartItemMaximum(existing), existing.quantity + normalized.quantity) - existing.quantity; 1067 if (addedQuantity <= 0) { 1068 return 0; 1069 } 1070 existing.quantity += addedQuantity; 1071 } else if (items.length < 20) { 1072 normalized.entryId = normalized.entryId || createClientId("cart"); 1073 items.push(normalized); 1074 addedQuantity = normalized.quantity; 1075 } else { 1076 return 0; 1077 } 1078 1079 writeCart(items); 1080 return addedQuantity; 1081 } 1082 1083 function updateCartItem(key, quantity) { 1084 var items = readCart(); 1085 var target = items.find(function (item) { return item.key === key; }); 1086 1087 if (!target) { 1088 return; 1089 } 1090 1091 if (quantity <= 0) { 1092 items = items.filter(function (item) { return item.key !== key; }); 1093 } else { 1094 target.quantity = Math.min(cartItemMaximum(target), Math.max(1, Number.parseInt(quantity, 10) || 1)); 1095 } 1096 1097 writeCart(items); 1098 } 1099 1100 function cartAnalyticsItem(item, index, quantity) { 1101 return { 1102 item_id: item.productId || item.productHandle, 1103 item_name: item.title, 1104 item_variant: item.variantTitle, 1105 item_subscription_plan: item.sellingPlanId || undefined, 1106 price: item.price ? item.price.amount : 0, 1107 quantity: quantity == null ? item.quantity : quantity, 1108 index: index + 1 1109 }; 1110 } 1111 1112 function pushCartQuantityEvent(eventName, item, quantity, index) { 1113 var count = Number(quantity); 1114 var price; 1115 1116 if (!eventName || !item || !Number.isFinite(count) || count <= 0) { 1117 return; 1118 } 1119 1120 price = item.price ? Number(item.price.amount) : 0; 1121 if (!Number.isFinite(price)) { 1122 price = 0; 1123 } 1124 sendAnalyticsEvent(eventName, { 1125 currency: item.price && item.price.currencyCode || settings.currency || "JPY", 1126 value: Number((price * count).toFixed(2)), 1127 items: [cartAnalyticsItem(item, Number.isFinite(index) ? index : 0, count)] 1128 }); 1129 } 1130 1131 function openUtilityPanel(panelId, trigger) { 1132 document.dispatchEvent(new CustomEvent("clc:open-panel", { 1133 detail: { panelId: panelId, trigger: trigger || null } 1134 })); 1135 } 1136 1137 function revealGalleryThumbnail(button) { 1138 var strip = button && button.closest(".clc-gallery-thumbnails, .clc-gallery-thumbs"); 1139 var stripRect; 1140 var buttonRect; 1141 var inset = 6; 1142 1143 if (!strip) { 1144 return; 1145 } 1146 1147 stripRect = strip.getBoundingClientRect(); 1148 buttonRect = button.getBoundingClientRect(); 1149 1150 if (buttonRect.left < stripRect.left + inset) { 1151 strip.scrollLeft -= stripRect.left + inset - buttonRect.left; 1152 } else if (buttonRect.right > stripRect.right - inset) { 1153 strip.scrollLeft += buttonRect.right - stripRect.right + inset; 1154 } 1155 1156 if (buttonRect.top < stripRect.top + inset) { 1157 strip.scrollTop -= stripRect.top + inset - buttonRect.top; 1158 } else if (buttonRect.bottom > stripRect.bottom - inset) { 1159 strip.scrollTop += buttonRect.bottom - stripRect.bottom + inset; 1160 } 1161 } 1162 1163 function initGallery(productElement) { 1164 var galleryMain = productElement.querySelector(".clc-gallery-main"); 1165 var mainImage = productElement.querySelector(".clc-gallery-main img"); 1166 var thumbnails = toArray(productElement.querySelectorAll(".clc-gallery-thumb")); 1167 var swipePointerId = null; 1168 var swipeStartX = null; 1169 var swipeStartY = null; 1170 var swipeThreshold = 42; 1171 1172 if (!mainImage || !thumbnails.length) { 1173 return; 1174 } 1175 1176 function activate(button) { 1177 var url = button.getAttribute("data-url"); 1178 var alt = button.getAttribute("data-alt"); 1179 var thumbnailImage = button.querySelector("img"); 1180 1181 if (!url && thumbnailImage) { 1182 url = thumbnailImage.currentSrc || thumbnailImage.src; 1183 } 1184 1185 if (alt == null && thumbnailImage) { 1186 alt = thumbnailImage.alt; 1187 } 1188 1189 if (!url) { 1190 return; 1191 } 1192 1193 applyResponsiveProductImage(mainImage, url); 1194 mainImage.alt = alt || ""; 1195
1196 thumbnails.forEach(function (thumbnail) { 1197 var active = thumbnail === button; 1198 thumbnail.classList.toggle("is-active", active); 1199 thumbnail.setAttribute("aria-pressed", active ? "true" : "false"); 1200 }); 1201 1202 revealGalleryThumbnail(button); 1203 } 1204 1205 function activeThumbnailIndex() { 1206 var activeIndex = thumbnails.findIndex(function (thumbnail) { 1207 return thumbnail.classList.contains("is-active") || thumbnail.getAttribute("aria-pressed") === "true"; 1208 }); 1209 1210 return activeIndex >= 0 ? activeIndex : 0; 1211 } 1212 1213 function activateRelative(direction) { 1214 var currentIndex = activeThumbnailIndex(); 1215 var targetIndex = Math.max(0, Math.min(thumbnails.length - 1, currentIndex + direction)); 1216 1217 if (targetIndex !== currentIndex) { 1218 activate(thumbnails[targetIndex]); 1219 } 1220 } 1221 1222 function beginSwipe(clientX, clientY) { 1223 swipeStartX = Number(clientX); 1224 swipeStartY = Number(clientY); 1225 } 1226 1227 function resetSwipe() { 1228 swipePointerId = null; 1229 swipeStartX = null; 1230 swipeStartY = null; 1231 } 1232 1233 function finishSwipe(clientX, clientY) { 1234 var deltaX; 1235 var deltaY; 1236 var horizontalDistance; 1237 var verticalDistance; 1238 1239 if (swipeStartX == null || swipeStartY == null) { 1240 resetSwipe(); 1241 return; 1242 } 1243 1244 deltaX = Number(clientX) - swipeStartX; 1245 deltaY = Number(clientY) - swipeStartY; 1246 horizontalDistance = Math.abs(deltaX); 1247 verticalDistance = Math.abs(deltaY); 1248 resetSwipe(); 1249 1250 if (horizontalDistance < swipeThreshold || horizontalDistance <= verticalDistance * 1.15) { 1251 return; 1252 } 1253 1254 activateRelative(deltaX < 0 ? 1 : -1); 1255 } 1256 1257 thumbnails.forEach(function (button) { 1258 button.addEventListener("focus", function () { 1259 revealGalleryThumbnail(button); 1260 }); 1261 1262 button.addEventListener("click", function (event) { 1263 event.preventDefault(); 1264 activate(button); 1265 }); 1266 1267 button.addEventListener("keydown", function (event) { 1268 var index = thumbnails.indexOf(button); 1269 var targetIndex = index; 1270 1271 if (event.key === "ArrowRight" || event.key === "ArrowDown") { 1272 targetIndex = (index + 1) % thumbnails.length; 1273 } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") { 1274 targetIndex = (index - 1 + thumbnails.length) % thumbnails.length; 1275 } else if (event.key === "Home") { 1276 targetIndex = 0; 1277 } else if (event.key === "End") { 1278 targetIndex = thumbnails.length - 1; 1279 } else { 1280 return; 1281 } 1282 1283 event.preventDefault(); 1284 activate(thumbnails[targetIndex]); 1285 thumbnails[targetIndex].focus(); 1286 }); 1287 }); 1288 1289 if (!galleryMain || thumbnails.length < 2) { 1290 return; 1291 } 1292 1293 galleryMain.setAttribute("data-clc-gallery-swipe-ready", "true"); 1294 mainImage.setAttribute("draggable", "false"); 1295 1296 if (window.PointerEvent) { 1297 galleryMain.addEventListener("pointerdown", function (event) { 1298 if ( 1299 event.isPrimary === false || 1300 (event.pointerType && event.pointerType !== "touch" && event.pointerType !== "pen") 1301 ) { 1302 return; 1303 } 1304 1305 swipePointerId = event.pointerId; 1306 beginSwipe(event.clientX, event.clientY); 1307 1308 if (typeof galleryMain.setPointerCapture === "function") { 1309 try { 1310 galleryMain.setPointerCapture(event.pointerId); 1311 } catch (error) { 1312 // Synthetic events and older WebViews can reject pointer capture. 1313 } 1314 } 1315 }); 1316 1317 galleryMain.addEventListener("pointerup", function (event) { 1318 if (swipePointerId == null || event.pointerId !== swipePointerId) { 1319 return; 1320 } 1321 1322 finishSwipe(event.clientX, event.clientY); 1323 }); 1324 1325 galleryMain.addEventListener("pointercancel", resetSwipe); 1326 } else { 1327 galleryMain.addEventListener("touchstart", function (event) { 1328 if (event.touches.length !== 1) { 1329 resetSwipe(); 1330 return; 1331 } 1332 1333 beginSwipe(event.touches[0].clientX, event.touches[0].clientY); 1334 }, { passive: true }); 1335 1336 galleryMain.addEventListener("touchend", function (event) { 1337 if (event.changedTouches.length !== 1) { 1338 resetSwipe(); 1339 return; 1340 } 1341 1342 finishSwipe(event.changedTouches[0].clientX, event.changedTouches[0].clientY); 1343 }, { passive: true }); 1344 1345 galleryMain.addEventListener("touchcancel", resetSwipe, { passive: true }); 1346 } 1347 } 1348 1349 function initQuantity(productElement) { 1350 var input = productElement.querySelector(".clc-qty-input"); 1351 var buttons = toArray(productElement.querySelectorAll("[data-clc-qty]")); 1352 var productMaximum = productElement.getAttribute("data-product-handle") === "trial-set" ? 1 : 20; 1353 1354 if (!input) { 1355 return function () { return 1; }; 1356 } 1357 1358 function bounds() { 1359 var minimum = Number(input.min); 1360 var maximum = Number(input.max); 1361 var step = Number(input.step); 1362 1363 return { 1364 min: Number.isFinite(minimum) ? minimum : 1, 1365 max: Number.isFinite(maximum) && maximum > 0 ? Math.min(maximum, productMaximum) : productMaximum, 1366 step: Number.isFinite(step) && step > 0 ? step : 1 1367 }; 1368 } 1369 1370 function normalizeQuantity(value) { 1371 var limits = bounds(); 1372 var quantity = Number.parseInt(value, 10); 1373 1374 if (!Number.isFinite(quantity)) { 1375 quantity = limits.min; 1376 } 1377 1378 return Math.min(limits.max, Math.max(limits.min, quantity)); 1379 } 1380 1381 function synchronizeButtons(quantity) { 1382 var limits = bounds(); 1383 1384 buttons.forEach(function (button) { 1385 var direction = button.getAttribute("data-clc-qty"); 1386 button.disabled = direction === "increase" ? quantity >= limits.max : quantity <= limits.min; 1387 }); 1388 } 1389 1390 function commit(value) { 1391 var quantity = normalizeQuantity(value); 1392
1393 input.value = String(quantity); 1394 synchronizeButtons(quantity); 1395 input.dispatchEvent(new Event("input", { bubbles: true })); 1396 } 1397 1398 buttons.forEach(function (button) { 1399 button.addEventListener("click", function (event) { 1400 var limits = bounds(); 1401 var direction = button.getAttribute("data-clc-qty"); 1402 var delta = direction === "decrease" ? -limits.step : limits.step; 1403 event.preventDefault(); 1404 commit(normalizeQuantity(input.value) + delta); 1405 }); 1406 }); 1407 1408 input.addEventListener("change", function () { 1409 commit(input.value); 1410 }); 1411 1412 input.max = String(Math.min(Number(input.max) || productMaximum, productMaximum)); 1413 commit(input.value); 1414 1415 return function () { 1416 return normalizeQuantity(input.value); 1417 }; 1418 } 1419 1420 function initProduct(productElement) { 1421 if (productElement.getAttribute("data-clc-initialized") === "true") { 1422 return; 1423 } 1424 1425 productElement.setAttribute("data-clc-initialized", "true"); 1426 1427 var product = parseProductData(productElement); 1428 var variants = connectionNodes(product && product.variants); 1429 var optionInputs = toArray(productElement.querySelectorAll(".clc-option-input[data-option-name]")); 1430 var planInputs = toArray(productElement.querySelectorAll(".clc-plan-input")); 1431 var priceCurrent = productElement.querySelector(".clc-price-current"); 1432 var priceCompare = productElement.querySelector(".clc-price-compare"); 1433 var priceNote = productElement.querySelector(".clc-price-note"); 1434 var availability = productElement.querySelector(".clc-availability"); 1435 var checkoutButton = productElement.querySelector(".clc-checkout-button"); 1436 var purchaseForm = productElement.querySelector(".clc-purchase-form, .clc-product-form"); 1437 var errorBox = productElement.querySelector(".clc-purchase-error"); 1438 var productStatus = productElement.querySelector("[data-clc-product-status]"); 1439 var mainImage = productElement.querySelector(".clc-gallery-main img"); 1440 var currency; 1441 var currentVariant; 1442 var currentPricing; 1443 var busy = false; 1444 var hasRendered = false; 1445 var getQuantity; 1446 1447 initGallery(productElement); 1448 getQuantity = initQuantity(productElement); 1449 1450 if (!product || !variants.length) { 1451 if (checkoutButton) { 1452 checkoutButton.disabled = true; 1453 } 1454 return; 1455 } 1456 1457 currency = productCurrency(product, variants); 1458 currentVariant = preferredVariant(product, variants, productElement, optionInputs); 1459 synchronizeOptionInputs(optionInputs, currentVariant); 1460 1461 if (priceNote && !own.call(priceNote.dataset, "defaultText")) { 1462 priceNote.dataset.defaultText = priceNote.textContent || ""; 1463 } 1464 1465 if (checkoutButton && !checkoutButton.dataset.defaultLabel) { 1466 checkoutButton.dataset.defaultLabel = checkoutButton.textContent.trim(); 1467 } 1468 1469 if (errorBox && !errorBox.getAttribute("role")) { 1470 errorBox.setAttribute("role", "alert"); 1471 } 1472 1473 function showError(message) { 1474 if (!errorBox) { 1475 return; 1476 } 1477 1478 setText(errorBox, message); 1479 setHidden(errorBox, !message); 1480 } 1481 1482 function selectedPlan() { 1483 return selectedPlanId(planInputs); 1484 } 1485 1486 function announceProduct(message) { 1487 setText(productStatus, message || ""); 1488 } 1489 1490 function updateVariantImage(variant) { 1491 var image = productImage(variant); 1492 1493 if (!mainImage || !image || !image.url) { 1494 return; 1495 } 1496 1497 applyResponsiveProductImage(mainImage, image.url); 1498 mainImage.alt = image.altText || product.title || ""; 1499 if (image.width > 0 && image.height > 0) { 1500 mainImage.width = image.width; 1501 mainImage.height = image.height; 1502 } 1503 1504 toArray(productElement.querySelectorAll(".clc-gallery-thumb")).forEach(function (thumbnail) { 1505 var active = thumbnail.getAttribute("data-url") === image.url; 1506 thumbnail.classList.toggle("is-active", active); 1507 thumbnail.setAttribute("aria-pressed", active ? "true" : "false"); 1508 if (active) { 1509 revealGalleryThumbnail(thumbnail); 1510 } 1511 }); 1512 } 1513 1514 function render() { 1515 var planId; 1516 var inventoryAvailable = isVariantAvailable(currentVariant); 1517 var available; 1518 var compareVisible; 1519 var note = ""; 1520 var inactiveLabel; 1521 var planSync; 1522 1523 planSync = synchronizePlanInputs( 1524 planInputs, 1525 currentVariant, 1526 Boolean(product.requiresSellingPlan), 1527 !hasRendered 1528 ); 1529 planId = selectedPlan(); 1530 available = inventoryAvailable && Boolean(currentVariant && currentVariant.id) && 1531 (!product.requiresSellingPlan || Boolean(planId)); 1532 inactiveLabel = !currentVariant ? 1533 (labels.unavailable || "鏿ããçµã¿åããã¯è³¼å ¥ã§ãã¾ããã") : 1534 (inventoryAvailable && product.requiresSellingPlan && !planId ?
1535 (labels.selectPlan || "è³¼å ¥æ¹æ³ã鏿ãã¦ãã ãã") : 1536 (labels.soldOut || labels.unavailable || "ç¾å¨è³¼å ¥ã§ãã¾ãã")); 1537 currentPricing = variantPricing(currentVariant, planId, currency); 1538 1539 setText(priceCurrent, formatMoney(currentPricing.current)); 1540 1541 compareVisible = Boolean( 1542 currentPricing.compare && currentPricing.current && 1543 currentPricing.compare.amount > currentPricing.current.amount 1544 ); 1545 setText(priceCompare, compareVisible ? formatMoney(currentPricing.compare) : ""); 1546 setHidden(priceCompare, !compareVisible); 1547 1548 if (priceCurrent) { 1549 priceCurrent.classList.toggle("is-sale", compareVisible); 1550 } 1551 1552 if (currentPricing.perDelivery && currentPricing.later) { 1553 var orderCount = Number(currentPricing.adjustment && currentPricing.adjustment.orderCount) || 1; 1554 note = orderCount === 1 ? 1555 renderTemplate(labels.stagedPrice || "åå {price} ï¼ 2åç®ä»¥é {later}ï¼ç¨è¾¼ï¼", { 1556 price: formatMoney(currentPricing.perDelivery), 1557 later: formatMoney(currentPricing.later) 1558 }) : 1559 renderTemplate(labels.multiStagePrice || "1ã{count}åç® {price} ï¼ {next}åç®ä»¥é {later}ï¼ç¨è¾¼ï¼", { 1560 count: orderCount, 1561 next: orderCount + 1, 1562 price: formatMoney(currentPricing.perDelivery), 1563 later: formatMoney(currentPricing.later) 1564 }); 1565 } else if (currentPricing.perDelivery) { 1566 note = renderTemplate(labels.perDelivery || labels.perDeliveryPrice || "1åããã {price}", { 1567 price: formatMoney(currentPricing.perDelivery) 1568 }); 1569 } else if (currentPricing.allocation && currentPricing.allocation.sellingPlan) { 1570 note = currentPricing.allocation.sellingPlan.description || ""; 1571 } else if (priceNote) { 1572 note = priceNote.dataset.defaultText || ""; 1573 } 1574 1575 setText(priceNote, note); 1576 setHidden(priceNote, !note); 1577 1578 var summaryPanel = productElement.querySelector(".clc-subscription-summary"); 1579 if (summaryPanel) { 1580 var summary = currentPricing.allocation && currentPricing.allocation.sellingPlan && 1581 currentPricing.allocation.sellingPlan.purchaseSummary; 1582 setHidden(summaryPanel, !summary || !available); 1583 if (summary && available) { 1584 toArray(summaryPanel.querySelectorAll("[data-clc-summary]")).forEach(function (field) { 1585 var key = field.getAttribute("data-clc-summary"); 1586 setText(field, key === "price" ? note : (summary[key] || "")); 1587 }); 1588 } 1589 } 1590 1591 if (availability) { 1592 availability.classList.toggle("is-available", available); 1593 availability.classList.toggle("is-unavailable", !available); 1594 setText(availability, available ? 1595 (labels.inStock || labels.available || "å¨åº«ãã") : 1596 inactiveLabel); 1597 } 1598 1599 if (checkoutButton) { 1600 checkoutButton.disabled = busy || !available; 1601 1602 if (!busy) { 1603 setText(checkoutButton, available ? 1604 (checkoutButton.dataset.defaultLabel || labels.checkout || "è³¼å ¥æç¶ãã¸") : 1605 inactiveLabel); 1606 } 1607 1608 if (currentVariant && currentVariant.id != null) { 1609 checkoutButton.setAttribute("data-variant-id", String(currentVariant.id)); 1610 } else { 1611 checkoutButton.removeAttribute("data-variant-id"); 1612 } 1613 } 1614 1615 updateVariantImage(currentVariant); 1616 synchronizeVariantUrl(product, currentVariant, productElement); 1617 1618 if (hasRendered && planSync.selectionCleared) { 1619 announceProduct(labels.planChanged || "鏿ããã³ã¼ã¹ã§ã¯è³¼å ¥æ¹æ³ãå¤ãããããè³¼å ¥æ¹æ³ãé¸ã³ç´ãã¦ãã ããã"); 1620 } 1621 1622 hasRendered = true; 1623 } 1624 1625 function selectVariantFromInputs(pushEvent) { 1626 var matched = findSelectedVariant(variants, optionInputs); 1627 1628 showError(""); 1629 announceProduct(""); 1630 currentVariant = matched; 1631 render(); 1632 1633 if (pushEvent && currentVariant) { 1634 pushEcommerce("select_variant", product, currentVariant, currentPricing, 1, selectedPlan()); 1635 } 1636 } 1637
1638 optionInputs.forEach(function (input) { 1639 input.addEventListener("change", function () { 1640 selectVariantFromInputs(true); 1641 }); 1642 }); 1643 1644 planInputs.forEach(function (input) { 1645 input.addEventListener("change", function () { 1646 if (!input.checked || input.disabled) { 1647 return; 1648 } 1649 1650 showError(""); 1651 announceProduct(""); 1652 render(); 1653 pushEcommerce("select_subscription_plan", product, currentVariant, currentPricing, 1, selectedPlan()); 1654 }); 1655 }); 1656 1657 if (purchaseForm && checkoutButton) { 1658 purchaseForm.addEventListener("submit", function (event) { 1659 var quantity; 1660 var image; 1661 var allocation; 1662 var added; 1663 1664 event.preventDefault(); 1665 1666 if (busy || !currentVariant || !isVariantAvailable(currentVariant)) { 1667 return; 1668 } 1669 1670 if (product.requiresSellingPlan && !selectedPlan()) { 1671 showError(labels.selectPlan || "è³¼å ¥æ¹æ³ã鏿ãã¦ãã ããã"); 1672 return; 1673 } 1674 1675 quantity = getQuantity(); 1676 1677 if (!Number.isFinite(quantity) || quantity < 1) { 1678 showError(labels.quantityError || "æ°éã1以ä¸ã«ãã¦ãã ããã"); 1679 return; 1680 } 1681 1682 image = productImage(currentVariant) || product.featuredImage || {}; 1683 allocation = currentPricing && currentPricing.allocation; 1684 added = addCartItem({ 1685 productHandle: String(product.handle || productElement.getAttribute("data-product-handle") || ""), 1686 productId: String(product.id || product.handle || ""), 1687 title: String(product.title || "åå"), 1688 variantId: String(currentVariant.id), 1689 variantTitle: String(currentVariant.title || ""), 1690 sellingPlanId: selectedPlan() || "", 1691 sellingPlanName: String(allocation && allocation.sellingPlan && allocation.sellingPlan.name || ""), 1692 quantity: quantity, 1693 price: currentPricing && currentPricing.current, 1694 imageUrl: String(image.url || ""), 1695 imageAlt: String(image.altText || product.title || "") 1696 }); 1697 1698 if (!added) { 1699 showError(labels.cartFull || "ã«ã¼ãã«è¿½å ã§ããååæ°ã®ä¸éã«éãã¾ããã"); 1700 return; 1701 } 1702 1703 showError(""); 1704 pushEcommerce("add_to_cart", product, currentVariant, currentPricing, added, selectedPlan()); 1705 announceProduct(labels.addedToCart || "ã«ã¼ãã«è¿½å ãã¾ããã"); 1706 setText(checkoutButton, labels.addedToCart || "ã«ã¼ãã«è¿½å ãã¾ãã"); 1707 openUtilityPanel("clc-cart-panel", checkoutButton); 1708 1709 window.setTimeout(function () { 1710 render(); 1711 }, 900); 1712 }); 1713 } 1714 1715 showError(""); 1716 render(); 1717 pushViewItemOnce(product, currentVariant, currentPricing); 1718 } 1719 1720 function initRichText(root) { 1721 if (!root || root.getAttribute("data-clc-rich-text-ready") === "true") { 1722 return; 1723 } 1724 1725 root.setAttribute("data-clc-rich-text-ready", "true"); 1726 1727 toArray(root.querySelectorAll("table")).forEach(function (table, tableIndex) { 1728 var rows = toArray(table.rows || []); 1729 var caption = table.querySelector("caption"); 1730 var heading = table.previousElementSibling; 1731 var headingText = ""; 1732 var consistentShape; 1733 var columnCount; 1734 var firstRowIsHeader; 1735 1736 while (heading && !/^H[2-4]$/.test(heading.tagName)) { 1737 heading = heading.previousElementSibling; 1738 } 1739 if (heading) { 1740 headingText = String(heading.textContent || "").trim(); 1741 } 1742 if (!caption) { 1743 caption = document.createElement("caption"); 1744 caption.className = "clc-screen-reader-text"; 1745 caption.textContent = headingText || (labels.tableScroll || "ååã»ãµã¼ãã¹æ å ±è¡¨") + " " + String(tableIndex + 1); 1746 table.insertBefore(caption, table.firstChild); 1747 } 1748 1749 columnCount = rows.length && rows[0].cells ? rows[0].cells.length : 0; 1750 consistentShape = columnCount > 0 && rows.every(function (row) { 1751 return row.cells && row.cells.length === columnCount; 1752 }); 1753 if (!consistentShape || table.querySelector("th")) { 1754 return; 1755 } 1756 1757 function promoteCell(cell, scope) { 1758 var header = document.createElement("th");
1759 toArray(cell.attributes || []).forEach(function (attribute) { 1760 header.setAttribute(attribute.name, attribute.value); 1761 }); 1762 header.setAttribute("scope", scope); 1763 while (cell.firstChild) { 1764 header.appendChild(cell.firstChild); 1765 } 1766 cell.parentNode.replaceChild(header, cell); 1767 } 1768 1769 firstRowIsHeader = Boolean(table.tHead) || 1770 /background(?:-color)?\s*:/i.test(rows[0].getAttribute("style") || "") || 1771 toArray(rows[0].cells).every(function (cell) { 1772 return Boolean(cell.querySelector("strong, b")); 1773 }); 1774 if (firstRowIsHeader) { 1775 toArray(rows[0].cells).forEach(function (cell) { 1776 promoteCell(cell, "col"); 1777 }); 1778 } 1779 rows.slice(firstRowIsHeader ? 1 : 0).forEach(function (row) { 1780 if (row.cells && row.cells[0]) { 1781 promoteCell(row.cells[0], "row"); 1782 } 1783 }); 1784 }); 1785 1786 var wrappers = toArray(root.querySelectorAll("table")).map(function (table) { 1787 var parent = table.parentElement; 1788 var wrapper; 1789 1790 if (parent && parent.classList.contains("clc-table-scroll")) { 1791 return parent; 1792 } 1793 1794 wrapper = document.createElement("div"); 1795 wrapper.className = "clc-table-scroll"; 1796 wrapper.setAttribute("data-clc-table-scroll", "true"); 1797 table.parentNode.insertBefore(wrapper, table); 1798 wrapper.appendChild(table); 1799 return wrapper; 1800 }); 1801 1802 function synchronizeTableAccess() { 1803 wrappers.forEach(function (wrapper) { 1804 var table = wrapper.querySelector("table"); 1805 var caption = table && table.querySelector("caption"); 1806 var isScrollable = wrapper.scrollWidth > wrapper.clientWidth + 1; 1807 1808 if (isScrollable) { 1809 wrapper.tabIndex = 0; 1810 wrapper.setAttribute("role", "region"); 1811 wrapper.setAttribute( 1812 "aria-label", 1813 caption && caption.textContent.trim() ? 1814 caption.textContent.trim() : 1815 (labels.tableScroll || "ååæ å ±è¡¨ãæ¨ªã«ã¹ã¯ãã¼ã«ã§ãã¾ãã") 1816 ); 1817 } else { 1818 wrapper.removeAttribute("tabindex"); 1819 wrapper.removeAttribute("role"); 1820 wrapper.removeAttribute("aria-label"); 1821 } 1822 }); 1823 } 1824 1825 synchronizeTableAccess(); 1826 1827 if (typeof window.requestAnimationFrame === "function") { 1828 window.requestAnimationFrame(synchronizeTableAccess); 1829 } 1830 1831 if (wrappers.length) { 1832 window.addEventListener("resize", synchronizeTableAccess, { passive: true }); 1833 } 1834 } 1835 1836 function initHorizontalScroll(root) { 1837 if (!root || root.getAttribute("data-clc-horizontal-scroll-ready") === "true") { 1838 return; 1839 } 1840 1841 root.setAttribute("data-clc-horizontal-scroll-ready", "true"); 1842 1843 function synchronizeAccess() { 1844 var isScrollable = root.scrollWidth > root.clientWidth + 1; 1845 1846 if (isScrollable) { 1847 root.tabIndex = 0; 1848 root.setAttribute("role", "region"); 1849 root.setAttribute( 1850 "aria-label", 1851 root.getAttribute("data-clc-scroll-label") || labels.horizontalScroll || "横ã«ã¹ã¯ãã¼ã«ã§ãã¾ãã" 1852 ); 1853 } else { 1854 root.removeAttribute("tabindex"); 1855 root.removeAttribute("role"); 1856 root.removeAttribute("aria-label"); 1857 } 1858 } 1859 1860 synchronizeAccess(); 1861 if (typeof window.requestAnimationFrame === "function") { 1862 window.requestAnimationFrame(synchronizeAccess); 1863 } 1864 window.addEventListener("resize", synchronizeAccess, { passive: true }); 1865 } 1866 1867 function controlFilterName(control) { 1868 var owner = control.hasAttribute("data-clc-filter") ? control : control.closest("[data-clc-filter]"); 1869 return owner ? owner.getAttribute("data-clc-filter") : ""; 1870 } 1871 1872 function filterControls(collection) { 1873 var controls = []; 1874 1875 toArray(collection.querySelectorAll("[data-clc-filter]")).forEach(function (element) { 1876 if (element.matches("input[type=\"checkbox\"], input[type=\"radio\"]")) { 1877 controls.push(element); 1878 return; 1879 } 1880 1881 toArray(element.querySelectorAll("input[type=\"checkbox\"], input[type=\"radio\"]")).forEac
1881h(function (input) { 1882 if (controls.indexOf(input) === -1) { 1883 controls.push(input); 1884 } 1885 }); 1886 }); 1887 1888 return controls; 1889 } 1890 1891 function numericCardValue(card, field) { 1892 var value = card.getAttribute("data-" + field) || ""; 1893 var parsed = Number(String(value).replace(/[^0-9.-]/g, "")); 1894 return Number.isFinite(parsed) ? parsed : 0; 1895 } 1896 1897 function cardFieldValue(card, field) { 1898 var normalizedField = field === "availability" ? "available" : field; 1899 normalizedField = normalizedField === "type" ? "product-type" : normalizedField; 1900 var value = card.getAttribute("data-" + normalizedField); 1901 1902 if (value == null && normalizedField === "title") { 1903 var title = card.querySelector(".clc-card-title"); 1904 value = title ? title.textContent : ""; 1905 } 1906 1907 return String(value == null ? "" : value); 1908 } 1909 1910 function matchesPriceFilter(card, control) { 1911 var owner = control.hasAttribute("data-clc-filter") ? control : control.closest("[data-clc-filter]"); 1912 var price = numericCardValue(card, "price"); 1913 var minimum = Number(control.getAttribute("data-min") || (owner && owner.getAttribute("data-min"))); 1914 var maximum = Number(control.getAttribute("data-max") || (owner && owner.getAttribute("data-max"))); 1915 var value = normalize(control.value); 1916 var range; 1917 1918 if (!Number.isFinite(minimum)) { 1919 minimum = -Infinity; 1920 } 1921 1922 if (!Number.isFinite(maximum)) { 1923 maximum = Infinity; 1924 } 1925 1926 if (minimum !== -Infinity || maximum !== Infinity) { 1927 return price >= minimum && price <= maximum; 1928 } 1929 1930 range = value.match(/^(?:Â¥|ï¿¥)?([0-9.]*)\s*[-:]\s*(?:Â¥|ï¿¥)?([0-9.]*)$/); 1931 if (range) { 1932 minimum = range[1] ? Number(range[1]) : -Infinity; 1933 maximum = range[2] ? Number(range[2]) : Infinity; 1934 return price >= minimum && price <= maximum; 1935 } 1936 1937 if (value.indexOf("under-") === 0) { 1938 return price < Number(value.replace("under-", "")); 1939 } 1940 1941 if (value.indexOf("over-") === 0) { 1942 return price >= Number(value.replace("over-", "")); 1943 } 1944 1945 return normalize(cardFieldValue(card, "price")) === value; 1946 } 1947 1948 function cardMatchesControl(card, field, control) { 1949 var expected = normalize(control.value); 1950 var actual; 1951 1952 if (field === "price") { 1953 return matchesPriceFilter(card, control); 1954 } 1955 1956 if (field === "available" || field === "availability") { 1957 actual = normalize(cardFieldValue(card, "available")); 1958 1959 if (["true", "1", "yes", "available", "in-stock", "instock"].indexOf(expected) !== -1) { 1960 return ["true", "1", "yes", "available", "in-stock", "instock"].indexOf(actual) !== -1; 1961 } 1962 1963 if (["false", "0", "no", "sold-out", "soldout", "unavailable"].indexOf(expected) !== -1) { 1964 return ["false", "0", "no", "sold-out", "soldout", "unavailable"].indexOf(actual) !== -1; 1965 } 1966 } 1967 1968 actual = normalize(cardFieldValue(card, field)); 1969 return actual === expected; 1970 } 1971 1972 function initCollection(collection) { 1973 if (collection.getAttribute("data-clc-initialized") === "true") { 1974 return; 1975 } 1976 1977 collection.setAttribute("data-clc-initialized", "true"); 1978 1979 var cards = toArray(collection.querySelectorAll(".clc-product-card")); 1980 var controls = filterControls(collection); 1981 var sortControl = collection.querySelector("[data-clc-sort]"); 1982 var resultCount = collection.querySelector("[data-clc-result-count]"); 1983 var collectionStatus = collection.querySelector("[data-clc-collection-status]"); 1984 var emptyState = collection.querySelector("[data-clc-empty]"); 1985 var grid = collection.querySelector(".clc-product-grid") || (cards[0] && cards[0].parentElement); 1986 var filterToggle = collection.querySelector("[data-clc-filter-toggle]"); 1987 var filterPanel = collection.querySelector("[data-clc-filter-panel]"); 1988 var filterClose = collection.querySelector("[data-clc-filter-close]"); 1989 var filterClear = collection.querySelector("[data-clc-filter-clear]"); 1990 var filterCount = collection.querySelector("[data-clc-filter-count]"); 1991 var filterMedia = window.matchMedia ? window.matchMedia("(max-width: 767px)") : null; 1992 var searchSummary = collection.querySelector("[data-clc-search-query]"); 1993 var searchQuery = normalize(searchSummary ? searchSummary.getAttribute("data-clc-search-query") : ""); 1994 var serverSearch = collection.getAttribute("data-clc-search-mode") === "server"; 1995 var visibleCount = cards.length; 1996 var collator = typeof Intl !== "undefined" && Intl.Collator ? 1997 new Intl.Collator(locale, { sensitivity: "base", numeric: true }) : null; 1998 1999 cards.forEach(function (card, index) { 2000 card.setAttribute("data-clc-original-index", String(index)); 2001 }); 2002 2003 function isMobileFilter() { 2004 return filterMedia ? filterMedia.matches : window.innerWidth <= 767; 2005 } 2006 2007 function announceCollection(message) { 2008 setText(collectionStatus, message || ""); 2009 } 2010 2011 function setFilterOpen(open, returnFocus) { 2012 var expanded = Boolean(open) && isMobileFilter(); 2013 2014 if (!filterPanel || !filterToggle) { 2015 return; 2016 } 2017 2018 filterToggle.setAttribute("aria-expanded", expanded ? "true" : "false"); 2019 filterPanel.classList.toggle("is-open", expanded); 2020 filterPanel.hidden = isMobileFilter() && !expanded; 2021 2022 if (expanded) { 2023 (filterClose || filterPanel).focus(); 2024 } else if (returnFocus) { 2025 filterToggle.focus(); 2026 } 2027 } 2028 2029 function synchronizeFilterViewport() { 2030 if (!filterPanel || !filterToggle) { 2031 return; 2032 } 2033 2034 if (isMobileFilter()) { 2035 setFilterOpen(filterToggle.getAttribute("aria-expanded") === "true", false); 2036 } else { 2037 filterToggle.setAttribute("aria-expanded", "false"); 2038 filterPanel.classList.remove("is-open"); 2039 filterPanel.hidden = false;
2040 } 2041 } 2042 2043 function activeGroups() { 2044 var groups = Object.create(null); 2045 2046 controls.forEach(function (control) { 2047 var field = controlFilterName(control); 2048 2049 if (!field || !control.checked) { 2050 return; 2051 } 2052 2053 if (!groups[field]) { 2054 groups[field] = []; 2055 } 2056 2057 groups[field].push(control); 2058 }); 2059 2060 return groups; 2061 } 2062 2063 function applyFilters(shouldAnnounce) { 2064 var groups = activeGroups(); 2065 var groupNames = Object.keys(groups); 2066 var visible = 0; 2067 var activeCount = controls.filter(function (control) { return control.checked; }).length; 2068 2069 cards.forEach(function (card) { 2070 var matchesSearch = serverSearch || !searchQuery || normalize([ 2071 cardFieldValue(card, "title"), 2072 cardFieldValue(card, "type"), 2073 card.getAttribute("data-handle") || "" 2074 ].join(" ")).indexOf(searchQuery) !== -1; 2075 var matches = matchesSearch && groupNames.every(function (field) { 2076 return groups[field].some(function (control) { 2077 return cardMatchesControl(card, field, control); 2078 }); 2079 }); 2080 2081 card.hidden = !matches; 2082 card.setAttribute("aria-hidden", matches ? "false" : "true"); 2083 2084 if (matches) { 2085 visible += 1; 2086 } 2087 }); 2088 2089 if (resultCount) { 2090 setText(resultCount, renderTemplate(labels.resultCount || "{count}ä»¶ã®åå", { count: visible })); 2091 } 2092 2093 visibleCount = visible; 2094 setText(filterCount, String(activeCount)); 2095 setHidden(filterCount, activeCount === 0); 2096 setHidden(filterClear, activeCount === 0); 2097 2098 setHidden(emptyState, visible !== 0); 2099 2100 if (shouldAnnounce) { 2101 announceCollection(renderTemplate(labels.filtered || "çµãè¾¼ã¿çµæã¯{count}ååã§ãã", { count: visible })); 2102 } 2103 } 2104 2105 function compareText(left, right, field) { 2106 var first = cardFieldValue(left, field); 2107 var second = cardFieldValue(right, field); 2108 2109 if (collator) { 2110 return collator.compare(first, second); 2111 } 2112 2113 return first < second ? -1 : (first > second ? 1 : 0); 2114 } 2115 2116 function applySort(shouldAnnounce) { 2117 var value = normalize(sortControl ? sortControl.value : "featured"); 2118 var ordered = cards.slice(); 2119 var selectedOption = sortControl && sortControl.options ? sortControl.options[sortControl.selectedIndex] : null; 2120 2121 ordered.sort(function (left, right) { 2122 if (["price-ascending", "price_asc", "price-asc", "price-low-high"].indexOf(value) !== -1) { 2123 return numericCardValue(left, "price") - numericCardValue(right, "price"); 2124 } 2125 2126 if (["price-descending", "price_desc", "price-desc", "price-high-low"].indexOf(value) !== -1) { 2127 return numericCardValue(right, "price") - numericCardValue(left, "price"); 2128 } 2129 2130 if (["title-ascending", "title_asc", "title-asc", "a-z"].indexOf(value) !== -1) { 2131 return compareText(left, right, "title"); 2132 } 2133 2134 if (["title-descending", "title_desc", "title-desc", "z-a"].indexOf(value) !== -1) { 2135 return compareText(right, left, "title"); 2136 } 2137 2138 return Number(left.getAttribute("data-clc-original-index")) - 2139 Number(right.getAttribute("data-clc-original-index")); 2140 }); 2141 2142 if (grid) { 2143 ordered.forEach(function (card) { 2144 grid.appendChild(card); 2145 }); 2146 } 2147 2148 if (shouldAnnounce) { 2149 announceCollection(renderTemplate(labels.sorted || "{sort}ã«ä¸¦ã¹æ¿ãã¾ããã{count}ååããã¾ãã", { 2150 sort: selectedOption ? selectedOption.textContent.trim() : "ããããé ", 2151 count: visibleCount 2152 })); 2153 } 2154 } 2155 2156 function listName() { 2157 var listTitle = collection.querySelector(".clc-collection-title"); 2158 return listTitle ? listTitle.textContent.trim() : "ååä¸è¦§"; 2159 } 2160 2161 function cardItem(card) { 2162 var link = card.querySelector("[data-clc-product-link], .clc-card-link"); 2163 var itemId = card.getAttribute("data-product-id") || card.getAttribute("data-handle") || ""; 2164 var itemName = cardFieldValue(card, "title"); 2165 var displayedCards = grid ? toArray(grid.querySelectorAll(".clc-product-card:not([hidden])")) : cards; 2166 var displayedIndex = displayedCards.indexOf(card); 2167 2168 if (!itemId && link) { 2169 try { 2170 var pathParts = new URL(link.href, window.location.href).pathname.split("/").filter(
2170Boolean); 2171 itemId = decodeURIComponent(pathParts[pathParts.length - 1] || ""); 2172 } catch (error) { 2173 itemId = link.getAttribute("href") || itemName; 2174 } 2175 } 2176 2177 return { 2178 item_id: String(itemId || itemName), 2179 item_name: itemName, 2180 item_category: cardFieldValue(card, "type"), 2181 item_list_name: listName(), 2182 price: numericCardValue(card, "price"), 2183 index: displayedIndex >= 0 ? displayedIndex + 1 : 1 2184 }; 2185 } 2186 2187 function pushCardSelection(card) { 2188 2189 sendAnalyticsEvent("select_item", { 2190 item_list_name: listName(), 2191 items: [cardItem(card)] 2192 }); 2193 } 2194 2195 function pushViewItemListOnce() { 2196 var key; 2197 var viewed; 2198 2199 if (!cards.length) { 2200 return; 2201 } 2202 2203 key = String(window.location.pathname || "") + "::" + listName(); 2204 viewed = window.__CLCCommerceViewedLists; 2205 2206 if (!viewed || typeof viewed.has !== "function" || typeof viewed.add !== "function") { 2207 viewed = new Set(); 2208 window.__CLCCommerceViewedLists = viewed; 2209 } 2210 2211 if (viewed.has(key)) { 2212 return; 2213 } 2214 2215 viewed.add(key); 2216 sendAnalyticsEvent("view_item_list", { 2217 item_list_name: listName(), 2218 items: cards.map(cardItem) 2219 }); 2220 } 2221 2222 controls.forEach(function (control) { 2223 control.addEventListener("change", function () { 2224 applyFilters(true); 2225 }); 2226 }); 2227 2228 if (sortControl) { 2229 sortControl.addEventListener("change", function () { 2230 applySort(true); 2231 }); 2232 } 2233 2234 if (filterToggle && filterPanel) { 2235 filterToggle.addEventListener("click", function () { 2236 setFilterOpen(filterToggle.getAttribute("aria-expanded") !== "true", false); 2237 }); 2238 2239 if (filterClose) { 2240 filterClose.addEventListener("click", function () { 2241 setFilterOpen(false, true); 2242 }); 2243 } 2244 2245 collection.addEventListener("keydown", function (event) { 2246 if (event.key === "Escape" && filterToggle.getAttribute("aria-expanded") === "true") { 2247 setFilterOpen(false, true); 2248 } 2249 }); 2250 2251 if (filterMedia && typeof filterMedia.addEventListener === "function") { 2252 filterMedia.addEventListener("change", synchronizeFilterViewport); 2253 } else if (filterMedia && typeof filterMedia.addListener === "function") { 2254 filterMedia.addListener(synchronizeFilterViewport); 2255 } else { 2256 window.addEventListener("resize", synchronizeFilterViewport, { passive: true }); 2257 } 2258 } 2259 2260 if (filterClear) { 2261 filterClear.addEventListener("click", function () { 2262 controls.forEach(function (control) { 2263 control.checked = false; 2264 }); 2265 applyFilters(true); 2266 }); 2267 } 2268 2269 cards.forEach(function (card) { 2270 card.addEventListener("click", function (event) { 2271 if (event.target.closest("[data-clc-product-link], .clc-card-link")) { 2272 pushCardSelection(card); 2273 } 2274 }); 2275 }); 2276 2277 synchronizeFilterViewport(); 2278 applySort(false); 2279 applyFilters(false); 2280 pushViewItemListOnce(); 2281 } 2282 2283 function initHomeHero(hero) { 2284 if (hero.getAttribute("data-clc-initialized") === "true") { 2285 return; 2286 } 2287 2288 var viewport = hero.querySelector("[data-clc-home-hero-viewport]"); 2289 var slides = toArray(hero.querySelectorAll("[data-clc-home-slide]")); 2290 var dots = toArray(hero.querySelectorAll("[data-clc-home-hero-dot]")); 2291 var previous = hero.querySelector("[data-clc-home-hero-previous]"); 2292 var next = hero.querySelector("[data-clc-home-hero-next]"); 2293 var status = hero.querySelector("[data-clc-home-hero-status]"); 2294 var reducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 2295 var currentIndex = 0; 2296 var scrollTimer = 0; 2297 2298 if (!viewport || !slides.length) { 2299 return; 2300 } 2301 2302 hero.setAttribute("data-clc-initialized", "true"); 2303 2304 function nearestIndex() { 2305 var left = viewport.scrollLeft; 2306 var bestIndex = 0; 2307 var bestDistance = Infinity; 2308 2309 slides.forEach(function (slide, index) { 2310 var distance = Math.abs(slide.offsetLeft - left); 2311 if (distance < bestDistance) { 2312 bestDistance = distance; 2313 bestIndex = index; 2314 } 2315 }); 2316 2317 return bestIndex; 2318 } 2319 2320 function updateState(index, announce) { 2321 currentIndex = Math.max(0, Math.min(slides.length - 1, index));
2322 slides.forEach(function (slide, slideIndex) { 2323 var active = slideIndex === currentIndex; 2324 2325 if (!active && slide.contains(document.activeElement)) { 2326 try { 2327 viewport.focus({ preventScroll: true }); 2328 } catch (error) { 2329 viewport.focus(); 2330 } 2331 } 2332 slide.setAttribute("aria-hidden", active ? "false" : "true"); 2333 if (active) { 2334 slide.removeAttribute("inert"); 2335 } else { 2336 slide.setAttribute("inert", ""); 2337 } 2338 }); 2339 dots.forEach(function (dot, dotIndex) { 2340 dot.setAttribute("aria-pressed", dotIndex === currentIndex ? "true" : "false"); 2341 }); 2342 if (previous) { 2343 previous.disabled = currentIndex === 0; 2344 } 2345 if (next) { 2346 next.disabled = currentIndex === slides.length - 1; 2347 } 2348 if (announce) { 2349 setText(status, String(currentIndex + 1) + " / " + String(slides.length)); 2350 } 2351 } 2352 2353 function showSlide(index, announce) { 2354 var targetIndex = Math.max(0, Math.min(slides.length - 1, index)); 2355 var target = slides[targetIndex]; 2356 2357 updateState(targetIndex, announce); 2358 if (typeof viewport.scrollTo === "function") { 2359 viewport.scrollTo({ 2360 left: target.offsetLeft, 2361 behavior: reducedMotion ? "auto" : "smooth" 2362 }); 2363 } else { 2364 viewport.scrollLeft = target.offsetLeft; 2365 } 2366 } 2367 2368 dots.forEach(function (dot) { 2369 dot.addEventListener("click", function () { 2370 showSlide(Number(dot.getAttribute("data-clc-home-hero-dot")) || 0, true); 2371 }); 2372 }); 2373 2374 if (previous) { 2375 previous.addEventListener("click", function () { 2376 showSlide(currentIndex - 1, true); 2377 }); 2378 } 2379 2380 if (next) { 2381 next.addEventListener("click", function () { 2382 showSlide(currentIndex + 1, true); 2383 }); 2384 } 2385 2386 viewport.addEventListener("keydown", function (event) { 2387 if (event.key === "ArrowLeft" || event.key === "ArrowRight") { 2388 event.preventDefault(); 2389 showSlide(currentIndex + (event.key === "ArrowRight" ? 1 : -1), true); 2390 } 2391 }); 2392 2393 viewport.addEventListener("scroll", function () { 2394 window.clearTimeout(scrollTimer); 2395 scrollTimer = window.setTimeout(function () { 2396 var nextIndex = nearestIndex(); 2397 updateState(nextIndex, nextIndex !== currentIndex); 2398 }, 120); 2399 }, { passive: true }); 2400 2401 window.addEventListener("resize", function () { 2402 showSlide(currentIndex, false); 2403 }, { passive: true }); 2404 2405 updateState(0, false); 2406 } 2407 2408 function initHomeTestimonials(testimonials) { 2409 if (testimonials.getAttribute("data-clc-initialized") === "true") { 2410 return; 2411 } 2412 2413 var slides = toArray(testimonials.querySelectorAll("[data-clc-home-testimonial-slide]")); 2414 var controls = toArray(testimonials.querySelectorAll("[data-clc-home-testimonial-control]")); 2415 var stage = testimonials.querySelector(".clc-home-testimonial-stage"); 2416 var status = testimonials.querySelector("[data-clc-home-testimonial-status]"); 2417 var reducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 2418 var currentIndex = 0; 2419 var autoplayTimer = 0; 2420 var touchStartX = null; 2421 2422 if (!slides.length || !controls.length) { 2423 return; 2424 } 2425 2426 testimonials.setAttribute("data-clc-initialized", "true"); 2427 2428 function normalizedIndex(index) { 2429 return (index + slides.length) % slides.length; 2430 } 2431 2432 function updateSlide(index, announce) { 2433 currentIndex = normalizedIndex(index); 2434 2435 slides.forEach(function (slide, slideIndex) { 2436 var selected = slideIndex === currentIndex; 2437 setHidden(slide, !selected); 2438 slide.classList.toggle("is-active", selected); 2439 }); 2440 2441 controls.forEach(function (control) { 2442 var selected = Number(control.getAttribute("data-clc-home-testimonial-control")) === currentIndex; 2443 control.setAttribute("aria-current", selected ? "true" : "false"); 2444 }); 2445 2446 if (announce) { 2447 setText(status, String(currentIndex + 1) + " / " + String(slides.length)); 2448 } 2449 } 2450 2451 function stopAutoplay() { 2452 if (autoplayTimer) { 2453 window.clearInterval(autoplayTimer); 2454 autoplayTimer = 0; 2455 } 2456 } 2457 2458 function startAutoplay() { 2459 stopAutoplay(); 2460 if (reducedMotion || document.hidden) { 2461 return; 2462 } 2463 autoplayTimer = window.setInterval(function () { 2464 updateSlide(currentIndex + 1, false); 2465 }, 5000); 2466 } 2467 2468 function activateControl(control, announce) { 2469 updateSlide(Number(control.getAttribute("data-clc-home-testimonial-control")) || 0, announce); 2470 startAutoplay(); 2471 } 2472
2473 controls.forEach(function (control) { 2474 control.addEventListener("click", function () { 2475 activateControl(control, true); 2476 }); 2477 2478 control.addEventListener("keydown", function (event) { 2479 var group; 2480 var groupControls; 2481 var groupIndex; 2482 var nextIndex; 2483 2484 if (["ArrowLeft", "ArrowRight", "Home", "End"].indexOf(event.key) === -1) { 2485 return; 2486 } 2487 2488 group = control.parentElement; 2489 groupControls = toArray(group.querySelectorAll("[data-clc-home-testimonial-control]")); 2490 groupIndex = groupControls.indexOf(control); 2491 nextIndex = groupIndex; 2492 2493 if (event.key === "Home") { 2494 nextIndex = 0; 2495 } else if (event.key === "End") { 2496 nextIndex = groupControls.length - 1; 2497 } else { 2498 nextIndex = (groupIndex + (event.key === "ArrowRight" ? 1 : -1) + groupControls.length) % groupControls.length; 2499 } 2500 2501 event.preventDefault(); 2502 groupControls[nextIndex].focus(); 2503 activateControl(groupControls[nextIndex], true); 2504 }); 2505 }); 2506 2507 testimonials.addEventListener("pointerenter", stopAutoplay); 2508 testimonials.addEventListener("pointerleave", startAutoplay); 2509 testimonials.addEventListener("focusin", stopAutoplay); 2510 testimonials.addEventListener("focusout", function () { 2511 window.setTimeout(function () { 2512 if (!testimonials.contains(document.activeElement)) { 2513 startAutoplay(); 2514 } 2515 }, 0); 2516 }); 2517 2518 if (stage) { 2519 stage.addEventListener("touchstart", function (event) { 2520 touchStartX = event.touches && event.touches[0] ? event.touches[0].clientX : null; 2521 stopAutoplay(); 2522 }, { passive: true }); 2523 2524 stage.addEventListener("touchend", function (event) { 2525 var touchEndX = event.changedTouches && event.changedTouches[0] ? event.changedTouches[0].clientX : null; 2526 var distance = touchStartX !== null && touchEndX !== null ? touchEndX - touchStartX : 0; 2527 2528 if (Math.abs(distance) >= 40) { 2529 updateSlide(currentIndex + (distance < 0 ? 1 : -1), true); 2530 } 2531 touchStartX = null; 2532 startAutoplay(); 2533 }, { passive: true }); 2534 } 2535 2536 document.addEventListener("visibilitychange", function () { 2537 if (document.hidden) { 2538 stopAutoplay(); 2539 } else { 2540 startAutoplay(); 2541 } 2542 }); 2543 2544 updateSlide(0, false); 2545 startAutoplay(); 2546 } 2547 2548 function initWeeklyCountdown(countdown) { 2549 if (countdown.getAttribute("data-clc-initialized") === "true") { 2550 return; 2551 } 2552 2553 var days = countdown.querySelector("[data-clc-countdown-days]"); 2554 var hours = countdown.querySelector("[data-clc-countdown-hours]"); 2555 var minutes = countdown.querySelector("[data-clc-countdown-minutes]"); 2556 var seconds = countdown.querySelector("[data-clc-countdown-seconds]"); 2557 var timer = 0; 2558 var tokyoOffset = 9 * 60 * 60 * 1000; 2559 2560 if (!days || !hours || !minutes || !seconds) { 2561 return; 2562 } 2563 2564 countdown.setAttribute("data-clc-initialized", "true"); 2565 2566 function pad(value) { 2567 return String(Math.max(0, value)).padStart(2, "0"); 2568 } 2569 2570 function nextDeadline(now) { 2571 var tokyoNow = new Date(now.getTime() + tokyoOffset); 2572 var weekday = tokyoNow.getUTCDay(); 2573 var daysAhead = (1 - weekday + 7) % 7; 2574 var tokyoMidnight = Date.UTC(tokyoNow.getUTCFullYear(), tokyoNow.getUTCMonth(), tokyoNow.getUTCDate()); 2575 var deadline = tokyoMidnight + (daysAhead * 86400000) - tokyoOffset; 2576 2577 if (deadline <= now.getTime()) { 2578 deadline += 7 * 86400000; 2579 } 2580 return deadline; 2581 } 2582 2583 function updateCountdown() { 2584 var now = new Date(); 2585 var remaining = Math.max(0, Math.floor((nextDeadline(now) - now.getTime()) / 1000)); 2586 var remainingDays = Math.floor(remaining / 86400); 2587 var remainingHours = Math.floor((remaining % 86400) / 3600); 2588 var remainingMinutes = Math.floor((remaining % 3600) / 60); 2589 var remainingSeconds = remaining % 60; 2590 2591 setText(days, pad(remainingDays)); 2592 setText(hours, pad(remainingHours)); 2593 setText(minutes, pad(remainingMinutes)); 2594 setText(seconds, pad(remainingSeconds)); 2595 countdown.setAttribute( 2596 "aria-label", 2597 "æ¬¡ã®æææ¥0æã¾ã§" + String(remainingDays) + "æ¥" + String(remainingHours) + "æé" + String(remainingMinutes) + "å" 2598 ); 2599 } 2600 2601 function startCountdown() { 2602 if (timer) { 2603 window.clearInterval(timer); 2604 } 2605 updateCountdown(); 2606 timer = window.setInterval(updateCountdown, 1000); 2607 } 2608 2609 startCountdown(); 2610 2611 document.addEventListener("visibilitychange", function () { 2612 if (!document.hidden) { 2613 updateCountdown(); 2614 } 2615 }); 2616 2617 window.addEventListener("pagehide", function () { 2618 window.clearInterval(timer); 2619 timer = 0; 2620 }); 2621 window.addEventListener("pageshow", function (event) { 2622 if (event.persisted) { 2623 startCountdown(); 2624 } 2625 }); 2626 } 2627 2628 function initHomeProductList(list) { 2629 if (list.getAttribute("data-clc-initialized") === "true") { 2630 return; 2631 } 2632 2633 var cards = toArray(list.querySelectorAll(".clc-product-card")); 2634 var listName = list.getAttribute("data-list-name") || "注ç®ã®åå"; 2635 var viewed = window.__CLCCommerceViewedLists; 2636 var viewKey = String(window.location.pathname || "") + "::" + listName; 2637 2638 list.setAttribute("data-clc-initialized", "true"); 2639 2640 function cardItem(card, index) { 2641 var itemId = card.getAttribute("data-product-id") || card.getAttribute("data-handle") || ""; 2642 return {
2643 item_id: String(itemId || cardFieldValue(card, "title")), 2644 item_name: cardFieldValue(card, "title"), 2645 item_category: cardFieldValue(card, "type"), 2646 item_list_name: listName, 2647 price: numericCardValue(card, "price"), 2648 index: index + 1 2649 }; 2650 } 2651 2652 cards.forEach(function (card, index) { 2653 card.addEventListener("click", function (event) { 2654 if (event.target.closest("[data-clc-product-link], .clc-card-link")) { 2655 sendAnalyticsEvent("select_item", { 2656 item_list_name: listName, 2657 items: [cardItem(card, index)] 2658 }); 2659 } 2660 }); 2661 }); 2662 2663 if (!cards.length) { 2664 return; 2665 } 2666 2667 if (!viewed || typeof viewed.has !== "function" || typeof viewed.add !== "function") { 2668 viewed = new Set(); 2669 window.__CLCCommerceViewedLists = viewed; 2670 } 2671 2672 if (!viewed.has(viewKey)) { 2673 viewed.add(viewKey); 2674 sendAnalyticsEvent("view_item_list", { 2675 item_list_name: listName, 2676 items: cards.map(cardItem) 2677 }); 2678 } 2679 } 2680 2681 function focusableElements(panel) { 2682 return toArray(panel.querySelectorAll( 2683 "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])" 2684 )).filter(function (element) { 2685 return !element.hidden && 2686 !element.closest("[hidden]") && 2687 element.getAttribute("aria-hidden") !== "true" && 2688 !element.classList.contains("clc-panel-backdrop"); 2689 }); 2690 } 2691 2692 function initUtilityPanels(storefront) { 2693 var panels = toArray(storefront.querySelectorAll("[data-clc-panel]")); 2694 var triggers = toArray(storefront.querySelectorAll("[data-clc-panel-open]")); 2695 var activePanel = null; 2696 var activeTrigger = null; 2697 var inertedBackground = []; 2698 2699 if (!panels.length || storefront.getAttribute("data-clc-panels-ready") === "true") { 2700 return; 2701 } 2702 storefront.setAttribute("data-clc-panels-ready", "true"); 2703 2704 function setTriggerState(panelId, expanded) { 2705 triggers.forEach(function (trigger) { 2706 if (trigger.getAttribute("data-clc-panel-open") !== panelId) { 2707 return; 2708 } 2709 trigger.setAttribute("aria-controls", panelId); 2710 trigger.setAttribute("aria-expanded", expanded ? "true" : "false"); 2711 }); 2712 } 2713
2714 function restoreBackground() { 2715 inertedBackground.forEach(function (state) { 2716 if (state.hadInert) { 2717 state.element.setAttribute("inert", ""); 2718 } else { 2719 state.element.removeAttribute("inert"); 2720 } 2721 if (state.ariaHidden == null) { 2722 state.element.removeAttribute("aria-hidden"); 2723 } else { 2724 state.element.setAttribute("aria-hidden", state.ariaHidden); 2725 } 2726 }); 2727 inertedBackground = []; 2728 } 2729 2730 function isolatePanel(panel) { 2731 restoreBackground(); 2732 toArray(storefront.children).forEach(function (element) { 2733 if (element === panel) { 2734 return; 2735 } 2736 inertedBackground.push({ 2737 element: element, 2738 hadInert: element.hasAttribute("inert"), 2739 ariaHidden: element.getAttribute("aria-hidden") 2740 }); 2741 element.setAttribute("inert", ""); 2742 element.setAttribute("aria-hidden", "true"); 2743 }); 2744 } 2745 2746 function closePanel(returnFocus) { 2747 var trigger = activeTrigger; 2748 var panelId; 2749 2750 if (!activePanel) { 2751 return; 2752 } 2753 panelId = activePanel.id; 2754 activePanel.classList.remove("is-open"); 2755 activePanel.hidden = true; 2756 activePanel = null; 2757 activeTrigger = null; 2758 document.body.classList.remove("clc-overlay-open");
2759 restoreBackground(); 2760 setTriggerState(panelId, false); 2761 if (returnFocus && trigger && document.contains(trigger)) { 2762 trigger.focus(); 2763 } 2764 } 2765 2766 function showPanel(panelId, trigger) { 2767 var panel = storefront.querySelector("#" + String(panelId || "")); 2768 var focusable; 2769 2770 if (!panel) { 2771 return; 2772 } 2773 if (activePanel && activePanel !== panel) { 2774 closePanel(false); 2775 } 2776 activePanel = panel; 2777 activeTrigger = trigger || null; 2778 panel.hidden = false; 2779 setTriggerState(panel.id, true); 2780 isolatePanel(panel); 2781 document.body.classList.add("clc-overlay-open"); 2782 document.dispatchEvent(new CustomEvent("clc:panel-opened", { 2783 detail: { panelId: panel.id, panel: panel, trigger: activeTrigger } 2784 })); 2785 window.requestAnimationFrame(function () { 2786 panel.classList.add("is-open"); 2787 focusable = focusableElements(panel); 2788 if (focusable.length) { 2789 focusable[0].focus(); 2790 } 2791 }); 2792 } 2793 2794 triggers.forEach(function (trigger) { 2795 var panelId = trigger.getAttribute("data-clc-panel-open"); 2796 trigger.setAttribute("aria-controls", panelId); 2797 trigger.setAttribute("aria-expanded", "false"); 2798 trigger.addEventListener("click", function () { 2799 showPanel(panelId, trigger); 2800 }); 2801 }); 2802 2803 panels.forEach(function (panel) { 2804 toArray(panel.querySelectorAll("[data-clc-panel-close]")).forEach(function (button) { 2805 button.addEventListener("click", function () { 2806 closePanel(true); 2807 }); 2808 }); 2809 2810 panel.addEventListener("keydown", function (event) { 2811 var focusable; 2812 var first; 2813 var last; 2814 2815 if (event.key === "Escape") { 2816 event.preventDefault(); 2817 closePanel(true); 2818 return; 2819 } 2820 if (event.key !== "Tab") { 2821 return; 2822 } 2823 focusable = focusableElements(panel); 2824 if (!focusable.length) { 2825 return; 2826 } 2827 first = focusable[0]; 2828 last = focusable[focusable.length - 1]; 2829 if (event.shiftKey && document.activeElement === first) { 2830 event.preventDefault(); 2831 last.focus(); 2832 } else if (!event.shiftKey && document.activeElement === last) { 2833 event.preventDefault(); 2834 first.focus(); 2835 } 2836 }); 2837 }); 2838 2839 document.addEventListener("keydown", function (event) { 2840 if (event.key === "Escape" && activePanel) { 2841 event.preventDefault(); 2842 closePanel(true); 2843 } 2844 }); 2845 2846 document.addEventListener("clc:open-panel", function (event) { 2847 var detail = event.detail || {}; 2848 showPanel(detail.panelId, detail.trigger); 2849 }); 2850 } 2851 2852 function initCartReminder(reminder) { 2853 var storefront = reminder.closest(".clc-storefront"); 2854 var image = reminder.querySelector("[data-clc-cart-reminder-image]"); 2855 var media = reminder.querySelector(".clc-cart-reminder-media"); 2856 var name = reminder.querySelector("[data-clc-cart-reminder-name]"); 2857 var meta = reminder.querySelector("[data-clc-cart-reminder-meta]"); 2858 var price = reminder.querySelector("[data-clc-cart-reminder-price]"); 2859 var quantity = reminder.querySelector("[data-clc-cart-reminder-quantity]"); 2860 var subtotal = reminder.querySelector("[data-clc-cart-reminder-subtotal]"); 2861 var extra = reminder.querySelector("[data-clc-cart-reminder-extra]"); 2862 var openCart = reminder.querySelector("[data-clc-cart-reminder-open-cart]"); 2863 var initialItems = readCart(); 2864 var seenKey = "clc_cart_reminder_seen_v1"; 2865 var seenInMemory = false; 2866 var timer = null; 2867 2868 if ( 2869 !storefront || 2870 !name || 2871 !openCart || 2872 !initialItems.length || 2873 reminder.getAttribute("data-clc-cart-reminder-ready") === "true" 2874 ) { 2875 return; 2876 } 2877 reminder.setAttribute("data-clc-cart-reminder-ready", "true"); 2878 2879 function hasBeenSeen() { 2880 if (seenInMemory) { 2881 return true; 2882 } 2883 try { 2884 return window.sessionStorage.getItem(seenKey) === "1"; 2885 } catch (error) { 2886 return false;
2887 } 2888 } 2889 2890 function markSeen() { 2891 seenInMemory = true; 2892 try { 2893 window.sessionStorage.setItem(seenKey, "1"); 2894 } catch (error) { 2895 // A session-only in-memory flag still prevents repeated interruption. 2896 } 2897 } 2898 2899 function render() { 2900 var items = readCart(); 2901 var item = items[0]; 2902 var details = []; 2903 var total = 0; 2904 var currency = "JPY"; 2905 2906 if (!item) { 2907 return false; 2908 } 2909 2910 setText(name, item.title); 2911 if (item.variantTitle && normalize(item.variantTitle) !== "default title") { 2912 details.push(item.variantTitle); 2913 } 2914 if (item.sellingPlanName) { 2915 details.push(item.sellingPlanName); 2916 } 2917 setText(meta, details.join(" ï¼ ")); 2918 setHidden(meta, details.length === 0); 2919 setText(price, item.price ? formatMoney(item.price) : "â"); 2920 setText(quantity, item.quantity); 2921 2922 if (image && media) { 2923 /* imageUrl 㯠localStorage 復å å¤ãå«ããã Shopify CDN ã® https URL ã®ã¿è¨±å¯ 2924 ï¼responsiveShopifyImageUrl ããã¹ãæ¤è¨¼ãå ¼ããã2026-08-30ç·ç¹æ¤ L-8ï¼ */ 2925 var safeImageSrc = item.imageUrl ? responsiveShopifyImageUrl(item.imageUrl, 192) : ""; 2926 if (safeImageSrc) { 2927 image.src = safeImageSrc; 2928 image.alt = item.imageAlt || item.title; 2929 setHidden(image, false); 2930 setHidden(media, false); 2931 } else { 2932 image.removeAttribute("src"); 2933 image.alt = ""; 2934 setHidden(image, true); 2935 setHidden(media, true); 2936 } 2937 } 2938 2939 items.forEach(function (cartItem) { 2940 if (cartItem.price) { 2941 total += cartItem.price.amount * cartItem.quantity; 2942 currency = cartItem.price.currencyCode || currency; 2943 } 2944 }); 2945 setText(subtotal, formatMoney({ amount: total, currencyCode: currency })); 2946 setText(extra, items.length > 1 ? "ã»ã" + String(items.length - 1) + "ååãã«ã¼ãã«å ¥ã£ã¦ãã¾ãã" : ""); 2947 setHidden(extra, items.length <= 1); 2948 return true; 2949 } 2950 2951 function showReminder() { 2952 var visiblePanel; 2953 2954 timer = null; 2955 if (hasBeenSeen() || !render()) { 2956 return; 2957 } 2958 if (document.visibilityState === "hidden") { 2959 document.addEventListener("visibilitychange", function onVisibilityChange() { 2960 if (document.visibilityState !== "visible") { 2961 return; 2962 } 2963 document.removeEventListener("visibilitychange", onVisibilityChange); 2964 timer = window.setTimeout(showReminder, 1200); 2965 }); 2966 return; 2967 } 2968 2969 visiblePanel = storefront.querySelector("[data-clc-panel]:not([hidden])"); 2970 if (visiblePanel && visiblePanel !== reminder) { 2971 return; 2972 } 2973 markSeen(); 2974 openUtilityPanel(reminder.id, null); 2975 } 2976 2977 openCart.addEventListener("click", function () { 2978 var cartTrigger = storefront.querySelector('[data-clc-panel-open="clc-cart-panel"]'); 2979 var close = reminder.querySelector("[data-clc-panel-close]"); 2980 2981 markSeen(); 2982 if (close) { 2983 close.click(); 2984 } 2985 window.setTimeout(function () { 2986 openUtilityPanel("clc-cart-panel", cartTrigger || null); 2987 }, 0); 2988 }); 2989 2990 document.addEventListener("clc:panel-opened", function (event) { 2991 var detail = event.detail || {}; 2992 if (detail.panelId === "clc-cart-panel") { 2993 markSeen(); 2994 } 2995 }); 2996 2997 document.addEventListener("clc:cart-changed", function () { 2998 if (readCart().length) { 2999 if (!reminder.hidden) { 3000 render(); 3001 } 3002 return; 3003 } 3004 if (!reminder.hidden) { 3005 var close = reminder.querySelector("[data-clc-panel-close]"); 3006 if (close) { 3007 close.click(); 3008 } 3009 } 3010 if (timer) { 3011 window.clearTimeout(timer); 3012 timer = null; 3013 } 3014 }); 3015 3016 timer = window.setTimeout(showReminder, 12000); 3017 } 3018 3019 function initCart(cart) { 3020 var list = cart.querySelector("[data-clc-cart-items]"); 3021 var empty = cart.querySelector("[data-clc-cart-empty]"); 3022 var summary = cart.querySelector("[data-clc-cart-summary]"); 3023 var subtotal = cart.querySelector("[data-clc-cart-subtotal]"); 3024 var checkout = cart.querySelector("[data-clc-cart-checkout]"); 3025 var errorBox = cart.querySelector("[data-clc-cart-error]"); 3026 var statusBox = cart.querySelector("[data-clc-cart-status]"); 3027 var cartHeading = cart.querySelector("#clc-cart-title"); 3028 var discountNotice = cart.querySelector("[data-clc-cart-discount]"); 3029 var discountCodeText = cart.querySelector("[data-clc-cart-discount-code]"); 3030 var busy = false;
3031 var preparedCheckout = null; 3032 var prepareController = null; 3033 var prepareSequence = 0; 3034 var lastViewedFingerprint = ""; 3035 var lastRenderedItemsFingerprint = ""; 3036 var pendingCartFocus = null; 3037 3038 if (!list || !checkout || cart.getAttribute("data-clc-cart-ready") === "true") { 3039 return; 3040 } 3041 cart.setAttribute("data-clc-cart-ready", "true"); 3042 3043 function updateCounts(items) { 3044 var count = items.reduce(function (total, item) { return total + item.quantity; }, 0); 3045 toArray(document.querySelectorAll("[data-clc-cart-count]")).forEach(function (badge) { 3046 setText(badge, String(count)); 3047 setHidden(badge, count === 0); 3048 }); 3049 } 3050 3051 function makeText(tag, className, value) { 3052 var element = document.createElement(tag); 3053 element.className = className; 3054 setText(element, value); 3055 return element; 3056 } 3057 3058 function cartFingerprint(items, discountCode) { 3059 return JSON.stringify({ 3060 items: items.map(function (item) { 3061 return [item.entryId || "", item.key, item.productHandle, item.variantId, item.sellingPlanId || "", item.quantity]; 3062 }), 3063 discountCode: normalizeDiscountCode(discountCode == null ? activeDiscountCode() : discountCode) 3064 }); 3065 } 3066 3067 function checkoutLines(items) { 3068 return items.map(function (item) { 3069 return { 3070 product_handle: item.productHandle, 3071 variant_id: item.variantId, 3072 selling_plan_id: item.sellingPlanId || "", 3073 quantity: item.quantity 3074 }; 3075 }); 3076 } 3077 3078 function pushViewCartOnce(items) { 3079 var fingerprint; 3080 var value; 3081 var currency; 3082 3083 if (!items.length) { 3084 return; 3085 } 3086 fingerprint = cartFingerprint(items); 3087 if (fingerprint === lastViewedFingerprint) { 3088 return; 3089 } 3090 3091 lastViewedFingerprint = fingerprint; 3092 value = items.reduce(function (total, item) { 3093 return total + (item.price ? item.price.amount * item.quantity : 0); 3094 }, 0); 3095 currency = items.find(function (item) { return item.price && item.price.currencyCode; }); 3096 sendAnalyticsEvent("view_cart", { 3097 currency: currency && currency.price.currencyCode || settings.currency || "JPY", 3098 value: Number(value.toFixed(2)), 3099 items: items.map(function (item, index) { 3100 return cartAnalyticsItem(item, index); 3101 }) 3102 }); 3103 } 3104
3105 function invalidatePreparedCheckout() { 3106 prepareSequence += 1; 3107 if (prepareController && typeof prepareController.abort === "function") { 3108 prepareController.abort(); 3109 } 3110 prepareController = null; 3111 preparedCheckout = null; 3112 busy = false; 3113 } 3114 3115 function itemsRenderFingerprint(items) { 3116 return JSON.stringify(items.map(function (item) { 3117 return [item.entryId, item.key, item.title, item.variantTitle, item.sellingPlanName, item.quantity, item.imageUrl, item.price]; 3118 })); 3119 } 3120 3121 function restoreCartFocus(items) { 3122 var intent = pendingCartFocus; 3123 var targetRow; 3124 var target; 3125 3126 pendingCartFocus = null; 3127 if (!intent) { 3128 return; 3129 } 3130 targetRow = toArray(list.querySelectorAll("[data-cart-entry-id]")).find(function (row) { 3131 return row.getAttribute("data-cart-entry-id") === intent.entryId; 3132 }); 3133 if (!targetRow && items.length) { 3134 targetRow = list.children[Math.min(intent.index, items.length - 1)] || null; 3135 } 3136 if (targetRow) { 3137 target = targetRow.querySelector('[data-cart-action="' + intent.action + '"]') || targetRow.querySelector("[data-cart-action]"); 3138 } 3139 if (!target && cartHeading) { 3140 target = cartHeading; 3141 } 3142 if (target && typeof target.focus === "function") { 3143 target.focus({ preventScroll: true }); 3144 } 3145 if (statusBox && intent.message) { 3146 setText(statusBox, ""); 3147 if (typeof window.requestAnimationFrame === "function") { 3148 window.requestAnimationFrame(function () { setText(statusBox, intent.message); }); 3149 } else { 3150 window.setTimeout(function () { setText(statusBox, intent.message); }, 0); 3151 } 3152 } 3153 } 3154 3155 function preparedLineForItem(item) { 3156 var lines = preparedCheckout && Array.isArray(preparedCheckout.currentLines) ? preparedCheckout.currentLines : []; 3157 return lines.find(function (line) { 3158 return line.variantId === item.variantId && line.sellingPlanId === (item.sellingPlanId || ""); 3159 }) || null; 3160 } 3161 3162 function analyticsItemWithCurrentPrice(item, index) { 3163 var analyticsItem = cartAnalyticsItem(item, index); 3164 var currentLine = preparedLineForItem(item); 3165 if (currentLine && currentLine.price) { 3166 analyticsItem.price = currentLine.price.amount; 3167 } 3168 return analyticsItem; 3169 } 3170 3171 function render() { 3172 var items = readCart(); 3173 var total = 0; 3174 var currency = "JPY"; 3175 var renderFingerprint = itemsRenderFingerprint(items); 3176 3177 if (renderFingerprint !== lastRenderedItemsFingerprint) { 3178 while (list.firstChild) { 3179 list.removeChild(list.firstChild); 3180 } 3181 items.forEach(function (item) { 3182 var row = document.createElement("li"); 3183 var media = document.createElement("div"); 3184 var content = document.createElement("div"); 3185 var details = []; 3186 var controls = document.createElement("div"); 3187 var decrease = document.createElement("button"); 3188 var quantity = makeText("span", "clc-cart-item-quantity", item.quantity); 3189 var increase = document.createElement("button"); 3190 var remove = document.createElement("button"); 3191 3192 row.className = "clc-cart-item"; 3193 row.setAttribute("data-cart-key", item.key); 3194 row.setAttribute("data-cart-entry-id", item.entryId || ""); 3195 media.className = "clc-cart-item-media"; 3196 /* localStorage 復å å¤ãå«ãããããã¹ãæ¤è¨¼ãéããªãURLã¯æç»ããªãï¼L-8ï¼ */ 3197 var safeRowImageSrc = item.imageUrl ? responsiveShopifyImageUrl(item.imageUrl, 192) : ""; 3198 if (safeRowImageSrc) { 3199 var image = document.createElement("img"); 3200 image.src = safeRowImageSrc; 3201 image.alt = item.imageAlt; 3202 image.width = 96; 3203 image.height = 96; 3204 image.loading = "lazy"; 3205 media.appendChild(image); 3206 } 3207 content.className = "clc-cart-item-content"; 3208 content.appendChild(makeText("h3", "clc-cart-item-title", item.title)); 3209 if (item.variantTitle && normalize(item.variantTitle) !== "default title") { 3210 details.push(item.variantTitle); 3211 } 3212 if (item.sellingPlanName) { 3213 details.push(item.sellingPlanName); 3214 } 3215 if (details.length) { 3216 content.appendChild(makeText("p", "clc-cart-item-meta", details.join(" ï¼ "))); 3217 } 3218 content.appendChild(makeText("p", "clc-cart-item-price", item.price ? formatMoney(item.price) : "â")); 3219
3220 controls.className = "clc-cart-item-controls"; 3221 decrease.type = "button"; 3222 decrease.className = "clc-cart-item-step"; 3223 decrease.setAttribute("data-cart-action", "decrease"); 3224 decrease.setAttribute("aria-label", item.title + "ã®æ°éãæ¸ãã"); 3225 setText(decrease, "â"); 3226 increase.type = "button"; 3227 increase.className = "clc-cart-item-step"; 3228 increase.setAttribute("data-cart-action", "increase"); 3229 increase.setAttribute("aria-label", item.title + "ã®æ°éãå¢ãã"); 3230 increase.disabled = item.quantity >= cartItemMaximum(item); 3231 setText(increase, "+"); 3232 remove.type = "button"; 3233 remove.className = "clc-cart-item-remove"; 3234 remove.setAttribute("data-cart-action", "remove"); 3235 remove.setAttribute("aria-label", item.title + "ãã«ã¼ãããåé¤"); 3236 setText(remove, labels.remove || "åé¤"); 3237 controls.appendChild(decrease); 3238 controls.appendChild(quantity); 3239 controls.appendChild(increase); 3240 controls.appendChild(remove); 3241 content.appendChild(controls); 3242 row.appendChild(media); 3243 row.appendChild(content); 3244 list.appendChild(row); 3245 3246 }); 3247 lastRenderedItemsFingerprint = renderFingerprint; 3248 restoreCartFocus(items); 3249 } 3250 3251 items.forEach(function (item, index) { 3252 var currentLine = preparedCheckout && preparedCheckout.fingerprint === cartFingerprint(items) ? preparedLineForItem(item) : null; 3253 var displayedPrice = currentLine && currentLine.price ? currentLine.price : item.price; 3254 var row = list.children[index]; 3255 if (row) { 3256 // Preserve quantity controls and keyboard focus while applying the 3257 // same current Shopify prices used by the subtotal and Checkout event. 3258 setText(row.querySelector(".clc-cart-item-price"), displayedPrice ? formatMoney(displayedPrice) : "â"); 3259 } 3260 if (item.price) { 3261 total += item.price.amount * item.quantity; 3262 currency = item.price.currencyCode || currency; 3263 } 3264 }); 3265 3266 if (preparedCheckout && preparedCheckout.fingerprint === cartFingerprint(items) && preparedCheckout.totalPrice) { 3267 total = preparedCheckout.totalPrice.amount; 3268 currency = preparedCheckout.totalPrice.currencyCode; 3269 } 3270 3271 updateCounts(items); 3272 setHidden(empty, items.length > 0); 3273 setHidden(summary, items.length === 0); 3274 setText(subtotal, items.length ? formatMoney({ amount: total, currencyCode: currency }) : "â"); 3275 var discountCode = activeDiscountCode(); 3276 setText(discountCodeText, discountCode); 3277 setHidden(discountNotice, !discountCode || items.length === 0); 3278 var fingerprint = cartFingerprint(items); 3279 var checkoutReady = Boolean( 3280 preparedCheckout && 3281 preparedCheckout.fingerprint === fingerprint && 3282 preparedCheckout.checkoutUrl 3283 ); 3284 3285 checkout.setAttribute("aria-disabled", busy || items.length === 0 || !checkoutReady ? "true" : "false"); 3286 checkout.setAttribute("aria-busy", busy ? "true" : "false"); 3287 if (!busy && checkoutReady) { 3288 // This must be the final Shopify Checkout URL before the user clicks. 3289 // Google's cross-domain linker decorates this real anchor; JavaScript 3290 // navigation to an asynchronously returned URL is not supported. 3291 checkout.href = preparedCheckout.checkoutUrl; 3292 checkout.removeAttribute("role"); // hrefãããéã¯æ¬ç©ã®ãªã³ã¯ã¨ãã¦éç¥ãã 3293 } else { 3294 checkout.removeAttribute("href"); 3295 checkout.setAttribute("role", "button"); // hrefç¡ãã®<a>ã¯ãã¼ã«ä¸å®ã«ãªãããæç¤ºãã 3296 } 3297 if (busy) { 3298 setText(checkout, labels.cartLoading || "è³¼å ¥ç»é¢ãæºåãã¦ãã¾ãâ¦"); 3299 } else { 3300 setText(checkout, labels.checkout || "è³¼å ¥æç¶ãã¸"); 3301 } 3302 } 3303 3304 function prepareCheckout(items) { 3305 var discountCode = activeDiscountCode(); 3306 var fingerprint = cartFingerprint(items, discountCode); 3307 var requestBody = { lines: checkoutLines(items) }; 3308 var requestId; 3309 3310 if (discountCode) { 3311 requestBody.discount_code = discountCode; 3312 } 3313
3314 if (!items.length || !settings.restUrl || !settings.nonce) { 3315 invalidatePreparedCheckout(); 3316 render(); 3317 return Promise.resolve(null); 3318 } 3319 3320 if (preparedCheckout && preparedCheckout.fingerprint === fingerprint) { 3321 busy = false; 3322 setHidden(errorBox, true); 3323 render(); 3324 return Promise.resolve(preparedCheckout); 3325 } 3326 3327 requestId = ++prepareSequence; 3328 if (prepareController && typeof prepareController.abort === "function") { 3329 prepareController.abort(); 3330 } 3331 prepareController = typeof window.AbortController === "function" ? new window.AbortController() : null; 3332 preparedCheckout = null; 3333 busy = true; 3334 setHidden(errorBox, true); 3335 render(); 3336 3337 return window.fetch(settings.restUrl, { 3338 method: "POST", 3339 credentials: "same-origin", 3340 headers: { 3341 "Content-Type": "application/json", 3342 "X-WP-Nonce": settings.nonce 3343 }, 3344 signal: prepareController ? prepareController.signal : undefined, 3345 body: JSON.stringify(requestBody) 3346 }).then(function (response) { 3347 return response.json().catch(function () { return {}; }).then(function (payload) { 3348 if (!response.ok) { 3349 throw new Error(payload.message || labels.checkoutError || "è³¼å ¥ç»é¢ãæºåã§ãã¾ããã§ããã"); 3350 } 3351 return payload; 3352 }); 3353 }).then(function (payload) { 3354 if (!payload.checkout_url) { 3355 throw new Error(labels.checkoutError || "è³¼å ¥ç»é¢ãæºåã§ãã¾ããã§ããã"); 3356 } 3357 if (requestId !== prepareSequence || cartFingerprint(readCart()) !== fingerprint) { 3358 return null; 3359 } 3360 3361 var totalPrice = payload.cart_total && Number.isFinite(Number(payload.cart_total.amount)) ? { 3362 amount: Number(payload.cart_total.amount), 3363 currencyCode: String(payload.cart_total.currency_code || settings.currency || "JPY") 3364 } : null; 3365 var currentLines = (Array.isArray(payload.cart_lines) ? payload.cart_lines : []).map(function (line) { 3366 var amount = Number(line && line.price && line.price.amount); 3367 if (!line || !/^gid:\/\/shopify\/ProductVariant\/[1-9][0-9]*$/.test(String(line.variant_id || "")) || !Number.isFinite(amount)) { 3368 return null; 3369 } 3370 return { 3371 variantId: String(line.variant_id), 3372 sellingPlanId: String(line.selling_plan_id || ""), 3373 quantity: Math.max(1, Number.parseInt(line.quantity, 10) || 1), 3374 price: { amount: amount, currencyCode: String(line.price.currency_code || settings.currency || "JPY") } 3375 }; 3376 }).filter(Boolean); 3377 3378 preparedCheckout = { 3379 fingerprint: fingerprint, 3380 checkoutUrl: String(payload.checkout_url), 3381 cartId: String(payload.cart_id || ""), 3382 cartToken: String(payload.cart_token || ""), 3383 discountCode: discountCode, 3384 items: items.slice(), 3385 totalPrice: totalPrice, 3386 currentLines: currentLines 3387 }; 3388 prepareController = null; 3389 busy = false; 3390 render(); 3391 return preparedCheckout; 3392 }).catch(function (error) { 3393 if (requestId !== prepareSequence) { 3394 return null; 3395 } 3396 prepareController = null; 3397 if (error && error.name === "AbortError") { 3398 return null; 3399 } 3400 preparedCheckout = null; 3401 busy = false; 3402 render(); 3403 setText(errorBox, error && error.message ? error.message : (labels.checkoutError || "è³¼å ¥ç»é¢ãæºåã§ãã¾ããã§ããã")); 3404 setHidden(errorBox, false); 3405 return null; 3406 }); 3407 } 3408 3409 list.addEventListener("click", function (event) { 3410 var button = event.target.closest("[data-cart-action]"); 3411 var row = button && button.closest("[data-cart-key]"); 3412 var key = row && row.getAttribute("data-cart-key"); 3413 var items = readCart(); 3414 var itemIndex = key && items.findIndex(function (candidate) { return candidate.key === key; }); 3415 var item = Number.isFinite(itemIndex) && itemIndex >= 0 ? items[itemIndex] : null; 3416 var action; 3417 var nextQuantity; 3418 var delta; 3419 3420 if (!button || !item) { 3421 return; 3422 } 3423 action = button.getAttribute("data-cart-action"); 3424 pendingCartFocus = { 3425 entryId: item.entryId || "", 3426 index: itemIndex, 3427 action: action === "remove" ? "decrease" : action, 3428 message: action === "remove" ? item.title + (labels.cartRemoved || "ãã«ã¼ãããåé¤ãã¾ããã") : item.title + (labels.cartQuantityChanged || "ã®æ°éã夿´ãã¾ããã") 3429 }; 3430 if (action === "increase") { 3431 nextQuantity = Math.min(cartItemMaximum(item), item.quantity + 1); 3432 delta = nextQuantity - item.quantity; 3433 if (delta <= 0) { 3434 return; 3435 } 3436 updateCartItem(key, nextQuantity); 3437 pushCartQuantityEvent("add_to_cart", item, delta, itemIndex); 3438 } else if (action === "decrease") { 3439 nextQuantity = Math.max(0, item.quantity - 1); 3440 delta = item.quantity - nextQuantity; 3441 updateCartItem(key, nextQuantity); 3442 pushCartQuantityEvent("remove_from_cart", item, delta, itemIndex); 3443 } else { 3444 updateCartItem(key, 0); 3445 pushCartQuantityEvent("remove_from_cart", item, item.quantity, itemIndex); 3446 } 3447 }); 3448 3449 checkout.addEventListener("click", function (event) { 3450 var items = readCart(); 3451 var fingerprint = cartFingerprint(items); 3452 var handoff = preparedCheckout; 3453 var analyticsItems; 3454 var value; 3455 var currency; 3456 3457 event.preventDefault(); 3458 3459 if (busy || !items.length) { 3460 return; 3461 } 3462 3463 if (!handoff || handoff.fingerprint !== fingerprint || !checkout.href) { 3464 prepareCheckout(items); 3465 return; 3466 } 3467
3468 // Do not stop propagation and do not replace this href. Google's linker 3469 // listens to the trusted click at document level and decorates the final 3470 // Shopify Checkout URL before the deferred navigation reads it. 3471 busy = true; 3472 setHidden(errorBox, true); 3473 checkout.setAttribute("aria-disabled", "true"); 3474 checkout.setAttribute("aria-busy", "true"); 3475 analyticsItems = items.map(function (item, index) { 3476 return analyticsItemWithCurrentPrice(item, index); 3477 }); 3478 value = handoff.totalPrice ? handoff.totalPrice.amount : items.reduce(function (total, item) { 3479 return total + (item.price ? item.price.amount * item.quantity : 0); 3480 }, 0); 3481 currency = handoff.totalPrice ? handoff.totalPrice.currencyCode : ((items[0].price && items[0].price.currencyCode) || "JPY"); 3482 3483 if (handoff.cartId && handoff.cartToken) { 3484 rememberPendingCheckout(handoff.cartId, handoff.cartToken, handoff.items, handoff.discountCode); 3485 } 3486 // Shopify Pixel owns the standard begin_checkout event when the hosted 3487 // Checkout actually starts. Keep this pre-navigation intent distinct so 3488 // one checkout is not counted once on WordPress and again on Shopify. 3489 sendAnalyticsEvent("checkout_click", { 3490 currency: currency, 3491 value: Number(value.toFixed(2)), 3492 items: analyticsItems 3493 }, function () { 3494 navigateToPreparedCheckout(checkout, handoff.checkoutUrl); 3495 }); 3496 }); 3497 3498 checkout.addEventListener("keydown", function (event) { 3499 if ( 3500 (event.key === "Enter" || event.key === " ") && 3501 checkout.getAttribute("aria-disabled") === "true" && 3502 !busy && 3503 readCart().length 3504 ) { 3505 event.preventDefault(); 3506 prepareCheckout(readCart()); 3507 } 3508 }); 3509 3510 document.addEventListener("clc:cart-changed", function () { 3511 invalidatePreparedCheckout(); 3512 render(); 3513 if (!cart.hidden) { 3514 prepareCheckout(readCart()); 3515 } 3516 }); 3517 document.addEventListener("clc:panel-opened", function (event) { 3518 var detail = event.detail || {}; 3519 if (detail.panelId === cart.id) { 3520 pushViewCartOnce(readCart()); 3521 // Reset any prior `_gl` decoration to the validated raw Checkout URL. 3522 // The next trusted click then receives a fresh linker value. 3523 prepareCheckout(readCart()); 3524 } 3525 }); 3526 window.addEventListener("pageshow", function (event) { 3527 if (!event.persisted) { 3528 return; 3529 } 3530 invalidatePreparedCheckout(); 3531 render(); 3532 if (!cart.hidden) { 3533 prepareCheckout(readCart()); 3534 } 3535 }); 3536 render(); 3537 } 3538 3539 function initQuiz(quiz) { 3540 var form = quiz.querySelector(".clc-quiz-form"); 3541 var steps = toArray(quiz.querySelectorAll("[data-clc-quiz-step]")); 3542 var progress = quiz.querySelector("[data-clc-quiz-progress]"); 3543 var progressTrack = quiz.querySelector("[data-clc-quiz-progress-track]"); 3544 var progressBar = quiz.querySelector("[data-clc-quiz-progress-bar]"); 3545 var back = quiz.querySelector("[data-clc-quiz-back]"); 3546 var status = quiz.querySelector("[data-clc-quiz-status]"); 3547 var result = quiz.querySelector("[data-clc-quiz-result]"); 3548 var resultHeading = quiz.querySelector("[data-clc-quiz-result-heading]"); 3549 var resultProfile = quiz.querySelector("[data-clc-quiz-result-profile]"); 3550 var profileData = quiz.querySelector("[data-clc-quiz-profiles]"); 3551 var productCards = toArray(quiz.querySelectorAll("[data-clc-quiz-product]")); 3552 var reset = quiz.querySelector("[data-clc-quiz-reset]"); 3553 var current = 0; 3554 var advanceTimer = 0; 3555 var profiles = {}; 3556 3557 if (!form || !steps.length || !result || quiz.getAttribute("data-clc-quiz-ready") === "true") { 3558 return; 3559 } 3560 quiz.setAttribute("data-clc-quiz-ready", "true"); 3561 3562 try { 3563 profiles = JSON.parse((profileData && profileData.textContent) || "{}"); 3564 } catch (error) { 3565 profiles = {}; 3566 } 3567 3568 function progressPercent(index) { 3569 return Math.round(((index + 1) / steps.length) * 100); 3570 } 3571 3572 function showStep(index, focusQuestion) { 3573 var percent; 3574 3575 window.clearTimeout(advanceTimer); 3576 current = Math.max(0, Math.min(steps.length - 1, index)); 3577 steps.forEach(function (step, stepIndex) { setHidden(step, stepIndex !== current); }); 3578 setText(progress, String(current + 1)); 3579 percent = progressPercent(current); 3580 if (progressTrack) { 3581 progressTrack.setAttribute("aria-valuenow", String(percent)); 3582 } 3583 if (progressBar) { 3584 progressBar.style.width = String(percent) + "%"; 3585 } 3586 if (back) { 3587 setHidden(back, current === 0); 3588 } 3589 setHidden(form, false); 3590 setHidden(result, true); 3591 steps[current].classList.remove("is-entering"); 3592 window.requestAnimationFrame(function () { 3593 steps[current].classList.add("is-entering"); 3594 }); 3595 if (focusQuestion) { 3596 window.setTimeout(function () { 3597 var legend = steps[current] && steps[current].querySelector("legend"); 3598 if (legend) { 3599 legend.focus({ preventScroll: true }); 3600 } 3601 }, 0); 3602 } 3603 } 3604 3605 function selectedInputs() { 3606 return toArray(form.querySelectorAll("input:checked[data-quiz-scores]")); 3607 } 3608 3609 function calculateRanking() { 3610 var scores = Object.create(null);
3611 selectedInputs().forEach(function (input) { 3612 String(input.getAttribute("data-quiz-scores") || "").split(",").forEach(function (pair) { 3613 var parts = pair.split(":"); 3614 var handle = parts[0]; 3615 var score = Number(parts[1]) || 0; 3616 scores[handle] = (scores[handle] || 0) + score; 3617 }); 3618 }); 3619 3620 return productCards.map(function (card, index) { 3621 var handle = card.getAttribute("data-clc-quiz-product") || ""; 3622 return { card: card, handle: handle, score: scores[handle] || 0, index: index }; 3623 }).sort(function (a, b) { 3624 return b.score - a.score || a.index - b.index; 3625 }); 3626 } 3627 3628 function showResults() { 3629 var ranked = calculateRanking().slice(0, 3); 3630 var taste = form.querySelector('[data-quiz-question="taste"]:checked'); 3631 var roast = form.querySelector('[data-quiz-question="roast"]:checked'); 3632 var profileKey = String((taste && taste.value) || "") + "_" + String((roast && roast.value) || ""); 3633 var resultItems = []; 3634 3635 productCards.forEach(function (card) { 3636 setHidden(card, true); 3637 card.classList.remove("is-recommended"); 3638 card.style.removeProperty("order"); 3639 }); 3640 ranked.forEach(function (item, index) { 3641 var image = item.card.querySelector("img"); 3642 setHidden(item.card, false); 3643 item.card.classList.add("is-recommended"); 3644 item.card.style.order = String(index + 1); 3645 if (image) { 3646 image.loading = "eager"; 3647 image.fetchPriority = index === 0 ? "high" : "auto"; 3648 } 3649 resultItems.push({ 3650 item_id: item.card.getAttribute("data-product-id") || item.handle, 3651 item_name: item.card.getAttribute("data-product-title") || item.handle, 3652 price: Number(item.card.getAttribute("data-price-amount")) || 0, 3653 index: index + 1 3654 }); 3655 }); 3656 setText(resultProfile, profiles[profileKey] || "ããªãã«ã´ã£ããã®ã³ã¼ãã¼ãè¦ã¤ãã¾ããã"); 3657 setHidden(form, true); 3658 setHidden(result, false); 3659 sendAnalyticsEvent("view_item_list", { 3660 item_list_id: "coffee_finder_results", 3661 item_list_name: "ã³ã¼ãã¼è¨ºæçµæ", 3662 items: resultItems 3663 }); 3664 sendAnalyticsEvent("select_content", { 3665 content_type: "coffee_finder", 3666 item_id: ranked.length ? ranked[0].handle : "" 3667 }); 3668 if (resultHeading) { 3669 resultHeading.focus({ preventScroll: true }); 3670 } 3671 } 3672 3673 function handleAnswer(input) { 3674 var questionId = input.getAttribute("data-quiz-question") || ""; 3675 var selectedLabel = input.closest("label"); 3676 3677 window.clearTimeout(advanceTimer); 3678 if (selectedLabel) { 3679 selectedLabel.classList.add("is-selected"); 3680 } 3681 setText(status, current < steps.length - 1 ? "åçã鏿ãã¾ãããæ¬¡ã®è³ªåã¸é²ã¿ã¾ãã" : "åçã鏿ãã¾ããã診æçµæã表示ãã¾ãã"); 3682 sendAnalyticsEvent("select_content", { 3683 content_type: "coffee_finder_answer", 3684 item_id: questionId + ":" + String(input.value || ""), 3685 index: current + 1 3686 }); 3687 advanceTimer = window.setTimeout(function () { 3688 if (current < steps.length - 1) { 3689 showStep(current + 1, true); 3690 } else { 3691 showResults(); 3692 } 3693 }, 320); 3694 } 3695 3696 form.addEventListener("change", function (event) { 3697 var input = event.target.closest('input[type="radio"][data-quiz-scores]'); 3698 if (input && !input.disabled) { 3699 handleAnswer(input); 3700 } 3701 }); 3702 form.addEventListener("submit", function (event) { 3703 event.preventDefault(); 3704 }); 3705 if (back) { 3706 back.addEventListener("click", function () { 3707 var target = Math.max(0, current - 1); 3708 steps.slice(target).forEach(function (step) { 3709 toArray(step.querySelectorAll('input[type="radio"]')).forEach(function (input) { 3710 input.checked = false; 3711 }); 3712 toArray(step.querySelectorAll("label.is-selected")).forEach(function (label) { 3713 label.classList.remove("is-selected"); 3714 }); 3715 }); 3716 showStep(target, true); 3717 }); 3718 } 3719 result.addEventListener("click", function (event) { 3720 var addButton = event.target.closest("[data-clc-quiz-add]"); 3721 var productLink = event.target.closest("[data-clc-quiz-product-link]"); 3722 var card = event.target.closest("[data-clc-quiz-product]"); 3723 3724 if (addButton && card) { 3725 var added = addCartItem({ 3726 productHandle: card.getAttribute("data-product-handle"), 3727 productId: card.getAttribute("data-product-id"), 3728 title: card.getAttribute("data-product-title"), 3729 variantId: card.getAttribute("data-variant-id"), 3730 variantTitle: "", 3731 sellingPlanId: "", 3732 sellingPlanName: "", 3733 quantity: 1, 3734 price: { 3735 amount: Number(card.getAttribute("data-price-amount")) || 0, 3736 currencyCode: card.getAttribute("data-price-currency") || "JPY" 3737 }, 3738 imageUrl: card.getAttribute("data-image-url"), 3739 imageAlt: card.getAttribute("data-product-title") 3740 }); 3741 3742 if (added) {
3743 setText(addButton, labels.addedToCart || "ã«ã¼ãã«è¿½å ãã¾ãã"); 3744 sendAnalyticsEvent("add_to_cart", { 3745 currency: card.getAttribute("data-price-currency") || "JPY", 3746 value: (Number(card.getAttribute("data-price-amount")) || 0) * added, 3747 items: [{ 3748 item_id: card.getAttribute("data-product-id") || card.getAttribute("data-product-handle"), 3749 item_name: card.getAttribute("data-product-title"), 3750 price: Number(card.getAttribute("data-price-amount")) || 0, 3751 quantity: added 3752 }] 3753 }); 3754 openUtilityPanel("clc-cart-panel", addButton); 3755 window.setTimeout(function () { 3756 setText(addButton, labels.addToCart || "ã«ã¼ãã«è¿½å "); 3757 }, 1800); 3758 } 3759 } else if (productLink && card) { 3760 sendAnalyticsEvent("select_item", { 3761 item_list_id: "coffee_finder_results", 3762 item_list_name: "ã³ã¼ãã¼è¨ºæçµæ", 3763 items: [{ 3764 item_id: card.getAttribute("data-product-id") || card.getAttribute("data-product-handle"), 3765 item_name: card.getAttribute("data-product-title"), 3766 price: Number(card.getAttribute("data-price-amount")) || 0, 3767 quantity: 1 3768 }] 3769 }); 3770 } 3771 }); 3772 if (reset) { 3773 reset.addEventListener("click", function () { 3774 window.clearTimeout(advanceTimer); 3775 form.reset(); 3776 toArray(form.querySelectorAll("label.is-selected")).forEach(function (label) { 3777 label.classList.remove("is-selected"); 3778 }); 3779 productCards.forEach(function (card) { setHidden(card, true); }); 3780 setHidden(result, true); 3781 setHidden(form, false); 3782 showStep(0, true); 3783 }); 3784 } 3785 showStep(0, false); 3786 } 3787 3788 function initNavigation(storefront, index) { 3789 var toggle = storefront.querySelector(".clc-nav-toggle"); 3790 var panel = storefront.querySelector(".clc-nav-panel"); 3791 var disclosures; 3792 3793 if (!toggle || !panel || toggle.getAttribute("data-clc-initialized") === "true") { 3794 return; 3795 } 3796 3797 toggle.setAttribute("data-clc-initialized", "true"); 3798 3799 if (!panel.id) { 3800 panel.id = "clc-nav-panel-" + String(index + 1); 3801 } 3802 3803 toggle.setAttribute("aria-controls", panel.id); 3804 3805 if (!toggle.hasAttribute("aria-expanded")) { 3806 toggle.setAttribute("aria-expanded", "false"); 3807 } 3808 3809 disclosures = toArray(panel.querySelectorAll(".clc-nav-disclosure")); 3810 3811 var media = window.matchMedia ? window.matchMedia("(max-width: 1439px)") : null; 3812 3813 function isMobile() { 3814 return media ? media.matches : window.innerWidth <= 1439; 3815 } 3816 3817 function closeDisclosures(except) { 3818 disclosures.forEach(function (disclosure) { 3819 if (disclosure !== except && disclosure.open) { 3820 disclosure.open = false; 3821 } 3822 }); 3823 } 3824 3825 function setOpen(open) { 3826 var expanded = Boolean(open) && isMobile(); 3827 toggle.setAttribute("aria-expanded", expanded ? "true" : "false"); 3828 toggle.setAttribute("aria-label", expanded ? 3829 (labels.closeMenu || "ã¡ãã¥ã¼ãéãã") : 3830 (labels.openMenu || "ã¡ãã¥ã¼ãéã")); 3831 panel.classList.toggle("is-open", expanded); 3832 setHidden(panel, isMobile() && !expanded); 3833 document.body.classList.toggle("clc-navigation-open", expanded); 3834 if (!expanded) { 3835 closeDisclosures(null); 3836 } 3837 } 3838 3839 function synchronizeViewport() { 3840 if (isMobile()) { 3841 setOpen(toggle.getAttribute("aria-expanded") === "true"); 3842 } else { 3843 toggle.setAttribute("aria-expanded", "false"); 3844 panel.classList.remove("is-open"); 3845 panel.hidden = false; 3846 document.body.classList.remove("clc-navigation-open"); 3847 closeDisclosures(null); 3848 } 3849 } 3850 3851 disclosures.forEach(function (disclosure) { 3852 var summary = disclosure.querySelector(":scope > summary"); 3853 3854 if (!summary) { 3855 return; 3856 } 3857 3858 disclosure.addEventListener("toggle", function () { 3859 if (disclosure.open) { 3860 closeDisclosures(disclosure); 3861 } 3862 }); 3863 }); 3864 3865 toggle.addEventListener("click", function () { 3866 setOpen(toggle.getAttribute("aria-expanded") !== "true"); 3867 }); 3868 3869 panel.addEventListener("click", function (event) { 3870 if (event.target.closest("a, [data-clc-panel-open]")) { 3871 closeDisclosures(null); 3872 if (isMobile()) { 3873 setOpen(false); 3874 } 3875 } 3876 }); 3877 3878 storefront.addEventListener("keydown", function (event) { 3879 var openDisclosure; 3880 3881 if (event.key !== "Escape") { 3882 return; 3883 } 3884 3885 openDisclosure = panel.querySelector(".clc-nav-disclosure[open]"); 3886 if (openDisclosure) { 3887 event.preventDefault(); 3888 openDisclosure.open = false;
3889 if (openDisclosure.querySelector(":scope > summary")) { 3890 openDisclosure.querySelector(":scope > summary").focus(); 3891 } 3892 return; 3893 } 3894 3895 if (toggle.getAttribute("aria-expanded") === "true") { 3896 event.preventDefault(); 3897 setOpen(false); 3898 toggle.focus(); 3899 } 3900 }); 3901 3902 storefront.addEventListener("focusin", function (event) { 3903 if ( 3904 isMobile() && 3905 toggle.getAttribute("aria-expanded") === "true" && 3906 event.target !== toggle && 3907 !panel.contains(event.target) 3908 ) { 3909 setOpen(false); 3910 } 3911 }); 3912 3913 document.addEventListener("pointerdown", function (event) { 3914 if (panel.contains(event.target) || toggle.contains(event.target)) { 3915 return; 3916 } 3917 3918 closeDisclosures(null); 3919 if (isMobile() && toggle.getAttribute("aria-expanded") === "true") { 3920 setOpen(false); 3921 } 3922 }); 3923 3924 if (media && typeof media.addEventListener === "function") { 3925 media.addEventListener("change", synchronizeViewport); 3926 } else if (media && typeof media.addListener === "function") { 3927 media.addListener(synchronizeViewport); 3928 } else { 3929 window.addEventListener("resize", synchronizeViewport); 3930 } 3931 3932 synchronizeViewport(); 3933 } 3934 3935 function initContactFormA11y(storefront) { 3936 toArray(storefront.querySelectorAll(".clc-contact-form .wpcf7-checkbox, .clc-contact-form .wpcf7-radio")).forEach(function (group, index) { 3937 var paragraph; 3938 var groupLabel; 3939 var labelId; 3940 3941 if (group.getAttribute("data-clc-group-ready") === "true") { 3942 return; 3943 } 3944 group.setAttribute("data-clc-group-ready", "true"); 3945 paragraph = group.closest("p"); 3946 groupLabel = paragraph ? paragraph.querySelector(":scope > label") : null; 3947 if (!groupLabel || !String(groupLabel.textContent || "").trim()) { 3948 return; 3949 } 3950 3951 labelId = groupLabel.id || "clc-contact-group-label-" + String(index + 1); 3952 groupLabel.id = labelId; 3953 group.setAttribute("role", "group"); 3954 group.setAttribute("aria-labelledby", labelId); 3955 if (group.classList.contains("wpcf7-validates-as-required")) { 3956 group.setAttribute("aria-required", "true"); 3957 } 3958 }); 3959 } 3960 3961 function initialize() { 3962 var storefronts = toArray(document.querySelectorAll(".clc-storefront")); 3963 3964 storefronts.forEach(function (storefront, index) { 3965 allIncluding(storefront, ".clc-product").forEach(initProduct); 3966 allIncluding(storefront, ".clc-collection").forEach(initCollection); 3967 allIncluding(storefront, ".clc-rich-text").forEach(initRichText); 3968 allIncluding(storefront, "[data-clc-horizontal-scroll]").forEach(initHorizontalScroll); 3969 allIncluding(storefront, "[data-clc-home-hero]").forEach(initHomeHero); 3970 allIncluding(storefront, "[data-clc-home-testimonials]").forEach(initHomeTestimonials); 3971 allIncluding(storefront, "[data-clc-weekly-countdown]").forEach(initWeeklyCountdown); 3972 allIncluding(storefront, "[data-clc-home-product-list]").forEach(initHomeProductList); 3973 allIncluding(storefront, "[data-clc-cart]").forEach(initCart); 3974 allIncluding(storefront, "[data-clc-quiz]").forEach(initQuiz); 3975 initContactFormA11y(storefront); 3976 initUtilityPanels(storefront); 3977 allIncluding(storefront, "[data-clc-cart-reminder]").forEach(initCartReminder); 3978 initNavigation(storefront, index); 3979 }); 3980 reconcilePendingCheckouts(); 3981 } 3982 3983 window.addEventListener("pageshow", function (event) { 3984 if (event.persisted) { 3985 reconcilePendingCheckouts(true); 3986 } 3987 }); 3988 window.addEventListener("storage", function (event) { 3989 if (event.storageArea !== window.localStorage) { 3990 return; 3991 } 3992 if (event.key === cartStorageKey) { 3993 cartStorageAvailable = true; 3994 document.dispatchEvent(new CustomEvent("clc:cart-changed", { 3995 detail: { items: readCart(), source: "storage" } 3996 })); 3997 } 3998 if (event.key === pendingCheckoutStorageKey) { 3999 pendingCheckoutStorageAvailable = true; 4000 reconcilePendingCheckouts(true); 4001 } 4002 }); 4003 ready(initialize); 4004}());
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.