sync: UI animations, select styling, TLS verify flag via proxy second line, brand spacing

This commit is contained in:
2025-09-14 13:15:33 +03:00
parent 81014d26f8
commit 338e65624f
2 changed files with 220 additions and 0 deletions

View File

@@ -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); }
}

View File

@@ -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+16A0U+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; // 916s
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() {