Update html/custom-css.ejs
This commit is contained in:
parent
0090429b21
commit
bbac68de4a
@ -4,16 +4,9 @@
|
|||||||
Copyright (C) 2021-2025 POKETUBE (https://codeberg.org/Ashley/poketube)
|
Copyright (C) 2021-2025 POKETUBE (https://codeberg.org/Ashley/poketube)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License, version 3 or later.
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
This program is distributed WITHOUT ANY WARRANTY.
|
||||||
(at your option) any later version.
|
See https://www.gnu.org/licenses/.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see https://www.gnu.org/licenses/.
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@ -82,7 +75,7 @@
|
|||||||
overflow-x:hidden;
|
overflow-x:hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Top nav — fixed spacing + centered search */
|
/* Top nav — centered search, icons aligned, forced translateY(0) */
|
||||||
nav{
|
nav{
|
||||||
display:flex;
|
display:flex;
|
||||||
align-items:center;
|
align-items:center;
|
||||||
@ -99,16 +92,16 @@
|
|||||||
nav .left{ display:flex; align-items:center }
|
nav .left{ display:flex; align-items:center }
|
||||||
nav .left img{ width:124px; display:block }
|
nav .left img{ width:124px; display:block }
|
||||||
nav .middle{
|
nav .middle{
|
||||||
flex:1;
|
flex:1; display:flex; justify-content:center; align-items:center;
|
||||||
display:flex;
|
transform: translateY(0) !important; /* requested */
|
||||||
justify-content:center;
|
}
|
||||||
align-items:center;
|
nav .middle .search{
|
||||||
|
position:relative; width:100%; max-width:640px;
|
||||||
|
transform: translateY(0) !important; /* requested */
|
||||||
}
|
}
|
||||||
nav .middle .search{ position:relative; width:100%; max-width:640px }
|
|
||||||
nav .middle input[type="search"]{
|
nav .middle input[type="search"]{
|
||||||
width:100%;
|
width:100%; height:40px;
|
||||||
height:40px;
|
padding:0 44px 0 16px; /* leave space for button */
|
||||||
padding:0 44px 0 16px;
|
|
||||||
border-radius:14px;
|
border-radius:14px;
|
||||||
border:1px solid rgba(255,255,255,.12);
|
border:1px solid rgba(255,255,255,.12);
|
||||||
outline:none;
|
outline:none;
|
||||||
@ -125,18 +118,23 @@
|
|||||||
box-shadow: 0 0 0 4px rgba(124,199,255,.15);
|
box-shadow: 0 0 0 4px rgba(124,199,255,.15);
|
||||||
background:#141826;
|
background:#141826;
|
||||||
}
|
}
|
||||||
|
/* Search button INSIDE the field */
|
||||||
nav .middle button{
|
nav .middle button{
|
||||||
position:absolute; right:6px; top:50%; transform:translateY(-50%);
|
position:absolute; right:6px; top:50%; transform:translateY(0) translateY(-50%) !important;
|
||||||
border:0; border-radius:10px; width:36px; height:36px; cursor:pointer;
|
border:0; border-radius:10px; width:36px; height:36px; cursor:pointer;
|
||||||
background:linear-gradient(180deg,#1b2231,#151b27);
|
background:linear-gradient(180deg,#1b2231,#151b27);
|
||||||
color:var(--text); display:grid; place-items:center;
|
color:var(--text); display:grid; place-items:center;
|
||||||
outline:none;
|
outline:none;
|
||||||
}
|
}
|
||||||
nav .middle button:focus{ box-shadow:0 0 0 3px rgba(124,199,255,.25) }
|
nav .middle button:focus{ box-shadow:0 0 0 3px rgba(124,199,255,.25) }
|
||||||
nav .right{ display:flex; align-items:center; gap:14px; padding-left:8px }
|
|
||||||
|
nav .right{
|
||||||
|
display:flex; align-items:center; gap:10px; padding-left:8px; height:40px;
|
||||||
|
}
|
||||||
nav .right a{
|
nav .right a{
|
||||||
color:#cfd6e6; opacity:.9; display:grid; place-items:center;
|
display:grid; place-items:center;
|
||||||
width:34px; height:34px; border-radius:10px; border:1px solid rgba(255,255,255,.07);
|
width:34px; height:34px; border-radius:10px;
|
||||||
|
color:#cfd6e6; border:1px solid rgba(255,255,255,.07);
|
||||||
background:linear-gradient(180deg,#161925,#121521);
|
background:linear-gradient(180deg,#161925,#121521);
|
||||||
transition:.15s transform,.15s border-color,.15s background-color,.15s color;
|
transition:.15s transform,.15s border-color,.15s background-color,.15s color;
|
||||||
}
|
}
|
||||||
@ -160,13 +158,8 @@
|
|||||||
}
|
}
|
||||||
@media (max-width:900px){ .hero{ grid-template-columns:1fr } }
|
@media (max-width:900px){ .hero{ grid-template-columns:1fr } }
|
||||||
|
|
||||||
.hero .title{
|
.hero .title{ font-weight:900; font-size:clamp(22px, 3vw, 34px); letter-spacing:.2px; line-height:1.1; }
|
||||||
font-weight:900; font-size:clamp(22px, 3vw, 34px);
|
.hero .subtitle{ color:var(--muted); margin-top:6px; font-size:clamp(13px,1.6vw,15px) }
|
||||||
letter-spacing:.2px; line-height:1.1;
|
|
||||||
}
|
|
||||||
.hero .subtitle{
|
|
||||||
color:var(--muted); margin-top:6px; font-size:clamp(13px,1.6vw,15px)
|
|
||||||
}
|
|
||||||
|
|
||||||
.chips{ display:flex; flex-wrap:wrap; gap:8px; margin-top:12px }
|
.chips{ display:flex; flex-wrap:wrap; gap:8px; margin-top:12px }
|
||||||
.chip{
|
.chip{
|
||||||
@ -175,16 +168,6 @@
|
|||||||
padding:6px 10px; border-radius:999px; font-size:12px; color:var(--muted);
|
padding:6px 10px; border-radius:999px; font-size:12px; color:var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Theme selector row */
|
|
||||||
.theme-row{
|
|
||||||
display:flex; gap:10px; align-items:center; margin-top:10px;
|
|
||||||
flex-wrap:wrap;
|
|
||||||
}
|
|
||||||
.theme-row select{
|
|
||||||
background:var(--panel-2); color:var(--text);
|
|
||||||
border:1px solid var(--border); border-radius:10px; padding:8px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tabs */
|
/* Tabs */
|
||||||
.tabs{
|
.tabs{
|
||||||
display:flex; gap:8px; padding:14px; margin-top:14px;
|
display:flex; gap:8px; padding:14px; margin-top:14px;
|
||||||
@ -236,17 +219,14 @@
|
|||||||
.btn.err{ background:rgba(239,71,111,.12); border-color:rgba(239,71,111,.4) }
|
.btn.err{ background:rgba(239,71,111,.12); border-color:rgba(239,71,111,.4) }
|
||||||
|
|
||||||
/* Editor (textarea + overlayed highlighted pre) */
|
/* Editor (textarea + overlayed highlighted pre) */
|
||||||
.editor-wrap{
|
.editor-wrap{ position:relative; height:min(70vh, 680px); overflow:auto; background:#0a0b12; }
|
||||||
position:relative; height:min(70vh, 680px); overflow:auto; background:#0a0b12;
|
|
||||||
}
|
|
||||||
.editor{
|
.editor{
|
||||||
position:absolute; inset:0; resize:none; width:100%; height:100%;
|
position:absolute; inset:0; resize:none; width:100%; height:100%;
|
||||||
padding:18px 18px 18px 54px;
|
padding:18px 18px 18px 54px;
|
||||||
border:0; outline:0; background:transparent; color:transparent;
|
border:0; outline:0; background:transparent; color:transparent; /* let overlay show */
|
||||||
caret-color:var(--text);
|
caret-color:var(--text);
|
||||||
font: 500 var(--code-size)/1.6 var(--mono);
|
font: 500 var(--code-size)/1.6 var(--mono);
|
||||||
white-space:pre; overflow:auto;
|
white-space:pre; overflow:auto; tab-size:2; -moz-tab-size:2;
|
||||||
tab-size:2; -moz-tab-size:2;
|
|
||||||
}
|
}
|
||||||
.hl{
|
.hl{
|
||||||
pointer-events:none; user-select:none;
|
pointer-events:none; user-select:none;
|
||||||
@ -275,38 +255,16 @@
|
|||||||
.tok.reg { color:#a5ffce }
|
.tok.reg { color:#a5ffce }
|
||||||
|
|
||||||
/* Preview */
|
/* Preview */
|
||||||
.preview{
|
.preview{ padding:14px; background:var(--panel-2); }
|
||||||
padding:14px; background:var(--panel-2);
|
|
||||||
}
|
|
||||||
.preview .box{
|
.preview .box{
|
||||||
border:1px dashed var(--border);
|
border:1px dashed var(--border);
|
||||||
border-radius:12px; padding:14px; background:#0b0d14;
|
border-radius:12px; padding:14px; background:#0b0d14;
|
||||||
min-height:180px;
|
min-height:180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Alerts */
|
footer{ max-width:1200px; margin:40px auto 50px; padding:0 16px; color:var(--muted); font-size:12px; }
|
||||||
.alert{
|
|
||||||
display:flex; align-items:flex-start; gap:10px;
|
|
||||||
padding:10px 12px; border-radius:12px; border:1px solid var(--border);
|
|
||||||
background:linear-gradient(180deg, rgba(255,255,255,.02), transparent);
|
|
||||||
font-size:13px; color:var(--muted);
|
|
||||||
}
|
|
||||||
.alert i{ margin-top:2px }
|
|
||||||
.alert.ok{ border-color:rgba(32,201,151,.35) }
|
|
||||||
.alert.warn{ border-color:rgba(255,180,0,.35) }
|
|
||||||
.alert.err{ border-color:rgba(239,71,111,.35) }
|
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
footer{
|
|
||||||
max-width:1200px; margin:40px auto 50px; padding:0 16px;
|
|
||||||
color:var(--muted); font-size:12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:focus, .tab:focus { outline: none; box-shadow: var(--ring) }
|
.btn:focus, .tab:focus { outline: none; box-shadow: var(--ring) }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Theme runtime style (updated by JS) -->
|
|
||||||
<style id="theme-style"></style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app gradient-bg">
|
<div class="app gradient-bg">
|
||||||
@ -340,21 +298,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="title">Customize Poke</div>
|
<div class="title">Customize Poke</div>
|
||||||
<div class="subtitle">Personalize styles and behavior. Your edits are stored locally in your browser. Nothing is uploaded.</div>
|
<div class="subtitle">Personalize styles and behavior. Your edits are stored locally in your browser. Nothing is uploaded.</div>
|
||||||
|
|
||||||
<div class="theme-row">
|
|
||||||
<label for="themeSelect">Preset theme:</label>
|
|
||||||
<select id="themeSelect" aria-label="Preset CSS theme">
|
|
||||||
<option value="midnight">Midnight (default)</option>
|
|
||||||
<option value="sakura">Sakura</option>
|
|
||||||
<option value="ocean">Ocean</option>
|
|
||||||
<option value="solarized">Solarized Dark</option>
|
|
||||||
<option value="amoled">AMOLED Black</option>
|
|
||||||
<option value="highcontrast">High Contrast</option>
|
|
||||||
</select>
|
|
||||||
<button class="btn" id="saveThemeBtn" title="Save theme">Save Theme</button>
|
|
||||||
<button class="btn warn" id="resetThemeBtn" title="Reset theme">Reset Theme</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chips" role="list" style="margin-top:10px">
|
<div class="chips" role="list" style="margin-top:10px">
|
||||||
<span class="chip"><i class="fa-light fa-lock"></i> Local-only storage</span>
|
<span class="chip"><i class="fa-light fa-lock"></i> Local-only storage</span>
|
||||||
<span class="chip"><i class="fa-light fa-floppy-disk"></i> Auto-save</span>
|
<span class="chip"><i class="fa-light fa-floppy-disk"></i> Auto-save</span>
|
||||||
@ -364,9 +307,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="alert ok" role="status" aria-live="polite">
|
<div class="alert ok" role="status" aria-live="polite">
|
||||||
<i class="fa-light fa-shield-check"></i>
|
<i class="fa-light fa-shield-check"></i>
|
||||||
<div>
|
<div><strong>Heads-up:</strong> Your CSS/JS never leaves the browser. If you clear site data, your customizations are removed. Export a backup first.</div>
|
||||||
<strong>Heads-up:</strong> Your CSS/JS never leaves the browser. If you clear site data, your customizations are removed. Export a backup first.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -412,13 +353,8 @@
|
|||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<div>
|
<div><div class="title">Live Preview</div><div class="hint">Applies your CSS below (isolated box)</div></div>
|
||||||
<div class="title">Live Preview</div>
|
<div class="btns"><button class="btn" id="reloadPreviewCss"><i class="fa-light fa-rotate"></i> Reload</button></div>
|
||||||
<div class="hint">Applies your CSS below (isolated box)</div>
|
|
||||||
</div>
|
|
||||||
<div class="btns">
|
|
||||||
<button class="btn" id="reloadPreviewCss"><i class="fa-light fa-rotate"></i> Reload</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<div class="box" id="cssPreviewBox">
|
<div class="box" id="cssPreviewBox">
|
||||||
@ -461,16 +397,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="head">
|
<div class="head"><div class="title">Security Notice</div><div class="hint">Run only code you wrote or fully trust.</div></div>
|
||||||
<div class="title">Security Notice</div>
|
|
||||||
<div class="hint">Run only code you wrote or fully trust.</div>
|
|
||||||
</div>
|
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<div class="alert warn">
|
<div class="alert warn">
|
||||||
<i class="fa-light fa-triangle-exclamation"></i>
|
<i class="fa-light fa-triangle-exclamation"></i>
|
||||||
<div>
|
<div>Your script executes on Poke pages via the site’s loader. If something breaks, use a private window or clear site storage to recover.</div>
|
||||||
Your script executes on Poke pages via the site’s loader. If something breaks, use a private window or clear site storage to recover.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div style="height:10px"></div>
|
<div style="height:10px"></div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
@ -498,8 +429,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
<!-- ============================== SCRIPTS =============================== -->
|
<!-- ============================== SCRIPTS =============================== -->
|
||||||
<script>
|
<script>
|
||||||
// --------------------- Utilities ---------------------
|
// ---------- tiny utils ----------
|
||||||
const $ = sel => document.querySelector(sel);
|
const $ = s => document.querySelector(s);
|
||||||
const on = (el, ev, fn) => el && el.addEventListener(ev, fn, { passive: true });
|
const on = (el, ev, fn) => el && el.addEventListener(ev, fn, { passive: true });
|
||||||
const saveFile = (name, content, type='text/plain') => {
|
const saveFile = (name, content, type='text/plain') => {
|
||||||
const blob = new Blob([content], { type });
|
const blob = new Blob([content], { type });
|
||||||
@ -517,65 +448,64 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
};
|
};
|
||||||
const debounce = (fn, ms=200) => { let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), ms); }; };
|
const debounce = (fn, ms=200) => { let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), ms); }; };
|
||||||
|
|
||||||
// --------------------- Minimal formatter ---------------------
|
// ---------- quick formatters ----------
|
||||||
function basicFormatCSS(src){
|
function basicFormatCSS(src){
|
||||||
try{
|
try{
|
||||||
return src
|
return src.replace(/\{/g,'{\n ').replace(/\;/g,';\n ').replace(/\}\s*/g,'\n}\n').replace(/\n\s+\n/g,'\n');
|
||||||
.replace(/\{/g,'{\n ')
|
|
||||||
.replace(/\;/g,';\n ')
|
|
||||||
.replace(/\}\s*/g,'\n}\n')
|
|
||||||
.replace(/\n\s+\n/g,'\n');
|
|
||||||
}catch{ return src }
|
}catch{ return src }
|
||||||
}
|
}
|
||||||
function basicFormatJS(src){
|
function basicFormatJS(src){
|
||||||
try{
|
try{
|
||||||
return src
|
return src.replace(/;\s*/g,';\n').replace(/\{\s*/g,'{\n ').replace(/\}\s*/g,'\n}\n').replace(/\)\s*\{/g,') {\n ').replace(/\n\s+\n/g,'\n');
|
||||||
.replace(/;\s*/g,';\n')
|
|
||||||
.replace(/\{\s*/g,'{\n ')
|
|
||||||
.replace(/\}\s*/g,'\n}\n')
|
|
||||||
.replace(/\)\s*\{/g,') {\n ')
|
|
||||||
.replace(/\n\s+\n/g,'\n');
|
|
||||||
}catch{ return src }
|
}catch{ return src }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------- Syntax highlighter (no libraries) ---------------------
|
// ---------- robust JS/CSS highlighter (no libs) ----------
|
||||||
// IMPORTANT: use single backslashes in regex literals (previous version double-escaped, breaking highlighting)
|
// Avoid the "classspan" issue by protecting tokens first, then styling keywords on the remainder,
|
||||||
|
// finally restoring protected segments. We never run regex over our injected <span> tags.
|
||||||
const JS_KEYWORDS = /\b(?:await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|from|function|if|import|in|instanceof|let|new|null|return|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/g;
|
const JS_KEYWORDS = /\b(?:await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|from|function|if|import|in|instanceof|let|new|null|return|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/g;
|
||||||
const JS_BUILTINS = /\b(?:Array|Object|String|Number|Boolean|Map|Set|WeakMap|WeakSet|Date|Math|JSON|Promise|RegExp|Error|TypeError|URL|Node|Element|document|window|console|fetch)\b/g;
|
const JS_BUILTINS = /\b(?:Array|Object|String|Number|Boolean|Map|Set|WeakMap|WeakSet|Date|Math|JSON|Promise|RegExp|Error|TypeError|URL|Node|Element|document|window|console|fetch)\b/g;
|
||||||
|
|
||||||
function escapeHTML(s){ return s.replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c])); }
|
const ESC = s => s.replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c]));
|
||||||
|
|
||||||
function highlightJS(code){
|
function highlightJS(code){
|
||||||
let s = escapeHTML(code);
|
let s = ESC(code);
|
||||||
// comments
|
const tokens = [];
|
||||||
s = s.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="tok comment">$1</span>');
|
|
||||||
s = s.replace(/(\/\/.*?$)/gm, '<span class="tok comment">$1</span>');
|
// helper: protect ranges (returns placeholder)
|
||||||
// strings + template literals
|
const protect = (re, cls) => {
|
||||||
s = s.replace(/(`[\s\S]*?`)/g, '<span class="tok str">$1</span>');
|
s = s.replace(re, m => {
|
||||||
s = s.replace(/('[^'\\]*(?:\\.[^'\\]*)*')/g, '<span class="tok str">$1</span>');
|
const idx = tokens.push(`<span class="tok ${cls}">${m}</span>`) - 1;
|
||||||
s = s.replace(/("[^"\\]*(?:\\.[^"\\]*)*")/g, '<span class="tok str">$1</span>');
|
return `\u0000${idx}\u0000`;
|
||||||
// regex literals (rough)
|
});
|
||||||
s = s.replace(/(\/(?:\\.|[^\\\/\n])+\/[gimsuy]*)/g, '<span class="tok reg">$1</span>');
|
};
|
||||||
// numbers/booleans/null
|
|
||||||
|
// protect comments, strings, regexes first (order matters)
|
||||||
|
protect(/\/\*[\s\S]*?\*\//g, 'comment');
|
||||||
|
protect(/\/\/.*?$/gm, 'comment');
|
||||||
|
protect(/`[\s\S]*?`/g, 'str');
|
||||||
|
protect(/'[^'\\]*(?:\\.[^'\\]*)*'/g, 'str');
|
||||||
|
protect(/"[^"\\]*(?:\\.[^"\\]*)*"/g, 'str');
|
||||||
|
protect(/\/(?:\\.|[^\\\/\n])+\/[gimsuy]*/g, 'reg');
|
||||||
|
|
||||||
|
// now safe to style plain text (no spans exist yet)
|
||||||
s = s.replace(/\b(0x[\da-fA-F]+|\d+\.\d+|\d+)\b/g, '<span class="tok num">$1</span>');
|
s = s.replace(/\b(0x[\da-fA-F]+|\d+\.\d+|\d+)\b/g, '<span class="tok num">$1</span>');
|
||||||
s = s.replace(/\b(true|false|null)\b/g, '<span class="tok bool">$1</span>');
|
s = s.replace(/\b(true|false|null)\b/g, '<span class="tok bool">$1</span>');
|
||||||
// keywords & builtins
|
|
||||||
s = s.replace(JS_KEYWORDS, '<span class="tok kw">$&</span>');
|
s = s.replace(JS_KEYWORDS, '<span class="tok kw">$&</span>');
|
||||||
s = s.replace(JS_BUILTINS, '<span class="tok fn">$&</span>');
|
s = s.replace(JS_BUILTINS, '<span class="tok fn">$&</span>');
|
||||||
// operators
|
|
||||||
s = s.replace(/([=+\-/*<>!&|%^~?:]+)/g, '<span class="tok op">$1</span>');
|
s = s.replace(/([=+\-/*<>!&|%^~?:]+)/g, '<span class="tok op">$1</span>');
|
||||||
|
|
||||||
|
// restore protected segments
|
||||||
|
s = s.replace(/\u0000(\d+)\u0000/g, (_, i) => tokens[+i]);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlightCSS(code){
|
function highlightCSS(code){
|
||||||
let s = escapeHTML(code);
|
let s = ESC(code);
|
||||||
// block comments
|
// simple & safe for CSS (no keyword-in-attribute problem)
|
||||||
s = s.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="tok comment">$1</span>');
|
s = s.replace(/\/\*[\s\S]*?\*\//g, '<span class="tok comment">$&</span>');
|
||||||
// at-rules
|
|
||||||
s = s.replace(/(@[a-zA-Z-]+)/g, '<span class="tok at">$1</span>');
|
s = s.replace(/(@[a-zA-Z-]+)/g, '<span class="tok at">$1</span>');
|
||||||
// selectors (naive: line before { )
|
|
||||||
s = s.replace(/^([^{}@\n][^{\n]+)(?=\s*\{)/gm, '<span class="tok selector">$1</span>');
|
s = s.replace(/^([^{}@\n][^{\n]+)(?=\s*\{)/gm, '<span class="tok selector">$1</span>');
|
||||||
// properties/values
|
|
||||||
s = s.replace(/([a-zA-Z-]+)(\s*:\s*)([^;}{]+)/g, (m, p, c, v) => {
|
s = s.replace(/([a-zA-Z-]+)(\s*:\s*)([^;}{]+)/g, (m, p, c, v) => {
|
||||||
v = v.replace(/(#[0-9a-fA-F]{3,8}|\b\d+(?:\.\d+)?(?:px|em|rem|%|vh|vw)?\b)/g, '<span class="tok num">$1</span>');
|
v = v.replace(/(#[0-9a-fA-F]{3,8}|\b\d+(?:\.\d+)?(?:px|em|rem|%|vh|vw)?\b)/g, '<span class="tok num">$1</span>');
|
||||||
v = v.replace(/("[^"]*"|'[^']*')/g, '<span class="tok str">$1</span>');
|
v = v.replace(/("[^"]*"|'[^']*')/g, '<span class="tok str">$1</span>');
|
||||||
@ -630,58 +560,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------- Theme presets ---------------------
|
// ---------- initialize ----------
|
||||||
const THEME_STYLE = $('#theme-style');
|
|
||||||
const THEME_KEY = 'poke-custom-theme';
|
|
||||||
const THEMES = {
|
|
||||||
midnight: `
|
|
||||||
:root{
|
|
||||||
--bg:#0c0c0f; --panel:#121218; --panel-2:#0f0f15; --text:#e9ecf1; --muted:#aab2c0;
|
|
||||||
--accent:#7cc7ff; --accent-2:#f97794; --border:#212230; --chip:#161824; --chipb:#0f111a;
|
|
||||||
}`,
|
|
||||||
sakura: `
|
|
||||||
:root{
|
|
||||||
--bg:#0e0a10; --panel:#1b1019; --panel-2:#160d14; --text:#ffeef4; --muted:#ffc8d8;
|
|
||||||
--accent:#ff9cc3; --accent-2:#ffd1e1; --border:#2a1622; --chip:#22111b; --chipb:#1a0d15;
|
|
||||||
}`,
|
|
||||||
ocean: `
|
|
||||||
:root{
|
|
||||||
--bg:#071017; --panel:#0b1b27; --panel-2:#091521; --text:#e6f3ff; --muted:#a9c4da;
|
|
||||||
--accent:#53b7ff; --accent-2:#3de1c5; --border:#102536; --chip:#0c1a26; --chipb:#0a1520;
|
|
||||||
}`,
|
|
||||||
solarized: `
|
|
||||||
:root{
|
|
||||||
--bg:#002b36; --panel:#073642; --panel-2:#062c35; --text:#eee8d5; --muted:#93a1a1;
|
|
||||||
--accent:#268bd2; --accent-2:#2aa198; --border:#0a3945; --chip:#0b2f38; --chipb:#0a2830;
|
|
||||||
}`,
|
|
||||||
amoled: `
|
|
||||||
:root{
|
|
||||||
--bg:#000; --panel:#050505; --panel-2:#0a0a0a; --text:#f2f2f2; --muted:#bdbdbd;
|
|
||||||
--accent:#8ab4ff; --accent-2:#ff8ab4; --border:#111; --chip:#0b0b0b; --chipb:#070707;
|
|
||||||
}`,
|
|
||||||
highcontrast: `
|
|
||||||
:root{
|
|
||||||
--bg:#000; --panel:#000; --panel-2:#000; --text:#fff; --muted:#e6e6e6;
|
|
||||||
--accent:#00ffff; --accent-2:#ff00ff; --border:#fff; --chip:#000; --chipb:#000;
|
|
||||||
}`
|
|
||||||
};
|
|
||||||
|
|
||||||
function applyTheme(name){
|
|
||||||
const css = THEMES[name] || THEMES.midnight;
|
|
||||||
THEME_STYLE.textContent = css;
|
|
||||||
$('#themeSelect').value = name in THEMES ? name : 'midnight';
|
|
||||||
}
|
|
||||||
function saveTheme(){ localStorage.setItem(THEME_KEY, $('#themeSelect').value); toast('Theme saved'); }
|
|
||||||
function resetTheme(){ localStorage.removeItem(THEME_KEY); applyTheme('midnight'); toast('Theme reset'); }
|
|
||||||
|
|
||||||
// --------------------- Initialize per tab ---------------------
|
|
||||||
// Theme boot
|
|
||||||
applyTheme(localStorage.getItem(THEME_KEY) || 'midnight');
|
|
||||||
on($('#themeSelect'), 'change', (e)=> applyTheme(e.target.value));
|
|
||||||
on($('#saveThemeBtn'), 'click', saveTheme);
|
|
||||||
on($('#resetThemeBtn'), 'click', resetTheme);
|
|
||||||
|
|
||||||
// CSS TAB
|
|
||||||
<% if (!tab) { %>
|
<% if (!tab) { %>
|
||||||
const cssEditor = makeEditor('#cssEd', '#cssHl', '#cssLines', {
|
const cssEditor = makeEditor('#cssEd', '#cssHl', '#cssLines', {
|
||||||
lang:'css', storageKey:'poke-custom-css',
|
lang:'css', storageKey:'poke-custom-css',
|
||||||
@ -701,7 +580,6 @@ nav { backdrop-filter: blur(16px) saturate(120%); }
|
|||||||
const applyCssPreview = () => { cssStylePreview.textContent = cssEditor.ed.value || ''; };
|
const applyCssPreview = () => { cssStylePreview.textContent = cssEditor.ed.value || ''; };
|
||||||
applyCssPreview();
|
applyCssPreview();
|
||||||
|
|
||||||
// Controls
|
|
||||||
on($('#saveCssBtn'), 'click', cssEditor.save);
|
on($('#saveCssBtn'), 'click', cssEditor.save);
|
||||||
on($('#formatCssBtn'), 'click', cssEditor.format);
|
on($('#formatCssBtn'), 'click', cssEditor.format);
|
||||||
on($('#copyCssBtn'), 'click', cssEditor.copy);
|
on($('#copyCssBtn'), 'click', cssEditor.copy);
|
||||||
@ -713,17 +591,13 @@ nav { backdrop-filter: blur(16px) saturate(120%); }
|
|||||||
cssEditor.setWrap(!onw); b.setAttribute('data-wrap', onw?'0':'1'); b.textContent = 'Wrap: ' + (onw?'Off':'On');
|
cssEditor.setWrap(!onw); b.setAttribute('data-wrap', onw?'0':'1'); b.textContent = 'Wrap: ' + (onw?'Off':'On');
|
||||||
});
|
});
|
||||||
on($('#reloadPreviewCss'), 'click', applyCssPreview);
|
on($('#reloadPreviewCss'), 'click', applyCssPreview);
|
||||||
|
|
||||||
// Live preview while typing
|
|
||||||
on(cssEditor.ed, 'input', debounce(applyCssPreview, 50));
|
on(cssEditor.ed, 'input', debounce(applyCssPreview, 50));
|
||||||
|
|
||||||
// Save on Ctrl/Cmd+S
|
|
||||||
on(document, 'keydown', (e)=>{
|
on(document, 'keydown', (e)=>{
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase()==='s') { e.preventDefault(); cssEditor.save(); }
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase()==='s') { e.preventDefault(); cssEditor.save(); }
|
||||||
});
|
});
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
// JS TAB
|
|
||||||
<% if (tab) { %>
|
<% if (tab) { %>
|
||||||
const jsEditor = makeEditor('#jsEd', '#jsHl', '#jsLines', {
|
const jsEditor = makeEditor('#jsEd', '#jsHl', '#jsLines', {
|
||||||
lang:'js', storageKey:'poke-custom-script',
|
lang:'js', storageKey:'poke-custom-script',
|
||||||
@ -744,14 +618,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const b = e.currentTarget; const onw = b.getAttribute('data-wrap')==='1';
|
const b = e.currentTarget; const onw = b.getAttribute('data-wrap')==='1';
|
||||||
jsEditor.setWrap(!onw); b.setAttribute('data-wrap', onw?'0':'1'); b.textContent = 'Wrap: ' + (onw?'Off':'On');
|
jsEditor.setWrap(!onw); b.setAttribute('data-wrap', onw?'0':'1'); b.textContent = 'Wrap: ' + (onw?'Off':'On');
|
||||||
});
|
});
|
||||||
|
|
||||||
on(document, 'keydown', (e)=>{
|
on(document, 'keydown', (e)=>{
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase()==='s') { e.preventDefault(); jsEditor.save(); }
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase()==='s') { e.preventDefault(); jsEditor.save(); }
|
||||||
});
|
});
|
||||||
<% } %>
|
<% } %>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Site loader that applies saved CSS/JS globally (kept for compatibility) -->
|
|
||||||
<script src="/css/custom-css.js"></script>
|
<script src="/css/custom-css.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user