(() => {
const FAV_KEY = "fanza_widget_favs_v1";
function loadFavs() {
try {
const raw = localStorage.getItem(FAV_KEY);
if (!raw) return new Set();
const arr = JSON.parse(raw);
if (!Array.isArray(arr)) return new Set();
return new Set(arr.map(String));
} catch (e) {
return new Set();
}
}
function saveFavs(set) {
try {
localStorage.setItem(FAV_KEY, JSON.stringify(Array.from(set)));
} catch (e) {}
}
function num(v, def = 0) {
const x = parseFloat(v);
return Number.isFinite(x) ? x : def;
}
function getTextAttr(el, name) {
return (el.getAttribute(name) || "").trim();
}
function parseCSV(s) {
if (!s) return [];
return s.split(",").map(x => x.trim()).filter(Boolean);
}
function uniq(arr) {
return Array.from(new Set(arr));
}
function initWidget(root) {
const favs = loadFavs();
const grid = root.querySelector("#item-grid") || root.querySelector(".item-grid");
if (!grid) return;
const cards = Array.from(grid.querySelectorAll(".item-card"));
// mark fav
cards.forEach(card => {
const cid = getTextAttr(card, "data-content-id");
if (cid && favs.has(cid)) card.classList.add("is-fav");
const btn = card.querySelector(".fav-btn");
if (btn) {
btn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
if (!cid) return;
if (favs.has(cid)) {
favs.delete(cid);
card.classList.remove("is-fav");
} else {
favs.add(cid);
card.classList.add("is-fav");
}
saveFavs(favs);
updateFavInfo();
apply();
});
}
});
// toolbar might not exist in fragment views
const sortSel = root.querySelector("#sort-select");
const labelSel = root.querySelector("#label-select");
const priceSel = root.querySelector("#price-filter");
const reviewSel = root.querySelector("#review-filter");
const searchInp = root.querySelector("#keyword-search");
const soloOnly = root.querySelector("#solo-only");
const favOnly = root.querySelector("#fav-only");
const genreWrap = root.querySelector("#genre-wrap");
const countInfo = root.querySelector("#count-info");
const favInfo = root.querySelector("#fav-info");
const moreBtn = root.querySelector("#more-button");
// build label options
if (labelSel) {
const labels = uniq(cards.map(c => getTextAttr(c, "data-label-name")).filter(Boolean)).sort((a,b)=>a.localeCompare(b,'ja'));
labels.forEach(l => {
const opt = document.createElement("option");
opt.value = l;
opt.textContent = l;
labelSel.appendChild(opt);
});
}
// build genre buttons (top frequency)
let activeGenre = "";
if (genreWrap) {
const freq = new Map();
cards.forEach(c => {
parseCSV(getTextAttr(c, "data-genre-names")).forEach(g => {
freq.set(g, (freq.get(g) || 0) + 1);
});
});
const sorted = Array.from(freq.entries()).sort((a,b)=>b[1]-a[1]).slice(0, 14);
if (sorted.length) {
const allBtn = document.createElement("button");
allBtn.type = "button";
allBtn.className = "genre-btn is-active";
allBtn.textContent = "すべて";
allBtn.addEventListener("click", () => {
activeGenre = "";
Array.from(genreWrap.querySelectorAll(".genre-btn")).forEach(x => x.classList.remove("is-active"));
allBtn.classList.add("is-active");
apply();
});
genreWrap.appendChild(allBtn);
sorted.forEach(([g]) => {
const b = document.createElement("button");
b.type = "button";
b.className = "genre-btn";
b.textContent = g;
b.addEventListener("click", () => {
activeGenre = g;
Array.from(genreWrap.querySelectorAll(".genre-btn")).forEach(x => x.classList.remove("is-active"));
b.classList.add("is-active");
apply();
});
genreWrap.appendChild(b);
});
}
}
// pagination
let pageSize = 24;
let visibleLimit = pageSize;
if (moreBtn) {
moreBtn.addEventListener("click", () => {
visibleLimit += pageSize;
apply(true);
});
}
function updateCount(visible, total) {
if (!countInfo) return;
countInfo.textContent = `表示: ${visible} / ${total}`;
}
function updateFavInfo() {
if (!favInfo) return;
favInfo.textContent = `お気に入り: ${favs.size}`;
}
updateFavInfo();
function matchPrice(card) {
if (!priceSel) return true;
const v = priceSel.value || "all";
if (v === "all") return true;
const p = num(getTextAttr(card, "data-price"), 0);
if (v === "~1000") return p > 0 && p <= 1000;
if (v === "1000-2000") return p >= 1000 && p <= 2000;
if (v === "2000-3000") return p >= 2000 && p <= 3000;
if (v === "3000~") return p >= 3000;
return true;
}
function matchReview(card) {
if (!reviewSel) return true;
const v = reviewSel.value || "all";
const r = num(getTextAttr(card, "data-review"), 0);
if (v === "all") return true;
if (v === "has") return r > 0;
if (v === "4.0") return r >= 4.0;
if (v === "4.5") return r >= 4.5;
return true;
}
function matchLabel(card) {
if (!labelSel) return true;
const v = labelSel.value || "all";
if (v === "all") return true;
return getTextAttr(card, "data-label-name") === v;
}
function matchSearch(card) {
if (!searchInp) return true;
const q = (searchInp.value || "").trim().toLowerCase();
if (!q) return true;
const title = (card.querySelector(".title")?.textContent || "").trim().toLowerCase();
const actress = getTextAttr(card, "data-actress-names").toLowerCase();
const maker = getTextAttr(card, "data-maker-name").toLowerCase();
const series = getTextAttr(card, "data-series-name").toLowerCase();
const genre = getTextAttr(card, "data-genre-names").toLowerCase();
const label = getTextAttr(card, "data-label-name").toLowerCase();
const blob = [title, actress, maker, series, genre, label].join(" ");
return blob.includes(q);
}
function matchSolo(card) {
if (!soloOnly) return true;
if (!soloOnly.checked) return true;
const n = parseInt(getTextAttr(card, "data-actress-count") || "0", 10);
return n === 1;
}
function matchFav(card) {
if (!favOnly) return true;
if (!favOnly.checked) return true;
const cid = getTextAttr(card, "data-content-id");
return cid && favs.has(cid);
}
function matchGenre(card) {
if (!activeGenre) return true;
const genres = parseCSV(getTextAttr(card, "data-genre-names"));
return genres.includes(activeGenre);
}
function sortCards(list) {
if (!sortSel) return list;
const mode = sortSel.value || "rank";
const copy = list.slice();
copy.sort((a,b) => {
if (mode === "rank") {
return num(getTextAttr(a,"data-rank"), 999999) - num(getTextAttr(b,"data-rank"), 999999);
}
if (mode === "date") {
// date string compare (YYYY-MM-DD ...)
return getTextAttr(b,"data-date").localeCompare(getTextAttr(a,"data-date"));
}
if (mode === "review") {
return num(getTextAttr(b,"data-review"), 0) - num(getTextAttr(a,"data-review"), 0);
}
if (mode === "price-asc") {
return num(getTextAttr(a,"data-price"), 0) - num(getTextAttr(b,"data-price"), 0);
}
if (mode === "price-desc") {
return num(getTextAttr(b,"data-price"), 0) - num(getTextAttr(a,"data-price"), 0);
}
return 0;
});
return copy;
}
function apply(keepLimit = false) {
if (!keepLimit) visibleLimit = pageSize;
// filter
let filtered = cards.filter(card =>
matchLabel(card) &&
matchPrice(card) &&
matchReview(card) &&
matchSearch(card) &&
matchSolo(card) &&
matchFav(card) &&
matchGenre(card)
);
// sort
filtered = sortCards(filtered);
// show/hide with pagination
const total = filtered.length;
const visible = Math.min(total, visibleLimit);
// hide all first
cards.forEach(c => { c.style.display = "none"; });
filtered.slice(0, visible).forEach(c => { c.style.display = ""; });
updateCount(visible, total);
if (moreBtn) {
moreBtn.disabled = visible >= total;
moreBtn.style.display = total > pageSize ? "" : (total > 0 ? "none" : "none");
}
}
// listeners
[sortSel,labelSel,priceSel,reviewSel,searchInp,soloOnly,favOnly].forEach(el => {
if (!el) return;
const evt = el.tagName === "INPUT" ? (el.type === "search" ? "input" : "change") : "change";
el.addEventListener(evt, () => apply());
});
apply();
}
function boot() {
document.querySelectorAll(".fanza-widget").forEach(initWidget);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", boot);
} else {
boot();
}
})();
Warning: Cannot modify header information - headers already sent by (output started at /home/wp669230/fresh-shorouto.com/public_html/wp-content/plugins/fanza-actress-widget-loader-unified/fanza-actress-widget-loader-unified.php:1) in /home/wp669230/fresh-shorouto.com/public_html/wp-includes/sitemaps/class-wp-sitemaps-renderer.php on line 126
https://fresh-shorouto.com/wp-sitemap-posts-post-1.xmlhttps://fresh-shorouto.com/wp-sitemap-posts-page-1.xmlhttps://fresh-shorouto.com/wp-sitemap-taxonomies-category-1.xmlhttps://fresh-shorouto.com/wp-sitemap-taxonomies-actress_name-1.xmlhttps://fresh-shorouto.com/wp-sitemap-users-1.xml