chore(lps): 임시 FE — 오픈마켓 크롤 소스 표기(색상·크롤 뱃지·몰별 색)

by_mall/top 에 등장하는 gmarket·auction·st11 을 소스 색상 태그(+‘크롤’ 뱃지)로 표기.
몰별 최저가 막대도 소스별 색. 폴백으로 크롤된 몰임을 UI에서 구분.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
민헌 2026-07-09 14:47:31 +09:00
parent f9714c3f06
commit 7e5814b0e5

View File

@ -93,8 +93,11 @@
.outcome-hero.nf{background:linear-gradient(135deg,#fbfbfc,#f4f5f7)} .outcome-hero.nf{background:linear-gradient(135deg,#fbfbfc,#f4f5f7)}
.price-big{font-size:32px;font-weight:800;letter-spacing:-.02em;line-height:1;font-variant-numeric:tabular-nums} .price-big{font-size:32px;font-weight:800;letter-spacing:-.02em;line-height:1;font-variant-numeric:tabular-nums}
.price-big .won{font-size:18px;font-weight:700;color:var(--text-500);margin-left:2px} .price-big .won{font-size:18px;font-weight:700;color:var(--text-500);margin-left:2px}
.src-tag{font-size:11px;font-weight:700;padding:3px 8px;border-radius:6px;color:#fff;text-transform:uppercase;letter-spacing:.03em} .src-tag{font-size:11px;font-weight:700;padding:3px 8px;border-radius:6px;color:#fff;letter-spacing:.02em;white-space:nowrap}
.src-tag.naver{background:var(--naver)} .src-tag.coupang{background:var(--coupang)} .src-tag.naver{background:var(--naver)} .src-tag.coupang{background:var(--coupang)}
.src-tag.gmarket{background:#00b25a} .src-tag.auction{background:#e60012} .src-tag.st11{background:#ff0038}
.src-tag.fallback{position:relative}
.src-tag.fallback::after{content:"크롤";position:absolute;top:-6px;right:-6px;font-size:8px;background:#1a1d24;color:#fff;padding:1px 3px;border-radius:4px;letter-spacing:0}
.ship-tag{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;font-weight:700;padding:2px 7px;border-radius:5px;border:1px solid transparent;white-space:nowrap} .ship-tag{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;font-weight:700;padding:2px 7px;border-radius:5px;border:1px solid transparent;white-space:nowrap}
.ship-tag.rocket{background:#eef4ff;color:#2f6fed;border-color:#d5e3ff} .ship-tag.rocket{background:#eef4ff;color:#2f6fed;border-color:#d5e3ff}
.ship-tag.rocket_merchant{background:#f3eeff;color:#7a4fe0;border-color:#e3d8ff} .ship-tag.rocket_merchant{background:#f3eeff;color:#7a4fe0;border-color:#e3d8ff}
@ -315,10 +318,23 @@ function shipBadge(p){
/* 실구매가(상품가+확정 배송비). 미확인이면 null. */ /* 실구매가(상품가+확정 배송비). 미확인이면 null. */
function totalPrice(p){ return (typeof p.shipping_fee==="number") ? p.price+p.shipping_fee : null; } function totalPrice(p){ return (typeof p.shipping_fee==="number") ? p.price+p.shipping_fee : null; }
/* 소스 메타: 표시명·색상. gmarket/auction/st11 은 네이버 폴백 크롤 소스. */
const SRC = {
naver: {label:"NAVER", color:"#03c75a", crawl:false},
coupang:{label:"COUPANG",color:"#f0455b", crawl:false},
gmarket:{label:"G마켓", color:"#00b25a", crawl:true},
auction:{label:"옥션", color:"#e60012", crawl:true},
st11: {label:"11번가", color:"#ff0038", crawl:true},
};
function srcColor(s){ return (SRC[s]||{}).color || "#6b7280"; }
function srcTag(p){
const m=SRC[p.source]||{label:p.source};
return `<span class="src-tag ${p.source}${m.crawl?' fallback':''}" title="${m.crawl?'네이버 미커버 → 실사이트 크롤':''}">${esc(m.label)}</span>`;
}
/* 판매몰 표시명: 네이버 가격비교(mall='네이버')는 '여러 판매자 최저가 롤업'임을 명시. */ /* 판매몰 표시명: 네이버 가격비교(mall='네이버')는 '여러 판매자 최저가 롤업'임을 명시. */
function mallLabel(p){ function mallLabel(p){
if(p.source==="coupang") return "쿠팡"; if(p.source==="coupang") return "쿠팡";
const m=p.mall_name||"기타"; const m=p.mall_name||(SRC[p.source]||{}).label||"기타";
return (m==="네이버") ? "네이버 가격비교" : m; return (m==="네이버") ? "네이버 가격비교" : m;
} }
function isCatalog(p){ return p.source==="naver" && (p.mall_name==="네이버"); } function isCatalog(p){ return p.source==="naver" && (p.mall_name==="네이버"); }
@ -443,7 +459,7 @@ function renderJob(d, submitted){
<div class="outcome-label">${found?'최저가 발견':(o.cached?'네거티브 캐시 (최근 없음)':'같은 상품 없음 · not_found')}</div> <div class="outcome-label">${found?'최저가 발견':(o.cached?'네거티브 캐시 (최근 없음)':'같은 상품 없음 · not_found')}</div>
${ found && low ? `<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap"> ${ found && low ? `<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
<div class="price-big">${won(low.price)}<span class="won">원</span></div> <div class="price-big">${won(low.price)}<span class="won">원</span></div>
<span class="src-tag ${low.source}">${low.source}</span> ${srcTag(low)}
${shipBadge(low)} ${shipBadge(low)}
</div> </div>
<div class="muted" style="font-size:11.5px;margin-top:5px">판매몰 <b style="color:var(--text-700)">${esc(mallLabel(low))}</b>${ isCatalog(low)?` <span title="여러 판매자 중 최저가 롤업 · 배송비 제외">· 여러 판매자 최저가</span>`:``}</div> <div class="muted" style="font-size:11.5px;margin-top:5px">판매몰 <b style="color:var(--text-700)">${esc(mallLabel(low))}</b>${ isCatalog(low)?` <span title="여러 판매자 중 최저가 롤업 · 배송비 제외">· 여러 판매자 최저가</span>`:``}</div>
@ -487,10 +503,10 @@ function renderJob(d, submitted){
const byMall=o.by_mall||[]; const byMall=o.by_mall||[];
if(byMall.length>1){ if(byMall.length>1){
const maxP=Math.max(...byMall.map(m=>m.price)); const maxP=Math.max(...byMall.map(m=>m.price));
html += `<div class="sec-title">몰별 최저가 <span class="cnt">네이버 결과에 포함된 판매몰 · 추가 조회 없음</span></div><div class="mall-bars">`; html += `<div class="sec-title">몰별 최저가 <span class="cnt">네이버 포함 몰 + 미커버 몰은 직접 크롤(크롤 뱃지)</span></div><div class="mall-bars">`;
byMall.forEach((m,i)=>{ byMall.forEach((m,i)=>{
const w=Math.max(8,Math.round(m.price/maxP*100)); const w=Math.max(8,Math.round(m.price/maxP*100));
const color=m.source==="coupang"?"var(--coupang)":"var(--naver)"; const color=srcColor(m.source);
html += `<div class="mall-bar ${i===0?'best':''}"> html += `<div class="mall-bar ${i===0?'best':''}">
<div class="mname"><span class="sdot" style="background:${color}"></span>${esc(mallLabel(m))}</div> <div class="mname"><span class="sdot" style="background:${color}"></span>${esc(mallLabel(m))}</div>
<div class="track"><div class="fill" style="width:${w}%;background:${color}"></div> <div class="track"><div class="fill" style="width:${w}%;background:${color}"></div>
@ -510,7 +526,7 @@ function renderJob(d, submitted){
const tot=totalPrice(p); const tot=totalPrice(p);
html += `<tr> html += `<tr>
<td><span class="rank ${i===0?'top':''}">${i+1}</span></td> <td><span class="rank ${i===0?'top':''}">${i+1}</span></td>
<td style="white-space:nowrap"><span class="src-tag ${p.source}">${p.source}</span><div class="muted" style="font-size:11px;font-weight:600;margin-top:3px">${esc(mallLabel(p))}</div></td> <td style="white-space:nowrap">${srcTag(p)}<div class="muted" style="font-size:11px;font-weight:600;margin-top:3px">${esc(mallLabel(p))}</div></td>
<td class="name-cell">${p.detail_url?`<a href="${esc(p.detail_url)}" target="_blank" rel="noopener">${esc(p.name)}</a>`:esc(p.name)}</td> <td class="name-cell">${p.detail_url?`<a href="${esc(p.detail_url)}" target="_blank" rel="noopener">${esc(p.name)}</a>`:esc(p.name)}</td>
<td>${shipBadge(p)}</td> <td>${shipBadge(p)}</td>
<td class="price-cell" style="text-align:right">${won(p.price)}원${ tot!=null&&p.shipping_fee>0?`<div class="muted" style="font-size:10.5px;font-weight:600">+배송 ${won(tot)}</div>`:``}</td> <td class="price-cell" style="text-align:right">${won(p.price)}원${ tot!=null&&p.shipping_fee>0?`<div class="muted" style="font-size:10.5px;font-weight:600">+배송 ${won(tot)}</div>`:``}</td>