(function () {
'use strict';
var root = document.getElementById('app');
var apiBase = (window.PK_CONFIG.apiBase || '').replace(/\/$/, '');
var state = { tests: [], test: null, bracket: null, result: null, template: 'celebration', sticker: true };
function toast(message) { var el = document.getElementById('toast'); el.textContent = message; el.classList.add('show'); setTimeout(function () { el.classList.remove('show'); }, 2200); }
function escapeHtml(value) { return String(value).replace(/[&<>"']/g, function (char) { return ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[char]; }); }
function request(path, options) {
options = options || {}; var token = localStorage.getItem('pk_token');
var headers = {'Content-Type':'application/json'}; if (token) headers.Authorization = 'Bearer ' + token;
return fetch(apiBase + path, {method:options.method || 'GET',headers:headers,body:options.data ? JSON.stringify(options.data) : undefined}).then(function (response) {
return response.json().catch(function () { return {}; }).then(function (data) { if (!response.ok) throw new Error(data.detail || '请求失败'); return data; });
});
}
function ensureLogin() {
if (localStorage.getItem('pk_token')) return Promise.resolve();
var id = localStorage.getItem('pk_preview_id') || (Date.now().toString(36) + Math.random().toString(36).slice(2)); localStorage.setItem('pk_preview_id', id);
return request('/api/v1/auth/xhs', {method:'POST',data:{app_key:'hub',code:'dev:web-' + id}}).then(function (data) { localStorage.setItem('pk_token', data.token); });
}
function loadHome() {
root.innerHTML = '
正在摆好擂台…
';
request('/api/v1/tests').then(function (tests) { state.tests = tests; renderHome(); }).catch(renderError);
}
function renderHome() {
root.innerHTML = 'TASTE LAB · PUBLIC PREVIEW
今天,认真选一个。
没有“都可以”。每轮只选一个,让你的偏爱一路赢到底。
全部测试
' + state.tests.length + ' 个题库' + state.tests.map(function (test, index) {
return '';
}).join('') + ' ';
root.querySelectorAll('.test-card').forEach(function (el) { el.addEventListener('click', function () { loadTest(el.dataset.slug); }); });
}
function loadTest(slug) { root.innerHTML = '正在抽签…
'; request('/api/v1/tests/' + slug).then(function (test) { state.test = test; state.bracket = {round:1,pool:test.candidates.slice(),index:0,winners:[],eliminated:[],decisions:[]}; renderPair(); }).catch(renderError); }
function pair() { return [state.bracket.pool[state.bracket.index], state.bracket.pool[state.bracket.index + 1] || null]; }
function renderPair() {
var items = pair(), done = state.bracket.decisions.length, total = state.test.candidates.length - 1;
root.innerHTML = '第 ' + state.bracket.round + ' 轮' + done + ' / ' + total + '
此刻只能留一个,
你会选谁?
VS
';
root.querySelectorAll('.choice').forEach(function (el) { el.addEventListener('click', function () { choose(items[Number(el.dataset.side)]); }); });
}
function choose(winner) {
var b = state.bracket, items = pair(), loser = items[0].id === winner.id ? items[1] : items[0];
b.winners.push(winner); b.eliminated.unshift(loser); b.decisions.push({round:b.round,left_id:items[0].id,right_id:items[1].id,winner_id:winner.id}); b.index += 2;
if (b.index < b.pool.length && !b.pool[b.index + 1]) { b.winners.push(b.pool[b.index]); b.index += 1; }
if (b.index >= b.pool.length) { b.pool = b.winners; b.winners = []; b.index = 0; b.round += 1; }
if (b.pool.length === 1) complete(); else renderPair();
}
function complete() {
var b = state.bracket, champion = b.pool[0], final = b.decisions[b.decisions.length - 1], runner = final.left_id === champion.id ? final.right_id : final.left_id;
root.innerHTML = '正在保存你的冠军…
';
ensureLogin().then(function () { return request('/api/v1/tests/' + state.test.slug + '/runs',{method:'POST',data:{app_key:'hub',champion_id:champion.id,runner_up_id:runner,ranking_ids:b.pool.concat(b.eliminated).map(function(i){return i.id;}),decisions:b.decisions}}); }).then(function (result) { state.result = result; renderResult(); }).catch(renderError);
}
function renderResult() {
var r = state.result;
root.innerHTML = 'FINAL ANSWER · ' + escapeHtml(r.test_title) + '' + escapeHtml(r.champion_name) + '
' + (r.runner_up_name ? '决赛击败 ' + escapeHtml(r.runner_up_name) : '一路赢到最后') + '
';
drawPoster(); root.querySelectorAll('[data-theme]').forEach(function (el) { el.addEventListener('click', function () { state.template = el.dataset.theme; root.querySelectorAll('[data-theme]').forEach(function(x){x.classList.remove('active');});el.classList.add('active');drawPoster(); }); });
document.getElementById('stickerToggle').addEventListener('click',function(){state.sticker=!state.sticker;this.textContent='手绘贴纸:'+(state.sticker?'开启':'关闭');drawPoster();});
document.getElementById('savePoster').addEventListener('click',function(){var a=document.createElement('a');a.download='我的PK冠军-'+r.champion_name+'.png';a.href=document.getElementById('poster').toDataURL('image/png');a.click();toast('分享图已保存');});
document.getElementById('replay').addEventListener('click',function(){loadTest(r.test_slug);});
}
function drawPoster() {
var canvas=document.getElementById('poster'),ctx=canvas.getContext('2d'),r=state.result,themes={celebration:['#ff6b35','#fffaf4','#25211f','#ffe568'],editorial:['#f3eadf','#201d1b','#fff','#285ee8'],sticker:['#c9f06b','#1e2810','#ff72a7','#fff']},t=themes[state.template];
ctx.fillStyle=t[0];ctx.fillRect(0,0,750,1000);ctx.fillStyle=t[1];ctx.font='700 24px system-ui';ctx.fillText('PK 研究所 / MY PICK',54,72);ctx.font='900 58px system-ui';ctx.fillText('我的冠军是',54,156);ctx.fillStyle=t[2];ctx.fillRect(46,205,658,440);ctx.fillStyle=state.template==='celebration'?'#fffaf4':t[1];ctx.font='600 26px system-ui';ctx.fillText(r.test_title,84,270);ctx.font='900 '+(r.champion_name.length>7?62:82)+'px system-ui';ctx.fillText(r.champion_name,84,405);ctx.fillStyle=t[3];ctx.fillRect(84,440,Math.min(500,r.champion_name.length*80),18);ctx.fillStyle=state.template==='celebration'?'#fffaf4':t[1];ctx.font='24px system-ui';ctx.fillText('一路二选一,它赢到了最后。',84,525);ctx.fillStyle=t[1];ctx.font='700 25px system-ui';ctx.fillText('我的 TOP 4',54,720);(r.ranking_names||[]).slice(0,4).forEach(function(name,i){ctx.font='26px system-ui';ctx.fillText((i+1)+'. '+name,64,774+i*48);});if(state.sticker){ctx.fillStyle=t[3];ctx.beginPath();ctx.arc(645,125,58,0,Math.PI*2);ctx.fill();ctx.fillStyle=t[2];ctx.font='900 28px system-ui';ctx.fillText('WIN!',607,134);ctx.font='52px system-ui';ctx.fillText('✦',640,700);ctx.fillText('♡',550,900);}ctx.fillStyle=t[1];ctx.font='20px system-ui';ctx.fillText('保存这张图,把答案分享给同频的人',54,956);
}
function loadHistory() { root.innerHTML='正在翻档案…
';ensureLogin().then(function(){return request('/api/v1/me/results');}).then(renderHistory).catch(renderError); }
function renderHistory(results) { root.innerHTML='MY TASTE ARCHIVE
我的偏爱档案
每一次纠结,都算数。
做过的测试
'+results.length+' 条结果连接独立饭类版两个小程序需要一次确认才能合并身份。
'+(results.length?results.map(function(r,i){return '
';}).join(''):'
还没有结果,去选第一个冠军吧。
')+'
';document.getElementById('linkCode').addEventListener('click',function(){request('/api/v1/me/link-code',{method:'POST'}).then(function(d){document.getElementById('code').textContent=d.code;}).catch(renderError);});root.querySelectorAll('.result-row').forEach(function(el){el.addEventListener('click',function(){state.result=results[Number(el.dataset.i)];renderResult();});}); }
function renderError(error) { root.innerHTML='暂时没接上服务
'+escapeHtml(error.message||'未知错误')+'
';document.getElementById('retry').addEventListener('click',loadHome); }
document.getElementById('homeButton').addEventListener('click',loadHome); document.getElementById('meButton').addEventListener('click',loadHistory); loadHome();
}());