Files
amazon-za-delivery/index.html
T
2026-08-24 16:51:55 +08:00

46 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>Amazon - ZA daily tracker</title>
<style>
:root{--bg:#f4f7fb;--panel:#fff;--ink:#162033;--muted:#667085;--line:#dce3ec;--blue:#2f6fed;--gold:#d79b24;--orange:#e56d35;--soft:#eaf1ff;--shadow:0 8px 28px rgba(23,43,77,.08)}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.55 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}main{max-width:1180px;margin:auto;padding:42px 24px 64px}.eyebrow{color:var(--blue);font-weight:700;letter-spacing:.08em;text-transform:uppercase}.hero h1{font-size:clamp(28px,4vw,46px);line-height:1.1;margin:8px 0 12px}.hero p{color:var(--muted);font-size:16px;margin:0;max-width:800px}.status{display:inline-flex;align-items:center;gap:8px;margin-top:18px;padding:7px 11px;border:1px solid var(--line);border-radius:999px;background:var(--panel);color:var(--muted)}.dot{width:8px;height:8px;border-radius:50%;background:var(--blue)}section{margin-top:30px}h2{font-size:22px;margin:0 0 12px}.summary{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.card,.panel{background:var(--panel);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow)}.card{padding:18px}.card .label{color:var(--muted);font-weight:600}.card .value{font-size:28px;font-weight:750;margin-top:6px;font-variant-numeric:tabular-nums}.card .note{color:var(--muted);font-size:12px;margin-top:3px}.panel{padding:22px;margin-top:14px}.takeaway{border-left:4px solid var(--blue);padding-left:14px;color:var(--muted)}.takeaway strong{color:var(--ink)}.charts{display:grid;grid-template-columns:1fr 1fr;gap:14px}.chart{min-height:300px;overflow-x:auto}.chart-head{margin-bottom:16px}.chart-title{font-weight:750;font-size:16px}.chart-sub{color:var(--muted);font-size:12px}.bars{height:220px;display:flex;align-items:end;gap:14px;padding:20px 10px 28px;border-bottom:1px solid var(--line);position:relative}.bar-group{flex:1;height:100%;display:flex;align-items:end;justify-content:center;gap:6px;position:relative}.bar{min-width:8px;max-width:34px;width:33%;border-radius:6px 6px 0 0;position:relative}.bar span{position:absolute;top:-21px;left:50%;transform:translateX(-50%);font-size:11px;font-weight:700;white-space:nowrap}.bar-label{position:absolute;bottom:-25px;left:50%;transform:translateX(-50%);color:var(--muted);font-size:11px}.legend{display:flex;gap:14px;margin-top:13px;color:var(--muted);font-size:12px}.swatch{width:9px;height:9px;border-radius:2px;display:inline-block;margin-right:5px}.rate-bars .bar{width:52%;background:var(--orange)}.progress{height:14px;background:var(--soft);border-radius:999px;overflow:hidden;margin:18px 0 8px}.progress-fill{height:100%;background:var(--blue);border-radius:inherit}.progress-labels{display:flex;justify-content:space-between;color:var(--muted);font-size:12px}table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums}th,td{padding:11px 9px;border-bottom:1px solid var(--line);text-align:right;white-space:nowrap}th:first-child,td:first-child{text-align:left}th{color:var(--muted);font-size:12px}tbody tr:hover{background:var(--soft)}tr.future{color:var(--muted);opacity:.65}.footer-note{color:var(--muted);font-size:12px;margin-top:18px}.error{padding:18px;border:1px solid #f0b7b7;background:#fff2f2;color:#912b2b;border-radius:12px}.login-shell{min-height:100vh;display:grid;place-items:center;padding:24px}.login-card{width:min(430px,100%);background:var(--panel);border:1px solid var(--line);border-radius:22px;padding:30px;box-shadow:var(--shadow)}.login-card h1{font-size:28px;margin:8px 0}.login-card p{color:var(--muted);margin:0 0 22px}.field{display:block;margin-top:14px;font-weight:700}.field input{display:block;width:100%;margin-top:7px;border:1px solid var(--line);border-radius:11px;padding:12px 13px;background:var(--bg);color:var(--ink);font:inherit;outline:none}.field input:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(47,111,237,.14)}button{width:100%;margin-top:20px;border:0;border-radius:11px;padding:13px;background:var(--blue);color:white;font:inherit;font-weight:750;cursor:pointer}button:disabled{opacity:.65;cursor:wait}.login-error{min-height:22px;color:#c13a3a;font-size:13px;margin-top:10px}.access-note{font-size:12px!important;margin-top:18px!important}#report[hidden],#login[hidden]{display:none!important}@media(max-width:850px){.summary{grid-template-columns:1fr 1fr}.charts{grid-template-columns:1fr}}@media(max-width:520px){main{padding:26px 14px}.summary{grid-template-columns:1fr}.panel{padding:16px;overflow:auto}.card .value{font-size:24px}.login-card{padding:24px}}
.table-wrap{overflow-x:auto;padding:0}.table-wrap table{min-width:950px}.table-wrap th{position:sticky;top:0;background:var(--soft);z-index:1;text-transform:uppercase;letter-spacing:.03em}.table-wrap th,.table-wrap td{padding:12px 14px}.table-wrap tbody tr:nth-child(even):not(.future){background:rgba(47,111,237,.035)}.table-wrap tbody tr:hover{background:var(--soft)}.rate-spike{background:rgba(229,109,53,.16);color:#b94f20;font-weight:800;border-radius:7px}.trend-svg{display:block;width:100%;height:260px;min-width:760px}.trend-grid{stroke:var(--line);stroke-width:1}.trend-axis{fill:var(--muted);font-size:10px}.trend-line{fill:none;stroke-width:3;stroke-linejoin:round;stroke-linecap:round}.trend-point{stroke:var(--panel);stroke-width:2}
@media(prefers-color-scheme:dark){:root{--bg:#0c1220;--panel:#121a2a;--ink:#eef3fb;--muted:#9ba9bd;--line:#28354a;--soft:#182743;--shadow:none}}
</style>
</head>
<body><div id="login" class="login-shell"><form id="loginForm" class="login-card" autocomplete="on"><div class="eyebrow">Restricted Report</div><h1>登录查看报告</h1><p>请输入获授权的邮箱与密码。</p><label class="field">邮箱<input id="email" type="email" autocomplete="username" required></label><label class="field">密码<input id="password" type="password" autocomplete="current-password" required></label><button id="loginButton" type="submit">登录</button><div id="loginError" class="login-error" role="alert" aria-live="polite"></div><p class="access-note">仅授权名单内的邮箱可以访问。</p></form></div><main id="report" hidden>
<header class="hero"><div class="eyebrow">Daily Delivery · ZA</div><h1>Amazon - ZA daily tracker</h1><p>包名:com.amazon.mShop.android.shopping。聚焦 repostback 交付、E1+E2 转化与 Midas / Payable spend。</p><div class="status"><span class="dot"></span><span id="range">正在载入数据…</span></div></header>
<section><h2>核心指标</h2><div class="summary" id="cards"></div></section>
<section><h2>8 月预算消耗</h2><div id="budgetText" class="takeaway"></div><div class="summary" id="budgetCards" style="margin-top:14px"></div><div class="panel"><div class="chart-title">月度预算使用进度</div><div class="chart-sub">预算按 E1+E2 × 49.50 ZAR 扣减</div><div class="progress"><div id="budgetProgress" class="progress-fill"></div></div><div class="progress-labels"><span id="budgetUsed"></span><span id="budgetTotal"></span></div></div></section>
<section><h2>每日交付与转化</h2><div class="takeaway"><strong>如何阅读:</strong>Install 仅取交付 repostback,不包含 app open;消耗趋势对比每日 Midas 与 Payable spend,转化趋势用于定位 Event rate 波动。</div><div class="panel chart"><div class="chart-head"><div class="chart-title">本月每日消耗趋势</div><div class="chart-sub">USD;Midas spend 与 Payable spend</div></div><div id="spendTrend"></div><div class="legend"><span><i class="swatch" style="background:var(--blue)"></i>Midas spend</span><span><i class="swatch" style="background:var(--gold)"></i>Payable spend</span></div></div><div class="charts"><div class="panel chart"><div class="chart-head"><div class="chart-title">每日 Install</div><div class="chart-sub">仅取 delivery repostback,排除 af_app_opened</div></div><div id="installChart" class="bars"></div></div><div class="panel chart"><div class="chart-head"><div class="chart-title">E1+E2% 趋势</div><div class="chart-sub">(Event1 + Event2) ÷ Install</div></div><div id="rateTrend"></div></div></div></section>
<section><h2>8 月每日明细</h2><div class="panel table-wrap"><table><thead><tr><th>日期</th><th>Install</th><th>Event1</th><th>Event2</th><th>Total</th><th>Event rate</th><th>Payable (ZAR)</th><th>日预算</th><th>FFR</th><th>Midas (USD)</th></tr></thead><tbody id="rows"></tbody></table><div class="footer-note">口径:Install 仅取交付 repostback;Total = E1+E2;FFR = Total ÷ Daily cap。Event rate 较前一日波动 ≥5 个百分点时突出显示;灰色日期为数据截止日之后。</div></div></section>
<section><h2>自然周汇总</h2><div class="panel table-wrap"><table><thead><tr><th>自然周(周一至周日)</th><th>Install</th><th>Event1+2</th><th>Event1+2%</th><th>Payable spend</th><th>Midas spend</th></tr></thead><tbody id="weeklyRows"></tbody></table><div class="footer-note">金额为 USD;首周和末周按 8 月自然月边界截取,当前周统计至数据截止日。</div></div></section>
<section><h2>建议关注</h2><div class="panel"><ul><li>优先关注 FFR 明显低于 100% 的日期,判断是流量不足、事件率下降还是日 cap 执行问题。</li><li>剩余预算需要的日均消耗已高于当前日预算;如目标是完整消耗月预算,需要确认是否提高每日 cap。</li><li>对比 Payable spend 与 Midas spend 的日级差额,排查差额扩大日期的事件质量和计费口径。</li></ul></div></section>
</main><script>
const fmt=n=>new Intl.NumberFormat('en-US').format(n), money=n=>'$'+new Intl.NumberFormat('en-US',{minimumFractionDigits:2,maximumFractionDigits:2}).format(n), pct=n=>n==null?'—':n.toFixed(2)+'%';
const label=d=>d.slice(5).replace('-','/');
function bars(el,data,series,max){const showValues=data.length<=12;el.style.minWidth=Math.max(0,data.length*48)+'px';el.innerHTML=data.map(d=>`<div class="bar-group">${series.map(s=>`<div class="bar" style="height:${Math.max(2,d[s.key]/max*100)}%;background:${s.color}">${showValues?`<span>${s.format(d[s.key])}</span>`:''}</div>`).join('')}<div class="bar-label">${label(d.date)}</div></div>`).join('')}
function lineChart(el,data,series,format){const w=Math.max(760,data.length*42),h=260,p={l:48,r:18,t:18,b:34},vals=data.flatMap(d=>series.map(s=>Number(d[s.key])||0)),max=Math.max(...vals,1),min=0,x=i=>p.l+i*(w-p.l-p.r)/Math.max(data.length-1,1),y=v=>h-p.b-(v-min)*(h-p.t-p.b)/(max-min||1),ticks=[0,.25,.5,.75,1];el.innerHTML=`<svg class="trend-svg" viewBox="0 0 ${w} ${h}" role="img">${ticks.map(t=>`<line class="trend-grid" x1="${p.l}" x2="${w-p.r}" y1="${y(max*t)}" y2="${y(max*t)}"/><text class="trend-axis" x="${p.l-7}" y="${y(max*t)+3}" text-anchor="end">${format(max*t)}</text>`).join('')}${data.map((d,i)=>`<text class="trend-axis" x="${x(i)}" y="${h-10}" text-anchor="middle">${label(d.date)}</text>`).join('')}${series.map(s=>`<polyline class="trend-line" stroke="${s.color}" points="${data.map((d,i)=>`${x(i)},${y(d[s.key]||0)}`).join(' ')}"/>${data.map((d,i)=>`<circle class="trend-point" fill="${s.color}" cx="${x(i)}" cy="${y(d[s.key]||0)}" r="3"><title>${d.date} · ${s.name}: ${format(d[s.key]||0)}</title></circle>`).join('')}`).join('')}</svg>`}
const ACCESS={emails:['a91dc567bcdb6d054cf121d7e4016454ee3d316e3b452b851ae4d3087bc99bb2'],salt:'amazon-za-delivery-2026-08',password:'2344c6c9cf72430cafbf48e014d7f721a53484f766f1455f0c12232515ec61e9'};
async function sha256(value){const bytes=new TextEncoder().encode(value);const digest=await crypto.subtle.digest('SHA-256',bytes);return [...new Uint8Array(digest)].map(x=>x.toString(16).padStart(2,'0')).join('')}
function showReport(){document.getElementById('login').hidden=true;document.getElementById('report').hidden=false;loadReport()}
async function loadReport(){fetch('data.json',{cache:'no-store'}).then(r=>{if(!r.ok)throw Error('data.json '+r.status);return r.json()}).then(data=>{
const t=data.totals,b=data.budget,all=data.daily,d=all.filter(x=>x.is_actual);document.getElementById('range').textContent=`2026-08-01 至 2026-08-31 · 数据截至 ${data.meta.data_cutoff}`;
const cards=[['Install',fmt(t.install),'仅 delivery repostback;排除 app open'],['E1 + E2',fmt(t.e1e2),`${fmt(t.event1)} E1 · ${fmt(t.event2)} E2`],['E1+E2%',pct(t.e1e2_rate),'累计 E1+E2 ÷ 累计 Install'],['Payable spend',money(t.payable_spend),`Midas spend ${money(t.midas_spend)}`]];document.getElementById('cards').innerHTML=cards.map(x=>`<div class="card"><div class="label">${x[0]}</div><div class="value">${x[1]}</div><div class="note">${x[2]}</div></div>`).join('');
const budgetCards=[['月度预算','R '+fmt(b.monthly_budget_zar),money(b.monthly_budget_usd)],['已用预算','R '+fmt(b.used_budget_zar),`${pct(b.used_rate)} used · MTD expected FFR ${pct(b.mtd_expected_ffr)}`],['剩余预算','R '+fmt(b.remaining_budget_zar),money(b.remaining_budget_usd)],['剩余日均需求','R '+fmt(b.required_daily_budget_zar),`${fmt(b.required_daily_events)} E1+E2 / day`]];document.getElementById('budgetCards').innerHTML=budgetCards.map(x=>`<div class="card"><div class="label">${x[0]}</div><div class="value">${x[1]}</div><div class="note">${x[2]}</div></div>`).join('');
document.getElementById('budgetText').innerHTML=`<strong>预算消耗低于截至当前日期的均匀进度。</strong> 已用 R ${fmt(b.used_budget_zar)},同期预算应为 R ${fmt(b.expected_budget_to_date_zar)};剩余 ${b.remaining_days} 天需要日均 R ${fmt(b.required_daily_budget_zar)},高于当前日预算 R ${fmt(b.daily_budget_zar)}。`;
document.getElementById('budgetProgress').style.width=Math.min(b.used_rate,100)+'%';document.getElementById('budgetUsed').textContent=`已用 ${pct(b.used_rate)} · R ${fmt(b.used_budget_zar)}`;document.getElementById('budgetTotal').textContent=`总预算 R ${fmt(b.monthly_budget_zar)}`;
lineChart(document.getElementById('spendTrend'),d,[{key:'midas_spend',name:'Midas spend',color:'var(--blue)'},{key:'payable_spend',name:'Payable spend',color:'var(--gold)'}],v=>'$'+Math.round(v));
bars(document.getElementById('installChart'),d,[{key:'install',color:'var(--blue)',format:fmt}],Math.max(...d.map(x=>x.install),1));
lineChart(document.getElementById('rateTrend'),d,[{key:'e1e2_rate',name:'E1+E2%',color:'var(--orange)'}],v=>v.toFixed(0)+'%');
document.getElementById('rows').innerHTML=all.map((x,i)=>{const prev=i?all[i-1]:null,spike=x.is_actual&&prev&&prev.is_actual&&Math.abs((x.e1e2_rate||0)-(prev.e1e2_rate||0))>=5;return `<tr class="${x.is_actual?'':'future'}"><td>${x.date}</td><td>${fmt(x.install)}</td><td>${fmt(x.event1)}</td><td>${fmt(x.event2)}</td><td>${fmt(x.e1e2)}</td><td class="${spike?'rate-spike':''}">${pct(x.e1e2_rate)}</td><td>R ${fmt(x.payable_spend_zar)}</td><td>R ${fmt(x.daily_budget_zar)}</td><td>${pct(x.ffr_rate)}</td><td>${money(x.midas_spend)}</td></tr>`}).join('');
document.getElementById('weeklyRows').innerHTML=data.weekly.map(w=>`<tr><td>${w.label}</td><td>${fmt(w.install)}</td><td>${fmt(w.e1e2)}</td><td>${pct(w.e1e2_rate)}</td><td>${money(w.payable_spend)}</td><td>${money(w.midas_spend)}</td></tr>`).join('');
}).catch(e=>{document.getElementById('range').textContent='数据载入失败:'+e.message})}
document.getElementById('loginForm').addEventListener('submit',async e=>{e.preventDefault();const button=document.getElementById('loginButton'),error=document.getElementById('loginError');button.disabled=true;error.textContent='';try{const email=document.getElementById('email').value.trim().toLowerCase(),password=document.getElementById('password').value;const [emailHash,passwordHash]=await Promise.all([sha256(email),sha256(ACCESS.salt+':'+password)]);if(ACCESS.emails.includes(emailHash)&&passwordHash===ACCESS.password){sessionStorage.setItem('amazonZaAccess','granted');showReport()}else{error.textContent='邮箱或密码不正确。'}}catch(_){error.textContent='当前浏览器无法完成验证,请更换现代浏览器。'}finally{button.disabled=false}});
if(sessionStorage.getItem('amazonZaAccess')==='granted')showReport();
</script></body></html>