https://coffee-labo.co.jp/wp-content/plugins/coffee-labo-commerce/assets/js/storefront.js?ver=0.5.42
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 if (availability) { 1579 availability.classList.toggle("is-available", available); 1580 availability.classList.toggle("is-unavailable", !available); 1581 setText(availability, available ? 1582 (labels.inStock || labels.available || "å¨åº«ãã") : 1583 inactiveLabel); 1584 } 1585 1586 if (checkoutButton) { 1587 checkoutButton.disabled = busy || !available; 1588 1589 if (!busy) { 1590 setText(checkoutButton, available ? 1591 (checkoutButton.dataset.defaultLabel || labels.checkout || "è³¼å ¥æç¶ãã¸") : 1592 inactiveLabel); 1593 } 1594 1595 if (currentVariant && currentVariant.id != null) { 1596 checkoutButton.setAttribute("data-variant-id", String(currentVariant.id)); 1597 } else { 1598 checkoutButton.removeAttribute("data-variant-id"); 1599 } 1600 } 1601 1602 updateVariantImage(currentVariant); 1603 synchronizeVariantUrl(product, currentVariant, productElement); 1604 1605 if (hasRendered && planSync.selectionCleared) { 1606 announceProduct(labels.planChanged || "鏿ããã³ã¼ã¹ã§ã¯è³¼å ¥æ¹æ³ãå¤ãããããè³¼å ¥æ¹æ³ãé¸ã³ç´ãã¦ãã ããã"); 1607 } 1608 1609 hasRendered = true; 1610 } 1611 1612 function selectVariantFromInputs(pushEvent) { 1613 var matched = findSelectedVariant(variants, optionInputs); 1614 1615 showError(""); 1616 announceProduct(""); 1617 currentVariant = matched; 1618 render(); 1619 1620 if (pushEvent && currentVariant) { 1621 pushEcommerce("select_variant", product, currentVariant, currentPricing, 1, selectedPlan()); 1622 } 1623 } 1624 1625 optionInputs.forEach(function (input) { 1626 input.addEventListener("change", function () { 1627 selectVariantFromInputs(true); 1628 }); 1629 }); 1630 1631 planInputs.forEach(function (input) { 1632 input.addEventListener("change", function () { 1633 if (!input.checked || input.disabled) { 1634 return; 1635 } 1636 1637 showError(""); 1638 announceProduct(""); 1639 render(); 1640 pushEcommerce("select_subscription_plan", product, currentVariant, currentPricing, 1, selectedPlan()); 1641 }); 1642 }); 1643 1644 if (purchaseForm && checkoutButton) { 1645 purchaseForm.addEventListener("submit", function (event) { 1646 var quantity; 1647 var image; 1648 var allocation; 1649 var added; 1650 1651 event.preventDefault(); 1652 1653 if (busy || !currentVariant || !isVariantAvailable(currentVariant)) { 1654 return; 1655 } 1656 1657 if (product.requiresSellingPlan && !selectedPlan()) { 1658 showError(labels.selectPlan || "è³¼å ¥æ¹æ³ã鏿ãã¦ãã ããã"); 1659 return; 1660 } 1661 1662 quantity = getQuantity(); 1663 1664 if (!Number.isFinite(quantity) || quantity < 1) { 1665 showError(labels.quantityError || "æ°éã1以ä¸ã«ãã¦ãã ããã"); 1666 return; 1667 } 1668 1669 image = productImage(currentVariant) || product.featuredImage || {}; 1670 allocation = currentPricing && currentPricing.allocation; 1671 added = addCartItem({ 1672 productHandle: String(product.handle || productElement.getAttribute("data-product-handle") || ""), 1673 productId: String(product.id || product.handle || ""), 1674 title: String(product.title || "åå"), 1675 variantId: String(currentVariant.id),
1676 variantTitle: String(currentVariant.title || ""), 1677 sellingPlanId: selectedPlan() || "", 1678 sellingPlanName: String(allocation && allocation.sellingPlan && allocation.sellingPlan.name || ""), 1679 quantity: quantity, 1680 price: currentPricing && currentPricing.current, 1681 imageUrl: String(image.url || ""), 1682 imageAlt: String(image.altText || product.title || "") 1683 }); 1684 1685 if (!added) { 1686 showError(labels.cartFull || "ã«ã¼ãã«è¿½å ã§ããååæ°ã®ä¸éã«éãã¾ããã"); 1687 return; 1688 } 1689 1690 showError(""); 1691 pushEcommerce("add_to_cart", product, currentVariant, currentPricing, added, selectedPlan()); 1692 announceProduct(labels.addedToCart || "ã«ã¼ãã«è¿½å ãã¾ããã"); 1693 setText(checkoutButton, labels.addedToCart || "ã«ã¼ãã«è¿½å ãã¾ãã"); 1694 openUtilityPanel("clc-cart-panel", checkoutButton); 1695 1696 window.setTimeout(function () { 1697 render(); 1698 }, 900); 1699 }); 1700 } 1701 1702 showError(""); 1703 render(); 1704 pushViewItemOnce(product, currentVariant, currentPricing); 1705 } 1706 1707 function initRichText(root) { 1708 if (!root || root.getAttribute("data-clc-rich-text-ready") === "true") { 1709 return; 1710 } 1711 1712 root.setAttribute("data-clc-rich-text-ready", "true"); 1713 1714 toArray(root.querySelectorAll("table")).forEach(function (table, tableIndex) { 1715 var rows = toArray(table.rows || []); 1716 var caption = table.querySelector("caption"); 1717 var heading = table.previousElementSibling; 1718 var headingText = ""; 1719 var consistentShape; 1720 var columnCount; 1721 var firstRowIsHeader; 1722 1723 while (heading && !/^H[2-4]$/.test(heading.tagName)) { 1724 heading = heading.previousElementSibling; 1725 } 1726 if (heading) { 1727 headingText = String(heading.textContent || "").trim(); 1728 } 1729 if (!caption) { 1730 caption = document.createElement("caption"); 1731 caption.className = "clc-screen-reader-text"; 1732 caption.textContent = headingText || (labels.tableScroll || "ååã»ãµã¼ãã¹æ å ±è¡¨") + " " + String(tableIndex + 1); 1733 table.insertBefore(caption, table.firstChild); 1734 } 1735 1736 columnCount = rows.length && rows[0].cells ? rows[0].cells.length : 0; 1737 consistentShape = columnCount > 0 && rows.every(function (row) { 1738 return row.cells && row.cells.length === columnCount; 1739 }); 1740 if (!consistentShape || table.querySelector("th")) { 1741 return; 1742 } 1743 1744 function promoteCell(cell, scope) { 1745 var header = document.createElement("th"); 1746 toArray(cell.attributes || []).forEach(function (attribute) { 1747 header.setAttribute(attribute.name, attribute.value); 1748 }); 1749 header.setAttribute("scope", scope); 1750 while (cell.firstChild) { 1751 header.appendChild(cell.firstChild); 1752 } 1753 cell.parentNode.replaceChild(header, cell); 1754 } 1755 1756 firstRowIsHeader = Boolean(table.tHead) || 1757 /background(?:-color)?\s*:/i.test(rows[0].getAttribute("style") || "") || 1758 toArray(rows[0].cells).every(function (cell) { 1759 return Boolean(cell.querySelector("strong, b")); 1760 }); 1761 if (firstRowIsHeader) { 1762 toArray(rows[0].cells).forEach(function (cell) { 1763 promoteCell(cell, "col"); 1764 }); 1765 } 1766 rows.slice(firstRowIsHeader ? 1 : 0).forEach(function (row) { 1767 if (row.cells && row.cells[0]) {
1768 promoteCell(row.cells[0], "row"); 1769 } 1770 }); 1771 }); 1772 1773 var wrappers = toArray(root.querySelectorAll("table")).map(function (table) { 1774 var parent = table.parentElement; 1775 var wrapper; 1776 1777 if (parent && parent.classList.contains("clc-table-scroll")) { 1778 return parent; 1779 } 1780 1781 wrapper = document.createElement("div"); 1782 wrapper.className = "clc-table-scroll"; 1783 wrapper.setAttribute("data-clc-table-scroll", "true"); 1784 table.parentNode.insertBefore(wrapper, table); 1785 wrapper.appendChild(table); 1786 return wrapper; 1787 }); 1788 1789 function synchronizeTableAccess() { 1790 wrappers.forEach(function (wrapper) { 1791 var table = wrapper.querySelector("table"); 1792 var caption = table && table.querySelector("caption"); 1793 var isScrollable = wrapper.scrollWidth > wrapper.clientWidth + 1; 1794 1795 if (isScrollable) { 1796 wrapper.tabIndex = 0; 1797 wrapper.setAttribute("role", "region"); 1798 wrapper.setAttribute( 1799 "aria-label", 1800 caption && caption.textContent.trim() ? 1801 caption.textContent.trim() : 1802 (labels.tableScroll || "ååæ å ±è¡¨ãæ¨ªã«ã¹ã¯ãã¼ã«ã§ãã¾ãã") 1803 ); 1804 } else { 1805 wrapper.removeAttribute("tabindex"); 1806 wrapper.removeAttribute("role"); 1807 wrapper.removeAttribute("aria-label"); 1808 } 1809 }); 1810 } 1811 1812 synchronizeTableAccess(); 1813 1814 if (typeof window.requestAnimationFrame === "function") { 1815 window.requestAnimationFrame(synchronizeTableAccess); 1816 } 1817 1818 if (wrappers.length) { 1819 window.addEventListener("resize", synchronizeTableAccess, { passive: true }); 1820 } 1821 } 1822 1823 function initHorizontalScroll(root) { 1824 if (!root || root.getAttribute("data-clc-horizontal-scroll-ready") === "true") { 1825 return; 1826 } 1827 1828 root.setAttribute("data-clc-horizontal-scroll-ready", "true"); 1829 1830 function synchronizeAccess() { 1831 var isScrollable = root.scrollWidth > root.clientWidth + 1; 1832 1833 if (isScrollable) { 1834 root.tabIndex = 0; 1835 root.setAttribute("role", "region"); 1836 root.setAttribute( 1837 "aria-label", 1838 root.getAttribute("data-clc-scroll-label") || labels.horizontalScroll || "横ã«ã¹ã¯ãã¼ã«ã§ãã¾ãã" 1839 ); 1840 } else { 1841 root.removeAttribute("tabindex"); 1842 root.removeAttribute("role"); 1843 root.removeAttribute("aria-label"); 1844 } 1845 } 1846 1847 synchronizeAccess(); 1848 if (typeof window.requestAnimationFrame === "function") { 1849 window.requestAnimationFrame(synchronizeAccess); 1850 } 1851 window.addEventListener("resize", synchronizeAccess, { passive: true }); 1852 } 1853 1854 function controlFilterName(control) { 1855 var owner = control.hasAttribute("data-clc-filter") ? control : control.closest("[data-clc-filter]"); 1856 return owner ? owner.getAttribute("data-clc-filter") : ""; 1857 } 1858 1859 function filterControls(collection) { 1860 var controls = []; 1861 1862 toArray(collection.querySelectorAll("[data-clc-filter]")).forEach(function (element) { 1863 if (element.matches("input[type=\"checkbox\"], input[type=\"radio\"]")) { 1864 controls.push(element); 1865 return; 1866 } 1867 1868 toArray(element.querySelectorAll("input[type=\"checkbox\"], input[type=\"radio\"]")).forEach(function (input) { 1869 if (controls.indexOf(input) === -1) { 1870 controls.push(input); 1871 } 1872 }); 1873 }); 1874 1875 return controls; 1876 } 1877 1878 function numericCardValue(card, field) { 1879 var value = card.getAttribute("data-" + field) || ""; 1880 var parsed = Number(String(value).replace(/[^0-9.-]/g, "")); 1881 return Number.isFinite(parsed) ? parsed : 0; 1882 } 1883 1884 function cardFieldValue(card, field) { 1885 var normalizedField = field === "availability" ? "available" : field; 1886 normalizedField = normalizedField === "type" ? "product-type" : normalizedField; 1887 var value = card.getAttribute("data-" + normalizedField); 1888 1889 if (value == null && normalizedField === "title") { 1890 var title = card.querySelector(".clc-card-title"); 1891 value = title ? title.textContent : ""; 1892 } 1893 1894 return String(value == null ? "" : value); 1895 } 1896 1897 function matchesPriceFilter(card, control) { 1898 var owner = control.hasAttribute("data-clc-filter") ? control : control.closest("[data-clc-filter]"); 1899 var price = numericCardValue(card, "price"); 1900 var minimum = Number(control.getAttribute("data-min") || (owner && owner.getAttribute("data-min"))); 1901 var maximum = Number(control.getAttribute("data-max") || (owner && owner.getAttribute("data-max"))); 1902 var value = normalize(control.value); 1903 var range; 1904 1905 if (!Number.isFinite(minimum)) { 1906 minimum = -Infinity; 1907 } 1908 1909 if (!Number.isFinite(maximum)) { 1910 maximum = Infinity; 1911 } 1912 1913 if (minimum !== -Infinity || maximum !== Infinity) { 1914 return price >= minimum && price <= maximum; 1915 } 1916 1917 range = value.match(/^(?:Â¥|ï¿¥)?([0-9.]*)\s*[-:]\s*(?:Â¥|ï¿¥)?([0-9.]*)$/); 1918 if (range) { 1919 minimum = range[1] ? Number(range[1]) : -Infinity; 1920 maximum = range[2] ? Number(range[2]) : Infinity; 1921 return price >= minimum && price <= maximum; 1922 } 1923 1924 if (value.indexOf("under-") === 0) { 1925 return price < Number(value.replace("under-", "")); 1926 } 1927 1928 if (value.indexOf("over-") === 0) { 1929 return price >= Number(value.replace("over-", "")); 1930 } 1931 1932 return normalize(cardFieldValue(card, "price")) === value; 1933 } 1934 1935 function cardMatchesControl(card, field, control) { 1936 var expected = normalize(control.value); 1937 var actual; 1938 1939 if (field === "price") { 1940 return matchesPriceFilter(card, control); 1941 } 1942 1943 if (field === "available" || field === "availability") { 1944 actual = normalize(cardFieldValue(card, "available")); 1945 1946 if (["true", "1", "yes", "available", "in-stock", "instock"].indexOf(expected) !== -1) { 1947 return ["true", "1", "yes", "available", "in-stock", "instock"].indexOf(actual) !== -1; 1948 } 1949 1950 if (["false", "0", "no", "sold-out", "soldout", "unavailable"].indexOf(expected) !== -1) { 1951 return ["false", "0", "no", "sold-out", "soldout", "unavailable"].indexOf(actual) !== -1; 1952 } 1953 } 1954 1955 actual = normalize(cardFieldValue(card, field)); 1956 return actual === expected; 1957 } 1958 1959 function initCollection(collection) { 1960 if (collection.getAttribute("data-clc-initialized") === "true") { 1961 return; 1962 } 1963 1964 collection.setAttribute("data-clc-initialized", "true"); 1965 1966 var cards = toArray(collection.querySelectorAll(".clc-product-card")); 1967 var controls = filterControls(collection); 1968 var sortControl = collection.querySelector("[data-clc-sort]"); 1969 var resultCount = collection.querySelector("[data-clc-result-count]"); 1970 var collectionStatus = collection.querySelector("[data-clc-collection-status]"); 1971 var emptyState = collection.querySelector("[data-clc-empty]"); 1972 var grid = collection.querySelector(".clc-product-grid") || (cards[0] && cards[0].parentElement); 1973 var filterToggle = collection.querySelector("[data-clc-filter-toggle]"); 1974 var filterPanel = collection.querySelector("[data-clc-filter-panel]"); 1975 var filterClose = collection.querySelector("[data-clc-filter-close]"); 1976 var filterClear = collection.querySelector("[data-clc-filter-clear]"); 1977 var filterCount = collection.querySelector("[data-clc-filter-count]"); 1978 var filterMedia = window.matchMedia ? window.matchMedia("(max-width: 767px)") : null; 1979 var searchSummary = collection.querySelector("[data-clc-search-query]"); 1980 var searchQuery = normalize(searchSummary ? searchSummary.getAttribute("data-clc-search-query") : ""); 1981 var serverSearch = collection.getAttribute("data-clc-search-mode") === "server"; 1982 var visibleCount = cards.length; 1983 var collator = typeof Intl !== "undefined" && Intl.Collator ? 1984 new Intl.Collator(locale, { sensitivity: "base", numeric: true }) : null; 1985 1986 cards.forEach(function (card, index) { 1987 card.setAttribute("data-clc-original-index", String(index)); 1988 }); 1989 1990 function isMobileFilter() { 1991 return filterMedia ? filterMedia.matches : window.innerWidth <= 767; 1992 } 1993 1994 function announceCollection(message) { 1995 setText(collectionStatus, message || ""); 1996 } 1997 1998 function setFilterOpen(open, returnFocus) { 1999 var expanded = Boolean(open) && isMobileFilter(); 2000 2001 if (!filterPanel || !filterToggle) { 2002 return; 2003 } 2004 2005 filterToggle.setAttribute("aria-expanded", expanded ? "true" : "false"); 2006 filterPanel.classList.toggle("is-open", expanded); 2007 filterPanel.hidden = isMobileFilter() && !expanded; 2008 2009 if (expanded) { 2010 (filterClose || filterPanel).focus(); 2011 } else if (returnFocus) { 2012 filterToggle.focus(); 2013 } 2014 } 2015 2016 function synchronizeFilterViewport() { 2017 if (!filterPanel || !filterToggle) { 2018 return; 2019 } 2020 2021 if (isMobileFilter()) { 2022 setFilterOpen(filterToggle.getAttribute("aria-expanded") === "true", false); 2023 } else { 2024 filterToggle.setAttribute("aria-expanded", "false"); 2025 filterPanel.classList.remove("is-open"); 2026 filterPanel.hidden = false;
2027 } 2028 } 2029 2030 function activeGroups() { 2031 var groups = Object.create(null); 2032 2033 controls.forEach(function (control) { 2034 var field = controlFilterName(control); 2035 2036 if (!field || !control.checked) { 2037 return; 2038 } 2039 2040 if (!groups[field]) { 2041 groups[field] = []; 2042 } 2043 2044 groups[field].push(control); 2045 }); 2046 2047 return groups; 2048 } 2049 2050 function applyFilters(shouldAnnounce) { 2051 var groups = activeGroups(); 2052 var groupNames = Object.keys(groups); 2053 var visible = 0; 2054 var activeCount = controls.filter(function (control) { return control.checked; }).length; 2055 2056 cards.forEach(function (card) { 2057 var matchesSearch = serverSearch || !searchQuery || normalize([ 2058 cardFieldValue(card, "title"), 2059 cardFieldValue(card, "type"), 2060 card.getAttribute("data-handle") || "" 2061 ].join(" ")).indexOf(searchQuery) !== -1; 2062 var matches = matchesSearch && groupNames.every(function (field) { 2063 return groups[field].some(function (control) { 2064 return cardMatchesControl(card, field, control); 2065 }); 2066 }); 2067 2068 card.hidden = !matches; 2069 card.setAttribute("aria-hidden", matches ? "false" : "true"); 2070 2071 if (matches) { 2072 visible += 1; 2073 } 2074 }); 2075 2076 if (resultCount) { 2077 setText(resultCount, renderTemplate(labels.resultCount || "{count}ä»¶ã®åå", { count: visible })); 2078 } 2079 2080 visibleCount = visible; 2081 setText(filterCount, String(activeCount)); 2082 setHidden(filterCount, activeCount === 0); 2083 setHidden(filterClear, activeCount === 0); 2084 2085 setHidden(emptyState, visible !== 0); 2086 2087 if (shouldAnnounce) { 2088 announceCollection(renderTemplate(labels.filtered || "çµãè¾¼ã¿çµæã¯{count}ååã§ãã", { count: visible })); 2089 } 2090 } 2091 2092 function compareText(left, right, field) { 2093 var first = cardFieldValue(left, field); 2094 var second = cardFieldValue(right, field); 2095 2096 if (collator) { 2097 return collator.compare(first, second); 2098 } 2099 2100 return first < second ? -1 : (first > second ? 1 : 0); 2101 } 2102 2103 function applySort(shouldAnnounce) { 2104 var value = normalize(sortControl ? sortControl.value : "featured"); 2105 var ordered = cards.slice(); 2106 var selectedOption = sortControl && sortControl.options ? sortControl.options[sortControl.selectedIndex] : null; 2107 2108 ordered.sort(function (left, right) { 2109 if (["price-ascending", "price_asc", "price-asc", "price-low-high"].indexOf(value) !== -1) { 2110 return numericCardValue(left, "price") - numericCardValue(right, "price"); 2111 } 2112 2113 if (["price-descending", "price_desc", "price-desc", "price-high-low"].indexOf(value) !== -1) { 2114 return numericCardValue(right, "price") - numericCardValue(left, "price"); 2115 } 2116 2117 if (["title-ascending", "title_asc", "title-asc", "a-z"].indexOf(value) !== -1) { 2118 return compareText(left, right, "title"); 2119 } 2120 2121 if (["title-descending", "title_desc", "title-desc", "z-a"].indexOf(value) !== -1) { 2122 return compareText(right, left, "title"); 2123 } 2124 2125 return Number(left.getAttribute("data-clc-original-index")) - 2126 Number(right.getAttribute("data-clc-original-index")); 2127 }); 2128 2129 if (grid) { 2130 ordered.forEach(function (card) { 2131 grid.appendChild(card); 2132 }); 2133 } 2134 2135 if (shouldAnnounce) { 2136 announceCollection(renderTemplate(labels.sorted || "{sort}ã«ä¸¦ã¹æ¿ãã¾ããã{count}ååããã¾ãã", { 2137 sort: selectedOption ? selectedOption.textContent.trim() : "ããããé ", 2138 count: visibleCount 2139 })); 2140 } 2141 } 2142 2143 function listName() { 2144 var listTitle = collection.querySelector(".clc-collection-title"); 2145 return listTitle ? listTitle.textContent.trim() : "ååä¸è¦§"; 2146 } 2147 2148 function cardItem(card) { 2149 var link = card.querySelector("[data-clc-product-link], .clc-card-link"); 2150 var itemId = card.getAttribute("data-product-id") || card.getAttribute("data-handle") || ""; 2151 var itemName = cardFieldValue(card, "title"); 2152 var displayedCards = grid ? toArray(grid.querySelectorAll(".clc-product-card:not([hidden])")) : cards; 2153 var displayedIndex = displayedCards.indexOf(card); 2154 2155 if (!itemId && link) { 2156 try { 2157 var pathParts = new URL(link.href, window.location.href).pathname.split("/").filter(
2157Boolean); 2158 itemId = decodeURIComponent(pathParts[pathParts.length - 1] || ""); 2159 } catch (error) { 2160 itemId = link.getAttribute("href") || itemName; 2161 } 2162 } 2163 2164 return { 2165 item_id: String(itemId || itemName), 2166 item_name: itemName, 2167 item_category: cardFieldValue(card, "type"), 2168 item_list_name: listName(), 2169 price: numericCardValue(card, "price"), 2170 index: displayedIndex >= 0 ? displayedIndex + 1 : 1 2171 }; 2172 } 2173 2174 function pushCardSelection(card) { 2175 2176 sendAnalyticsEvent("select_item", { 2177 item_list_name: listName(), 2178 items: [cardItem(card)] 2179 }); 2180 } 2181 2182 function pushViewItemListOnce() { 2183 var key; 2184 var viewed; 2185 2186 if (!cards.length) { 2187 return; 2188 } 2189 2190 key = String(window.location.pathname || "") + "::" + listName(); 2191 viewed = window.__CLCCommerceViewedLists; 2192 2193 if (!viewed || typeof viewed.has !== "function" || typeof viewed.add !== "function") { 2194 viewed = new Set(); 2195 window.__CLCCommerceViewedLists = viewed; 2196 } 2197 2198 if (viewed.has(key)) { 2199 return; 2200 } 2201 2202 viewed.add(key); 2203 sendAnalyticsEvent("view_item_list", { 2204 item_list_name: listName(), 2205 items: cards.map(cardItem) 2206 }); 2207 } 2208 2209 controls.forEach(function (control) { 2210 control.addEventListener("change", function () { 2211 applyFilters(true); 2212 }); 2213 }); 2214 2215 if (sortControl) { 2216 sortControl.addEventListener("change", function () { 2217 applySort(true); 2218 }); 2219 } 2220 2221 if (filterToggle && filterPanel) { 2222 filterToggle.addEventListener("click", function () { 2223 setFilterOpen(filterToggle.getAttribute("aria-expanded") !== "true", false); 2224 }); 2225 2226 if (filterClose) { 2227 filterClose.addEventListener("click", function () { 2228 setFilterOpen(false, true); 2229 }); 2230 } 2231 2232 collection.addEventListener("keydown", function (event) { 2233 if (event.key === "Escape" && filterToggle.getAttribute("aria-expanded") === "true") { 2234 setFilterOpen(false, true); 2235 } 2236 }); 2237 2238 if (filterMedia && typeof filterMedia.addEventListener === "function") { 2239 filterMedia.addEventListener("change", synchronizeFilterViewport); 2240 } else if (filterMedia && typeof filterMedia.addListener === "function") { 2241 filterMedia.addListener(synchronizeFilterViewport); 2242 } else { 2243 window.addEventListener("resize", synchronizeFilterViewport, { passive: true }); 2244 } 2245 } 2246 2247 if (filterClear) { 2248 filterClear.addEventListener("click", function () { 2249 controls.forEach(function (control) { 2250 control.checked = false; 2251 }); 2252 applyFilters(true); 2253 }); 2254 } 2255 2256 cards.forEach(function (card) { 2257 card.addEventListener("click", function (event) { 2258 if (event.target.closest("[data-clc-product-link], .clc-card-link")) { 2259 pushCardSelection(card); 2260 } 2261 }); 2262 }); 2263 2264 synchronizeFilterViewport(); 2265 applySort(false); 2266 applyFilters(false); 2267 pushViewItemListOnce(); 2268 } 2269 2270 function initHomeHero(hero) { 2271 if (hero.getAttribute("data-clc-initialized") === "true") { 2272 return; 2273 } 2274 2275 var viewport = hero.querySelector("[data-clc-home-hero-viewport]"); 2276 var slides = toArray(hero.querySelectorAll("[data-clc-home-slide]")); 2277 var dots = toArray(hero.querySelectorAll("[data-clc-home-hero-dot]")); 2278 var previous = hero.querySelector("[data-clc-home-hero-previous]"); 2279 var next = hero.querySelector("[data-clc-home-hero-next]"); 2280 var status = hero.querySelector("[data-clc-home-hero-status]"); 2281 var reducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 2282 var currentIndex = 0; 2283 var scrollTimer = 0; 2284 2285 if (!viewport || !slides.length) { 2286 return; 2287 } 2288 2289 hero.setAttribute("data-clc-initialized", "true"); 2290 2291 function nearestIndex() { 2292 var left = viewport.scrollLeft; 2293 var bestIndex = 0; 2294 var bestDistance = Infinity; 2295 2296 slides.forEach(function (slide, index) { 2297 var distance = Math.abs(slide.offsetLeft - left); 2298 if (distance < bestDistance) { 2299 bestDistance = distance; 2300 bestIndex = index; 2301 } 2302 }); 2303 2304 return bestIndex; 2305 } 2306 2307 function updateState(index, announce) { 2308 currentIndex = Math.max(0, Math.min(slides.length - 1, index));
2309 slides.forEach(function (slide, slideIndex) { 2310 var active = slideIndex === currentIndex; 2311 2312 if (!active && slide.contains(document.activeElement)) { 2313 try { 2314 viewport.focus({ preventScroll: true }); 2315 } catch (error) { 2316 viewport.focus(); 2317 } 2318 } 2319 slide.setAttribute("aria-hidden", active ? "false" : "true"); 2320 if (active) { 2321 slide.removeAttribute("inert"); 2322 } else { 2323 slide.setAttribute("inert", ""); 2324 } 2325 }); 2326 dots.forEach(function (dot, dotIndex) { 2327 dot.setAttribute("aria-pressed", dotIndex === currentIndex ? "true" : "false"); 2328 }); 2329 if (previous) { 2330 previous.disabled = currentIndex === 0; 2331 } 2332 if (next) { 2333 next.disabled = currentIndex === slides.length - 1; 2334 } 2335 if (announce) { 2336 setText(status, String(currentIndex + 1) + " / " + String(slides.length)); 2337 } 2338 } 2339 2340 function showSlide(index, announce) { 2341 var targetIndex = Math.max(0, Math.min(slides.length - 1, index)); 2342 var target = slides[targetIndex]; 2343 2344 updateState(targetIndex, announce); 2345 if (typeof viewport.scrollTo === "function") { 2346 viewport.scrollTo({ 2347 left: target.offsetLeft, 2348 behavior: reducedMotion ? "auto" : "smooth" 2349 }); 2350 } else { 2351 viewport.scrollLeft = target.offsetLeft; 2352 } 2353 } 2354 2355 dots.forEach(function (dot) { 2356 dot.addEventListener("click", function () { 2357 showSlide(Number(dot.getAttribute("data-clc-home-hero-dot")) || 0, true); 2358 }); 2359 }); 2360 2361 if (previous) { 2362 previous.addEventListener("click", function () { 2363 showSlide(currentIndex - 1, true); 2364 }); 2365 } 2366 2367 if (next) { 2368 next.addEventListener("click", function () { 2369 showSlide(currentIndex + 1, true); 2370 }); 2371 } 2372 2373 viewport.addEventListener("keydown", function (event) { 2374 if (event.key === "ArrowLeft" || event.key === "ArrowRight") { 2375 event.preventDefault(); 2376 showSlide(currentIndex + (event.key === "ArrowRight" ? 1 : -1), true); 2377 } 2378 }); 2379 2380 viewport.addEventListener("scroll", function () { 2381 window.clearTimeout(scrollTimer); 2382 scrollTimer = window.setTimeout(function () { 2383 var nextIndex = nearestIndex(); 2384 updateState(nextIndex, nextIndex !== currentIndex); 2385 }, 120); 2386 }, { passive: true }); 2387 2388 window.addEventListener("resize", function () { 2389 showSlide(currentIndex, false); 2390 }, { passive: true }); 2391 2392 updateState(0, false); 2393 } 2394 2395 function initHomeTestimonials(testimonials) { 2396 if (testimonials.getAttribute("data-clc-initialized") === "true") { 2397 return; 2398 } 2399 2400 var slides = toArray(testimonials.querySelectorAll("[data-clc-home-testimonial-slide]")); 2401 var controls = toArray(testimonials.querySelectorAll("[data-clc-home-testimonial-control]")); 2402 var stage = testimonials.querySelector(".clc-home-testimonial-stage"); 2403 var status = testimonials.querySelector("[data-clc-home-testimonial-status]"); 2404 var reducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 2405 var currentIndex = 0; 2406 var autoplayTimer = 0; 2407 var touchStartX = null; 2408 2409 if (!slides.length || !controls.length) { 2410 return; 2411 } 2412 2413 testimonials.setAttribute("data-clc-initialized", "true"); 2414 2415 function normalizedIndex(index) { 2416 return (index + slides.length) % slides.length; 2417 } 2418 2419 function updateSlide(index, announce) { 2420 currentIndex = normalizedIndex(index); 2421 2422 slides.forEach(function (slide, slideIndex) { 2423 var selected = slideIndex === currentIndex; 2424 setHidden(slide, !selected); 2425 slide.classList.toggle("is-active", selected); 2426 }); 2427 2428 controls.forEach(function (control) { 2429 var selected = Number(control.getAttribute("data-clc-home-testimonial-control")) === currentIndex; 2430 control.setAttribute("aria-current", selected ? "true" : "false"); 2431 }); 2432 2433 if (announce) { 2434 setText(status, String(currentIndex + 1) + " / " + String(slides.length)); 2435 } 2436 } 2437 2438 function stopAutoplay() { 2439 if (autoplayTimer) { 2440 window.clearInterval(autoplayTimer); 2441 autoplayTimer = 0; 2442 } 2443 } 2444 2445 function startAutoplay() { 2446 stopAutoplay(); 2447 if (reducedMotion || document.hidden) { 2448 return; 2449 } 2450 autoplayTimer = window.setInterval(function () { 2451 updateSlide(currentIndex + 1, false); 2452 }, 5000); 2453 } 2454 2455 function activateControl(control, announce) { 2456 updateSlide(Number(control.getAttribute("data-clc-home-testimonial-control")) || 0, announce); 2457 startAutoplay(); 2458 } 2459
2460 controls.forEach(function (control) { 2461 control.addEventListener("click", function () { 2462 activateControl(control, true); 2463 }); 2464 2465 control.addEventListener("keydown", function (event) { 2466 var group; 2467 var groupControls; 2468 var groupIndex; 2469 var nextIndex; 2470 2471 if (["ArrowLeft", "ArrowRight", "Home", "End"].indexOf(event.key) === -1) { 2472 return; 2473 } 2474 2475 group = control.parentElement; 2476 groupControls = toArray(group.querySelectorAll("[data-clc-home-testimonial-control]")); 2477 groupIndex = groupControls.indexOf(control); 2478 nextIndex = groupIndex; 2479 2480 if (event.key === "Home") { 2481 nextIndex = 0; 2482 } else if (event.key === "End") { 2483 nextIndex = groupControls.length - 1; 2484 } else { 2485 nextIndex = (groupIndex + (event.key === "ArrowRight" ? 1 : -1) + groupControls.length) % groupControls.length; 2486 } 2487 2488 event.preventDefault(); 2489 groupControls[nextIndex].focus(); 2490 activateControl(groupControls[nextIndex], true); 2491 }); 2492 }); 2493 2494 testimonials.addEventListener("pointerenter", stopAutoplay); 2495 testimonials.addEventListener("pointerleave", startAutoplay); 2496 testimonials.addEventListener("focusin", stopAutoplay); 2497 testimonials.addEventListener("focusout", function () { 2498 window.setTimeout(function () { 2499 if (!testimonials.contains(document.activeElement)) { 2500 startAutoplay(); 2501 } 2502 }, 0); 2503 }); 2504 2505 if (stage) { 2506 stage.addEventListener("touchstart", function (event) { 2507 touchStartX = event.touches && event.touches[0] ? event.touches[0].clientX : null; 2508 stopAutoplay(); 2509 }, { passive: true }); 2510 2511 stage.addEventListener("touchend", function (event) { 2512 var touchEndX = event.changedTouches && event.changedTouches[0] ? event.changedTouches[0].clientX : null; 2513 var distance = touchStartX !== null && touchEndX !== null ? touchEndX - touchStartX : 0; 2514 2515 if (Math.abs(distance) >= 40) { 2516 updateSlide(currentIndex + (distance < 0 ? 1 : -1), true); 2517 } 2518 touchStartX = null; 2519 startAutoplay(); 2520 }, { passive: true }); 2521 } 2522 2523 document.addEventListener("visibilitychange", function () { 2524 if (document.hidden) { 2525 stopAutoplay(); 2526 } else { 2527 startAutoplay(); 2528 } 2529 }); 2530 2531 updateSlide(0, false); 2532 startAutoplay(); 2533 } 2534 2535 function initWeeklyCountdown(countdown) { 2536 if (countdown.getAttribute("data-clc-initialized") === "true") { 2537 return; 2538 } 2539 2540 var days = countdown.querySelector("[data-clc-countdown-days]"); 2541 var hours = countdown.querySelector("[data-clc-countdown-hours]"); 2542 var minutes = countdown.querySelector("[data-clc-countdown-minutes]"); 2543 var seconds = countdown.querySelector("[data-clc-countdown-seconds]"); 2544 var timer = 0; 2545 var tokyoOffset = 9 * 60 * 60 * 1000; 2546 2547 if (!days || !hours || !minutes || !seconds) { 2548 return; 2549 } 2550 2551 countdown.setAttribute("data-clc-initialized", "true"); 2552 2553 function pad(value) { 2554 return String(Math.max(0, value)).padStart(2, "0"); 2555 } 2556 2557 function nextDeadline(now) { 2558 var tokyoNow = new Date(now.getTime() + tokyoOffset); 2559 var weekday = tokyoNow.getUTCDay(); 2560 var daysAhead = (1 - weekday + 7) % 7; 2561 var tokyoMidnight = Date.UTC(tokyoNow.getUTCFullYear(), tokyoNow.getUTCMonth(), tokyoNow.getUTCDate()); 2562 var deadline = tokyoMidnight + (daysAhead * 86400000) - tokyoOffset; 2563 2564 if (deadline <= now.getTime()) { 2565 deadline += 7 * 86400000; 2566 } 2567 return deadline; 2568 } 2569 2570 function updateCountdown() { 2571 var now = new Date(); 2572 var remaining = Math.max(0, Math.floor((nextDeadline(now) - now.getTime()) / 1000)); 2573 var remainingDays = Math.floor(remaining / 86400); 2574 var remainingHours = Math.floor((remaining % 86400) / 3600); 2575 var remainingMinutes = Math.floor((remaining % 3600) / 60); 2576 var remainingSeconds = remaining % 60; 2577 2578 setText(days, pad(remainingDays)); 2579 setText(hours, pad(remainingHours)); 2580 setText(minutes, pad(remainingMinutes)); 2581 setText(seconds, pad(remainingSeconds)); 2582 countdown.setAttribute( 2583 "aria-label", 2584 "æ¬¡ã®æææ¥0æã¾ã§" + String(remainingDays) + "æ¥" + String(remainingHours) + "æé" + String(remainingMinutes) + "å" 2585 ); 2586 } 2587 2588 function startCountdown() { 2589 if (timer) { 2590 window.clearInterval(timer); 2591 } 2592 updateCountdown(); 2593 timer = window.setInterval(updateCountdown, 1000); 2594 } 2595 2596 startCountdown(); 2597 2598 document.addEventListener("visibilitychange", function () { 2599 if (!document.hidden) { 2600 updateCountdown(); 2601 } 2602 }); 2603 2604 window.addEventListener("pagehide", function () { 2605 window.clearInterval(timer); 2606 timer = 0; 2607 }); 2608 window.addEventListener("pageshow", function (event) { 2609 if (event.persisted) { 2610 startCountdown(); 2611 } 2612 }); 2613 } 2614 2615 function initHomeProductList(list) { 2616 if (list.getAttribute("data-clc-initialized") === "true") { 2617 return; 2618 } 2619 2620 var cards = toArray(list.querySelectorAll(".clc-product-card")); 2621 var listName = list.getAttribute("data-list-name") || "注ç®ã®åå"; 2622 var viewed = window.__CLCCommerceViewedLists; 2623 var viewKey = String(window.location.pathname || "") + "::" + listName; 2624 2625 list.setAttribute("data-clc-initialized", "true"); 2626 2627 function cardItem(card, index) { 2628 var itemId = card.getAttribute("data-product-id") || card.getAttribute("data-handle") || ""; 2629 return {
2630 item_id: String(itemId || cardFieldValue(card, "title")), 2631 item_name: cardFieldValue(card, "title"), 2632 item_category: cardFieldValue(card, "type"), 2633 item_list_name: listName, 2634 price: numericCardValue(card, "price"), 2635 index: index + 1 2636 }; 2637 } 2638 2639 cards.forEach(function (card, index) { 2640 card.addEventListener("click", function (event) { 2641 if (event.target.closest("[data-clc-product-link], .clc-card-link")) { 2642 sendAnalyticsEvent("select_item", { 2643 item_list_name: listName, 2644 items: [cardItem(card, index)] 2645 }); 2646 } 2647 }); 2648 }); 2649 2650 if (!cards.length) { 2651 return; 2652 } 2653 2654 if (!viewed || typeof viewed.has !== "function" || typeof viewed.add !== "function") { 2655 viewed = new Set(); 2656 window.__CLCCommerceViewedLists = viewed; 2657 } 2658 2659 if (!viewed.has(viewKey)) { 2660 viewed.add(viewKey); 2661 sendAnalyticsEvent("view_item_list", { 2662 item_list_name: listName, 2663 items: cards.map(cardItem) 2664 }); 2665 } 2666 } 2667 2668 function focusableElements(panel) { 2669 return toArray(panel.querySelectorAll( 2670 "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])" 2671 )).filter(function (element) { 2672 return !element.hidden && 2673 !element.closest("[hidden]") && 2674 element.getAttribute("aria-hidden") !== "true" && 2675 !element.classList.contains("clc-panel-backdrop"); 2676 }); 2677 } 2678 2679 function initUtilityPanels(storefront) { 2680 var panels = toArray(storefront.querySelectorAll("[data-clc-panel]")); 2681 var triggers = toArray(storefront.querySelectorAll("[data-clc-panel-open]")); 2682 var activePanel = null; 2683 var activeTrigger = null; 2684 var inertedBackground = []; 2685 2686 if (!panels.length || storefront.getAttribute("data-clc-panels-ready") === "true") { 2687 return; 2688 } 2689 storefront.setAttribute("data-clc-panels-ready", "true"); 2690 2691 function setTriggerState(panelId, expanded) { 2692 triggers.forEach(function (trigger) { 2693 if (trigger.getAttribute("data-clc-panel-open") !== panelId) { 2694 return; 2695 } 2696 trigger.setAttribute("aria-controls", panelId); 2697 trigger.setAttribute("aria-expanded", expanded ? "true" : "false"); 2698 }); 2699 } 2700
2701 function restoreBackground() { 2702 inertedBackground.forEach(function (state) { 2703 if (state.hadInert) { 2704 state.element.setAttribute("inert", ""); 2705 } else { 2706 state.element.removeAttribute("inert"); 2707 } 2708 if (state.ariaHidden == null) { 2709 state.element.removeAttribute("aria-hidden"); 2710 } else { 2711 state.element.setAttribute("aria-hidden", state.ariaHidden); 2712 } 2713 }); 2714 inertedBackground = []; 2715 } 2716 2717 function isolatePanel(panel) { 2718 restoreBackground(); 2719 toArray(storefront.children).forEach(function (element) { 2720 if (element === panel) { 2721 return; 2722 } 2723 inertedBackground.push({ 2724 element: element, 2725 hadInert: element.hasAttribute("inert"), 2726 ariaHidden: element.getAttribute("aria-hidden") 2727 }); 2728 element.setAttribute("inert", ""); 2729 element.setAttribute("aria-hidden", "true"); 2730 }); 2731 } 2732 2733 function closePanel(returnFocus) { 2734 var trigger = activeTrigger; 2735 var panelId; 2736 2737 if (!activePanel) { 2738 return; 2739 } 2740 panelId = activePanel.id; 2741 activePanel.classList.remove("is-open"); 2742 activePanel.hidden = true; 2743 activePanel = null; 2744 activeTrigger = null; 2745 document.body.classList.remove("clc-overlay-open");
2746 restoreBackground(); 2747 setTriggerState(panelId, false); 2748 if (returnFocus && trigger && document.contains(trigger)) { 2749 trigger.focus(); 2750 } 2751 } 2752 2753 function showPanel(panelId, trigger) { 2754 var panel = storefront.querySelector("#" + String(panelId || "")); 2755 var focusable; 2756 2757 if (!panel) { 2758 return; 2759 } 2760 if (activePanel && activePanel !== panel) { 2761 closePanel(false); 2762 } 2763 activePanel = panel; 2764 activeTrigger = trigger || null; 2765 panel.hidden = false; 2766 setTriggerState(panel.id, true); 2767 isolatePanel(panel); 2768 document.body.classList.add("clc-overlay-open"); 2769 document.dispatchEvent(new CustomEvent("clc:panel-opened", { 2770 detail: { panelId: panel.id, panel: panel, trigger: activeTrigger } 2771 })); 2772 window.requestAnimationFrame(function () { 2773 panel.classList.add("is-open"); 2774 focusable = focusableElements(panel); 2775 if (focusable.length) { 2776 focusable[0].focus(); 2777 } 2778 }); 2779 } 2780 2781 triggers.forEach(function (trigger) { 2782 var panelId = trigger.getAttribute("data-clc-panel-open"); 2783 trigger.setAttribute("aria-controls", panelId); 2784 trigger.setAttribute("aria-expanded", "false"); 2785 trigger.addEventListener("click", function () { 2786 showPanel(panelId, trigger); 2787 }); 2788 }); 2789 2790 panels.forEach(function (panel) { 2791 toArray(panel.querySelectorAll("[data-clc-panel-close]")).forEach(function (button) { 2792 button.addEventListener("click", function () { 2793 closePanel(true); 2794 }); 2795 }); 2796 2797 panel.addEventListener("keydown", function (event) { 2798 var focusable; 2799 var first; 2800 var last; 2801 2802 if (event.key === "Escape") { 2803 event.preventDefault(); 2804 closePanel(true); 2805 return; 2806 } 2807 if (event.key !== "Tab") { 2808 return; 2809 } 2810 focusable = focusableElements(panel); 2811 if (!focusable.length) { 2812 return; 2813 } 2814 first = focusable[0]; 2815 last = focusable[focusable.length - 1]; 2816 if (event.shiftKey && document.activeElement === first) { 2817 event.preventDefault(); 2818 last.focus(); 2819 } else if (!event.shiftKey && document.activeElement === last) { 2820 event.preventDefault(); 2821 first.focus(); 2822 } 2823 }); 2824 }); 2825 2826 document.addEventListener("keydown", function (event) { 2827 if (event.key === "Escape" && activePanel) { 2828 event.preventDefault(); 2829 closePanel(true); 2830 } 2831 }); 2832 2833 document.addEventListener("clc:open-panel", function (event) { 2834 var detail = event.detail || {}; 2835 showPanel(detail.panelId, detail.trigger); 2836 }); 2837 } 2838 2839 function initCartReminder(reminder) { 2840 var storefront = reminder.closest(".clc-storefront"); 2841 var image = reminder.querySelector("[data-clc-cart-reminder-image]"); 2842 var media = reminder.querySelector(".clc-cart-reminder-media"); 2843 var name = reminder.querySelector("[data-clc-cart-reminder-name]"); 2844 var meta = reminder.querySelector("[data-clc-cart-reminder-meta]"); 2845 var price = reminder.querySelector("[data-clc-cart-reminder-price]"); 2846 var quantity = reminder.querySelector("[data-clc-cart-reminder-quantity]"); 2847 var subtotal = reminder.querySelector("[data-clc-cart-reminder-subtotal]"); 2848 var extra = reminder.querySelector("[data-clc-cart-reminder-extra]"); 2849 var openCart = reminder.querySelector("[data-clc-cart-reminder-open-cart]"); 2850 var initialItems = readCart(); 2851 var seenKey = "clc_cart_reminder_seen_v1"; 2852 var seenInMemory = false; 2853 var timer = null; 2854 2855 if ( 2856 !storefront || 2857 !name || 2858 !openCart || 2859 !initialItems.length || 2860 reminder.getAttribute("data-clc-cart-reminder-ready") === "true" 2861 ) { 2862 return; 2863 } 2864 reminder.setAttribute("data-clc-cart-reminder-ready", "true"); 2865 2866 function hasBeenSeen() { 2867 if (seenInMemory) { 2868 return true; 2869 } 2870 try { 2871 return window.sessionStorage.getItem(seenKey) === "1"; 2872 } catch (error) { 2873 return false;
2874 } 2875 } 2876 2877 function markSeen() { 2878 seenInMemory = true; 2879 try { 2880 window.sessionStorage.setItem(seenKey, "1"); 2881 } catch (error) { 2882 // A session-only in-memory flag still prevents repeated interruption. 2883 } 2884 } 2885 2886 function render() { 2887 var items = readCart(); 2888 var item = items[0]; 2889 var details = []; 2890 var total = 0; 2891 var currency = "JPY"; 2892 2893 if (!item) { 2894 return false; 2895 } 2896 2897 setText(name, item.title); 2898 if (item.variantTitle && normalize(item.variantTitle) !== "default title") { 2899 details.push(item.variantTitle); 2900 } 2901 if (item.sellingPlanName) { 2902 details.push(item.sellingPlanName); 2903 } 2904 setText(meta, details.join(" ï¼ ")); 2905 setHidden(meta, details.length === 0); 2906 setText(price, item.price ? formatMoney(item.price) : "â"); 2907 setText(quantity, item.quantity); 2908 2909 if (image && media) { 2910 /* imageUrl 㯠localStorage 復å å¤ãå«ããã Shopify CDN ã® https URL ã®ã¿è¨±å¯ 2911 ï¼responsiveShopifyImageUrl ããã¹ãæ¤è¨¼ãå ¼ããã2026-08-30ç·ç¹æ¤ L-8ï¼ */ 2912 var safeImageSrc = item.imageUrl ? responsiveShopifyImageUrl(item.imageUrl, 192) : ""; 2913 if (safeImageSrc) { 2914 image.src = safeImageSrc; 2915 image.alt = item.imageAlt || item.title; 2916 setHidden(image, false); 2917 setHidden(media, false); 2918 } else { 2919 image.removeAttribute("src"); 2920 image.alt = ""; 2921 setHidden(image, true); 2922 setHidden(media, true); 2923 } 2924 } 2925 2926 items.forEach(function (cartItem) { 2927 if (cartItem.price) { 2928 total += cartItem.price.amount * cartItem.quantity; 2929 currency = cartItem.price.currencyCode || currency; 2930 } 2931 }); 2932 setText(subtotal, formatMoney({ amount: total, currencyCode: currency })); 2933 setText(extra, items.length > 1 ? "ã»ã" + String(items.length - 1) + "ååãã«ã¼ãã«å ¥ã£ã¦ãã¾ãã" : ""); 2934 setHidden(extra, items.length <= 1); 2935 return true; 2936 } 2937 2938 function showReminder() { 2939 var visiblePanel; 2940 2941 timer = null; 2942 if (hasBeenSeen() || !render()) { 2943 return; 2944 } 2945 if (document.visibilityState === "hidden") { 2946 document.addEventListener("visibilitychange", function onVisibilityChange() { 2947 if (document.visibilityState !== "visible") { 2948 return; 2949 } 2950 document.removeEventListener("visibilitychange", onVisibilityChange); 2951 timer = window.setTimeout(showReminder, 1200); 2952 }); 2953 return; 2954 } 2955 2956 visiblePanel = storefront.querySelector("[data-clc-panel]:not([hidden])"); 2957 if (visiblePanel && visiblePanel !== reminder) { 2958 return; 2959 } 2960 markSeen(); 2961 openUtilityPanel(reminder.id, null); 2962 } 2963 2964 openCart.addEventListener("click", function () { 2965 var cartTrigger = storefront.querySelector('[data-clc-panel-open="clc-cart-panel"]'); 2966 var close = reminder.querySelector("[data-clc-panel-close]"); 2967 2968 markSeen(); 2969 if (close) { 2970 close.click(); 2971 } 2972 window.setTimeout(function () { 2973 openUtilityPanel("clc-cart-panel", cartTrigger || null); 2974 }, 0); 2975 }); 2976 2977 document.addEventListener("clc:panel-opened", function (event) { 2978 var detail = event.detail || {}; 2979 if (detail.panelId === "clc-cart-panel") { 2980 markSeen(); 2981 } 2982 }); 2983 2984 document.addEventListener("clc:cart-changed", function () { 2985 if (readCart().length) { 2986 if (!reminder.hidden) { 2987 render(); 2988 } 2989 return; 2990 } 2991 if (!reminder.hidden) { 2992 var close = reminder.querySelector("[data-clc-panel-close]"); 2993 if (close) { 2994 close.click(); 2995 } 2996 } 2997 if (timer) { 2998 window.clearTimeout(timer); 2999 timer = null; 3000 } 3001 }); 3002 3003 timer = window.setTimeout(showReminder, 12000); 3004 } 3005 3006 function initCart(cart) { 3007 var list = cart.querySelector("[data-clc-cart-items]"); 3008 var empty = cart.querySelector("[data-clc-cart-empty]"); 3009 var summary = cart.querySelector("[data-clc-cart-summary]"); 3010 var subtotal = cart.querySelector("[data-clc-cart-subtotal]"); 3011 var checkout = cart.querySelector("[data-clc-cart-checkout]"); 3012 var errorBox = cart.querySelector("[data-clc-cart-error]"); 3013 var statusBox = cart.querySelector("[data-clc-cart-status]"); 3014 var cartHeading = cart.querySelector("#clc-cart-title"); 3015 var discountNotice = cart.querySelector("[data-clc-cart-discount]"); 3016 var discountCodeText = cart.querySelector("[data-clc-cart-discount-code]"); 3017 var busy = false;
3018 var preparedCheckout = null; 3019 var prepareController = null; 3020 var prepareSequence = 0; 3021 var lastViewedFingerprint = ""; 3022 var lastRenderedItemsFingerprint = ""; 3023 var pendingCartFocus = null; 3024 3025 if (!list || !checkout || cart.getAttribute("data-clc-cart-ready") === "true") { 3026 return; 3027 } 3028 cart.setAttribute("data-clc-cart-ready", "true"); 3029 3030 function updateCounts(items) { 3031 var count = items.reduce(function (total, item) { return total + item.quantity; }, 0); 3032 toArray(document.querySelectorAll("[data-clc-cart-count]")).forEach(function (badge) { 3033 setText(badge, String(count)); 3034 setHidden(badge, count === 0); 3035 }); 3036 } 3037 3038 function makeText(tag, className, value) { 3039 var element = document.createElement(tag); 3040 element.className = className; 3041 setText(element, value); 3042 return element; 3043 } 3044 3045 function cartFingerprint(items, discountCode) { 3046 return JSON.stringify({ 3047 items: items.map(function (item) { 3048 return [item.entryId || "", item.key, item.productHandle, item.variantId, item.sellingPlanId || "", item.quantity]; 3049 }), 3050 discountCode: normalizeDiscountCode(discountCode == null ? activeDiscountCode() : discountCode) 3051 }); 3052 } 3053 3054 function checkoutLines(items) { 3055 return items.map(function (item) { 3056 return { 3057 product_handle: item.productHandle, 3058 variant_id: item.variantId, 3059 selling_plan_id: item.sellingPlanId || "", 3060 quantity: item.quantity 3061 }; 3062 }); 3063 } 3064 3065 function pushViewCartOnce(items) { 3066 var fingerprint; 3067 var value; 3068 var currency; 3069 3070 if (!items.length) { 3071 return; 3072 } 3073 fingerprint = cartFingerprint(items); 3074 if (fingerprint === lastViewedFingerprint) { 3075 return; 3076 } 3077 3078 lastViewedFingerprint = fingerprint; 3079 value = items.reduce(function (total, item) { 3080 return total + (item.price ? item.price.amount * item.quantity : 0); 3081 }, 0); 3082 currency = items.find(function (item) { return item.price && item.price.currencyCode; }); 3083 sendAnalyticsEvent("view_cart", { 3084 currency: currency && currency.price.currencyCode || settings.currency || "JPY", 3085 value: Number(value.toFixed(2)), 3086 items: items.map(function (item, index) { 3087 return cartAnalyticsItem(item, index); 3088 }) 3089 }); 3090 } 3091
3092 function invalidatePreparedCheckout() { 3093 prepareSequence += 1; 3094 if (prepareController && typeof prepareController.abort === "function") { 3095 prepareController.abort(); 3096 } 3097 prepareController = null; 3098 preparedCheckout = null; 3099 busy = false; 3100 } 3101 3102 function itemsRenderFingerprint(items) { 3103 return JSON.stringify(items.map(function (item) { 3104 return [item.entryId, item.key, item.title, item.variantTitle, item.sellingPlanName, item.quantity, item.imageUrl, item.price]; 3105 })); 3106 } 3107 3108 function restoreCartFocus(items) { 3109 var intent = pendingCartFocus; 3110 var targetRow; 3111 var target; 3112 3113 pendingCartFocus = null; 3114 if (!intent) { 3115 return; 3116 } 3117 targetRow = toArray(list.querySelectorAll("[data-cart-entry-id]")).find(function (row) { 3118 return row.getAttribute("data-cart-entry-id") === intent.entryId; 3119 }); 3120 if (!targetRow && items.length) { 3121 targetRow = list.children[Math.min(intent.index, items.length - 1)] || null; 3122 } 3123 if (targetRow) { 3124 target = targetRow.querySelector('[data-cart-action="' + intent.action + '"]') || targetRow.querySelector("[data-cart-action]"); 3125 } 3126 if (!target && cartHeading) { 3127 target = cartHeading; 3128 } 3129 if (target && typeof target.focus === "function") { 3130 target.focus({ preventScroll: true }); 3131 } 3132 if (statusBox && intent.message) { 3133 setText(statusBox, ""); 3134 if (typeof window.requestAnimationFrame === "function") { 3135 window.requestAnimationFrame(function () { setText(statusBox, intent.message); }); 3136 } else { 3137 window.setTimeout(function () { setText(statusBox, intent.message); }, 0); 3138 } 3139 } 3140 } 3141 3142 function preparedLineForItem(item) { 3143 var lines = preparedCheckout && Array.isArray(preparedCheckout.currentLines) ? preparedCheckout.currentLines : []; 3144 return lines.find(function (line) { 3145 return line.variantId === item.variantId && line.sellingPlanId === (item.sellingPlanId || ""); 3146 }) || null; 3147 } 3148 3149 function analyticsItemWithCurrentPrice(item, index) { 3150 var analyticsItem = cartAnalyticsItem(item, index); 3151 var currentLine = preparedLineForItem(item); 3152 if (currentLine && currentLine.price) { 3153 analyticsItem.price = currentLine.price.amount; 3154 } 3155 return analyticsItem; 3156 } 3157 3158 function render() { 3159 var items = readCart(); 3160 var total = 0; 3161 var currency = "JPY"; 3162 var renderFingerprint = itemsRenderFingerprint(items); 3163 3164 if (renderFingerprint !== lastRenderedItemsFingerprint) { 3165 while (list.firstChild) { 3166 list.removeChild(list.firstChild); 3167 } 3168 items.forEach(function (item) { 3169 var row = document.createElement("li"); 3170 var media = document.createElement("div"); 3171 var content = document.createElement("div"); 3172 var details = []; 3173 var controls = document.createElement("div"); 3174 var decrease = document.createElement("button"); 3175 var quantity = makeText("span", "clc-cart-item-quantity", item.quantity); 3176 var increase = document.createElement("button"); 3177 var remove = document.createElement("button"); 3178 3179 row.className = "clc-cart-item"; 3180 row.setAttribute("data-cart-key", item.key); 3181 row.setAttribute("data-cart-entry-id", item.entryId || ""); 3182 media.className = "clc-cart-item-media"; 3183 /* localStorage 復å å¤ãå«ãããããã¹ãæ¤è¨¼ãéããªãURLã¯æç»ããªãï¼L-8ï¼ */ 3184 var safeRowImageSrc = item.imageUrl ? responsiveShopifyImageUrl(item.imageUrl, 192) : ""; 3185 if (safeRowImageSrc) { 3186 var image = document.createElement("img"); 3187 image.src = safeRowImageSrc; 3188 image.alt = item.imageAlt; 3189 image.width = 96; 3190 image.height = 96; 3191 image.loading = "lazy"; 3192 media.appendChild(image); 3193 } 3194 content.className = "clc-cart-item-content"; 3195 content.appendChild(makeText("h3", "clc-cart-item-title", item.title)); 3196 if (item.variantTitle && normalize(item.variantTitle) !== "default title") { 3197 details.push(item.variantTitle); 3198 } 3199 if (item.sellingPlanName) { 3200 details.push(item.sellingPlanName); 3201 } 3202 if (details.length) { 3203 content.appendChild(makeText("p", "clc-cart-item-meta", details.join(" ï¼ "))); 3204 } 3205 content.appendChild(makeText("p", "clc-cart-item-price", item.price ? formatMoney(item.price) : "â")); 3206
3207 controls.className = "clc-cart-item-controls"; 3208 decrease.type = "button"; 3209 decrease.className = "clc-cart-item-step"; 3210 decrease.setAttribute("data-cart-action", "decrease"); 3211 decrease.setAttribute("aria-label", item.title + "ã®æ°éãæ¸ãã"); 3212 setText(decrease, "â"); 3213 increase.type = "button"; 3214 increase.className = "clc-cart-item-step"; 3215 increase.setAttribute("data-cart-action", "increase"); 3216 increase.setAttribute("aria-label", item.title + "ã®æ°éãå¢ãã"); 3217 increase.disabled = item.quantity >= cartItemMaximum(item); 3218 setText(increase, "+"); 3219 remove.type = "button"; 3220 remove.className = "clc-cart-item-remove"; 3221 remove.setAttribute("data-cart-action", "remove"); 3222 remove.setAttribute("aria-label", item.title + "ãã«ã¼ãããåé¤"); 3223 setText(remove, labels.remove || "åé¤"); 3224 controls.appendChild(decrease); 3225 controls.appendChild(quantity); 3226 controls.appendChild(increase); 3227 controls.appendChild(remove); 3228 content.appendChild(controls); 3229 row.appendChild(media); 3230 row.appendChild(content); 3231 list.appendChild(row); 3232 3233 }); 3234 lastRenderedItemsFingerprint = renderFingerprint; 3235 restoreCartFocus(items); 3236 } 3237 3238 items.forEach(function (item, index) { 3239 var currentLine = preparedCheckout && preparedCheckout.fingerprint === cartFingerprint(items) ? preparedLineForItem(item) : null; 3240 var displayedPrice = currentLine && currentLine.price ? currentLine.price : item.price; 3241 var row = list.children[index]; 3242 if (row) { 3243 // Preserve quantity controls and keyboard focus while applying the 3244 // same current Shopify prices used by the subtotal and Checkout event. 3245 setText(row.querySelector(".clc-cart-item-price"), displayedPrice ? formatMoney(displayedPrice) : "â"); 3246 } 3247 if (item.price) { 3248 total += item.price.amount * item.quantity; 3249 currency = item.price.currencyCode || currency; 3250 } 3251 }); 3252 3253 if (preparedCheckout && preparedCheckout.fingerprint === cartFingerprint(items) && preparedCheckout.totalPrice) { 3254 total = preparedCheckout.totalPrice.amount; 3255 currency = preparedCheckout.totalPrice.currencyCode; 3256 } 3257 3258 updateCounts(items); 3259 setHidden(empty, items.length > 0); 3260 setHidden(summary, items.length === 0); 3261 setText(subtotal, items.length ? formatMoney({ amount: total, currencyCode: currency }) : "â"); 3262 var discountCode = activeDiscountCode(); 3263 setText(discountCodeText, discountCode); 3264 setHidden(discountNotice, !discountCode || items.length === 0); 3265 var fingerprint = cartFingerprint(items); 3266 var checkoutReady = Boolean( 3267 preparedCheckout && 3268 preparedCheckout.fingerprint === fingerprint && 3269 preparedCheckout.checkoutUrl 3270 ); 3271 3272 checkout.setAttribute("aria-disabled", busy || items.length === 0 || !checkoutReady ? "true" : "false"); 3273 checkout.setAttribute("aria-busy", busy ? "true" : "false"); 3274 if (!busy && checkoutReady) { 3275 // This must be the final Shopify Checkout URL before the user clicks. 3276 // Google's cross-domain linker decorates this real anchor; JavaScript 3277 // navigation to an asynchronously returned URL is not supported. 3278 checkout.href = preparedCheckout.checkoutUrl; 3279 checkout.removeAttribute("role"); // hrefãããéã¯æ¬ç©ã®ãªã³ã¯ã¨ãã¦éç¥ãã 3280 } else { 3281 checkout.removeAttribute("href"); 3282 checkout.setAttribute("role", "button"); // hrefç¡ãã®<a>ã¯ãã¼ã«ä¸å®ã«ãªãããæç¤ºãã 3283 } 3284 if (busy) { 3285 setText(checkout, labels.cartLoading || "è³¼å ¥ç»é¢ãæºåãã¦ãã¾ãâ¦"); 3286 } else { 3287 setText(checkout, labels.checkout || "è³¼å ¥æç¶ãã¸"); 3288 } 3289 } 3290 3291 function prepareCheckout(items) { 3292 var discountCode = activeDiscountCode(); 3293 var fingerprint = cartFingerprint(items, discountCode); 3294 var requestBody = { lines: checkoutLines(items) }; 3295 var requestId; 3296 3297 if (discountCode) { 3298 requestBody.discount_code = discountCode; 3299 } 3300
3301 if (!items.length || !settings.restUrl || !settings.nonce) { 3302 invalidatePreparedCheckout(); 3303 render(); 3304 return Promise.resolve(null); 3305 } 3306 3307 if (preparedCheckout && preparedCheckout.fingerprint === fingerprint) { 3308 busy = false; 3309 setHidden(errorBox, true); 3310 render(); 3311 return Promise.resolve(preparedCheckout); 3312 } 3313 3314 requestId = ++prepareSequence; 3315 if (prepareController && typeof prepareController.abort === "function") { 3316 prepareController.abort(); 3317 } 3318 prepareController = typeof window.AbortController === "function" ? new window.AbortController() : null; 3319 preparedCheckout = null; 3320 busy = true; 3321 setHidden(errorBox, true); 3322 render(); 3323 3324 return window.fetch(settings.restUrl, { 3325 method: "POST", 3326 credentials: "same-origin", 3327 headers: { 3328 "Content-Type": "application/json", 3329 "X-WP-Nonce": settings.nonce 3330 }, 3331 signal: prepareController ? prepareController.signal : undefined, 3332 body: JSON.stringify(requestBody) 3333 }).then(function (response) { 3334 return response.json().catch(function () { return {}; }).then(function (payload) { 3335 if (!response.ok) { 3336 throw new Error(payload.message || labels.checkoutError || "è³¼å ¥ç»é¢ãæºåã§ãã¾ããã§ããã"); 3337 } 3338 return payload; 3339 }); 3340 }).then(function (payload) { 3341 if (!payload.checkout_url) { 3342 throw new Error(labels.checkoutError || "è³¼å ¥ç»é¢ãæºåã§ãã¾ããã§ããã"); 3343 } 3344 if (requestId !== prepareSequence || cartFingerprint(readCart()) !== fingerprint) { 3345 return null; 3346 } 3347 3348 var totalPrice = payload.cart_total && Number.isFinite(Number(payload.cart_total.amount)) ? { 3349 amount: Number(payload.cart_total.amount), 3350 currencyCode: String(payload.cart_total.currency_code || settings.currency || "JPY") 3351 } : null; 3352 var currentLines = (Array.isArray(payload.cart_lines) ? payload.cart_lines : []).map(function (line) { 3353 var amount = Number(line && line.price && line.price.amount); 3354 if (!line || !/^gid:\/\/shopify\/ProductVariant\/[1-9][0-9]*$/.test(String(line.variant_id || "")) || !Number.isFinite(amount)) { 3355 return null; 3356 } 3357 return { 3358 variantId: String(line.variant_id), 3359 sellingPlanId: String(line.selling_plan_id || ""), 3360 quantity: Math.max(1, Number.parseInt(line.quantity, 10) || 1), 3361 price: { amount: amount, currencyCode: String(line.price.currency_code || settings.currency || "JPY") } 3362 }; 3363 }).filter(Boolean); 3364 3365 preparedCheckout = { 3366 fingerprint: fingerprint, 3367 checkoutUrl: String(payload.checkout_url), 3368 cartId: String(payload.cart_id || ""), 3369 cartToken: String(payload.cart_token || ""), 3370 discountCode: discountCode, 3371 items: items.slice(), 3372 totalPrice: totalPrice, 3373 currentLines: currentLines 3374 }; 3375 prepareController = null; 3376 busy = false; 3377 render(); 3378 return preparedCheckout; 3379 }).catch(function (error) { 3380 if (requestId !== prepareSequence) { 3381 return null; 3382 } 3383 prepareController = null; 3384 if (error && error.name === "AbortError") { 3385 return null; 3386 } 3387 preparedCheckout = null; 3388 busy = false; 3389 render(); 3390 setText(errorBox, error && error.message ? error.message : (labels.checkoutError || "è³¼å ¥ç»é¢ãæºåã§ãã¾ããã§ããã")); 3391 setHidden(errorBox, false); 3392 return null; 3393 }); 3394 } 3395 3396 list.addEventListener("click", function (event) { 3397 var button = event.target.closest("[data-cart-action]"); 3398 var row = button && button.closest("[data-cart-key]"); 3399 var key = row && row.getAttribute("data-cart-key"); 3400 var items = readCart(); 3401 var itemIndex = key && items.findIndex(function (candidate) { return candidate.key === key; }); 3402 var item = Number.isFinite(itemIndex) && itemIndex >= 0 ? items[itemIndex] : null; 3403 var action; 3404 var nextQuantity; 3405 var delta; 3406 3407 if (!button || !item) { 3408 return; 3409 } 3410 action = button.getAttribute("data-cart-action"); 3411 pendingCartFocus = { 3412 entryId: item.entryId || "", 3413 index: itemIndex, 3414 action: action === "remove" ? "decrease" : action, 3415 message: action === "remove" ? item.title + (labels.cartRemoved || "ãã«ã¼ãããåé¤ãã¾ããã") : item.title + (labels.cartQuantityChanged || "ã®æ°éã夿´ãã¾ããã") 3416 }; 3417 if (action === "increase") { 3418 nextQuantity = Math.min(cartItemMaximum(item), item.quantity + 1); 3419 delta = nextQuantity - item.quantity; 3420 if (delta <= 0) { 3421 return; 3422 } 3423 updateCartItem(key, nextQuantity); 3424 pushCartQuantityEvent("add_to_cart", item, delta, itemIndex); 3425 } else if (action === "decrease") { 3426 nextQuantity = Math.max(0, item.quantity - 1); 3427 delta = item.quantity - nextQuantity; 3428 updateCartItem(key, nextQuantity); 3429 pushCartQuantityEvent("remove_from_cart", item, delta, itemIndex); 3430 } else { 3431 updateCartItem(key, 0); 3432 pushCartQuantityEvent("remove_from_cart", item, item.quantity, itemIndex); 3433 } 3434 }); 3435 3436 checkout.addEventListener("click", function (event) { 3437 var items = readCart(); 3438 var fingerprint = cartFingerprint(items); 3439 var handoff = preparedCheckout; 3440 var analyticsItems; 3441 var value; 3442 var currency; 3443 3444 event.preventDefault(); 3445 3446 if (busy || !items.length) { 3447 return; 3448 } 3449 3450 if (!handoff || handoff.fingerprint !== fingerprint || !checkout.href) { 3451 prepareCheckout(items); 3452 return; 3453 } 3454
3455 // Do not stop propagation and do not replace this href. Google's linker 3456 // listens to the trusted click at document level and decorates the final 3457 // Shopify Checkout URL before the deferred navigation reads it. 3458 busy = true; 3459 setHidden(errorBox, true); 3460 checkout.setAttribute("aria-disabled", "true"); 3461 checkout.setAttribute("aria-busy", "true"); 3462 analyticsItems = items.map(function (item, index) { 3463 return analyticsItemWithCurrentPrice(item, index); 3464 }); 3465 value = handoff.totalPrice ? handoff.totalPrice.amount : items.reduce(function (total, item) { 3466 return total + (item.price ? item.price.amount * item.quantity : 0); 3467 }, 0); 3468 currency = handoff.totalPrice ? handoff.totalPrice.currencyCode : ((items[0].price && items[0].price.currencyCode) || "JPY"); 3469 3470 if (handoff.cartId && handoff.cartToken) { 3471 rememberPendingCheckout(handoff.cartId, handoff.cartToken, handoff.items, handoff.discountCode); 3472 } 3473 // Shopify Pixel owns the standard begin_checkout event when the hosted 3474 // Checkout actually starts. Keep this pre-navigation intent distinct so 3475 // one checkout is not counted once on WordPress and again on Shopify. 3476 sendAnalyticsEvent("checkout_click", { 3477 currency: currency, 3478 value: Number(value.toFixed(2)), 3479 items: analyticsItems 3480 }, function () { 3481 navigateToPreparedCheckout(checkout, handoff.checkoutUrl); 3482 }); 3483 }); 3484 3485 checkout.addEventListener("keydown", function (event) { 3486 if ( 3487 (event.key === "Enter" || event.key === " ") && 3488 checkout.getAttribute("aria-disabled") === "true" && 3489 !busy && 3490 readCart().length 3491 ) { 3492 event.preventDefault(); 3493 prepareCheckout(readCart()); 3494 } 3495 }); 3496 3497 document.addEventListener("clc:cart-changed", function () { 3498 invalidatePreparedCheckout(); 3499 render(); 3500 if (!cart.hidden) { 3501 prepareCheckout(readCart()); 3502 } 3503 }); 3504 document.addEventListener("clc:panel-opened", function (event) { 3505 var detail = event.detail || {}; 3506 if (detail.panelId === cart.id) { 3507 pushViewCartOnce(readCart()); 3508 // Reset any prior `_gl` decoration to the validated raw Checkout URL. 3509 // The next trusted click then receives a fresh linker value. 3510 prepareCheckout(readCart()); 3511 } 3512 }); 3513 window.addEventListener("pageshow", function (event) { 3514 if (!event.persisted) { 3515 return; 3516 } 3517 invalidatePreparedCheckout(); 3518 render(); 3519 if (!cart.hidden) { 3520 prepareCheckout(readCart()); 3521 } 3522 }); 3523 render(); 3524 } 3525 3526 function initQuiz(quiz) { 3527 var form = quiz.querySelector(".clc-quiz-form"); 3528 var steps = toArray(quiz.querySelectorAll("[data-clc-quiz-step]")); 3529 var progress = quiz.querySelector("[data-clc-quiz-progress]"); 3530 var progressTrack = quiz.querySelector("[data-clc-quiz-progress-track]"); 3531 var progressBar = quiz.querySelector("[data-clc-quiz-progress-bar]"); 3532 var back = quiz.querySelector("[data-clc-quiz-back]"); 3533 var status = quiz.querySelector("[data-clc-quiz-status]"); 3534 var result = quiz.querySelector("[data-clc-quiz-result]"); 3535 var resultHeading = quiz.querySelector("[data-clc-quiz-result-heading]"); 3536 var resultProfile = quiz.querySelector("[data-clc-quiz-result-profile]"); 3537 var profileData = quiz.querySelector("[data-clc-quiz-profiles]"); 3538 var productCards = toArray(quiz.querySelectorAll("[data-clc-quiz-product]")); 3539 var reset = quiz.querySelector("[data-clc-quiz-reset]"); 3540 var current = 0; 3541 var advanceTimer = 0; 3542 var profiles = {}; 3543 3544 if (!form || !steps.length || !result || quiz.getAttribute("data-clc-quiz-ready") === "true") { 3545 return; 3546 } 3547 quiz.setAttribute("data-clc-quiz-ready", "true"); 3548 3549 try { 3550 profiles = JSON.parse((profileData && profileData.textContent) || "{}"); 3551 } catch (error) { 3552 profiles = {}; 3553 } 3554 3555 function progressPercent(index) { 3556 return Math.round(((index + 1) / steps.length) * 100); 3557 } 3558 3559 function showStep(index, focusQuestion) { 3560 var percent; 3561 3562 window.clearTimeout(advanceTimer); 3563 current = Math.max(0, Math.min(steps.length - 1, index)); 3564 steps.forEach(function (step, stepIndex) { setHidden(step, stepIndex !== current); }); 3565 setText(progress, String(current + 1)); 3566 percent = progressPercent(current); 3567 if (progressTrack) { 3568 progressTrack.setAttribute("aria-valuenow", String(percent)); 3569 } 3570 if (progressBar) { 3571 progressBar.style.width = String(percent) + "%"; 3572 } 3573 if (back) { 3574 setHidden(back, current === 0); 3575 } 3576 setHidden(form, false); 3577 setHidden(result, true); 3578 steps[current].classList.remove("is-entering"); 3579 window.requestAnimationFrame(function () { 3580 steps[current].classList.add("is-entering"); 3581 }); 3582 if (focusQuestion) { 3583 window.setTimeout(function () { 3584 var legend = steps[current] && steps[current].querySelector("legend"); 3585 if (legend) { 3586 legend.focus({ preventScroll: true }); 3587 } 3588 }, 0); 3589 } 3590 } 3591 3592 function selectedInputs() { 3593 return toArray(form.querySelectorAll("input:checked[data-quiz-scores]")); 3594 } 3595 3596 function calculateRanking() { 3597 var scores = Object.create(null);
3598 selectedInputs().forEach(function (input) { 3599 String(input.getAttribute("data-quiz-scores") || "").split(",").forEach(function (pair) { 3600 var parts = pair.split(":"); 3601 var handle = parts[0]; 3602 var score = Number(parts[1]) || 0; 3603 scores[handle] = (scores[handle] || 0) + score; 3604 }); 3605 }); 3606 3607 return productCards.map(function (card, index) { 3608 var handle = card.getAttribute("data-clc-quiz-product") || ""; 3609 return { card: card, handle: handle, score: scores[handle] || 0, index: index }; 3610 }).sort(function (a, b) { 3611 return b.score - a.score || a.index - b.index; 3612 }); 3613 } 3614 3615 function showResults() { 3616 var ranked = calculateRanking().slice(0, 3); 3617 var taste = form.querySelector('[data-quiz-question="taste"]:checked'); 3618 var roast = form.querySelector('[data-quiz-question="roast"]:checked'); 3619 var profileKey = String((taste && taste.value) || "") + "_" + String((roast && roast.value) || ""); 3620 var resultItems = []; 3621 3622 productCards.forEach(function (card) { 3623 setHidden(card, true); 3624 card.classList.remove("is-recommended"); 3625 card.style.removeProperty("order"); 3626 }); 3627 ranked.forEach(function (item, index) { 3628 var image = item.card.querySelector("img"); 3629 setHidden(item.card, false); 3630 item.card.classList.add("is-recommended"); 3631 item.card.style.order = String(index + 1); 3632 if (image) { 3633 image.loading = "eager"; 3634 image.fetchPriority = index === 0 ? "high" : "auto"; 3635 } 3636 resultItems.push({ 3637 item_id: item.card.getAttribute("data-product-id") || item.handle, 3638 item_name: item.card.getAttribute("data-product-title") || item.handle, 3639 price: Number(item.card.getAttribute("data-price-amount")) || 0, 3640 index: index + 1 3641 }); 3642 }); 3643 setText(resultProfile, profiles[profileKey] || "ããªãã«ã´ã£ããã®ã³ã¼ãã¼ãè¦ã¤ãã¾ããã"); 3644 setHidden(form, true); 3645 setHidden(result, false); 3646 sendAnalyticsEvent("view_item_list", { 3647 item_list_id: "coffee_finder_results", 3648 item_list_name: "ã³ã¼ãã¼è¨ºæçµæ", 3649 items: resultItems 3650 }); 3651 sendAnalyticsEvent("select_content", { 3652 content_type: "coffee_finder", 3653 item_id: ranked.length ? ranked[0].handle : "" 3654 }); 3655 if (resultHeading) { 3656 resultHeading.focus({ preventScroll: true }); 3657 } 3658 } 3659 3660 function handleAnswer(input) { 3661 var questionId = input.getAttribute("data-quiz-question") || ""; 3662 var selectedLabel = input.closest("label"); 3663 3664 window.clearTimeout(advanceTimer); 3665 if (selectedLabel) { 3666 selectedLabel.classList.add("is-selected"); 3667 } 3668 setText(status, current < steps.length - 1 ? "åçã鏿ãã¾ãããæ¬¡ã®è³ªåã¸é²ã¿ã¾ãã" : "åçã鏿ãã¾ããã診æçµæã表示ãã¾ãã"); 3669 sendAnalyticsEvent("select_content", { 3670 content_type: "coffee_finder_answer", 3671 item_id: questionId + ":" + String(input.value || ""), 3672 index: current + 1 3673 }); 3674 advanceTimer = window.setTimeout(function () { 3675 if (current < steps.length - 1) { 3676 showStep(current + 1, true); 3677 } else { 3678 showResults(); 3679 } 3680 }, 320); 3681 } 3682 3683 form.addEventListener("change", function (event) { 3684 var input = event.target.closest('input[type="radio"][data-quiz-scores]'); 3685 if (input && !input.disabled) { 3686 handleAnswer(input); 3687 } 3688 }); 3689 form.addEventListener("submit", function (event) { 3690 event.preventDefault(); 3691 }); 3692 if (back) { 3693 back.addEventListener("click", function () { 3694 var target = Math.max(0, current - 1); 3695 steps.slice(target).forEach(function (step) { 3696 toArray(step.querySelectorAll('input[type="radio"]')).forEach(function (input) { 3697 input.checked = false; 3698 }); 3699 toArray(step.querySelectorAll("label.is-selected")).forEach(function (label) { 3700 label.classList.remove("is-selected"); 3701 }); 3702 }); 3703 showStep(target, true); 3704 }); 3705 } 3706 result.addEventListener("click", function (event) { 3707 var addButton = event.target.closest("[data-clc-quiz-add]"); 3708 var productLink = event.target.closest("[data-clc-quiz-product-link]"); 3709 var card = event.target.closest("[data-clc-quiz-product]"); 3710 3711 if (addButton && card) { 3712 var added = addCartItem({ 3713 productHandle: card.getAttribute("data-product-handle"), 3714 productId: card.getAttribute("data-product-id"), 3715 title: card.getAttribute("data-product-title"), 3716 variantId: card.getAttribute("data-variant-id"), 3717 variantTitle: "", 3718 sellingPlanId: "", 3719 sellingPlanName: "", 3720 quantity: 1, 3721 price: { 3722 amount: Number(card.getAttribute("data-price-amount")) || 0, 3723 currencyCode: card.getAttribute("data-price-currency") || "JPY" 3724 }, 3725 imageUrl: card.getAttribute("data-image-url"), 3726 imageAlt: card.getAttribute("data-product-title") 3727 }); 3728 3729 if (added) {
3730 setText(addButton, labels.addedToCart || "ã«ã¼ãã«è¿½å ãã¾ãã"); 3731 sendAnalyticsEvent("add_to_cart", { 3732 currency: card.getAttribute("data-price-currency") || "JPY", 3733 value: (Number(card.getAttribute("data-price-amount")) || 0) * added, 3734 items: [{ 3735 item_id: card.getAttribute("data-product-id") || card.getAttribute("data-product-handle"), 3736 item_name: card.getAttribute("data-product-title"), 3737 price: Number(card.getAttribute("data-price-amount")) || 0, 3738 quantity: added 3739 }] 3740 }); 3741 openUtilityPanel("clc-cart-panel", addButton); 3742 window.setTimeout(function () { 3743 setText(addButton, labels.addToCart || "ã«ã¼ãã«è¿½å "); 3744 }, 1800); 3745 } 3746 } else if (productLink && card) { 3747 sendAnalyticsEvent("select_item", { 3748 item_list_id: "coffee_finder_results", 3749 item_list_name: "ã³ã¼ãã¼è¨ºæçµæ", 3750 items: [{ 3751 item_id: card.getAttribute("data-product-id") || card.getAttribute("data-product-handle"), 3752 item_name: card.getAttribute("data-product-title"), 3753 price: Number(card.getAttribute("data-price-amount")) || 0, 3754 quantity: 1 3755 }] 3756 }); 3757 } 3758 }); 3759 if (reset) { 3760 reset.addEventListener("click", function () { 3761 window.clearTimeout(advanceTimer); 3762 form.reset(); 3763 toArray(form.querySelectorAll("label.is-selected")).forEach(function (label) { 3764 label.classList.remove("is-selected"); 3765 }); 3766 productCards.forEach(function (card) { setHidden(card, true); }); 3767 setHidden(result, true); 3768 setHidden(form, false); 3769 showStep(0, true); 3770 }); 3771 } 3772 showStep(0, false); 3773 } 3774 3775 function initNavigation(storefront, index) { 3776 var toggle = storefront.querySelector(".clc-nav-toggle"); 3777 var panel = storefront.querySelector(".clc-nav-panel"); 3778 var disclosures; 3779 3780 if (!toggle || !panel || toggle.getAttribute("data-clc-initialized") === "true") { 3781 return; 3782 } 3783 3784 toggle.setAttribute("data-clc-initialized", "true"); 3785 3786 if (!panel.id) { 3787 panel.id = "clc-nav-panel-" + String(index + 1); 3788 } 3789 3790 toggle.setAttribute("aria-controls", panel.id); 3791 3792 if (!toggle.hasAttribute("aria-expanded")) { 3793 toggle.setAttribute("aria-expanded", "false"); 3794 } 3795 3796 disclosures = toArray(panel.querySelectorAll(".clc-nav-disclosure")); 3797 3798 var media = window.matchMedia ? window.matchMedia("(max-width: 1439px)") : null; 3799 3800 function isMobile() { 3801 return media ? media.matches : window.innerWidth <= 1439; 3802 } 3803 3804 function closeDisclosures(except) { 3805 disclosures.forEach(function (disclosure) { 3806 if (disclosure !== except && disclosure.open) { 3807 disclosure.open = false; 3808 } 3809 }); 3810 } 3811 3812 function setOpen(open) { 3813 var expanded = Boolean(open) && isMobile(); 3814 toggle.setAttribute("aria-expanded", expanded ? "true" : "false"); 3815 toggle.setAttribute("aria-label", expanded ? 3816 (labels.closeMenu || "ã¡ãã¥ã¼ãéãã") : 3817 (labels.openMenu || "ã¡ãã¥ã¼ãéã")); 3818 panel.classList.toggle("is-open", expanded); 3819 setHidden(panel, isMobile() && !expanded); 3820 document.body.classList.toggle("clc-navigation-open", expanded); 3821 if (!expanded) { 3822 closeDisclosures(null); 3823 } 3824 } 3825 3826 function synchronizeViewport() { 3827 if (isMobile()) { 3828 setOpen(toggle.getAttribute("aria-expanded") === "true"); 3829 } else { 3830 toggle.setAttribute("aria-expanded", "false"); 3831 panel.classList.remove("is-open"); 3832 panel.hidden = false; 3833 document.body.classList.remove("clc-navigation-open"); 3834 closeDisclosures(null); 3835 } 3836 } 3837 3838 disclosures.forEach(function (disclosure) { 3839 var summary = disclosure.querySelector(":scope > summary"); 3840 3841 if (!summary) { 3842 return; 3843 } 3844 3845 disclosure.addEventListener("toggle", function () { 3846 if (disclosure.open) { 3847 closeDisclosures(disclosure); 3848 } 3849 }); 3850 }); 3851 3852 toggle.addEventListener("click", function () { 3853 setOpen(toggle.getAttribute("aria-expanded") !== "true"); 3854 }); 3855 3856 panel.addEventListener("click", function (event) { 3857 if (event.target.closest("a, [data-clc-panel-open]")) { 3858 closeDisclosures(null); 3859 if (isMobile()) { 3860 setOpen(false); 3861 } 3862 } 3863 }); 3864 3865 storefront.addEventListener("keydown", function (event) { 3866 var openDisclosure; 3867 3868 if (event.key !== "Escape") { 3869 return; 3870 } 3871 3872 openDisclosure = panel.querySelector(".clc-nav-disclosure[open]"); 3873 if (openDisclosure) { 3874 event.preventDefault(); 3875 openDisclosure.open = false;
3876 if (openDisclosure.querySelector(":scope > summary")) { 3877 openDisclosure.querySelector(":scope > summary").focus(); 3878 } 3879 return; 3880 } 3881 3882 if (toggle.getAttribute("aria-expanded") === "true") { 3883 event.preventDefault(); 3884 setOpen(false); 3885 toggle.focus(); 3886 } 3887 }); 3888 3889 storefront.addEventListener("focusin", function (event) { 3890 if ( 3891 isMobile() && 3892 toggle.getAttribute("aria-expanded") === "true" && 3893 event.target !== toggle && 3894 !panel.contains(event.target) 3895 ) { 3896 setOpen(false); 3897 } 3898 }); 3899 3900 document.addEventListener("pointerdown", function (event) { 3901 if (panel.contains(event.target) || toggle.contains(event.target)) { 3902 return; 3903 } 3904 3905 closeDisclosures(null); 3906 if (isMobile() && toggle.getAttribute("aria-expanded") === "true") { 3907 setOpen(false); 3908 } 3909 }); 3910 3911 if (media && typeof media.addEventListener === "function") { 3912 media.addEventListener("change", synchronizeViewport); 3913 } else if (media && typeof media.addListener === "function") { 3914 media.addListener(synchronizeViewport); 3915 } else { 3916 window.addEventListener("resize", synchronizeViewport); 3917 } 3918 3919 synchronizeViewport(); 3920 } 3921 3922 function initContactFormA11y(storefront) { 3923 toArray(storefront.querySelectorAll(".clc-contact-form .wpcf7-checkbox, .clc-contact-form .wpcf7-radio")).forEach(function (group, index) { 3924 var paragraph; 3925 var groupLabel; 3926 var labelId; 3927 3928 if (group.getAttribute("data-clc-group-ready") === "true") { 3929 return; 3930 } 3931 group.setAttribute("data-clc-group-ready", "true"); 3932 paragraph = group.closest("p"); 3933 groupLabel = paragraph ? paragraph.querySelector(":scope > label") : null; 3934 if (!groupLabel || !String(groupLabel.textContent || "").trim()) { 3935 return; 3936 } 3937 3938 labelId = groupLabel.id || "clc-contact-group-label-" + String(index + 1); 3939 groupLabel.id = labelId; 3940 group.setAttribute("role", "group"); 3941 group.setAttribute("aria-labelledby", labelId); 3942 if (group.classList.contains("wpcf7-validates-as-required")) { 3943 group.setAttribute("aria-required", "true"); 3944 } 3945 }); 3946 } 3947 3948 function initialize() { 3949 var storefronts = toArray(document.querySelectorAll(".clc-storefront")); 3950 3951 storefronts.forEach(function (storefront, index) { 3952 allIncluding(storefront, ".clc-product").forEach(initProduct); 3953 allIncluding(storefront, ".clc-collection").forEach(initCollection); 3954 allIncluding(storefront, ".clc-rich-text").forEach(initRichText); 3955 allIncluding(storefront, "[data-clc-horizontal-scroll]").forEach(initHorizontalScroll); 3956 allIncluding(storefront, "[data-clc-home-hero]").forEach(initHomeHero); 3957 allIncluding(storefront, "[data-clc-home-testimonials]").forEach(initHomeTestimonials); 3958 allIncluding(storefront, "[data-clc-weekly-countdown]").forEach(initWeeklyCountdown); 3959 allIncluding(storefront, "[data-clc-home-product-list]").forEach(initHomeProductList); 3960 allIncluding(storefront, "[data-clc-cart]").forEach(initCart); 3961 allIncluding(storefront, "[data-clc-quiz]").forEach(initQuiz); 3962 initContactFormA11y(storefront); 3963 initUtilityPanels(storefront); 3964 allIncluding(storefront, "[data-clc-cart-reminder]").forEach(initCartReminder); 3965 initNavigation(storefront, index); 3966 }); 3967 reconcilePendingCheckouts(); 3968 } 3969 3970 window.addEventListener("pageshow", function (event) { 3971 if (event.persisted) { 3972 reconcilePendingCheckouts(true); 3973 } 3974 }); 3975 window.addEventListener("storage", function (event) { 3976 if (event.storageArea !== window.localStorage) { 3977 return; 3978 } 3979 if (event.key === cartStorageKey) { 3980 cartStorageAvailable = true; 3981 document.dispatchEvent(new CustomEvent("clc:cart-changed", { 3982 detail: { items: readCart(), source: "storage" } 3983 })); 3984 } 3985 if (event.key === pendingCheckoutStorageKey) { 3986 pendingCheckoutStorageAvailable = true; 3987 reconcilePendingCheckouts(true); 3988 } 3989 }); 3990 ready(initialize); 3991}());
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.