diff --git a/public/js/admin.js b/public/js/admin.js
index 0629857..120b20c 100644
--- a/public/js/admin.js
+++ b/public/js/admin.js
@@ -1,320 +1 @@
-/* Newbury Nights — admin console */
-const $ = (s, r = document) => r.querySelector(s);
-const $$ = (s, r = document) => [...r.querySelectorAll(s)];
-
-let TOKEN = null;
-let GHOSTS = [];
-let SETS = [];
-
-const api = (path, opts = {}) =>
- fetch(path, {
- ...opts,
- headers: {
- ...(opts.body && !(opts.body instanceof FormData) ? { 'Content-Type': 'application/json' } : {}),
- ...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}),
- ...(opts.headers || {}),
- },
- });
-
-/* ---------- Auth ---------- */
-$('#lg-go').addEventListener('click', login);
-$('#lg-pass').addEventListener('keydown', (e) => { if (e.key === 'Enter') login(); });
-
-async function login() {
- const username = $('#lg-user').value.trim();
- const password = $('#lg-pass').value;
- const err = $('#lg-error');
- err.classList.add('hidden');
- const res = await api('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) });
- if (!res.ok) {
- err.textContent = 'Invalid username or password.';
- err.classList.remove('hidden');
- return;
- }
- const data = await res.json();
- TOKEN = data.token;
- $('#acct-user').textContent = data.user.username;
- $('#login').classList.add('hidden');
- $('#app').classList.remove('hidden');
- await Promise.all([loadGhosts(), loadSets()]);
-}
-
-$('#logout').addEventListener('click', async () => {
- await api('/auth/logout', { method: 'POST' });
- location.reload();
-});
-
-/* ---------- Tabs ---------- */
-$$('.tab').forEach((t) =>
- t.addEventListener('click', () => {
- $$('.tab').forEach((x) => x.classList.remove('active'));
- t.classList.add('active');
- $$('.tab-panel').forEach((p) => p.classList.add('hidden'));
- $(`#tab-${t.dataset.tab}`).classList.remove('hidden');
- })
-);
-
-/* ---------- Ghosts ---------- */
-async function loadGhosts() {
- const res = await api('/api/admin/ghosts');
- GHOSTS = (await res.json()).ghosts;
- renderGhosts();
-}
-
-$('#ghost-search').addEventListener('input', renderGhosts);
-
-function renderGhosts() {
- const q = $('#ghost-search').value.toLowerCase();
- const rows = GHOSTS.filter(
- (g) => !q || g.name.toLowerCase().includes(q) || (g.ability || '').toLowerCase().includes(q)
- ).map(ghostRow).join('');
- $('#ghosts-table tbody').innerHTML = rows;
-
- $$('#ghosts-table .edit').forEach((b) =>
- b.addEventListener('click', () => openGhost(+b.dataset.id)));
- $$('#ghosts-table .del').forEach((b) =>
- b.addEventListener('click', () => deleteGhost(+b.dataset.id)));
- $$('#ghosts-table .toggle').forEach((b) =>
- b.addEventListener('click', () => toggleGhost(+b.dataset.id)));
-}
-
-function ghostRow(g) {
- const img = g.image_path ? `
` : '';
- const setRef = g.set_number ? `${g.set_number}` : '—';
- return `
- | ${img} |
- ${esc(g.name)}${g.is_boss ? ' boss' : ''}
- ${g.display_name && g.display_name !== g.name ? `↳ ${esc(g.display_name)} ` : ''} |
- ${g.type} |
- ${'★'.repeat(g.rarity)} |
- ${g.health} | ${g.damage} |
- ${esc(g.ability || '—')} |
- ${setRef} |
- ${g.enabled ? '◉' : '◯'} |
-
-
-
- |
-
`;
-}
-
-async function toggleGhost(id) {
- const g = GHOSTS.find((x) => x.id === id);
- await api(`/api/admin/ghosts/${id}`, { method: 'PATCH', body: JSON.stringify({ enabled: !g.enabled }) });
- await loadGhosts();
-}
-
-async function deleteGhost(id) {
- const g = GHOSTS.find((x) => x.id === id);
- if (!confirm(`Delete "${g.name}"? This also removes it from any set rosters.`)) return;
- await api(`/api/admin/ghosts/${id}`, { method: 'DELETE' });
- await Promise.all([loadGhosts(), loadSets()]);
-}
-
-/* ghost modal */
-let editingGhostId = null;
-$('#new-ghost').addEventListener('click', () => openGhost(null));
-
-function openGhost(id) {
- editingGhostId = id;
- const g = id ? GHOSTS.find((x) => x.id === id) : null;
- $('#gm-title').textContent = g ? 'Edit ghost' : 'New ghost';
- $('#gm-name').value = g?.name || '';
- $('#gm-display').value = g?.display_name || '';
- $('#gm-type').value = g?.type || 'red';
- $('#gm-rarity').value = g?.rarity ?? 1;
- $('#gm-health').value = g?.health ?? 300;
- $('#gm-damage').value = g?.damage ?? 150;
- $('#gm-speed').value = g?.speed ?? 3;
- $('#gm-range').value = g?.range ?? 3;
- $('#gm-charge').value = g?.charge_shot ?? 2;
- $('#gm-ability').value = g?.ability || '';
- $('#gm-setnum').value = g?.set_number || '';
- $('#gm-setname').value = g?.set_name || '';
- $('#gm-boss').checked = !!g?.is_boss;
- $('#gm-enabled').checked = g ? !!g.enabled : true;
- $('#gm-file').value = '';
- const prev = $('#gm-preview');
- if (g?.image_path) { prev.src = `/uploads/${g.image_path}`; prev.classList.remove('hidden'); }
- else prev.classList.add('hidden');
- $('#ghost-modal').classList.remove('hidden');
-}
-
-$('#gm-save').addEventListener('click', saveGhost);
-
-async function saveGhost() {
- const payload = {
- name: $('#gm-name').value.trim(),
- displayName: $('#gm-display').value.trim() || $('#gm-name').value.trim(),
- type: $('#gm-type').value,
- rarity: +$('#gm-rarity').value,
- health: +$('#gm-health').value,
- damage: +$('#gm-damage').value,
- speed: +$('#gm-speed').value,
- range: +$('#gm-range').value,
- chargeShot: +$('#gm-charge').value,
- ability: $('#gm-ability').value.trim() || null,
- setNumber: $('#gm-setnum').value.trim() || null,
- setName: $('#gm-setname').value.trim() || null,
- isBoss: $('#gm-boss').checked,
- enabled: $('#gm-enabled').checked,
- };
- if (!payload.name) { alert('Name is required.'); return; }
-
- let id = editingGhostId;
- if (id) {
- await api(`/api/admin/ghosts/${id}`, { method: 'PATCH', body: JSON.stringify(payload) });
- } else {
- const res = await api('/api/admin/ghosts', { method: 'POST', body: JSON.stringify(payload) });
- id = (await res.json()).id;
- }
-
- const file = $('#gm-file').files[0];
- if (file && id) {
- const fd = new FormData();
- fd.append('image', file);
- await api(`/api/admin/ghosts/${id}/image`, { method: 'POST', body: fd });
- }
-
- $('#ghost-modal').classList.add('hidden');
- await Promise.all([loadGhosts(), loadSets()]);
-}
-
-/* ---------- Sets ---------- */
-async function loadSets() {
- const res = await api('/api/admin/sets');
- SETS = (await res.json()).sets;
- renderSets();
-}
-
-function renderSets() {
- $('#sets-list').innerHTML = SETS.map(setCard).join('') ||
- 'No sets yet. Create one to wire a scan code to a ghost roster.
';
- $$('#sets-list .edit-set').forEach((b) =>
- b.addEventListener('click', () => openSet(+b.dataset.id)));
- $$('#sets-list .del-set').forEach((b) =>
- b.addEventListener('click', () => deleteSet(+b.dataset.id)));
-}
-
-function setCard(s) {
- const chips = s.roster.map((r) =>
- `${esc(r.name)}${r.is_boss ? ' ★' : ''}`
- ).join('');
- return `
-
- ${esc(s.code)}
- ${esc(s.set_name)}
- ${s.set_number ? `#${esc(s.set_number)}` : ''}
-
-
-
-
-
${chips || 'no roster'}
-
`;
-}
-
-async function deleteSet(id) {
- const s = SETS.find((x) => x.id === id);
- if (!confirm(`Delete set "${s.set_name}" (${s.code})?`)) return;
- await api(`/api/admin/sets/${id}`, { method: 'DELETE' });
- await loadSets();
-}
-
-/* set modal */
-let editingSetId = null;
-$('#new-set').addEventListener('click', () => openSet(null));
-$('#sm-roster-search').addEventListener('input', filterRosterChecklist);
-
-function openSet(id) {
- editingSetId = id;
- const s = id ? SETS.find((x) => x.id === id) : null;
- $('#sm-title').textContent = s ? 'Edit set' : 'New set';
- $('#sm-code').value = s?.code || '';
- $('#sm-setnum').value = s?.set_number || '';
- $('#sm-setname').value = s?.set_name || '';
- $('#sm-enabled').checked = s ? !!s.enabled : true;
-
- // boss dropdown
- const bossSel = $('#sm-boss');
- bossSel.innerHTML = '' +
- GHOSTS.filter((g) => g.is_boss).map((g) =>
- ``).join('');
- bossSel.value = s?.boss_ghost_id || '';
-
- // roster checklist
- const selected = new Set((s?.roster || []).map((r) => r.id));
- $('#sm-roster').innerHTML = GHOSTS.map((g) =>
- ``).join('');
- $('#sm-roster-search').value = '';
- $('#set-modal').classList.remove('hidden');
-}
-
-function filterRosterChecklist() {
- const q = $('#sm-roster-search').value.toLowerCase();
- $$('#sm-roster .rcheck').forEach((el) => {
- el.style.display = !q || el.dataset.name.includes(q) ? '' : 'none';
- });
-}
-
-$('#sm-save').addEventListener('click', saveSet);
-
-async function saveSet() {
- const payload = {
- code: $('#sm-code').value.trim(),
- setNumber: $('#sm-setnum').value.trim() || null,
- setName: $('#sm-setname').value.trim(),
- bossGhostId: $('#sm-boss').value ? +$('#sm-boss').value : null,
- enabled: $('#sm-enabled').checked,
- };
- if (!payload.code || !payload.setName) { alert('Scan code and set name are required.'); return; }
-
- let id = editingSetId;
- if (id) {
- await api(`/api/admin/sets/${id}`, { method: 'PATCH', body: JSON.stringify(payload) });
- } else {
- const res = await api('/api/admin/sets', { method: 'POST', body: JSON.stringify(payload) });
- if (res.status === 409) { alert('That scan code already exists.'); return; }
- id = (await res.json()).id;
- }
-
- const ghostIds = $$('#sm-roster input:checked').map((i) => +i.value);
- await api(`/api/admin/sets/${id}/roster`, { method: 'PUT', body: JSON.stringify({ ghostIds }) });
-
- $('#set-modal').classList.add('hidden');
- await loadSets();
-}
-
-/* ---------- Account ---------- */
-$('#cp-go').addEventListener('click', async () => {
- const msg = $('#cp-msg');
- msg.classList.add('hidden'); msg.classList.remove('ok');
- const res = await api('/auth/change-password', {
- method: 'POST',
- body: JSON.stringify({
- currentPassword: $('#cp-current').value,
- newPassword: $('#cp-new').value,
- }),
- });
- const data = await res.json().catch(() => ({}));
- if (res.ok) {
- msg.textContent = 'Password updated.'; msg.classList.add('ok');
- } else {
- msg.textContent = data.error || 'Could not update password.';
- }
- msg.classList.remove('hidden');
- $('#cp-current').value = ''; $('#cp-new').value = '';
-});
-
-/* ---------- modal close ---------- */
-$$('[data-close]').forEach((b) =>
- b.addEventListener('click', () => $$('.modal').forEach((m) => m.classList.add('hidden'))));
-$$('.modal').forEach((m) =>
- m.addEventListener('click', (e) => { if (e.target === m) m.classList.add('hidden'); }));
-
-function esc(s) {
- return String(s ?? '').replace(/[&<>"']/g, (c) =>
- ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
-}
+LyogTmV3YnVyeSBOaWdodHMg4oCUIGFkbWluIGNvbnNvbGUgKi8KY29uc3QgJCA9IChzLCByID0gZG9jdW1lbnQpID0+IHIucXVlcnlTZWxlY3RvcihzKTsKY29uc3QgJCQgPSAocywgciA9IGRvY3VtZW50KSA9PiBbLi4uci5xdWVyeVNlbGVjdG9yQWxsKHMpXTsKCmxldCBUT0tFTiA9IG51bGw7CmxldCBHSE9TVFMgPSBbXTsKbGV0IFNFVFMgPSBbXTsKCmNvbnN0IGFwaSA9IChwYXRoLCBvcHRzID0ge30pID0+CiAgZmV0Y2gocGF0aCwgewogICAgLi4ub3B0cywKICAgIGhlYWRlcnM6IHsKICAgICAgLi4uKG9wdHMuYm9keSAmJiAhKG9wdHMuYm9keSBpbnN0YW5jZW9mIEZvcm1EYXRhKSA/IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9IDoge30pLAogICAgICAuLi4oVE9LRU4gPyB7IEF1dGhvcml6YXRpb246IGBCZWFyZXIgJHtUT0tFTn1gIH0gOiB7fSksCiAgICAgIC4uLihvcHRzLmhlYWRlcnMgfHwge30pLAogICAgfSwKICB9KTsKCi8qIC0tLS0tLS0tLS0gQXV0aCAtLS0tLS0tLS0tICovCiQoJyNsZy1nbycpLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgbG9naW4pOwokKCcjbGctcGFzcycpLmFkZEV2ZW50TGlzdGVuZXIoJ2tleWRvd24nLCAoZSkgPT4geyBpZiAoZS5rZXkgPT09ICdFbnRlcicpIGxvZ2luKCk7IH0pOwoKYXN5bmMgZnVuY3Rpb24gbG9naW4oKSB7CiAgY29uc3QgdXNlcm5hbWUgPSAkKCcjbGctdXNlcicpLnZhbHVlLnRyaW0oKTsKICBjb25zdCBwYXNzd29yZCA9ICQoJyNsZy1wYXNzJykudmFsdWU7CiAgY29uc3QgZXJyID0gJCgnI2xnLWVycm9yJyk7CiAgZXJyLmNsYXNzTGlzdC5hZGQoJ2hpZGRlbicpOwogIGNvbnN0IHJlcyA9IGF3YWl0IGFwaSgnL2F1dGgvbG9naW4nLCB7IG1ldGhvZDogJ1BPU1QnLCBib2R5OiBKU09OLnN0cmluZ2lmeSh7IHVzZXJuYW1lLCBwYXNzd29yZCB9KSB9KTsKICBpZiAoIXJlcy5vaykgewogICAgZXJyLnRleHRDb250ZW50ID0gJ0ludmFsaWQgdXNlcm5hbWUgb3IgcGFzc3dvcmQuJzsKICAgIGVyci5jbGFzc0xpc3QucmVtb3ZlKCdoaWRkZW4nKTsKICAgIHJldHVybjsKICB9CiAgY29uc3QgZGF0YSA9IGF3YWl0IHJlcy5qc29uKCk7CiAgVE9LRU4gPSBkYXRhLnRva2VuOwogICQoJyNhY2N0LXVzZXInKS50ZXh0Q29udGVudCA9IGRhdGEudXNlci51c2VybmFtZTsKICAkKCcjbG9naW4nKS5jbGFzc0xpc3QuYWRkKCdoaWRkZW4nKTsKICAkKCcjYXBwJykuY2xhc3NMaXN0LnJlbW92ZSgnaGlkZGVuJyk7CiAgYXdhaXQgUHJvbWlzZS5hbGwoW2xvYWRHaG9zdHMoKSwgbG9hZFNldHMoKV0pOwp9CgokKCcjbG9nb3V0JykuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBhc3luYyAoKSA9PiB7CiAgYXdhaXQgYXBpKCcvYXV0aC9sb2dvdXQnLCB7IG1ldGhvZDogJ1BPU1QnIH0pOwogIGxvY2F0aW9uLnJlbG9hZCgpOwp9KTsKCi8qIC0tLS0tLS0tLS0gVGFicyAtLS0tLS0tLS0tICovCiQkKCcudGFiJykuZm9yRWFjaCgodCkgPT4KICB0LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gewogICAgJCQoJy50YWInKS5mb3JFYWNoKCh4KSA9PiB4LmNsYXNzTGlzdC5yZW1vdmUoJ2FjdGl2ZScpKTsKICAgIHQuY2xhc3NMaXN0LmFkZCgnYWN0aXZlJyk7CiAgICAkJCgnLnRhYi1wYW5lbCcpLmZvckVhY2goKHApID0+IHAuY2xhc3NMaXN0LmFkZCgnaGlkZGVuJykpOwogICAgJChgI3RhYi0ke3QuZGF0YXNldC50YWJ9YCkuY2xhc3NMaXN0LnJlbW92ZSgnaGlkZGVuJyk7CiAgfSkKKTsKCi8qIC0tLS0tLS0tLS0gR2hvc3RzIC0tLS0tLS0tLS0gKi8KYXN5bmMgZnVuY3Rpb24gbG9hZEdob3N0cygpIHsKICBjb25zdCByZXMgPSBhd2FpdCBhcGkoJy9hcGkvYWRtaW4vZ2hvc3RzJyk7CiAgR0hPU1RTID0gKGF3YWl0IHJlcy5qc29uKCkpLmdob3N0czsKICByZW5kZXJHaG9zdHMoKTsKfQoKJCgnI2dob3N0LXNlYXJjaCcpLmFkZEV2ZW50TGlzdGVuZXIoJ2lucHV0JywgcmVuZGVyR2hvc3RzKTsKCmZ1bmN0aW9uIHJlbmRlckdob3N0cygpIHsKICBjb25zdCBxID0gJCgnI2dob3N0LXNlYXJjaCcpLnZhbHVlLnRvTG93ZXJDYXNlKCk7CiAgY29uc3Qgcm93cyA9IEdIT1NUUy5maWx0ZXIoCiAgICAoZykgPT4gIXEgfHwgZy5uYW1lLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMocSkgfHwgKGcuYWJpbGl0eSB8fCAnJykudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhxKQogICkubWFwKGdob3N0Um93KS5qb2luKCcnKTsKICAkKCcjZ2hvc3RzLXRhYmxlIHRib2R5JykuaW5uZXJIVE1MID0gcm93czsKCiAgJCQoJyNnaG9zdHMtdGFibGUgLmVkaXQnKS5mb3JFYWNoKChiKSA9PgogICAgYi5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IG9wZW5HaG9zdCgrYi5kYXRhc2V0LmlkKSkpOwogICQkKCcjZ2hvc3RzLXRhYmxlIC5kZWwnKS5mb3JFYWNoKChiKSA9PgogICAgYi5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IGRlbGV0ZUdob3N0KCtiLmRhdGFzZXQuaWQpKSk7CiAgJCQoJyNnaG9zdHMtdGFibGUgLnRvZ2dsZScpLmZvckVhY2goKGIpID0+CiAgICBiLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gdG9nZ2xlR2hvc3QoK2IuZGF0YXNldC5pZCkpKTsKfQoKZnVuY3Rpb24gZ2hvc3RSb3coZykgewogIGNvbnN0IGltZyA9IGcuaW1hZ2VfcGF0aAogICAgPyBgPGltZyBzcmM9Ii91cGxvYWRzLyR7Zy5pbWFnZV9wYXRofSIgYWx0PSIiPmAKICAgIDogKGcud2VibV9wYXRoCiAgICAgICAgPyBgPHZpZGVvIHNyYz0iL3VwbG9hZHMvJHtnLndlYm1fcGF0aH0iIG11dGVkIGxvb3AgYXV0b3BsYXkgcGxheXNpbmxpbmU+PC92aWRlbz5gCiAgICAgICAgOiAnJyk7CiAgY29uc3Qgc2V0UmVmID0gZy5zZXRfbnVtYmVyID8gYCR7Zy5zZXRfbnVtYmVyfWAgOiAn4oCUJzsKICByZXR1cm4gYDx0cj4KICAgIDx0ZCBjbGFzcz0idGh1bWItY2VsbCI+JHtpbWd9PC90ZD4KICAgIDx0ZD48ZGl2PiR7ZXNjKGcubmFtZSl9JHtnLmlzX2Jvc3MgPyAnIDxzcGFuIGNsYXNzPSJwaWxsIj5ib3NzPC9zcGFuPicgOiAnJ308L2Rpdj4KICAgICAgICAke2cuZGlzcGxheV9uYW1lICYmIGcuZGlzcGxheV9uYW1lICE9PSBnLm5hbWUgPyBgPGRpdiBjbGFzcz0ic3ViIj7ihrMgJHtlc2MoZy5kaXNwbGF5X25hbWUpfTwvZGl2PmAgOiAnJ308L3RkPgogICAgPHRkPjxzcGFuIGNsYXNzPSJkb3QgJHtnLnR5cGV9Ij48L3NwYW4+ICR7Zy50eXBlfTwvdGQ+CiAgICA8dGQgY2xhc3M9InN0YXJzIj4keyfimIUnLnJlcGVhdChnLnJhcml0eSl9PC90ZD4KICAgIDx0ZD4ke2cuaGVhbHRofTwvdGQ+PHRkPiR7Zy5kYW1hZ2V9PC90ZD4KICAgIDx0ZD4ke2VzYyhnLmFiaWxpdHkgfHwgJ+KAlCcpfTwvdGQ+CiAgICA8dGQgY2xhc3M9Im1vbm8iPiR7c2V0UmVmfTwvdGQ+CiAgICA8dGQ+PHNwYW4gY2xhc3M9InRvZ2dsZSAke2cuZW5hYmxlZCA/ICdvbicgOiAnb2ZmJ30iIGRhdGEtaWQ9IiR7Zy5pZH0iPiR7Zy5lbmFibGVkID8gJ+KXiScgOiAn4pevJ308L3NwYW4+PC90ZD4KICAgIDx0ZD48ZGl2IGNsYXNzPSJyb3ctYWN0aW9ucyI+CiAgICAgIDxidXR0b24gY2xhc3M9ImVkaXQiIGRhdGEtaWQ9IiR7Zy5pZH0iPkVkaXQ8L2J1dHRvbj4KICAgICAgPGJ1dHRvbiBjbGFzcz0iZGFuZ2VyIGRlbCIgZGF0YS1pZD0iJHtnLmlkfSI+RGVsPC9idXR0b24+CiAgICA8L2Rpdj48L3RkPgogIDwvdHI+YDsKfQoKYXN5bmMgZnVuY3Rpb24gdG9nZ2xlR2hvc3QoaWQpIHsKICBjb25zdCBnID0gR0hPU1RTLmZpbmQoKHgpID0+IHguaWQgPT09IGlkKTsKICBhd2FpdCBhcGkoYC9hcGkvYWRtaW4vZ2hvc3RzLyR7aWR9YCwgeyBtZXRob2Q6ICdQQVRDSCcsIGJvZHk6IEpTT04uc3RyaW5naWZ5KHsgZW5hYmxlZDogIWcuZW5hYmxlZCB9KSB9KTsKICBhd2FpdCBsb2FkR2hvc3RzKCk7Cn0KCmFzeW5jIGZ1bmN0aW9uIGRlbGV0ZUdob3N0KGlkKSB7CiAgY29uc3QgZyA9IEdIT1NUUy5maW5kKCh4KSA9PiB4LmlkID09PSBpZCk7CiAgaWYgKCFjb25maXJtKGBEZWxldGUgIiR7Zy5uYW1lfSI/IFRoaXMgYWxzbyByZW1vdmVzIGl0IGZyb20gYW55IHNldCByb3N0ZXJzLmApKSByZXR1cm47CiAgYXdhaXQgYXBpKGAvYXBpL2FkbWluL2dob3N0cy8ke2lkfWAsIHsgbWV0aG9kOiAnREVMRVRFJyB9KTsKICBhd2FpdCBQcm9taXNlLmFsbChbbG9hZEdob3N0cygpLCBsb2FkU2V0cygpXSk7Cn0KCi8qIGdob3N0IG1vZGFsICovCmxldCBlZGl0aW5nR2hvc3RJZCA9IG51bGw7CiQoJyNuZXctZ2hvc3QnKS5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IG9wZW5HaG9zdChudWxsKSk7CgpmdW5jdGlvbiBvcGVuR2hvc3QoaWQpIHsKICBlZGl0aW5nR2hvc3RJZCA9IGlkOwogIGNvbnN0IGcgPSBpZCA/IEdIT1NUUy5maW5kKCh4KSA9PiB4LmlkID09PSBpZCkgOiBudWxsOwogICQoJyNnbS10aXRsZScpLnRleHRDb250ZW50ID0gZyA/ICdFZGl0IGdob3N0JyA6ICdOZXcgZ2hvc3QnOwogICQoJyNnbS1uYW1lJykudmFsdWUgPSBnPy5uYW1lIHx8ICcnOwogICQoJyNnbS1kaXNwbGF5JykudmFsdWUgPSBnPy5kaXNwbGF5X25hbWUgfHwgJyc7CiAgJCgnI2dtLXR5cGUnKS52YWx1ZSA9IGc/LnR5cGUgfHwgJ3JlZCc7CiAgJCgnI2dtLXJhcml0eScpLnZhbHVlID0gZz8ucmFyaXR5ID8/IDE7CiAgJCgnI2dtLWhlYWx0aCcpLnZhbHVlID0gZz8uaGVhbHRoID8/IDMwMDsKICAkKCcjZ20tZGFtYWdlJykudmFsdWUgPSBnPy5kYW1hZ2UgPz8gMTUwOwogICQoJyNnbS1zcGVlZCcpLnZhbHVlID0gZz8uc3BlZWQgPz8gMzsKICAkKCcjZ20tcmFuZ2UnKS52YWx1ZSA9IGc/LnJhbmdlID8/IDM7CiAgJCgnI2dtLWNoYXJnZScpLnZhbHVlID0gZz8uY2hhcmdlX3Nob3QgPz8gMjsKICAkKCcjZ20tYWJpbGl0eScpLnZhbHVlID0gZz8uYWJpbGl0eSB8fCAnJzsKICAkKCcjZ20tc2V0bnVtJykudmFsdWUgPSBnPy5zZXRfbnVtYmVyIHx8ICcnOwogICQoJyNnbS1zZXRuYW1lJykudmFsdWUgPSBnPy5zZXRfbmFtZSB8fCAnJzsKICAkKCcjZ20tYm9zcycpLmNoZWNrZWQgPSAhIWc/LmlzX2Jvc3M7CiAgJCgnI2dtLWVuYWJsZWQnKS5jaGVja2VkID0gZyA/ICEhZy5lbmFibGVkIDogdHJ1ZTsKICAkKCcjZ20tZmlsZScpLnZhbHVlID0gJyc7CiAgLy8gU2hvdyB0aGUgZXhpc3Rpbmcgc3RvcmVkIG1lZGlhOiBwcmVmZXIgdGhlIFdlYk0gdmlkZW8sIGVsc2UgYSBzdGlsbCBpbWFnZQogIC8vIChpbWFnZV9wYXRoIGlzIHRoZSBHSUYvUE5HLCBvciB0aGUgV2ViUCB0aHVtYm5haWwgZm9yIGNvbnZlcnRlZCBnaG9zdHMpLgogIGlmIChnPy53ZWJtX3BhdGgpIHNob3dQcmV2aWV3KGAvdXBsb2Fkcy8ke2cud2VibV9wYXRofWAsICd2aWRlbycpOwogIGVsc2UgaWYgKGc/LmltYWdlX3BhdGgpIHNob3dQcmV2aWV3KGAvdXBsb2Fkcy8ke2cuaW1hZ2VfcGF0aH1gLCAnaW1hZ2UnKTsKICBlbHNlIGlmIChnPy53ZWJwX3BhdGgpIHNob3dQcmV2aWV3KGAvdXBsb2Fkcy8ke2cud2VicF9wYXRofWAsICdpbWFnZScpOwogIGVsc2UgaGlkZVByZXZpZXcoKTsKICAkKCcjZ2hvc3QtbW9kYWwnKS5jbGFzc0xpc3QucmVtb3ZlKCdoaWRkZW4nKTsKfQoKLy8gU3dhcCB0aGUgbW9kYWwgcHJldmlldyBiZXR3ZWVuIGFuIDxpbWc+IGFuZCBhIDx2aWRlbz4gZGVwZW5kaW5nIG9uIG1lZGlhIGtpbmQuCmZ1bmN0aW9uIHNob3dQcmV2aWV3KHNyYywga2luZCkgewogIGNvbnN0IGltZyA9ICQoJyNnbS1wcmV2aWV3Jyk7CiAgY29uc3QgdmlkID0gJCgnI2dtLXByZXZpZXctdmlkJyk7CiAgaWYgKGtpbmQgPT09ICd2aWRlbycpIHsKICAgIGltZy5jbGFzc0xpc3QuYWRkKCdoaWRkZW4nKTsgaW1nLnJlbW92ZUF0dHJpYnV0ZSgnc3JjJyk7CiAgICB2aWQuc3JjID0gc3JjOyB2aWQuY2xhc3NMaXN0LnJlbW92ZSgnaGlkZGVuJyk7CiAgICB2aWQucGxheT8uKCkuY2F0Y2goKCkgPT4ge30pOwogIH0gZWxzZSB7CiAgICB2aWQuY2xhc3NMaXN0LmFkZCgnaGlkZGVuJyk7IHZpZC5wYXVzZT8uKCk7IHZpZC5yZW1vdmVBdHRyaWJ1dGUoJ3NyYycpOwogICAgaW1nLnNyYyA9IHNyYzsgaW1nLmNsYXNzTGlzdC5yZW1vdmUoJ2hpZGRlbicpOwogIH0KfQoKZnVuY3Rpb24gaGlkZVByZXZpZXcoKSB7CiAgY29uc3QgaW1nID0gJCgnI2dtLXByZXZpZXcnKTsKICBjb25zdCB2aWQgPSAkKCcjZ20tcHJldmlldy12aWQnKTsKICBpbWcuY2xhc3NMaXN0LmFkZCgnaGlkZGVuJyk7IGltZy5yZW1vdmVBdHRyaWJ1dGUoJ3NyYycpOwogIHZpZC5jbGFzc0xpc3QuYWRkKCdoaWRkZW4nKTsgdmlkLnBhdXNlPy4oKTsgdmlkLnJlbW92ZUF0dHJpYnV0ZSgnc3JjJyk7Cn0KCi8vIExpdmUgbG9jYWwgcHJldmlldyB3aGVuIGEgZmlsZSBpcyBjaG9zZW4gKGJlZm9yZSB1cGxvYWQpLgokKCcjZ20tZmlsZScpLmFkZEV2ZW50TGlzdGVuZXIoJ2NoYW5nZScsICgpID0+IHsKICBjb25zdCBmaWxlID0gJCgnI2dtLWZpbGUnKS5maWxlc1swXTsKICBpZiAoIWZpbGUpIHJldHVybjsKICBjb25zdCB1cmwgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKGZpbGUpOwogIGNvbnN0IGlzVmlkZW8gPSAvXC4obXA0fHdlYm0pJC9pLnRlc3QoZmlsZS5uYW1lKSB8fCBmaWxlLnR5cGUuc3RhcnRzV2l0aCgndmlkZW8vJyk7CiAgc2hvd1ByZXZpZXcodXJsLCBpc1ZpZGVvID8gJ3ZpZGVvJyA6ICdpbWFnZScpOwp9KTsKCiQoJyNnbS1zYXZlJykuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBzYXZlR2hvc3QpOwoKYXN5bmMgZnVuY3Rpb24gc2F2ZUdob3N0KCkgewogIGNvbnN0IHBheWxvYWQgPSB7CiAgICBuYW1lOiAkKCcjZ20tbmFtZScpLnZhbHVlLnRyaW0oKSwKICAgIGRpc3BsYXlOYW1lOiAkKCcjZ20tZGlzcGxheScpLnZhbHVlLnRyaW0oKSB8fCAkKCcjZ20tbmFtZScpLnZhbHVlLnRyaW0oKSwKICAgIHR5cGU6ICQoJyNnbS10eXBlJykudmFsdWUsCiAgICByYXJpdHk6ICskKCcjZ20tcmFyaXR5JykudmFsdWUsCiAgICBoZWFsdGg6ICskKCcjZ20taGVhbHRoJykudmFsdWUsCiAgICBkYW1hZ2U6ICskKCcjZ20tZGFtYWdlJykudmFsdWUsCiAgICBzcGVlZDogKyQoJyNnbS1zcGVlZCcpLnZhbHVlLAogICAgcmFuZ2U6ICskKCcjZ20tcmFuZ2UnKS52YWx1ZSwKICAgIGNoYXJnZVNob3Q6ICskKCcjZ20tY2hhcmdlJykudmFsdWUsCiAgICBhYmlsaXR5OiAkKCcjZ20tYWJpbGl0eScpLnZhbHVlLnRyaW0oKSB8fCBudWxsLAogICAgc2V0TnVtYmVyOiAkKCcjZ20tc2V0bnVtJykudmFsdWUudHJpbSgpIHx8IG51bGwsCiAgICBzZXROYW1lOiAkKCcjZ20tc2V0bmFtZScpLnZhbHVlLnRyaW0oKSB8fCBudWxsLAogICAgaXNCb3NzOiAkKCcjZ20tYm9zcycpLmNoZWNrZWQsCiAgICBlbmFibGVkOiAkKCcjZ20tZW5hYmxlZCcpLmNoZWNrZWQsCiAgfTsKICBpZiAoIXBheWxvYWQubmFtZSkgeyBhbGVydCgnTmFtZSBpcyByZXF1aXJlZC4nKTsgcmV0dXJuOyB9CgogIGxldCBpZCA9IGVkaXRpbmdHaG9zdElkOwogIGlmIChpZCkgewogICAgYXdhaXQgYXBpKGAvYXBpL2FkbWluL2dob3N0cy8ke2lkfWAsIHsgbWV0aG9kOiAnUEFUQ0gnLCBib2R5OiBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSB9KTsKICB9IGVsc2UgewogICAgY29uc3QgcmVzID0gYXdhaXQgYXBpKCcvYXBpL2FkbWluL2dob3N0cycsIHsgbWV0aG9kOiAnUE9TVCcsIGJvZHk6IEpTT04uc3RyaW5naWZ5KHBheWxvYWQpIH0pOwogICAgaWQgPSAoYXdhaXQgcmVzLmpzb24oKSkuaWQ7CiAgfQoKICBjb25zdCBmaWxlID0gJCgnI2dtLWZpbGUnKS5maWxlc1swXTsKICBpZiAoZmlsZSAmJiBpZCkgewogICAgY29uc3QgZmQgPSBuZXcgRm9ybURhdGEoKTsKICAgIGZkLmFwcGVuZCgnaW1hZ2UnLCBmaWxlKTsKICAgIGF3YWl0IGFwaShgL2FwaS9hZG1pbi9naG9zdHMvJHtpZH0vaW1hZ2VgLCB7IG1ldGhvZDogJ1BPU1QnLCBib2R5OiBmZCB9KTsKICB9CgogICQoJyNnaG9zdC1tb2RhbCcpLmNsYXNzTGlzdC5hZGQoJ2hpZGRlbicpOwogIGF3YWl0IFByb21pc2UuYWxsKFtsb2FkR2hvc3RzKCksIGxvYWRTZXRzKCldKTsKfQoKLyogLS0tLS0tLS0tLSBTZXRzIC0tLS0tLS0tLS0gKi8KYXN5bmMgZnVuY3Rpb24gbG9hZFNldHMoKSB7CiAgY29uc3QgcmVzID0gYXdhaXQgYXBpKCcvYXBpL2FkbWluL3NldHMnKTsKICBTRVRTID0gKGF3YWl0IHJlcy5qc29uKCkpLnNldHM7CiAgcmVuZGVyU2V0cygpOwp9CgpmdW5jdGlvbiByZW5kZXJTZXRzKCkgewogICQoJyNzZXRzLWxpc3QnKS5pbm5lckhUTUwgPSBTRVRTLm1hcChzZXRDYXJkKS5qb2luKCcnKSB8fAogICAgJzxwIGNsYXNzPSJtdXRlZCI+Tm8gc2V0cyB5ZXQuIENyZWF0ZSBvbmUgdG8gd2lyZSBhIHNjYW4gY29kZSB0byBhIGdob3N0IHJvc3Rlci48L3A+JzsKICAkJCgnI3NldHMtbGlzdCAuZWRpdC1zZXQnKS5mb3JFYWNoKChiKSA9PgogICAgYi5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IG9wZW5TZXQoK2IuZGF0YXNldC5pZCkpKTsKICAkJCgnI3NldHMtbGlzdCAuZGVsLXNldCcpLmZvckVhY2goKGIpID0+CiAgICBiLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gZGVsZXRlU2V0KCtiLmRhdGFzZXQuaWQpKSk7Cn0KCmZ1bmN0aW9uIHNldENhcmQocykgewogIGNvbnN0IGNoaXBzID0gcy5yb3N0ZXIubWFwKChyKSA9PgogICAgYDxzcGFuIGNsYXNzPSJyY2hpcCI+PHNwYW4gY2xhc3M9ImRvdCAke3IudHlwZX0iPjwvc3Bhbj4ke2VzYyhyLm5hbWUpfSR7ci5pc19ib3NzID8gJyDimIUnIDogJyd9PC9zcGFuPmAKICApLmpvaW4oJycpOwogIHJldHVybiBgPGRpdiBjbGFzcz0ic2V0LWNhcmQgJHtzLmVuYWJsZWQgPyAnJyA6ICdvZmYnfSI+CiAgICA8ZGl2IGNsYXNzPSJzZXQtaGVhZCI+CiAgICAgIDxzcGFuIGNsYXNzPSJjb2RlIj4ke2VzYyhzLmNvZGUpfTwvc3Bhbj4KICAgICAgPHNwYW4gY2xhc3M9InNuYW1lIj4ke2VzYyhzLnNldF9uYW1lKX08L3NwYW4+CiAgICAgICR7cy5zZXRfbnVtYmVyID8gYDxzcGFuIGNsYXNzPSJtb25vIG11dGVkIj4jJHtlc2Mocy5zZXRfbnVtYmVyKX08L3NwYW4+YCA6ICcnfQogICAgICA8c3BhbiBjbGFzcz0iZ3JvdyI+PC9zcGFuPgogICAgICA8YnV0dG9uIGNsYXNzPSJlZGl0LXNldCIgZGF0YS1pZD0iJHtzLmlkfSI+RWRpdDwvYnV0dG9uPgogICAgICA8YnV0dG9uIGNsYXNzPSJkYW5nZXIgZGVsLXNldCIgZGF0YS1pZD0iJHtzLmlkfSI+RGVsZXRlPC9idXR0b24+CiAgICA8L2Rpdj4KICAgIDxkaXYgY2xhc3M9InNldC1yb3N0ZXIiPiR7Y2hpcHMgfHwgJzxzcGFuIGNsYXNzPSJtdXRlZCI+bm8gcm9zdGVyPC9zcGFuPid9PC9kaXY+CiAgPC9kaXY+YDsKfQoKYXN5bmMgZnVuY3Rpb24gZGVsZXRlU2V0KGlkKSB7CiAgY29uc3QgcyA9IFNFVFMuZmluZCgoeCkgPT4geC5pZCA9PT0gaWQpOwogIGlmICghY29uZmlybShgRGVsZXRlIHNldCAiJHtzLnNldF9uYW1lfSIgKCR7cy5jb2RlfSk/YCkpIHJldHVybjsKICBhd2FpdCBhcGkoYC9hcGkvYWRtaW4vc2V0cy8ke2lkfWAsIHsgbWV0aG9kOiAnREVMRVRFJyB9KTsKICBhd2FpdCBsb2FkU2V0cygpOwp9CgovKiBzZXQgbW9kYWwgKi8KbGV0IGVkaXRpbmdTZXRJZCA9IG51bGw7CiQoJyNuZXctc2V0JykuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoKSA9PiBvcGVuU2V0KG51bGwpKTsKJCgnI3NtLXJvc3Rlci1zZWFyY2gnKS5hZGRFdmVudExpc3RlbmVyKCdpbnB1dCcsIGZpbHRlclJvc3RlckNoZWNrbGlzdCk7CgpmdW5jdGlvbiBvcGVuU2V0KGlkKSB7CiAgZWRpdGluZ1NldElkID0gaWQ7CiAgY29uc3QgcyA9IGlkID8gU0VUUy5maW5kKCh4KSA9PiB4LmlkID09PSBpZCkgOiBudWxsOwogICQoJyNzbS10aXRsZScpLnRleHRDb250ZW50ID0gcyA/ICdFZGl0IHNldCcgOiAnTmV3IHNldCc7CiAgJCgnI3NtLWNvZGUnKS52YWx1ZSA9IHM/LmNvZGUgfHwgJyc7CiAgJCgnI3NtLXNldG51bScpLnZhbHVlID0gcz8uc2V0X251bWJlciB8fCAnJzsKICAkKCcjc20tc2V0bmFtZScpLnZhbHVlID0gcz8uc2V0X25hbWUgfHwgJyc7CiAgJCgnI3NtLWVuYWJsZWQnKS5jaGVja2VkID0gcyA/ICEhcy5lbmFibGVkIDogdHJ1ZTsKCiAgLy8gYm9zcyBkcm9wZG93bgogIGNvbnN0IGJvc3NTZWwgPSAkKCcjc20tYm9zcycpOwogIGJvc3NTZWwuaW5uZXJIVE1MID0gJzxvcHRpb24gdmFsdWU9IiI+4oCUIG5vbmUg4oCUPC9vcHRpb24+JyArCiAgICBHSE9TVFMuZmlsdGVyKChnKSA9PiBnLmlzX2Jvc3MpLm1hcCgoZykgPT4KICAgICAgYDxvcHRpb24gdmFsdWU9IiR7Zy5pZH0iPiR7ZXNjKGcubmFtZSl9PC9vcHRpb24+YCkuam9pbignJyk7CiAgYm9zc1NlbC52YWx1ZSA9IHM/LmJvc3NfZ2hvc3RfaWQgfHwgJyc7CgogIC8vIHJvc3RlciBjaGVja2xpc3QKICBjb25zdCBzZWxlY3RlZCA9IG5ldyBTZXQoKHM/LnJvc3RlciB8fCBbXSkubWFwKChyKSA9PiByLmlkKSk7CiAgJCgnI3NtLXJvc3RlcicpLmlubmVySFRNTCA9IEdIT1NUUy5tYXAoKGcpID0+CiAgICBgPGxhYmVsIGNsYXNzPSJyY2hlY2siIGRhdGEtbmFtZT0iJHtlc2MoZy5uYW1lLnRvTG93ZXJDYXNlKCkpfSI+CiAgICAgIDxpbnB1dCB0eXBlPSJjaGVja2JveCIgdmFsdWU9IiR7Zy5pZH0iICR7c2VsZWN0ZWQuaGFzKGcuaWQpID8gJ2NoZWNrZWQnIDogJyd9PgogICAgICA8c3BhbiBjbGFzcz0iZG90ICR7Zy50eXBlfSI+PC9zcGFuPiAke2VzYyhnLm5hbWUpfSAkeyfimIUnLnJlcGVhdChnLnJhcml0eSl9CiAgICA8L2xhYmVsPmApLmpvaW4oJycpOwogICQoJyNzbS1yb3N0ZXItc2VhcmNoJykudmFsdWUgPSAnJzsKICAkKCcjc2V0LW1vZGFsJykuY2xhc3NMaXN0LnJlbW92ZSgnaGlkZGVuJyk7Cn0KCmZ1bmN0aW9uIGZpbHRlclJvc3RlckNoZWNrbGlzdCgpIHsKICBjb25zdCBxID0gJCgnI3NtLXJvc3Rlci1zZWFyY2gnKS52YWx1ZS50b0xvd2VyQ2FzZSgpOwogICQkKCcjc20tcm9zdGVyIC5yY2hlY2snKS5mb3JFYWNoKChlbCkgPT4gewogICAgZWwuc3R5bGUuZGlzcGxheSA9ICFxIHx8IGVsLmRhdGFzZXQubmFtZS5pbmNsdWRlcyhxKSA/ICcnIDogJ25vbmUnOwogIH0pOwp9CgokKCcjc20tc2F2ZScpLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgc2F2ZVNldCk7Cgphc3luYyBmdW5jdGlvbiBzYXZlU2V0KCkgewogIGNvbnN0IHBheWxvYWQgPSB7CiAgICBjb2RlOiAkKCcjc20tY29kZScpLnZhbHVlLnRyaW0oKSwKICAgIHNldE51bWJlcjogJCgnI3NtLXNldG51bScpLnZhbHVlLnRyaW0oKSB8fCBudWxsLAogICAgc2V0TmFtZTogJCgnI3NtLXNldG5hbWUnKS52YWx1ZS50cmltKCksCiAgICBib3NzR2hvc3RJZDogJCgnI3NtLWJvc3MnKS52YWx1ZSA/ICskKCcjc20tYm9zcycpLnZhbHVlIDogbnVsbCwKICAgIGVuYWJsZWQ6ICQoJyNzbS1lbmFibGVkJykuY2hlY2tlZCwKICB9OwogIGlmICghcGF5bG9hZC5jb2RlIHx8ICFwYXlsb2FkLnNldE5hbWUpIHsgYWxlcnQoJ1NjYW4gY29kZSBhbmQgc2V0IG5hbWUgYXJlIHJlcXVpcmVkLicpOyByZXR1cm47IH0KCiAgbGV0IGlkID0gZWRpdGluZ1NldElkOwogIGlmIChpZCkgewogICAgYXdhaXQgYXBpKGAvYXBpL2FkbWluL3NldHMvJHtpZH1gLCB7IG1ldGhvZDogJ1BBVENIJywgYm9keTogSlNPTi5zdHJpbmdpZnkocGF5bG9hZCkgfSk7CiAgfSBlbHNlIHsKICAgIGNvbnN0IHJlcyA9IGF3YWl0IGFwaSgnL2FwaS9hZG1pbi9zZXRzJywgeyBtZXRob2Q6ICdQT1NUJywgYm9keTogSlNPTi5zdHJpbmdpZnkocGF5bG9hZCkgfSk7CiAgICBpZiAocmVzLnN0YXR1cyA9PT0gNDA5KSB7IGFsZXJ0KCdUaGF0IHNjYW4gY29kZSBhbHJlYWR5IGV4aXN0cy4nKTsgcmV0dXJuOyB9CiAgICBpZCA9IChhd2FpdCByZXMuanNvbigpKS5pZDsKICB9CgogIGNvbnN0IGdob3N0SWRzID0gJCQoJyNzbS1yb3N0ZXIgaW5wdXQ6Y2hlY2tlZCcpLm1hcCgoaSkgPT4gK2kudmFsdWUpOwogIGF3YWl0IGFwaShgL2FwaS9hZG1pbi9zZXRzLyR7aWR9L3Jvc3RlcmAsIHsgbWV0aG9kOiAnUFVUJywgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBnaG9zdElkcyB9KSB9KTsKCiAgJCgnI3NldC1tb2RhbCcpLmNsYXNzTGlzdC5hZGQoJ2hpZGRlbicpOwogIGF3YWl0IGxvYWRTZXRzKCk7Cn0KCi8qIC0tLS0tLS0tLS0gQWNjb3VudCAtLS0tLS0tLS0tICovCiQoJyNjcC1nbycpLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgYXN5bmMgKCkgPT4gewogIGNvbnN0IG1zZyA9ICQoJyNjcC1tc2cnKTsKICBtc2cuY2xhc3NMaXN0LmFkZCgnaGlkZGVuJyk7IG1zZy5jbGFzc0xpc3QucmVtb3ZlKCdvaycpOwogIGNvbnN0IHJlcyA9IGF3YWl0IGFwaSgnL2F1dGgvY2hhbmdlLXBhc3N3b3JkJywgewogICAgbWV0aG9kOiAnUE9TVCcsCiAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7CiAgICAgIGN1cnJlbnRQYXNzd29yZDogJCgnI2NwLWN1cnJlbnQnKS52YWx1ZSwKICAgICAgbmV3UGFzc3dvcmQ6ICQoJyNjcC1uZXcnKS52YWx1ZSwKICAgIH0pLAogIH0pOwogIGNvbnN0IGRhdGEgPSBhd2FpdCByZXMuanNvbigpLmNhdGNoKCgpID0+ICh7fSkpOwogIGlmIChyZXMub2spIHsKICAgIG1zZy50ZXh0Q29udGVudCA9ICdQYXNzd29yZCB1cGRhdGVkLic7IG1zZy5jbGFzc0xpc3QuYWRkKCdvaycpOwogIH0gZWxzZSB7CiAgICBtc2cudGV4dENvbnRlbnQgPSBkYXRhLmVycm9yIHx8ICdDb3VsZCBub3QgdXBkYXRlIHBhc3N3b3JkLic7CiAgfQogIG1zZy5jbGFzc0xpc3QucmVtb3ZlKCdoaWRkZW4nKTsKICAkKCcjY3AtY3VycmVudCcpLnZhbHVlID0gJyc7ICQoJyNjcC1uZXcnKS52YWx1ZSA9ICcnOwp9KTsKCi8qIC0tLS0tLS0tLS0gbW9kYWwgY2xvc2UgLS0tLS0tLS0tLSAqLwokJCgnW2RhdGEtY2xvc2VdJykuZm9yRWFjaCgoYikgPT4KICBiLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gJCQoJy5tb2RhbCcpLmZvckVhY2goKG0pID0+IG0uY2xhc3NMaXN0LmFkZCgnaGlkZGVuJykpKSk7CiQkKCcubW9kYWwnKS5mb3JFYWNoKChtKSA9PgogIG0uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoZSkgPT4geyBpZiAoZS50YXJnZXQgPT09IG0pIG0uY2xhc3NMaXN0LmFkZCgnaGlkZGVuJyk7IH0pKTsKCmZ1bmN0aW9uIGVzYyhzKSB7CiAgcmV0dXJuIFN0cmluZyhzID8/ICcnKS5yZXBsYWNlKC9bJjw+IiddL2csIChjKSA9PgogICAgKHsgJyYnOiAnJmFtcDsnLCAnPCc6ICcmbHQ7JywgJz4nOiAnJmd0OycsICciJzogJyZxdW90OycsICInIjogJyYjMzk7JyB9W2NdKSk7Cn0K
\ No newline at end of file