poke/html/custom-css.ejs
2025-08-19 14:08:52 +02:00

249 lines
13 KiB
Plaintext
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 class="gradient-bg" lang="en">
<head>
<meta charset="utf-8" />
<title>PokeTube | Customize</title>
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<link href="/css/yt-ukraine.svg?v=6" rel="icon">
<meta content="Poke | Customize" property="og:title">
<meta content="Theme Poke and add custom scripts — go wild!" property="twitter:description">
<meta content="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/aa70111e-5bcd-4379-8b23-332a33012b78.image.png?v=1701898829884" property="og:image" />
<meta content="summary_large_image" name="twitter:card" />
<meta content="#f97794" name="theme-color" />
<link href="/css/app-cdn.min.css" rel="stylesheet">
<link href="/css/app.main.css?v=44600" rel="stylesheet">
<link href="/css/search.main.css?v=57" rel="stylesheet">
<link href="/css/watch.main.css" rel="stylesheet">
<style>
@import url("https://p.poketube.fun/https://site-assets.fontawesome.com/releases/v6.1.1/css/all.css");
:root{
--bg:#0c0c0f; --panel:#121218; --panel-2:#0f0f15; --text:#e9ecf1; --muted:#aab2c0;
--accent:#7cc7ff; --accent-2:#f97794; --ok:#20c997; --warn:#ffb400; --err:#ef476f;
--border:#212230; --chip:#161824; --chipb:#0f111a; --ring:0 0 0 2px rgba(124,199,255,.25);
--radius:16px; --radius-lg:22px; --shadow:0 10px 30px rgba(0,0,0,.35);
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, "Ubuntu Mono", Consolas, "Liberation Mono", monospace;
--sans: Inter, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, "Helvetica Neue", Arial, "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol", sans-serif;
--code-size:13.5px;
}
html.gradient-bg{
min-height:100%;
background:
radial-gradient(1200px 800px at 10% 10%, rgba(249,119,148,.22), transparent 60%),
radial-gradient(1200px 800px at 90% 20%, rgba(124,199,255,.18), transparent 60%),
radial-gradient(800px 600px at 30% 80%, rgba(124,255,208,.12), transparent 60%),
linear-gradient(135deg, #0a0a0f, #0b0b12 60%, #0c0c15);
background-attachment: fixed;
}
*{box-sizing:border-box}
body{margin:0;color:var(--text);font-family:var(--sans);background:transparent;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:hidden}
/* -------- NAVBAR -------- */
.customize-navbar{
display:flex; align-items:center; gap:16px; padding:12px 20px;
position:sticky; top:0; z-index:2000; min-height:62px;
backdrop-filter:blur(12px) saturate(120%);
background:linear-gradient(180deg, rgba(14,14,24,.78), rgba(14,14,24,.45));
border-bottom:1px solid rgba(255,255,255,.06);
}
.customize-navbar__left{display:flex;align-items:center}
.customize-navbar__logo{width:124px;display:block}
.customize-navbar__middle{flex:1; display:flex; justify-content:center; align-items:center}
.customize-navbar__search{position:relative; width:100%; max-width:640px}
.customize-navbar__input{
width:100%; height:40px; padding:0 44px 0 16px;
border-radius:14px; border:1px solid rgba(255,255,255,.12); outline:none;
background:#151720;
color:var(--text); box-shadow:inset 0 0 0 1px rgba(255,255,255,.02);
}
.customize-navbar__input::placeholder{color:#8b93a6}
.customize-navbar__btn{
position:absolute; right:6px; top:50%;
transform:translateY(-50%);
border:0; border-radius:10px; width:36px; height:36px; cursor:pointer;
background:linear-gradient(180deg,#1b2231,#151b27); color:var(--text);
display:grid; place-items:center;
}
.customize-navbar__right{display:flex;align-items:center;gap:10px;padding-left:8px;height:40px}
.customize-navbar__icon{
display:grid; place-items:center; width:34px; height:34px; border-radius:10px;
color:#cfd6e6; border:1px solid rgba(255,255,255,.07);
background:linear-gradient(180deg,#161925,#121521);
}
/* -------- PAGE LAYOUT -------- */
.container{max-width:1200px;margin:22px auto 64px;padding:0 16px}
.hero{display:grid;grid-template-columns:1fr;gap:16px;background:var(--panel);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px;box-shadow:var(--shadow)}
.title{text-align:center;font-weight:900;font-size:clamp(22px,3vw,34px)}
.subtitle{text-align:center;color:var(--muted);margin-top:6px}
.tabs{display:flex;justify-content:center;gap:8px;padding:14px;margin-top:14px;background:var(--panel-2);border:1px solid var(--border);border-radius:var(--radius)}
.tab{padding:10px 14px;border-radius:10px;cursor:pointer;text-transform:uppercase;font-size:12px;color:var(--muted);text-decoration:none}
.tab.active{color:var(--text);background:rgba(124,199,255,.06)}
.work{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:14px}
@media (max-width:1000px){.work{grid-template-columns:1fr}}
.panel{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow)}
.panel .head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--border)}
.panel .head .title{font-weight:700;font-size:14px}
.panel .head .hint{color:var(--muted);font-size:12px}
.btns{display:flex;gap:8px;flex-wrap:wrap}
.btn{border:1px solid var(--border);background:var(--chip);color:var(--text);padding:8px 10px;border-radius:10px;font-size:12px;cursor:pointer}
/* -------- Editors (no syntax highlighting) -------- */
.editor-wrap{position:relative;height:min(70vh,680px);overflow:auto;background:#0a0b12}
.ln{
position:absolute; left:0; top:0; bottom:0; width:40px;
background:#08090f; color:#6272a4; border-right:1px solid #0d0f1a;
text-align:right; padding:18px 6px; font:500 var(--code-size)/1.6 var(--mono);
}
.live-editor{
margin:0; outline:0; border:0; background:transparent;
font:500 var(--code-size)/1.6 var(--mono);
color:var(--text);
white-space:pre; tab-size:2; -moz-tab-size:2;
padding:18px 18px 18px 54px;
min-height:100%;
}
</style>
</head>
<body>
<div class="app gradient-bg">
<!-- NAVBAR -->
<nav class="customize-navbar">
<div class="customize-navbar__left">
<a href="/143"><img class="customize-navbar__logo" src="/css/logo.svg?v=5" alt="PokeTube"></a>
</div>
<div class="customize-navbar__middle">
<form class="customize-navbar__search" action="/search">
<input id="fname" name="query" class="customize-navbar__input" type="search" placeholder="Search videos, channels…" />
<button class="customize-navbar__btn" type="submit"><i class="fa-light fa-search"></i></button>
</form>
</div>
<div class="customize-navbar__right">
<a class="customize-navbar__icon" href="/domains"><i class="fa-light fa-server"></i></a>
<a class="customize-navbar__icon" href="/privacy"><i class="fa-light fa-shield"></i></a>
<a class="customize-navbar__icon" href="/video/upload?from="><i class="fa-light fa-video"></i></a>
<a class="customize-navbar__icon" href="https://codeberg.org/Ashley/poketube/issues"><i class="fa-light fa-bug"></i></a>
</div>
</nav>
<div class="container">
<section class="hero">
<div>
<div class="title">Customize Poke</div>
<div class="subtitle">Edits are stored in the browser. Export a backup before clearing site data.</div>
</div>
</section>
<div class="tabs">
<% if (!tab) { %>
<a class="tab active" href="/customize">Custom CSS</a>
<a class="tab" href="/customize?tab=js">Custom JS</a>
<% } else { %>
<a class="tab" href="/customize">Custom CSS</a>
<a class="tab active" href="/customize?tab=js">Custom JS</a>
<% } %>
</div>
<% if (!tab) { %>
<section class="work">
<div class="panel">
<div class="head">
<div><div class="title">Custom CSS Editor</div><div class="hint">Saved to <code>localStorage['poke-custom-css']</code></div></div>
<div class="btns">
<button class="btn ok" id="saveCssBtn">Save</button>
<button class="btn" id="formatCssBtn">Format</button>
<button class="btn" id="wrapCssBtn" data-wrap="1">Wrap: On</button>
<button class="btn" id="copyCssBtn">Copy</button>
<button class="btn warn" id="exportCssBtn">Export</button>
<label class="btn warn" for="importCssFile">Import<input id="importCssFile" type="file" accept=".css,text/css,text/plain" style="display:none"></label>
<button class="btn err" id="resetCssBtn">Reset</button>
</div>
</div>
<div class="editor-wrap"><pre class="ln" id="cssLines">1</pre><pre id="cssEd" class="live-editor" contenteditable></pre></div>
</div>
</section>
<% } %>
<% if (tab) { %>
<section class="work">
<div class="panel">
<div class="head"><div class="title">Custom JS Editor</div><div class="hint">Saved to <code>localStorage['poke-custom-script']</code></div></div>
<div class="btns">
<button class="btn ok" id="saveJsBtn">Save</button>
<button class="btn" id="formatJsBtn">Format</button>
<button class="btn" id="wrapJsBtn" data-wrap="1">Wrap: On</button>
<button class="btn" id="copyJsBtn">Copy</button>
<button class="btn warn" id="exportJsBtn">Export</button>
<label class="btn warn" for="importJsFile">Import<input id="importJsFile" type="file" accept=".js,text/javascript,text/plain" style="display:none"></label>
<button class="btn err" id="resetJsBtn">Reset</button>
</div>
<div class="editor-wrap"><pre class="ln" id="jsLines">1</pre><pre id="jsEd" class="live-editor" contenteditable></pre></div>
</div>
</section>
<% } %>
</div>
<footer>
<div style="text-align:center;margin-top:32px;color:var(--muted)">© 20212025 PokeTube • Free software under the GNU GPL. Customizations are stored locally on the device.</div>
</footer>
</div>
<!-- ================= SCRIPTS ================ -->
<script>
const $ = s => document.querySelector(s);
const on = (el, ev, fn) => el && el.addEventListener(ev, fn, { passive:true });
const saveFile = (name, content, type='text/plain') => {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url; a.download = name;
document.body.appendChild(a); a.click(); URL.revokeObjectURL(url); a.remove();
};
const copyText = async (txt) => { try { await navigator.clipboard.writeText(txt); } catch {} };
const debounce = (fn,ms=120)=>{ let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a),ms); }; };
function setLines(preEl, lnEl){
const lines = preEl.textContent.split('\n').length || 1;
let out=''; for (let i=1;i<=lines;i++) out += i + (i<lines?'\n':'');
lnEl.textContent = out;
}
function setWrap(preEl, onwrap){
preEl.style.whiteSpace = onwrap ? 'pre-wrap' : 'pre';
preEl.style.overflowWrap = onwrap ? 'anywhere' : 'normal';
}
function loadEditor(preEl, storageKey, fallback){
const saved = localStorage.getItem(storageKey);
preEl.textContent = saved ?? fallback;
setLines(preEl, preEl.previousElementSibling);
}
function saveEditor(preEl, storageKey){
localStorage.setItem(storageKey, preEl.textContent);
}
<% if (!tab) { %>
const cssEd = $('#cssEd'), cssLines = $('#cssLines');
loadEditor(cssEd, 'poke-custom-css', "/* Custom CSS */");
const cssPreviewStyle = document.createElement('style'); document.body.appendChild(cssPreviewStyle);
const applyCssPreview = ()=>{ cssPreviewStyle.textContent = cssEd.textContent || ''; };
on(cssEd,'input',debounce(()=>{setLines(cssEd,cssLines);applyCssPreview()},50));
on($('#saveCssBtn'),'click',()=> saveEditor(cssEd,'poke-custom-css'));
on($('#exportCssBtn'),'click',()=> saveFile('poke-custom-css.css', cssEd.textContent,'text/css'));
<% } %>
<% if (tab) { %>
const jsEd = $('#jsEd'), jsLines = $('#jsLines');
loadEditor(jsEd, 'poke-custom-script', "// Custom JS");
on(jsEd,'input',debounce(()=> setLines(jsEd,jsLines),50));
on($('#saveJsBtn'),'click',()=> saveEditor(jsEd,'poke-custom-script'));
on($('#exportJsBtn'),'click',()=> saveFile('poke-custom-script.js', jsEd.textContent,'application/javascript'));
<% } %>
</script>
<script src="/css/custom-css.js"></script>
</body>
</html>