sync: UI animations, select styling, TLS verify flag via proxy second line, brand spacing
This commit is contained in:
@@ -1085,4 +1085,86 @@ header .actions {
|
||||
@media (max-width: 860px) {
|
||||
header .brand { margin-right: 10px; }
|
||||
header .actions { margin-left: 6px; }
|
||||
}
|
||||
/* Danmaku (bullet chat) over header */
|
||||
header { position: relative; }
|
||||
|
||||
#danmaku-layer {
|
||||
position: fixed; /* покрывает весь вьюпорт, не только шапку */
|
||||
inset: 0;
|
||||
pointer-events: none; /* не перехватывать клики по кнопкам */
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity .18s ease-in-out;
|
||||
z-index: 8000; /* ниже боковых панелей/дроверов (run-drawer ~10000), выше канваса */
|
||||
}
|
||||
#danmaku-layer.is-on { opacity: 1; }
|
||||
|
||||
/* Базовый стиль «пули» */
|
||||
.danmaku-bullet {
|
||||
position: absolute;
|
||||
left: 100%; /* старт за правым краем */
|
||||
top: 8px;
|
||||
white-space: nowrap;
|
||||
color: #e5e7eb;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,.6);
|
||||
background: rgba(15, 20, 26, .35);
|
||||
border: 1px solid rgba(96,165,250,.28);
|
||||
border-radius: 8px;
|
||||
padding: 2px 8px;
|
||||
line-height: 1.15;
|
||||
transform: translateZ(0); /* чуть сгладить анимацию */
|
||||
animation-name: danmaku-move;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
/* Вариации размера/выразительности */
|
||||
.danmaku-bullet.sm { font: 11px/1.15 Inter, system-ui, Arial, sans-serif; opacity: .9; }
|
||||
.danmaku-bullet.md { font: 12px/1.15 Inter, system-ui, Arial, sans-serif; }
|
||||
.danmaku-bullet.lg { font: 13px/1.15 Inter, system-ui, Arial, sans-serif; box-shadow: 0 0 0 2px rgba(59,130,246,.12), 0 2px 6px rgba(0,0,0,.35); }
|
||||
|
||||
.danmaku-bullet.tint-blue { border-color: rgba(96,165,250,.35); color:#eaf2ff; }
|
||||
.danmaku-bullet.tint-green { border-color: rgba(52,211,153,.35); color:#dcfce7; }
|
||||
.danmaku-bullet.tint-pink { border-color: rgba(236,72,153,.35); color:#fde2f2; }
|
||||
.danmaku-bullet.tint-amber { border-color: rgba(245,158,11,.35); color:#fff3d6; }
|
||||
|
||||
/* Движение слева направо (через всю ширину хедера + небольшой запас) */
|
||||
@keyframes danmaku-move {
|
||||
from { left: 100%; }
|
||||
to { left: -40%; }
|
||||
}
|
||||
/* Rune highlight inside danmaku bullets */
|
||||
.danmaku-bullet .rune {
|
||||
color: var(--accent-2); /* фирменный синий */
|
||||
font-size: 1.35em; /* немного крупнее текста пули */
|
||||
letter-spacing: .06em;
|
||||
text-shadow:
|
||||
0 0 2px rgba(96,165,250,.95),
|
||||
0 0 6px rgba(96,165,250,.75),
|
||||
0 0 12px rgba(96,165,250,.45),
|
||||
0 0 20px rgba(96,165,250,.30);
|
||||
animation: rune-glow 1.8s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes rune-glow {
|
||||
0% { text-shadow: 0 0 2px rgba(96,165,250,.85), 0 0 6px rgba(96,165,250,.60), 0 0 10px rgba(96,165,250,.35); }
|
||||
50% { text-shadow: 0 0 3px rgba(96,165,250,1), 0 0 10px rgba(96,165,250,.85), 0 0 18px rgba(96,165,250,.55); }
|
||||
100% { text-shadow: 0 0 2px rgba(96,165,250,.90), 0 0 8px rgba(96,165,250,.70), 0 0 14px rgba(96,165,250,.45); }
|
||||
}
|
||||
/* Sisters highlight inside danmaku bullets (pleasant pink + gentle glow) */
|
||||
.danmaku-bullet .sisters {
|
||||
color: #f9a8d4; /* soft pink */
|
||||
font-weight: 600;
|
||||
letter-spacing: .02em;
|
||||
text-shadow:
|
||||
0 0 2px rgba(249,168,212,.95),
|
||||
0 0 6px rgba(249,168,212,.70),
|
||||
0 0 12px rgba(249,168,212,.40),
|
||||
0 0 18px rgba(249,168,212,.28);
|
||||
animation: sisters-glow 1.8s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes sisters-glow {
|
||||
0% { text-shadow: 0 0 2px rgba(249,168,212,.85), 0 0 6px rgba(249,168,212,.60), 0 0 10px rgba(249,168,212,.35); }
|
||||
50% { text-shadow: 0 0 3px rgba(249,168,212,1), 0 0 10px rgba(249,168,212,.85), 0 0 18px rgba(249,168,212,.55); }
|
||||
100% { text-shadow: 0 0 2px rgba(249,168,212,.90), 0 0 8px rgba(249,168,212,.70), 0 0 14px rgba(249,168,212,.45); }
|
||||
}
|
||||
@@ -81,6 +81,8 @@
|
||||
<button class="chip-btn" id="btn-logs" title="Журнал HTTP запросов/ответов">ЛОГИ</button>
|
||||
<a class="chip-btn" href="/" role="button">ДОМОЙ</a>
|
||||
</div>
|
||||
<!-- Danmaku overlay layer -->
|
||||
<div id="danmaku-layer" aria-hidden="true"></div>
|
||||
</header>
|
||||
|
||||
<!-- Run Drawer -->
|
||||
@@ -2392,6 +2394,142 @@ function toggleLeft() {
|
||||
setTimeout(applyLOD, 0);
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
try {
|
||||
const brand = document.querySelector('header .brand');
|
||||
const layer = document.getElementById('danmaku-layer');
|
||||
if (!brand || !layer) return;
|
||||
|
||||
// Пулевая лента (弾幕) — набор фраз
|
||||
// Поддерживает маркеры [rune]...[/rune] для явной подсветки рун.
|
||||
const msgs = [
|
||||
'Сделано гпт-5 с любовью для сестричек сисунь',
|
||||
'Играй и побеждай',
|
||||
'[rune]ᚨᛞᚨ[/rune] ✦ ваши чаты защищены оберегом НадТаверны от слопа',
|
||||
'РП‑магия заряжена ⚡',
|
||||
'Пиши, как дышишь — и мир откликнется',
|
||||
'Сюжет идёт по плану… или нет?',
|
||||
'NPC шепчет: «/roll d20»',
|
||||
// от автора
|
||||
'От Roo: берегите свои миры — а я присмотрю за багами 🛡️✨',
|
||||
// новые странные фразы
|
||||
'Бард шепчет кружке: «ты — артефакт +1 к вдохновению» 🍺✨',
|
||||
'Где-то в подвале убежал printf, ищем следы по логам… 🐾',
|
||||
'[rune]ᚠᚱᛁᛞᚨ[/rune] на кости выпала — к криту и печенькам 🍪',
|
||||
'Сюжет идёт по плану… пока кубик не решит иначе 🎲',
|
||||
'NPC недоволен: «кто украл мою функцию?» function() {…} 🤖',
|
||||
'Только для сестричек сисунь — VIP‑проход в НадТаверну',
|
||||
'РП‑магия на максимуме, щиты подняты, слова летят ⚡',
|
||||
'Играй сердцем, кодь с умом, логи — наш оракул 📜',
|
||||
'Пиши, как дышишь, пей, как гном — но не смешивай 🍻',
|
||||
'[rune]ᚨᛚᚷᛟ[/rune] говорит: «tabs vs spaces?» ␣'
|
||||
];
|
||||
const tints = ['tint-blue','tint-green','tint-pink','tint-amber'];
|
||||
const sizes = ['sm','md','lg'];
|
||||
|
||||
// Автоподсветка рун: U+16A0–U+16FF (Runic)
|
||||
const RUNE_RE = /[\u16A0-\u16FF]+/g;
|
||||
function escHtml(s) {
|
||||
return String(s ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
// Оборачиваем рунические символы в уже экранированной строке
|
||||
function wrapRunesEscaped(escaped) {
|
||||
return escaped.replace(RUNE_RE, (m) => '<span class="rune">' + m + '</span>');
|
||||
}
|
||||
|
||||
// Розовый хайлайт для фразы «сестричек сисунь» + случайные «девчачьи» эмодзи
|
||||
const EMOJI_GIRLY = ['💖','✨','🌸','💅','👑','🩷','🌷','🦋'];
|
||||
function pickTwo(arr) {
|
||||
const pool = arr.slice();
|
||||
const a = pool.splice((Math.random()*pool.length)|0, 1)[0] || arr[0];
|
||||
const b = pool.splice((Math.random()*pool.length)|0, 1)[0] || a;
|
||||
return [a, b];
|
||||
}
|
||||
function markSisters(src) {
|
||||
try {
|
||||
return String(src || '').replace(/сестричек\s+сисунь/gi, m => `[sisters]${m}[/sisters]`);
|
||||
} catch { return String(src || ''); }
|
||||
}
|
||||
|
||||
// Рендер безопасного HTML, поддержка маркеров [rune]...[/rune], [sisters]...[/sisters]
|
||||
function renderBulletHTML(text) {
|
||||
const raw = markSisters(String(text ?? ''));
|
||||
const hasMarkers = /\[(rune|sisters)\]/i.test(raw);
|
||||
if (hasMarkers) {
|
||||
let out = '';
|
||||
let last = 0;
|
||||
const re = /\[(rune|sisters)\]([\s\S]+?)\[\/(rune|sisters)\]/gi;
|
||||
let m;
|
||||
while ((m = re.exec(raw)) !== null) {
|
||||
// незамеченная часть — экранировать и автоподсветить руны
|
||||
out += wrapRunesEscaped(escHtml(raw.slice(last, m.index)));
|
||||
const open = (m[1] || '').toLowerCase();
|
||||
const close = (m[3] || '').toLowerCase();
|
||||
const inner = String(m[2] ?? '');
|
||||
if (open === close) {
|
||||
if (open === 'rune') {
|
||||
out += '<span class="rune">' + escHtml(inner) + '</span>';
|
||||
} else if (open === 'sisters') {
|
||||
const [e1, e2] = pickTwo(EMOJI_GIRLY);
|
||||
out += '<span class="sisters">' + escHtml(inner) + ' ' + e1 + ' ' + e2 + '</span>';
|
||||
}
|
||||
} else {
|
||||
// на всякий случай — экранируем весь блок, если теги не совпали
|
||||
out += wrapRunesEscaped(escHtml(m[0]));
|
||||
}
|
||||
last = m.index + m[0].length;
|
||||
}
|
||||
out += wrapRunesEscaped(escHtml(raw.slice(last)));
|
||||
return out;
|
||||
}
|
||||
// Без маркеров — экранировать и автоподсветить руны
|
||||
return wrapRunesEscaped(escHtml(raw));
|
||||
}
|
||||
|
||||
function spawn(text) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'danmaku-bullet ' + sizes[(Math.random()*sizes.length)|0] + ' ' + tints[(Math.random()*tints.length)|0];
|
||||
// Вставляем безопасный HTML с подсветкой рун и розовым акцентом фразы «сестричек сисунь»
|
||||
el.innerHTML = renderBulletHTML(text);
|
||||
const vpH = (layer && layer.clientHeight) || window.innerHeight || document.documentElement.clientHeight || 600;
|
||||
const top = Math.random() * Math.max(40, vpH - 40);
|
||||
el.style.top = Math.max(2, Math.min(vpH - 24, top)) + 'px';
|
||||
const dur = 9 + Math.random() * 7; // 9–16s
|
||||
el.style.animationDuration = dur + 's';
|
||||
layer.appendChild(el);
|
||||
el.addEventListener('animationend', () => { try { el.remove(); } catch(_){} });
|
||||
}
|
||||
|
||||
let timer = null;
|
||||
let last = -1;
|
||||
function start() {
|
||||
if (timer) return;
|
||||
layer.classList.add('is-on');
|
||||
// Небольшой «всплеск» при старте
|
||||
for (let i=0;i<5;i++){
|
||||
setTimeout(()=>{ last = (last+1) % msgs.length; spawn(msgs[last]); }, i*160);
|
||||
}
|
||||
timer = setInterval(()=>{ spawn(msgs[Math.floor(Math.random()*msgs.length)]); }, 700);
|
||||
}
|
||||
function stop() {
|
||||
if (timer) { clearInterval(timer); timer=null; }
|
||||
setTimeout(()=>{
|
||||
layer.classList.remove('is-on');
|
||||
try { Array.from(layer.querySelectorAll('.danmaku-bullet')).forEach(n=>n.remove()); } catch(_){}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
brand.addEventListener('mouseenter', start);
|
||||
brand.addEventListener('mouseleave', stop);
|
||||
// Тач-устройства: короткое автозакрытие
|
||||
brand.addEventListener('touchstart', ()=>{ start(); setTimeout(stop, 2800); }, {passive:true});
|
||||
} catch(_) {}
|
||||
})();
|
||||
</script>
|
||||
<!-- SSE highlight script -->
|
||||
<script>
|
||||
(function() {
|
||||
|
||||
Reference in New Issue
Block a user