('meta[property="og:title"]')?.getAttribute("content") ??
document.querySelector("title")?.textContent ??
null;
const cleanedTitle = ogTitle
? ogTitle.replace(FC2_PPV_PREFIX_RE, "").replace(FC2_TITLE_SUFFIX_RE, "").trim()
: null;
const title = cleanedTitle && cleanedTitle.length > 0 ? cleanedTitle : null;
// Publish date: spec rows live in multiple
// blocks; the date one reads "Sale Day : YYYY/MM/DD" (EN) or "販売日 : YYYY/MM/DD" (JP).
let publishedAt: number | null = null;
for (const p of Array.from(document.querySelectorAll(".items_article_softDevice p"))) {
const text = p.textContent;
if (!text) continue;
if (!/Sale Day|販売日|发布日|Sold Date/i.test(text)) continue;
const parsed = parseFC2Date(text);
if (parsed !== null) {
publishedAt = parsed;
break;
}
}
// Price: .normal_price lives inside .items_article_headerPriceArea and on the
// mylist box. If a sibling element with "sale"/"discount"/"special" in its class
// is present, that is the active sale price and flags onPromo.
const priceArea = document.querySelector(".items_article_headerPriceArea");
const saleEl =
priceArea?.querySelector(
'[class*="sale_price"], [class*="discount"], [class*="special_price"]'
) ?? null;
const priceSource =
saleEl?.textContent ??
priceArea?.querySelector(".normal_price")?.textContent ??
document.querySelector(".normal_price")?.textContent ??
null;
const priceYen = parsePriceJPY(priceSource);
const onPromo = saleEl !== null;
return { publishedAt, priceYen, onPromo, title };
};