Files
tecdo-spend-amount/index.html
T
2026-09-10 17:19:56 +08:00

422 lines
21 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">
<title>Tecdo 消耗与余额</title>
<link rel="icon" href="data:,">
<style>
:root {
color-scheme: light;
--page: #f9f9f7; --surface: #fcfcfb; --ink: #0b0b0b; --ink-2: #52514e; --muted: #898781;
--grid: #e1e0d9; --axis: #c3c2b7; --border: rgba(11,11,11,.10);
--s1: #2a78d6; --s2: #eb6834; --warn: #fab219; --crit: #d03b3b;
}
@media (prefers-color-scheme: dark) {
:root:where(:not([data-theme="light"])) {
color-scheme: dark;
--page: #0d0d0d; --surface: #1a1a19; --ink: #ffffff; --ink-2: #c3c2b7; --muted: #898781;
--grid: #2c2c2a; --axis: #383835; --border: rgba(255,255,255,.10);
--s1: #3987e5; --s2: #d95926;
}
}
:root[data-theme="dark"] {
color-scheme: dark;
--page: #0d0d0d; --surface: #1a1a19; --ink: #ffffff; --ink-2: #c3c2b7; --muted: #898781;
--grid: #2c2c2a; --axis: #383835; --border: rgba(255,255,255,.10);
--s1: #3987e5; --s2: #d95926;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--page); color: var(--ink); font: 14px/1.5 system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; }
main { max-width: 1080px; margin: 0 auto; padding: 28px 20px 48px; }
h1 { font-size: 22px; font-weight: 600; margin: 0 0 4px; }
.meta { color: var(--ink-2); font-size: 13px; display: flex; flex-wrap: wrap; gap: 2px 14px; }
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; margin: 20px 0 16px; }
.tile, .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
.tile { padding: 14px 16px; }
.tile-label { color: var(--ink-2); font-size: 13px; }
.tile-value { font-size: 26px; font-weight: 600; margin-top: 2px; letter-spacing: -.01em; }
.tile-sub { color: var(--muted); font-size: 12px; margin-top: 4px; }
.card { padding: 16px 18px; margin-bottom: 16px; }
.card h2 { font-size: 15px; font-weight: 600; margin: 0; }
.desc { color: var(--ink-2); font-size: 12px; margin: 2px 0 12px; }
.legend { display: flex; flex-wrap: wrap; gap: 4px 18px; font-size: 12px; color: var(--ink-2); margin-bottom: 4px; }
.sw { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 6px; vertical-align: -1px; flex: none; }
.chart { position: relative; }
.chart svg { display: block; max-width: 100%; }
.grid { stroke: var(--grid); stroke-width: 1; }
.axis { stroke: var(--axis); stroke-width: 1; }
.tick { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.val { fill: var(--ink); font-size: 12px; font-weight: 600; }
.hover-band { fill: var(--ink); opacity: .05; }
.line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.dot { stroke: var(--surface); stroke-width: 2; }
.cross { stroke: var(--axis); stroke-width: 1; }
.tip { position: absolute; z-index: 2; pointer-events: none; min-width: 170px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.14); padding: 8px 10px; font-size: 12px; }
.tip-title { font-weight: 600; margin-bottom: 4px; }
.tip-row { display: flex; align-items: center; gap: 6px; }
.tip-name { flex: 1; color: var(--ink-2); }
.tip-val { font-weight: 600; font-variant-numeric: tabular-nums; }
.tip-sub { color: var(--muted); padding-left: 16px; margin-bottom: 2px; }
.tip-total { border-top: 1px solid var(--grid); margin-top: 4px; padding-top: 4px; }
.empty { color: var(--ink-2); border: 1px dashed var(--axis); border-radius: 8px; padding: 18px; font-size: 13px; }
.table-wrap { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: 13px; white-space: nowrap; }
th { text-align: left; color: var(--muted); font-weight: 500; font-size: 12px; padding: 6px 10px; border-bottom: 1px solid var(--axis); }
td { padding: 7px 10px; border-bottom: 1px solid var(--grid); }
th.num, td.num { text-align: right; font-variant-numeric: tabular-nums; }
td.muted { color: var(--muted); }
tfoot td { font-weight: 600; border-bottom: none; }
.badge { display: inline-flex; align-items: center; gap: 5px; color: var(--ink); }
.badge-icon { display: inline-grid; place-items: center; width: 15px; height: 15px; border-radius: 50%; font-size: 10px; font-weight: 700; color: #0b0b0b; }
.badge.warn .badge-icon { background: var(--warn); }
.badge.crit .badge-icon { background: var(--crit); color: #fff; }
footer { color: var(--muted); font-size: 12px; margin-top: 8px; }
#err { background: var(--surface); border: 1px solid var(--crit); border-radius: 8px; padding: 14px; margin-top: 20px; }
</style>
</head>
<body>
<main>
<header>
<h1>Tecdo 客户消耗与余额</h1>
<div class="meta" id="meta">加载中…</div>
</header>
<div id="err" hidden></div>
<section class="tiles" id="tiles"></section>
<section class="card">
<h2>近 7 天每日消耗</h2>
<p class="desc">两个订单按天堆叠,悬停查看每个订单的消耗、安装和点击</p>
<div class="legend" id="spend-legend"></div>
<div class="chart" id="spend-chart"><div class="tip" hidden></div></div>
</section>
<section class="card">
<h2>余额走势</h2>
<p class="desc">每天日终余额 = 截至当天的累计充值 − 累计消耗</p>
<div class="empty" id="balance-empty" hidden>还没有充值记录。把充值日期和金额发给 Claude,或者编辑仓库里的 manual.json,余额和走势会自动算出来。</div>
<div class="chart" id="balance-chart"><div class="tip" hidden></div></div>
</section>
<section class="card">
<h2>每日明细</h2>
<p class="desc">按日期倒序,每个订单一行</p>
<div class="table-wrap">
<table id="daily">
<thead><tr><th>日期</th><th>订单</th><th>App</th><th>pkg name</th><th class="num">消耗</th><th class="num">安装</th><th class="num">点击</th><th class="num">eCPI</th></tr></thead>
<tbody></tbody><tfoot></tfoot>
</table>
</div>
</section>
<section class="card">
<h2>订单信息</h2>
<div class="table-wrap">
<table id="camps">
<thead><tr><th>订单</th><th>订单名</th><th>App</th><th>pkg name</th><th>开始日期</th><th class="num">出价</th><th class="num">日预算</th><th class="num">累计消耗</th></tr></thead>
<tbody></tbody>
</table>
</div>
</section>
<section class="card" id="recharge-card" hidden>
<h2>充值记录</h2>
<div class="table-wrap">
<table id="recharges">
<thead><tr><th>日期</th><th class="num">金额</th><th>备注</th></tr></thead>
<tbody></tbody><tfoot></tfoot>
</table>
</div>
</section>
<footer id="foot"></footer>
</main>
<script>
const $ = s => document.querySelector(s);
const NS = 'http://www.w3.org/2000/svg';
const el = (tag, attrs, parent) => { const e = document.createElementNS(NS, tag); for (const k in attrs) e.setAttribute(k, attrs[k]); if (parent) parent.appendChild(e); return e; };
const h = (tag, cls, text) => { const e = document.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = text; return e; };
const r2 = v => Math.round(v * 100) / 100;
const usd = (v, dp = 2) => (v < 0 ? '−$' : '$') + Math.abs(v).toLocaleString('en-US', { minimumFractionDigits: dp, maximumFractionDigits: dp });
const int = v => Number(v || 0).toLocaleString('en-US');
const shortDate = dt => dt.slice(5).replace('-', '/');
function niceStep(raw) {
const p = 10 ** Math.floor(Math.log10(raw)), n = raw / p;
return (n <= 1 ? 1 : n <= 2 ? 2 : n <= 2.5 ? 2.5 : n <= 5 ? 5 : 10) * p;
}
function scaleY(min, max, count = 4) {
if (max === min) max = min + 1;
const step = niceStep((max - min) / count);
const lo = Math.floor(min / step) * step, hi = Math.ceil(max / step) * step;
const ticks = [];
for (let v = lo; v <= hi + step / 2; v += step) ticks.push(r2(v));
return { lo, hi, ticks, dp: step % 1 ? 1 : 0 };
}
const roundTop = (x, y, w, hgt, r) =>
`M${x},${y + hgt}V${y + r}Q${x},${y} ${x + r},${y}H${x + w - r}Q${x + w},${y} ${x + w},${y + r}V${y + hgt}Z`;
function showTip(wrap, x, y, content) {
const t = wrap.querySelector('.tip');
t.replaceChildren(content);
t.hidden = false;
const left = Math.min(Math.max(x - t.offsetWidth / 2, 4), wrap.clientWidth - t.offsetWidth - 4);
t.style.left = left + 'px';
t.style.top = Math.max(4, y - t.offsetHeight - 14) + 'px';
}
const hideTip = wrap => { wrap.querySelector('.tip').hidden = true; };
const swatch = color => { const s = h('i', 'sw'); s.style.background = color; return s; };
function derive(d, m) {
const pkgs = m.pkg_names || {};
const camps = d.campaigns.map((c, i) => ({ ...c, color: `var(--s${i + 1})`, pkg: pkgs[c.id] || '' }));
const dates = [...new Set(d.daily.map(r => r.dt))].sort();
const cell = {};
d.daily.forEach(r => { (cell[r.dt] ??= {})[r.campaign_id] = r; });
const get = (dt, id) => (cell[dt] || {})[id];
const dayTotal = dt => r2(camps.reduce((s, c) => s + (get(dt, c.id)?.spend || 0), 0));
const rangeSpend = r2(d.daily.reduce((s, r) => s + r.spend, 0));
const rangeInstalls = d.daily.reduce((s, r) => s + r.installs, 0);
const rangeClicks = d.daily.reduce((s, r) => s + r.clicks, 0);
const lifetime = r2(camps.reduce((s, c) => s + c.lifetime_spend, 0));
const recharges = (m.recharges || []).filter(x => x.date && Number(x.amount)).sort((a, b) => a.date.localeCompare(b.date));
const rechargeTotal = r2(recharges.reduce((s, x) => s + Number(x.amount), 0));
let cum = lifetime - rangeSpend;
const balanceSeries = dates.map(dt => {
cum += dayTotal(dt);
const rc = recharges.filter(x => x.date <= dt).reduce((s, x) => s + Number(x.amount), 0);
return { dt, v: r2(rc - cum), spend: dayTotal(dt), recharge: r2(recharges.filter(x => x.date === dt).reduce((s, x) => s + Number(x.amount), 0)) };
});
return {
camps, dates, get, dayTotal, rangeSpend, rangeInstalls, rangeClicks, lifetime, recharges, rechargeTotal,
hasRecharge: recharges.length > 0, balance: r2(rechargeTotal - lifetime), balanceSeries,
avgDaily: dates.length ? rangeSpend / dates.length : 0,
};
}
function badge(kind, icon, text) {
const b = h('span', 'badge ' + kind);
b.append(h('span', 'badge-icon', icon), document.createTextNode(text));
return b;
}
function renderTiles(S, d) {
const box = $('#tiles');
box.replaceChildren();
const add = (label, value, sub) => {
const t = h('div', 'tile');
t.append(h('div', 'tile-label', label), h('div', 'tile-value', value));
if (sub) { const s = h('div', 'tile-sub'); s.append(sub); t.append(s); }
box.append(t);
};
const firstStart = S.camps.map(c => c.start).sort()[0];
add('近 7 天消耗', usd(S.rangeSpend), `日均 ${usd(S.avgDaily)}`);
add('近 7 天安装', int(S.rangeInstalls), S.rangeInstalls ? `eCPI ${usd(S.rangeSpend / S.rangeInstalls)}` : '暂无安装');
add('累计消耗', usd(S.lifetime), `${firstStart} 起,截至 ${d.range.end}`);
add('累计充值', S.hasRecharge ? usd(S.rechargeTotal) : '待补', S.hasRecharge ? `共 ${S.recharges.length} 笔` : '还没有充值记录');
if (!S.hasRecharge) {
add('当前余额', '待补', '累计充值 − 累计消耗');
} else {
const days = S.avgDaily > 0 ? S.balance / S.avgDaily : Infinity;
let sub;
if (S.balance <= 0) sub = badge('crit', '✕', '余额已用完');
else if (days < 7) sub = badge('warn', '!', `按日均约可用 ${Math.floor(days)} 天`);
else sub = isFinite(days) ? `按日均约可用 ${Math.floor(days)} 天` : '近 7 天没有消耗';
add('当前余额', usd(S.balance), sub);
}
}
function drawSpend(S) {
const wrap = $('#spend-chart');
wrap.querySelector('svg')?.remove();
hideTip(wrap);
const W = Math.max(300, wrap.clientWidth), H = 260, m = { l: 52, r: 12, t: 24, b: 30 };
const pw = W - m.l - m.r, ph = H - m.t - m.b;
const totals = S.dates.map(S.dayTotal);
const sc = scaleY(0, Math.max(...totals, 1));
const y = v => m.t + ph - (v - sc.lo) / (sc.hi - sc.lo) * ph;
const band = pw / Math.max(1, S.dates.length), bw = Math.min(24, band * 0.55);
const svg = el('svg', { viewBox: `0 0 ${W} ${H}`, width: W, height: H, role: 'img', 'aria-label': '近 7 天每日消耗,按订单堆叠' });
wrap.prepend(svg);
const hover = el('rect', { class: 'hover-band', y: m.t, height: ph, width: band, visibility: 'hidden' }, svg);
sc.ticks.forEach(t => {
el('line', { x1: m.l, x2: W - m.r, y1: y(t), y2: y(t), class: t === 0 ? 'axis' : 'grid' }, svg);
el('text', { x: m.l - 8, y: y(t) + 4, 'text-anchor': 'end', class: 'tick' }, svg).textContent = usd(t, sc.dp);
});
const maxI = totals.indexOf(Math.max(...totals)), lastI = totals.length - 1;
S.dates.forEach((dt, i) => {
const cx = m.l + band * (i + 0.5);
const segs = S.camps.map(c => ({ c, v: S.get(dt, c.id)?.spend || 0 })).filter(s => s.v > 0);
let base = 0;
segs.forEach((s, j) => {
const y0 = y(base), y1 = y(base + s.v), gap = j > 0 ? 2 : 0;
const hgt = Math.max(0, y0 - y1 - gap);
const r = j === segs.length - 1 ? Math.min(4, hgt) : 0;
el('path', { d: roundTop(cx - bw / 2, y1, bw, hgt, r), fill: s.c.color }, svg);
base += s.v;
});
el('text', { x: cx, y: H - m.b + 18, 'text-anchor': 'middle', class: 'tick' }, svg).textContent = shortDate(dt);
if ((i === maxI || i === lastI) && totals[i] > 0) {
el('text', { x: cx, y: y(totals[i]) - 7, 'text-anchor': 'middle', class: 'val' }, svg).textContent = usd(totals[i]);
}
});
S.dates.forEach((dt, i) => {
const cx = m.l + band * (i + 0.5);
const hit = el('rect', { x: cx - band / 2, y: m.t, width: band, height: ph, fill: 'transparent' }, svg);
hit.addEventListener('pointerenter', () => {
hover.setAttribute('x', cx - band / 2);
hover.setAttribute('visibility', 'visible');
showTip(wrap, cx, y(totals[i]), spendTip(S, dt, totals[i]));
});
hit.addEventListener('pointerleave', () => { hover.setAttribute('visibility', 'hidden'); hideTip(wrap); });
});
}
function spendTip(S, dt, total) {
const f = document.createDocumentFragment();
f.append(h('div', 'tip-title', dt));
S.camps.forEach(c => {
const r = S.get(dt, c.id);
const row = h('div', 'tip-row');
row.append(swatch(c.color), h('span', 'tip-name', c.app || c.id), h('span', 'tip-val', usd(r?.spend || 0)));
f.append(row);
if (r) f.append(h('div', 'tip-sub', `安装 ${int(r.installs)} · 点击 ${int(r.clicks)}`));
});
const tot = h('div', 'tip-row tip-total');
tot.append(h('span', 'tip-name', '合计'), h('span', 'tip-val', usd(total)));
f.append(tot);
return f;
}
function drawBalance(S) {
const wrap = $('#balance-chart');
wrap.querySelector('svg')?.remove();
hideTip(wrap);
$('#balance-empty').hidden = S.hasRecharge;
wrap.hidden = !S.hasRecharge;
if (!S.hasRecharge) return;
const W = Math.max(300, wrap.clientWidth), H = 220, m = { l: 64, r: 16, t: 28, b: 30 };
const pw = W - m.l - m.r, ph = H - m.t - m.b;
const pts = S.balanceSeries, vals = pts.map(p => p.v), li = pts.length - 1;
const sc = scaleY(Math.min(0, ...vals), Math.max(0, ...vals));
const y = v => m.t + ph - (v - sc.lo) / (sc.hi - sc.lo) * ph;
const stepX = pts.length > 1 ? pw / (pts.length - 1) : 0;
const x = i => pts.length > 1 ? m.l + i * stepX : m.l + pw / 2;
const svg = el('svg', { viewBox: `0 0 ${W} ${H}`, width: W, height: H, role: 'img', 'aria-label': '每日日终余额走势' });
wrap.prepend(svg);
sc.ticks.forEach(t => {
el('line', { x1: m.l, x2: W - m.r, y1: y(t), y2: y(t), class: t === 0 ? 'axis' : 'grid' }, svg);
el('text', { x: m.l - 8, y: y(t) + 4, 'text-anchor': 'end', class: 'tick' }, svg).textContent = usd(t, sc.dp);
});
pts.forEach((p, i) => { el('text', { x: x(i), y: H - m.b + 18, 'text-anchor': 'middle', class: 'tick' }, svg).textContent = shortDate(p.dt); });
const cross = el('line', { y1: m.t, y2: m.t + ph, class: 'cross', visibility: 'hidden' }, svg);
el('path', { d: 'M' + pts.map((p, i) => `${x(i)},${y(p.v)}`).join('L'), class: 'line', stroke: 'var(--s1)' }, svg);
el('circle', { cx: x(li), cy: y(pts[li].v), r: 4, class: 'dot', fill: 'var(--s1)' }, svg);
el('text', { x: x(li), y: y(pts[li].v) - 12, 'text-anchor': 'end', class: 'val' }, svg).textContent = usd(pts[li].v);
const hov = el('circle', { r: 5, class: 'dot', fill: 'var(--s1)', visibility: 'hidden' }, svg);
const hit = el('rect', { x: m.l - 12, y: m.t, width: pw + 24, height: ph, fill: 'transparent' }, svg);
hit.addEventListener('pointermove', e => {
const b = svg.getBoundingClientRect();
const px = (e.clientX - b.left) * (W / b.width);
const i = stepX ? Math.max(0, Math.min(li, Math.round((px - m.l) / stepX))) : 0;
const p = pts[i];
cross.setAttribute('x1', x(i)); cross.setAttribute('x2', x(i)); cross.setAttribute('visibility', 'visible');
hov.setAttribute('cx', x(i)); hov.setAttribute('cy', y(p.v)); hov.setAttribute('visibility', 'visible');
const f = document.createDocumentFragment();
f.append(h('div', 'tip-title', p.dt));
const row = (name, val) => { const r = h('div', 'tip-row'); r.append(h('span', 'tip-name', name), h('span', 'tip-val', val)); f.append(r); };
row('日终余额', usd(p.v));
row('当天消耗', usd(p.spend));
if (p.recharge) row('当天充值', usd(p.recharge));
showTip(wrap, x(i), y(p.v), f);
});
hit.addEventListener('pointerleave', () => { cross.setAttribute('visibility', 'hidden'); hov.setAttribute('visibility', 'hidden'); hideTip(wrap); });
}
function tr(cells) {
const row = h('tr');
cells.forEach(c => {
const td = h('td', c.cls || '');
if (c.color) td.append(swatch(c.color));
td.append(document.createTextNode(c.text));
row.append(td);
});
return row;
}
function renderTables(S) {
const pkgCell = c => c.pkg ? { text: c.pkg } : { text: '待补', cls: 'muted' };
const daily = $('#daily tbody');
daily.replaceChildren();
[...S.dates].reverse().forEach(dt => S.camps.forEach(c => {
const r = S.get(dt, c.id);
if (!r) return;
daily.append(tr([
{ text: dt }, { text: c.id, color: c.color }, { text: c.app || '—' }, pkgCell(c),
{ text: usd(r.spend), cls: 'num' }, { text: int(r.installs), cls: 'num' }, { text: int(r.clicks), cls: 'num' },
{ text: r.installs ? usd(r.spend / r.installs) : '—', cls: 'num' },
]));
}));
$('#daily tfoot').replaceChildren(tr([
{ text: '合计' }, { text: '' }, { text: '' }, { text: '' },
{ text: usd(S.rangeSpend), cls: 'num' }, { text: int(S.rangeInstalls), cls: 'num' }, { text: int(S.rangeClicks), cls: 'num' },
{ text: S.rangeInstalls ? usd(S.rangeSpend / S.rangeInstalls) : '—', cls: 'num' },
]));
const camps = $('#camps tbody');
camps.replaceChildren(...S.camps.map(c => tr([
{ text: c.id, color: c.color }, { text: c.name }, { text: c.app || '—' }, pkgCell(c), { text: c.start },
{ text: usd(c.bid), cls: 'num' }, { text: usd(c.daily_budget, 0), cls: 'num' }, { text: usd(c.lifetime_spend), cls: 'num' },
])));
$('#recharge-card').hidden = !S.hasRecharge;
$('#recharges tbody').replaceChildren(...S.recharges.map(x => tr([
{ text: x.date }, { text: usd(Number(x.amount)), cls: 'num' }, { text: x.note || '' },
])));
$('#recharges tfoot').replaceChildren(tr([{ text: '合计' }, { text: usd(S.rechargeTotal), cls: 'num' }, { text: '' }]));
}
let S;
const drawCharts = () => { if (S) { drawSpend(S); drawBalance(S); } };
Promise.all([
fetch('data.json', { cache: 'no-store' }).then(r => { if (!r.ok) throw new Error('data.json ' + r.status); return r.json(); }),
fetch('manual.json', { cache: 'no-store' }).then(r => r.ok ? r.json() : {}).catch(() => ({})),
]).then(([d, m]) => {
S = derive(d, m);
const updated = new Date(d.updated_at).toLocaleString('zh-CN', { hour12: false });
$('#meta').replaceChildren(
h('span', '', d.account ? d.account.name : 'Tecdo'),
h('span', '', `订单 ${S.camps.map(c => c.id).join('、')}`),
h('span', '', `数据区间 ${d.range.start} ~ ${d.range.end}`),
h('span', '', `更新于 ${updated}`),
);
const legend = $('#spend-legend');
legend.replaceChildren(...S.camps.map(c => { const s = h('span'); s.append(swatch(c.color), document.createTextNode(`${c.app || '订单'} · ${c.id}`)); return s; }));
renderTiles(S, d);
renderTables(S);
drawCharts();
$('#foot').textContent = `数据来源:MIDAS 综合报表(${d.source || ''})。每天 11:00 自动刷新,金额单位美元。充值记录和 pkg name 手工维护在 manual.json。`;
}).catch(e => {
$('#meta').textContent = '';
const err = $('#err');
err.hidden = false;
err.textContent = '数据加载失败:' + e.message;
});
let resizeTimer, lastWidth = 0;
function onResize() {
const w = document.querySelector('main').clientWidth;
if (w === lastWidth) return;
lastWidth = w;
clearTimeout(resizeTimer);
resizeTimer = setTimeout(drawCharts, 120);
}
new ResizeObserver(onResize).observe(document.querySelector('main'));
addEventListener('resize', onResize);
</script>
</body>
</html>