PageSourceSearch

https://www.utsuwayayuuyuu.com/assets/js/review-trending-json.js?v=20260927-02

js utsuwayayuuyuu.com collected 2026-10-04 13:26:21 UTC 18,525 bytes, 463 lines download raw bytes

1(function () {
2  "use strict";
3
4  var rightNowContainer = document.getElementById("reviewTrendingList");
5  var artistContainer = document.getElementById("trendingArtistList");
6  var freeShippingContainer = document.getElementById("freeShippingProducts");
7  var freeShippingSection = document.getElementById("free");
8  var newsContainer = document.querySelector('#news > ul');
9
10  if (!rightNowContainer && !artistContainer && !freeShippingContainer && !newsContainer) return;
11
12  var MAX_ITEMS = 12;
13  var JSON_PATH = "assets/data/trending.json";
14  var JSON_CACHE_INTERVAL_MS = 30 * 60 * 1000;
15  var FALLBACK_CACHE_MAX_AGE = 2 * 60 * 60 * 1000;
16  var REQUEST_TIMEOUT_MS = 10000;
17  var STORAGE_KEY = "reviewTrendingJson:v1";
18  var ARTIST_STORAGE_KEY = "trendingArtistsJson:v1";
19
20  if (rightNowContainer) {
21    rightNowContainer.addEventListener("click", openReviewLink);
22    rightNowContainer.addEventListener("keydown", openReviewLink);
23  }
24  load();
25
26  function load() {
27    if (!window.fetch) {
28      renderAllFallbacks();
29      return;
30    }
31
32    var controller = "AbortController" in window ? new AbortController() : null;
33    var timeout = controller
34      ? window.setTimeout(function () { controller.abort(); }, REQUEST_TIMEOUT_MS)
35      : null;
36    var options = { credentials: "same-origin", cache: "default" };
37    if (controller) options.signal = controller.signal;
38
39    fetch(buildJsonUrl(), options)
40      .then(function (response) {
41        if (!response.ok) throw new Error("Trending JSON request failed");
42        return response.json();
43      })
44      .then(function (data) {
45        clearRequestTimeout(timeout);
46        renderRightNowData(data);
47        renderArtistData(data);
48        renderFreeShippingData(data);
49        renderNewsData(data);
50      })
51      .catch(function () {
52        clearRequestTimeout(timeout);
53        renderAllFallbacks();
54      });
55  }
56
57  function renderNewsData(data) {
58    if (!newsContainer || !data || !Array.isArray(data.news) || data.news.length !== 11) return;
59    var ids = {};
60    var valid = data.news.every(function (item) {
61      var url = safeHttpUrl(item && item.url);
62      if (!item || typeof item.id !== 'string' || ids[item.id] || typeof item.text !== 'string' || !item.text.trim()
63        || !/^\d{4}-\d{2}-\d{2}$/.test(item.date) || item.displayDate !== item.date.replace(/-/g, '.')
64        || !url || new URL(url).hostname !== 'www.utsuwayayuuyuu.com'
65        || !/^(legacy|manual|new|restock)$/.test(item.type)) return false;
66      ids[item.id] = true;
67      return true;
68    });
69    if (!valid) return;
70    var fragment = document.createDocumentFragment();
71    data.news.forEach(function (item) {
72      var li = document.createElement('li');
73      var a = document.createElement('a');
74      var span = document.createElement('span');
75      a.href = item.url;
76      a.appendChild(document.createTextNode(item.prefixText || ''));
77      span.textContent = item.displayDate;
78      a.appendChild(span);
79      a.appendChild(document.createTextNode(item.text));
80      li.appendChild(a);
81      fragment.appendChild(li);
82    });
83    newsContainer.replaceChildren(fragment);
84  }
85
86  function renderRightNowData(data) {
87    if (!rightNowContainer) return;
88    var items = normalizeItems(data && data.rightNow);
89    if (items.length !== MAX_ITEMS) {
90      renderRightNowFallback();
91      return;
92    }
93    renderRightNow(items);
94    writeFallback(STORAGE_KEY, items);
95  }
96
97  function renderArtistData(data) {
98    if (!artistContainer) return;
99    var artists = normalizeArtists(data && data.artists);
100    if (!artists.length) {
101      renderArtistFallback();
102      return;
103    }
104    renderArtists(artists);
105    writeFallback(ARTIST_STORAGE_KEY, artists);
106  }
107
108  function renderFreeShippingData(data) {
109    if (!freeShippingContainer) return;
110    var freeShipping = normalizeFreeShipping(data && data.freeShipping);
111    if (!freeShipping) {
112      renderFreeShippingFallback();
113      return;
114    }
115    renderFreeShipping(freeShipping);
116  }
117
118  function buildJsonUrl() {
119    var url = new URL(JSON_PATH, document.baseURI);
120    var cacheBucket = Math.floor(Date.now() / JSON_CACHE_INTERVAL_MS);
121    url.searchParams.set("v", String(cacheBucket));
122    return url.href;
123  }
124
125  function clearRequestTimeout(timeout) {
126    if (timeout !== null) window.clearTimeout(timeout);
127  }
128
129  function normalizeItems(items) {
130    if (!Array.isArray(items)) return [];
131    return items.map(normalizeRightNowItem).filter(Boolean).slice(0, MAX_ITEMS);
132  }
133
134  function normalizeRightNowItem(item) {
135    var url = safeHttpUrl(item && item.url);
136    var image = safeHttpUrl(item && item.image);
137    var stock = item && item.stock ? item.stock : {};
138    if (!item || !item.productCode || !item.name || !url || !image || stock.available !== true) return null;
139    return {
140      productCode: String(item.productCode),
141      artist: String(item.artist || ""),
142      title: String(item.name),
143      url: url,
144      image: image,
145      price: String(item.price || ""),
146      stockCount: typeof stock.count === "number" ? stock.count : null,
147      reviewCount: typeof item.reviewCount === "number" ? item.reviewCount : 0,
148      ratingValue: typeof item.ratingValue === "number" ? item.ratingValue : 0,
149      description: String(item.description || "")
150    };
151  }
152
153  function normalizeArtists(artists) {
154    if (!Array.isArray(artists)) return [];
155    return artists.map(function (artist) {
156      var url = safeHttpUrl(artist && artist.url);
157      var photo = safeHttpUrl(artist && artist.photo);
158      if (!artist || !artist.id || !artist.name || !artist.title || !url || !photo) return null;
159      var products = Array.isArray(artist.products)
160        ? artist.products.map(normalizeArtistProduct).filter(Boolean).slice(0, 5)
161        : [];
162      return {
163        id: String(artist.id),
164        name: String(artist.name),
165        title: String(artist.title),
166        photo: photo,
167        prefecture: String(artist.prefecture || ""),
168        country: String(artist.country || ""),
169        url: url,
170        products: products
171      };
172    }).filter(Boolean);
173  }
174
175  function normalizeArtistProduct(item) {
176    var url = safeHttpUrl(item && item.url);
177    var mainImage = safeHttpUrl(item && (item.mainImage || item.image));
178    var hoverImage = item && item.hoverImage ? safeHttpUrl(item.hoverImage) : "";
179    var stock = item && item.stock ? item.stock : {};
180    if (!item || !item.productCode || !item.name || !url || !mainImage || stock.available !== true) return null;
181    return {
182      productCode: String(item.productCode),
183      name: String(item.name),
184      url: url,
185      mainImage: mainImage,
186      hoverImage: hoverImage,
187      dimensions: String(item.dimensions || ""),
188      price: String(item.price || "")
189    };
190  }
191
192  function normalizeFreeShipping(value) {
193    if (!value || value.selectionMode !== "main-sticky-sub-price" || !Array.isArray(value.items)) return null;
194    var main = value.main ? normalizeFreeShippingProduct(value.main, true) : null;
195    var items = value.items.map(function (item) {
196      return normalizeFreeShippingProduct(item, false);
197    }).filter(Boolean).slice(0, 4);
198    if (value.main && !main) return null;
199    return { main: main, items: items };
200  }
201
202  function normalizeFreeShippingProduct(item, isMain) {
203    var url = safeHttpUrl(item && item.url);
204    var image = safeHttpUrl(item && item.image);
205    var stock = item && item.stock ? item.stock : {};
206    if (!item || !item.productCode || !item.displayName || !url || !image || stock.available !== true) return null;
207    if (isMain && (!item.englishName || !item.description)) return null;
208    return {
209      productCode: String(item.productCode),
210      displayName: String(item.displayName),
211      englishName: String(item.englishName || ""),
212      description: String(item.description || ""),
213      url: url,
214      image: image,
215      price: String(item.price || "")
216    };
217  }
218
219  function safeHttpUrl(value) {
220    try {
221      var url = new URL(String(value || ""), document.baseURI);
222      return url.protocol === "http:" || url.protocol === "https:" ? url.href : "";
223    } catch (error) {
224      return "";
225    }
226  }
227
228  function renderRightNow(items) {
229    rightNowContainer.innerHTML = items.map(function (item, index) {
230      return (
231        '<div class="children order' + (index + 1) + '">' +
232        '<a href="' + escapeHtml(item.url) + '">' +
233        '<img loading="lazy" decoding="async" fetchpriority="low" src="' + escapeHtml(item.image) + '" alt="">' +
234        '<div class="trending-tags">' + renderReviewBadge(item) + renderStockBadge(item.stockCount) + "</div>" +
235        renderRating(item.ratingValue) +
236        "<h6>" + escapeHtml(parseCategory(item.title)) + "</h6>" +
237        '<p class="review-product-name">' + escapeHtml(item.title) + "</p>" +
238        (item.price ? '<p class="review-price">' + escapeHtml(item.price) + "</p>" : "") +
239        '<div class="detailWrap"><p>' + escapeHtml(item.description || makeDescription(item.title)) + "</p></div>" +
240        "</a></div>"
241      );
242    }).join("");
243  }
244
245  function renderArtists(artists) {
246    artistContainer.innerHTML = artists.map(function (artist, index) {
247      var rowClass = index === artists.length - 1 ? "artistNow2" : "artistNow";
248      var location = [artist.prefecture, artist.country].filter(
248Boolean).join(",");
249      return (
250        '<div class="' + rowClass + '" data-artist-id="' + escapeHtml(artist.id) + '">' +
251        '<div class="artistNow_icon"><a href="' + escapeHtml(artist.url) + '">' +
252        '<img loading="lazy" decoding="async" src="' + escapeHtml(artist.photo) + '" alt=""></a>' +
253        "<h6>" + escapeHtml(artist.title + ":" + artist.name) + "</h6>" +
254        "<p>" + escapeHtml(location) + "</p></div>" +
255        '<ul class="arist-gallery">' + artist.products.map(renderArtistProduct).join("") + "</ul></div>"
256      );
257    }).join("");
258    initializeArtistImages();
259    initializeArtistFlickity();
260    if (window.sr && typeof window.sr.reveal === "function") {
261      window.sr.reveal(".artistNow_icon", { origin: "bottom", easing: "ease", distance: "5%", duration: 1000, scale: 1, delay: 200, opacity: 0 });
262    }
263  }
264
265  function renderFreeShipping(freeShipping) {
266    var html = "";
267    if (freeShipping.main) html += renderFreeShippingMain(freeShipping.main);
268    html += freeShipping.items.map(renderFreeShippingItem).join("");
269    freeShippingContainer.innerHTML = html;
270    if (freeShippingSection) freeShippingSection.hidden = !freeShipping.main && !freeShipping.items.length;
271  }
272
273  function renderFreeShippingMain(item) {
274    return (
275      '<div class="free-item1" data-product-code="' + escapeHtml(item.productCode) + '">' +
276      '<div class="free-item-image"><a href="' + escapeHtml(item.url) + '">' +
277      '<img loading="lazy" decoding="async" src="' + escapeHtml(item.image) + '" alt="' + escapeHtml(item.displayName) + '"></a></div>' +
278      '<div class="free-item-coment"><h5>' + escapeHtml(item.displayName) + "</h5>" +
279      "<h6>" + escapeHtml(item.englishName) + "</h6>" +
280      "<p>" + escapeHtml(item.description) + "</p>" +
281      "<p>" + escapeHtml(formatFreeShippingPrice(item.price)) + "</p></div></div>"
282    );
283  }
284
285  function renderFreeShippingItem(item) {
286    return (
287      '<div class="free-item2" data-product-code="' + escapeHtml(item.productCode) + '">' +
288      '<a href="' + escapeHtml(item.url) + '"><img loading="lazy" decoding="async" src="' +
289      escapeHtml(item.image) + '" alt="' + escapeHtml(item.displayName) + '">' +
290      "<p>" + escapeHtml(item.displayName) + "</p>" +
291      "<p>" + escapeHtml(formatFreeShippingPrice(item.price)) + "</p></a></div>"
292    );
293  }
294
295  function renderArtistProduct(product) {
296    var hover = product.hoverImage ? ' data-hover-image="' + escapeHtml(product.hoverImage) + '"' : "";
297    return (
298      '<li class="gallery-cell"><a href="' + escapeHtml(product.url) + '">' +
299      '<img loading="lazy" decoding="async" src="' + escapeHtml(product.mainImage) + '"' + hover + ' alt="">' +
300      "<p>" + escapeHtml(cleanArtistProductName(product.name)) + "</p>" +
301      '<div class="size-date"><p>' + escapeHtml(product.dimensions) + "</p></div>" +
302      '<div class="price-date"><p>' + escapeHtml(formatArtistPrice(product.price)) + "</p></div>" +
303      "</a></li>"
304    );
305  }
306
307  function initializeArtistImages() {
308    artistContainer.querySelectorAll("img[data-hover-image]").forEach(function (image) {
309      var mainImage = image.src;
310      var hoverImage = image.getAttribute("data-hover-image");
311      image.addEventListener("mouseenter", function () { image.src = hoverImage; });
312      image.addEventListener("mouseleave", function () { image.src = mainImage; });
313    });
314  }
315
316  function initializeArtistFlickity() {
317    if (!window.jQuery || !window.jQuery.fn || typeof window.jQuery.fn.flickity !== "function") return;
318    window.jQuery("#trendingArtistList .arist-gallery").each(function () {
319      var gallery = window.jQuery(this);
320      if (gallery.data("flickity")) return;
321      gallery.flickity({ watchCSS: true, prevNextButtons: false, cellAlign: "left" });
322      gallery.find("img").each(function () {
323        var image = this;
324        var refresh = function () {
325          var instance = gallery.data("flickity");
326          if (!instance || !gallery.hasClass("flickity-enabled")) return;
327          var cell = image.closest ? image.closest(".gallery-cell") : null;
328          if (cell && typeof instance.cellSizeChange === "function") instance.cellSizeChange(cell);
329          if (typeof instance.resize === "function") instance.resize();
330        };
331        image.addEventListener("load", refresh, { once: true });
332        if (image.complete) window.setTimeout(refresh, 0);
333      });
334    });
335  }
336
337  function renderAllFallbacks() {
338    renderRightNowFallback();
339    renderArtistFallback();
340    renderFreeShippingFallback();
341  }
342
343  function renderRightNowFallback() {
344    if (!rightNowContainer) return;
345    var items = readFallback(STORAGE_KEY, normalizeItems);
346    if (items.length === MAX_ITEMS) {
347      renderRightNow(items);
348      return;
349    }
350    rightNowContainer.innerHTML = '<p class="auto-trending-loading">おすすめ商品を準備中です。</p>';
351  }
352
353  function renderArtistFallback() {
354    if (!artistContainer) return;
355    var artists = readFallback(ARTIST_STORAGE_KEY, normalizeArtists);
356    if (artists.length) renderArtists(artists);
357  }
358
359  function renderFreeShippingFallback() {
360    if (freeShippingContainer) freeShippingContainer.innerHTML = "";
361    if (freeShippingSection) freeShippingSection.hidden = true;
362  }
363
364  function writeFallback(key, items) {
365    try {
366      getStorage().setItem(key, JSON.stringify({ savedAt: Date.now(), items: items }));
367    } catch (error) {
368      return;
369    }
370  }
371
372  function readFallback(key, normalize) {
373    try {
374      var cached = JSON.parse(getStorage().getItem(key) || "null");
375      if (!cached || !cached.savedAt || Date.now() - cached.savedAt > FALLBACK_CACHE_MAX_AGE) return [];
376      return normalize(cached.items);
377    } catch (error) {
378      return [];
379    }
380  }
381
382  function getStorage() {
383    try {
384      if (window.localStorage) return window.localStorage;
385    } catch (error) {
386      return window.sessionStorage;
387    }
388    return window.sessionStorage;
389  }
390
391  function openReviewLink(event) {
392    var reviewLink = event.target.closest && event.target.closest(".review-jump-link");
393    if (!reviewLink) return;
394    if (event.type === "keydown" && event.key !== "Enter" && event.key !== " ") return;
395    event.preventDefault();
396    event.stopPropagation();
397    window.location.href = reviewLink.getAttribute("data-review-url");
398  }
399
400  function renderReviewBadge(item) {
401    if (!item.reviewCount) return "";
402    return '<span class="review-jump-link" role="link" tabindex="0" aria-label="' + item.reviewCount +
403      '件のレビューを見る" data-review-url="' + escapeHtml(getReviewUrl(item.url)) + '"><b>' +
404      item.reviewCount + "</b>件のレビュー</span>";
405  }
406
407  function renderStockBadge(stockCount) {
408    return stockCount === 1 ? '<span class="stock-limited">1点限り</span>' : "";
409  }
410
411  function renderRating(ratingValue) {
412    if (!ratingValue) return "";
413    var rating = Math.max(0, Math.min(5, Number(ratingValue)));
414    var rounded = Math.round(rating);
415    var stars = "★★★★★".slice(0, rounded) + "☆☆☆☆☆".slice(0, 5 - rounded);
416    return '<p class="review-rating"><span class="review-rating-label">おすすめ度:</span>' +
417      '<span class="review-rating-stars">' + stars + "</span>" +
418      '<span class="review-rating-score">' + rating.toFixed(1) + "</span></p>";
419  }
420
421  function getReviewUrl(url) {
422    return String(url || "").split("#")[0] + "#:~:text=" + encodeURIComponent("お客様のレビュー");
423  }
424
425  function cleanArtistProductName(name) {
426    return String(name || "").replace(/\s*作家\s*[「『].*$/u, "").trim();
427  }
428
429  function formatArtistPrice(price) {
430    var match = String(price || "").replace(/ï¿¥/g, "Â¥").match(/Â¥\s*([0-9,]+)/);
431    return match ? match[1] + "円" : String(price || "");
432  }
433
434  function formatFreeShippingPrice(price) {
435    var match = String(price || "").replace(/ï¿¥/g, "Â¥").match(/Â¥\s*([0-9,]+)/);
436    return match ? "税込" + match[1] + "円" : String(price || "");
437  }
438
439  function parseCategory(title) {
440    if (title.indexOf("ガラス") !== -1) return "ガラス";
441    if (title.indexOf("漆器") !== -1 || title.indexOf("漆") !== -1) return "漆器";
442    if (title.indexOf("花") !== -1 || title.indexOf("一輪挿し") !== -1) return "陶磁器・インテリア";
443    if (title.indexOf("茶碗") !== -1 || title.indexOf("抹茶碗") !== -1) return "和食器:茶碗";
444    if (title.indexOf("ぐい呑") !== -1 || title.indexOf("酒") !== -1 || title.indexOf("徳利") !== -1) return "和食器:酒器";
445    if (title.indexOf("皿") !== -1) return "和食器:皿";
446    return "和食器";
447  }
448
449  function makeDescription(title) {
450    if (title.indexOf("ガラス") !== -1) return "透明感のある、作家の手仕事が息づくガラス作品です。";
451    if (title.indexOf("花") !== -1 || title.indexOf("一輪挿し") !== -1) return "空間に表情を添える、作家の手仕事による花器です。";
452    return "作家の手仕事が息づく一品です。";
453  }
454
455  function escapeHtml(value) {
456    return String(value || "")
457      .replace(/&/g, "&amp;")
458      .replace(/</g, "&lt;")
459      .replace(/>/g, "&gt;")
460      .replace(/"/g, "&quot;")
461      .replace(/'/g, "&#039;");
462  }
463})();

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.