/* =========================================================
   BASE STRUCTURE
   Общая для всех тем разметка, размеры, анимации.
   Темы (themes/*.css) переопределяют только внешний вид:
   цвета, тени, форму рамки, шрифты.
========================================================= */

:root {

    --chat-width: 480px;
    --chat-height: 640px;

    --font-size: 18px;
    --username-size: 18px;

    --chat-opacity: 0.94;

    --max-messages: 9;

    --message-icon-size: 28px;
    --message-gap: 7px;
    --message-padding: 0px;
    --message-bg: transparent;
    --message-radius: 8px;
}

* {
    box-sizing: border-box;
}

html,
body {
    width: 100%;
    height: 100%;

    margin: 0;
    padding: 0;

    overflow: hidden;

    background: transparent;

    font-family: "Segoe UI", Arial, sans-serif;

    display: flex;
    align-items: center;
    justify-content: center;
}

.chat-widget {

    position: relative;

    flex-shrink: 0;

    width: var(--chat-width);
    height: var(--chat-height);

    overflow: hidden;

    color: var(--text, #eee);
}

/* Виджет "только события" не показывает список сообщений чата, поэтому
   ему не нужна полная высота чата (var(--chat-height)) — иначе баннеры
   занимают только верхнюю часть коробки, а остальное превью пустует.
   Высота подстраивается под фактическое содержимое (.messages). */
.chat-widget.events-only {
    height: auto;
    overflow: visible;

    /* Темы (themes/*.css, подключаются после base.css) рисуют на
       .chat-widget.theme-* декоративную рамку/фон/паддинг окна чата —
       без списка сообщений в events-only режиме эта рамка обтягивает
       только баннеры и выглядит как лишний прямоугольник вокруг них,
       поэтому глушим её здесь с более высокой специфичностью. */
    padding: 0 !important;
    background: none !important;
    border: none !important;
    box-shadow: none !important;
}

.chat-widget.events-only .messages {
    height: auto;
    overflow: visible;
}

.messages {

    position: relative;

    z-index: 2;

    width: 100%;
    height: 100%;

    display: flex;

    flex-direction: column;

    justify-content: flex-start;

    gap: var(--message-gap);

    overflow: hidden;
}

.message {

    position: relative;

    display: grid;

    grid-template-columns: var(--message-icon-size) max-content 1fr;
    grid-template-rows: var(--message-icon-size) auto;
    grid-template-areas:
        "icon user time"
        "text text text";

    align-items: center;
    align-content: start;

    column-gap: 10px;
    row-gap: 2px;

    padding: var(--message-padding);

    background: var(--message-bg);
    border-radius: var(--message-radius);

    font-size: var(--font-size);

    line-height: 1.35;

    will-change: transform, opacity;
}

.chat-widget.compact .message {

    grid-template-columns: 24px max-content 1fr;
    grid-template-rows: 24px auto;
    grid-template-areas:
        "icon user time"
        "text text text";

    column-gap: 7px;
    row-gap: 2px;

}

.chat-widget.compact .message-time {
    display: none;
}

.message-icon {

    grid-area: icon;

    width: var(--message-icon-size);
    height: var(--message-icon-size);

    display: flex;

    align-items: center;

    justify-content: center;

    font-size: calc(var(--message-icon-size) * 0.6);

}

.username {

    grid-area: user;

    font-size: var(--username-size);

    font-weight: 800;

    white-space: nowrap;

    min-width: 0;

    overflow: hidden;

    text-overflow: ellipsis;

}

.message-text {

    grid-area: text;

    min-width: 0;

    overflow-wrap: break-word;

    word-break: break-word;

    white-space: normal;

}

.emote {

    height: 1.6em;
    width: auto;

    vertical-align: middle;

    margin: -2px 1px;

}

.sticker {

    display: block;

    margin-top: 4px;

    max-width: 128px;
    max-height: 128px;

}

.sticker-lottie {

    width: 128px;
    height: 128px;

}

.message-image {

    display: block;

    margin-top: 4px;

    max-width: 100%;
    max-height: 220px;

    border-radius: 6px;

}

.message-time {

    grid-area: time;

    font-size: 13px;

    text-align: right;

    white-space: nowrap;

}

.message-source {

    font-size: 12px;

    opacity: 0.65;

    margin-left: 4px;

}


/* =========================================================
   ANIMATIONS: APPEARANCE (left -> right)
========================================================= */

@keyframes anim-in-slide {

    from {
        opacity: 0;
        transform: translateX(-40px);
    }

    to {
        opacity: 1;
        transform: translateX(0);
    }

}

@keyframes anim-in-fade {

    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }

}

@keyframes anim-in-pop {

    from {
        opacity: 0;
        transform: translateX(-25px) scale(0.85);
    }

    60% {
        opacity: 1;
        transform: translateX(4px) scale(1.03);
    }

    to {
        opacity: 1;
        transform: translateX(0) scale(1);
    }

}

.message.in-slide {
    animation: anim-in-slide 0.4s ease-out both;
}

.message.in-fade {
    animation: anim-in-fade 0.35s ease-out both;
}

.message.in-pop {
    animation: anim-in-pop 0.45s cubic-bezier(.2, .9, .3, 1.2) both;
}


/* =========================================================
   ANIMATIONS: DISAPPEARANCE (downward)
========================================================= */

@keyframes anim-out-fade-down {

    from {
        opacity: 1;
        max-height: 200px;
        transform: translateY(0);
    }

    to {
        opacity: 0;
        max-height: 0;
        transform: translateY(20px);
        margin-top: -7px;
    }

}

@keyframes anim-out-drop {

    from {
        opacity: 1;
        max-height: 200px;
        transform: translateY(0) scale(1);
    }

    to {
        opacity: 0;
        max-height: 0;
        transform: translateY(45px) scale(0.9);
        margin-top: -7px;
    }

}

.message.out-fade-down {
    animation: anim-out-fade-down 0.35s ease-in forwards;
    overflow: hidden;
}

.message.out-drop {
    animation: anim-out-drop 0.4s cubic-bezier(.5, -0.2, .8, .4) forwards;
    overflow: hidden;
}


/* =========================================================
   GOLD PULSE (new message highlight)
========================================================= */

@keyframes gold-pulse {

    0% {
        box-shadow:
            0 0 0 0 rgba(255, 176, 0, 0.9),
            inset 0 0 0 0 rgba(255, 176, 0, 0.5);

        background-color: rgba(255, 176, 0, 0.22);
    }

    100% {
        box-shadow:
            0 0 18px 6px rgba(255, 176, 0, 0),
            inset 0 0 12px 0 rgba(255, 176, 0, 0);

        background-color: transparent;
    }

}

.message.gold-pulse {

    border-radius: 6px;

    animation-name:
        var(--in-anim-name, anim-in-slide),
        gold-pulse;

    animation-duration:
        var(--in-anim-duration, 0.4s),
        1.4s;

    animation-timing-function:
        ease-out,
        ease-out;

    animation-fill-mode:
        both,
        forwards;

}


/* =========================================================
   EVENT BANNERS (bits / sub / gift sub / raid)
========================================================= */

.event-banner {

    position: relative;

    display: flex;

    align-items: center;

    gap: 10px;

    padding: 10px 14px;

    border-radius: 10px;

    overflow: hidden;

    font-size: var(--font-size);

    will-change: transform, opacity;

    animation: anim-in-pop 0.45s cubic-bezier(.2, .9, .3, 1.2) both;

}

/* Цвет каждого эффекта настраивается в settings.html (CONFIG.events.
   <effect>.color) и приходит как inline custom property --event-color
   на самом баннере — фон/рамка/декор ниже читают именно её вместо
   захардкоженного оттенка, чтобы не дублировать 4 набора цветов. */

/* Tier (группа 3 TODO.md) — общая надстройка поверх любого effect-стиля
   выше, а не отдельный набор keyframes на каждый effect×tier: tier 2/3
   лишь усиливают уже существующую анимацию масштабом и доп. свечением,
   цвет/частицы/длительность самого tier настраиваются в CONFIG.eventTiers
   и приходят через --event-color/JS (см. getEventConfig() в chat.js). */
.event-tier-2 {
    box-shadow: 0 0 18px 4px color-mix(in srgb, var(--event-color, #fff) 45%, transparent);
}

.event-tier-3 {
    box-shadow: 0 0 28px 8px color-mix(in srgb, var(--event-color, #fff) 60%, transparent);
    transform: scale(1.06);
}

.event-icon {
    font-size: 1.4em;
    flex-shrink: 0;
}

.event-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.event-title {
    font-size: 11px;
    font-weight: 800;
    letter-spacing: 1.5px;
    opacity: 0.75;
}

.event-user {
    font-weight: 800;
}

.event-text {
    font-size: 0.9em;
    opacity: 0.9;
    overflow-wrap: break-word;
}

/* BITS — резкая "электрическая" вспышка при появлении: рамка на миг
   ярко подсвечивается цветом эффекта и гаснет до обычной интенсивности. */
@keyframes event-bits-flash {

    0% {
        box-shadow: 0 0 0 3px var(--event-color, #50beff);
        border-color: var(--event-color, #50beff);
    }

    100% {
        box-shadow: 0 0 0 0 transparent;
        border-color: color-mix(in srgb, var(--event-color, #50beff) 50%, transparent);
    }

}

.event-bits {
    background: linear-gradient(135deg, color-mix(in srgb, var(--event-color, #50beff) 25%, transparent), color-mix(in srgb, var(--event-color, #50beff) 5%, transparent));
    border: 1px solid color-mix(in srgb, var(--event-color, #50beff) 50%, transparent);
    animation: anim-in-pop 0.45s cubic-bezier(.2, .9, .3, 1.2) both, event-bits-flash 0.6s ease-out both;
}

/* SUB — мягкое дыхание цветом (пульсация рамки), пока баннер на экране,
   вместо статичной рамки как у остальных эффектов. */
@keyframes event-sub-glow {

    0%, 100% {
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--event-color, #9b4dff) 40%, transparent);
    }

    50% {
        box-shadow: 0 0 14px 2px color-mix(in srgb, var(--event-color, #9b4dff) 55%, transparent);
    }

}

.event-sub {
    background: linear-gradient(135deg, color-mix(in srgb, var(--event-color, #9b4dff) 25%, transparent), color-mix(in srgb, var(--event-color, #9b4dff) 5%, transparent));
    border: 1px solid color-mix(in srgb, var(--event-color, #9b4dff) 50%, transparent);
    animation: anim-in-pop 0.45s cubic-bezier(.2, .9, .3, 1.2) both, event-sub-glow 1.8s ease-in-out 0.45s infinite;
}

/* GIFT SUB — декоративная "лента банта" сверху баннера + лёгкое
   покачивание при появлении, отличающее его от обычного sub. */
@keyframes event-giftsub-wiggle {

    0% { transform: rotate(0deg) scale(0.8); opacity: 0; }
    40% { transform: rotate(-3deg) scale(1.05); opacity: 1; }
    70% { transform: rotate(2deg) scale(0.98); }
    100% { transform: rotate(0deg) scale(1); }

}

.event-giftsub {
    background: linear-gradient(135deg, color-mix(in srgb, var(--event-color, #ff69b4) 25%, transparent), color-mix(in srgb, var(--event-color, #ff69b4) 5%, transparent));
    border: 1px solid color-mix(in srgb, var(--event-color, #ff69b4) 50%, transparent);
    animation: event-giftsub-wiggle 0.6s cubic-bezier(.2, .9, .3, 1.2) both;
}

.event-giftsub::before {

    content: "";

    position: absolute;

    top: 0;
    left: 0;
    right: 0;

    height: 4px;

    background: repeating-linear-gradient(
        90deg,
        var(--event-color, #ff69b4) 0 10px,
        transparent 10px 20px
    );

}

/* RAID — широкая "волна" цвета проходит через весь баннер один раз
   при появлении, подчёркивая массовость события. */
@keyframes event-raid-sweep {

    0% { background-position: -150% 0; }
    100% { background-position: 250% 0; }

}

.event-raid {
    background:
        linear-gradient(100deg, transparent 40%, color-mix(in srgb, var(--event-color, #ffb000) 45%, transparent) 50%, transparent 60%),
        linear-gradient(135deg, color-mix(in srgb, var(--event-color, #ffb000) 25%, transparent), color-mix(in srgb, var(--event-color, #ffb000) 5%, transparent));
    background-size: 300% 100%, 100% 100%;
    border: 1px solid color-mix(in srgb, var(--event-color, #ffb000) 50%, transparent);
    animation: anim-in-pop 0.45s cubic-bezier(.2, .9, .3, 1.2) both, event-raid-sweep 0.9s ease-out both;
}

@keyframes fx-particle-burst {

    0% {
        transform: translateY(0) scale(1);
        opacity: 1;
    }

    100% {
        transform: translateY(-40px) scale(0.4);
        opacity: 0;
    }

}

.fx-particle {

    position: absolute;

    bottom: 6px;

    width: 6px;
    height: 6px;

    border-radius: 50%;

    background: #7fd7ff;

    box-shadow: 0 0 6px #7fd7ff;

    pointer-events: none;

    animation: fx-particle-burst 0.9s ease-out forwards;

}

@keyframes fx-heart-float {

    0% {
        transform: translateY(0) scale(0.6);
        opacity: 1;
    }

    100% {
        transform: translateY(-50px) scale(1.1);
        opacity: 0;
    }

}

.fx-heart {

    position: absolute;

    bottom: 4px;

    font-size: 14px;

    pointer-events: none;

    animation: fx-heart-float 1.1s ease-out forwards;

}

.fx-heart::before {
    content: "♥";
    color: #ff6fb0;
}

@keyframes fx-confetti-fall {

    0% {
        transform: translateY(-10px) rotate(0deg);
        opacity: 1;
    }

    100% {
        transform: translateY(60px) rotate(360deg);
        opacity: 0;
    }

}

.fx-confetti {

    position: absolute;

    top: 0;

    width: 6px;
    height: 10px;

    background: hsl(calc(var(--i, 0) * 47deg), 90%, 60%);

    pointer-events: none;

    animation: fx-confetti-fall 1.2s ease-in forwards;

}


/* =========================================================
   EVENT VISUAL STYLES (адаптация examples_effect.py, Event Effect
   Lab) — выбираются полем CONFIG.events.<effect>.visualStyle или
   THEN.visualStyle правила Flow Builder'а, класс event-style-<name>
   вешается ДОПОЛНИТЕЛЬНО к event-<effect> на тот же .event-banner —
   DOM-структура (EVENT_ELEMENT_IDS) не меняется, стиль влияет
   только на анимацию/декор баннера, чтобы drag&drop/Flow Builder
   продолжали работать для любого выбранного стиля.
========================================================= */

/* ARMY ARRIVAL (raid) — резкий "прыжок" появления вместо плавной
   волны event-raid-sweep. */
@keyframes event-style-army-arrival-in {

    0% { transform: scale(.4) rotate(-4deg); opacity: 0; }
    60% { transform: scale(1.08) rotate(1deg); opacity: 1; }
    100% { transform: scale(1) rotate(0); }

}

.event-style-army-arrival {
    animation: event-style-army-arrival-in 0.5s cubic-bezier(.2, .8, .2, 1) both, event-raid-sweep 0.9s ease-out both;
}

/* SEAL (sub) — дублирует круглую рамку-печать вокруг баннера через
   ::before/::after вместо отдельных DOM-узлов из примера. */
@keyframes event-style-seal-spin {
    to { transform: translate(-50%, -50%) rotate(360deg); }
}

.event-style-seal {
    overflow: visible;
}

.event-style-seal::after {

    content: "";

    position: absolute;

    left: 50%;
    top: 50%;

    width: calc(100% + 30px);
    height: calc(100% + 40px);

    transform: translate(-50%, -50%);

    border: 1px dashed color-mix(in srgb, var(--event-color, #9b4dff) 55%, transparent);
    border-radius: 999px;

    pointer-events: none;

    animation: event-style-seal-spin 6s linear infinite;

}

/* GIFT CASCADE (giftsub) — несколько маленьких "подарков" падают
   поверх баннера, повторяя giftFall из examples_effect.py в
   уменьшенном декоративном виде. */
@keyframes event-style-gift-fall {

    0% { transform: translateY(-40px) rotate(var(--r, 0deg)); opacity: 0; }
    40% { opacity: 1; }
    100% { transform: translateY(0) rotate(var(--r, 0deg)); opacity: 1; }

}

.event-style-gift-cascade .event-icon {
    animation: event-style-gift-fall 0.6s cubic-bezier(.2, .8, .2, 1) both;
}

/* GEM BREAK (bits) — иконка "раскалывается" вращением с яркой
   вспышкой в середине анимации. */
@keyframes event-style-gem-break {

    0% { transform: scale(.3) rotate(0deg); opacity: 0; }
    55% { transform: scale(1.2) rotate(320deg); opacity: 1; filter: brightness(2); }
    100% { transform: scale(1) rotate(360deg); filter: brightness(1); }

}

.event-style-gem-break .event-icon {
    animation: event-style-gem-break 0.7s ease-out both;
}

/* SIGNAL (follow) — расходящиеся кольца вокруг иконки + луч-скан
   по всей ширине баннера, из signal/scan-line примера. */
@keyframes event-style-signal-ping {

    0% { transform: scale(.4); opacity: .9; }
    100% { transform: scale(2.2); opacity: 0; }

}

.event-style-signal {
    overflow: visible;
}

.event-style-signal .event-icon {
    position: relative;
}

.event-style-signal .event-icon::before,
.event-style-signal .event-icon::after {

    content: "";

    position: absolute;

    inset: -8px;

    border: 1px solid color-mix(in srgb, var(--event-color, #e0e6f0) 70%, transparent);
    border-radius: 50%;

    animation: event-style-signal-ping 1.4s ease-out infinite;

}

.event-style-signal .event-icon::after {
    animation-delay: .5s;
}

@keyframes event-style-scan-line {
    0% { left: -20%; opacity: 0; }
    10% { opacity: 1; }
    100% { left: 110%; opacity: 0; }
}

.event-style-signal::before {

    content: "";

    position: absolute;

    left: -20%;
    top: 50%;

    width: 40%;
    height: 2px;

    background: color-mix(in srgb, var(--event-color, #e0e6f0) 90%, white);
    box-shadow: 0 0 10px color-mix(in srgb, var(--event-color, #e0e6f0) 90%, white);

    pointer-events: none;

    animation: event-style-scan-line 1.6s ease-in-out infinite;

}

/* GLITCH WORLD (channel points) — дрожание/skew всего баннера +
   хроматическое дублирование текста, из glitch-world примера. */
@keyframes event-style-glitch-shake {

    0%, 100% { transform: none; filter: none; }
    25% { transform: translate(-3px, 1px) skewX(2deg); filter: contrast(1.4); }
    30% { transform: translate(3px, -1px) skewX(-2deg); }
    45% { transform: none; }
    70% { transform: translate(1px, -1px); }

}

.event-style-glitch-world {
    animation: anim-in-pop 0.3s ease both, event-style-glitch-shake 1.2s ease-in-out 0.3s 2;
}

.event-style-glitch-world .event-title,
.event-style-glitch-world .event-user {
    text-shadow: 2px 0 #ff3b6e, -2px 0 #3bfff0;
}

/* OVERLOAD (hype train) — тряска на пике + прогресс-бар, из
   overload/progress примера. event-progress добавляется отдельным
   DOM-узлом только для этого стиля в addEvent() (chat.js). */
@keyframes event-style-overload-shake {

    0%, 60% { transform: none; }
    70% { transform: scale(1.03) rotate(-1deg); }
    80% { transform: scale(.99) rotate(1deg); }
    90% { transform: scale(1.03) rotate(-1deg); }
    100% { transform: none; }

}

.event-style-overload {
    animation: anim-in-pop 0.45s cubic-bezier(.2, .9, .3, 1.2) both, event-style-overload-shake 2.4s ease-in-out 1s;
}

.event-progress {

    display: block;

    width: 100%;

    height: 6px;
    margin-top: 6px;

    border-radius: 6px;

    background: color-mix(in srgb, var(--event-color, #ff5a3c) 20%, #000);

    overflow: hidden;

}

.event-progress-fill {

    display: block;

    width: 0;
    height: 100%;

    background: var(--event-color, #ff5a3c);

    animation: event-style-progress-fill 3s ease forwards;

}

@keyframes event-style-progress-fill {
    to { width: 100%; }
}


/* =========================================================
   MESSAGE EFFECT STYLES (адаптация examples_effect.py, Chat Effect
   Lab) — выбираются THEN.effectStyle правила Flow Builder'а на
   WHEN="message", класс msg-effect-<name> вешается на .message
   дополнительно к in-*/gold-pulse — не меняет MESSAGE_ELEMENT_IDS.
========================================================= */

@keyframes msg-effect-neon-glow {
    from { box-shadow: 0 0 5px #00eaff, 0 0 10px #00eaff; }
    to { box-shadow: 0 0 10px #00eaff, 0 0 25px #00eaff, 0 0 45px #00eaff; }
}

.msg-effect-neon {
    animation: msg-effect-neon-glow 1.5s ease-in-out infinite alternate;
}

@keyframes msg-effect-rgb-shift {
    0% { text-shadow: 3px 0 red, -3px 0 cyan; }
    25% { text-shadow: -3px 0 red, 3px 0 cyan; }
    50% { text-shadow: 4px 0 magenta, -4px 0 lime; }
    75% { text-shadow: -2px 0 blue, 2px 0 red; }
    100% { text-shadow: 3px 0 red, -3px 0 cyan; }
}

.msg-effect-rgb .message-text,
.msg-effect-rgb .username {
    animation: msg-effect-rgb-shift 700ms infinite steps(2);
}

@keyframes msg-effect-glitch-jitter {
    0% { transform: translate(0); clip-path: inset(0); }
    20% { transform: translate(-4px, 2px); clip-path: inset(10% 0 70% 0); }
    40% { transform: translate(4px, -2px); clip-path: inset(50% 0 20% 0); }
    60% { transform: translate(-2px, 0); clip-path: inset(20% 0 50% 0); }
    80% { transform: translate(2px, 1px); clip-path: inset(70% 0 5% 0); }
    100% { transform: translate(0); clip-path: inset(0); }
}

.msg-effect-glitch {
    animation: msg-effect-glitch-jitter 0.5s infinite;
}

@keyframes msg-effect-impact-pop {
    0% { opacity: 0; transform: scale(2.5); filter: blur(8px); }
    60% { opacity: 1; transform: scale(.85); filter: blur(0); }
    80% { transform: scale(1.1); }
    100% { transform: scale(1); }
}

.msg-effect-impact {
    animation: msg-effect-impact-pop .65s cubic-bezier(.2, 1.6, .4, 1);
}

@keyframes msg-effect-burn-in {
    0% { opacity: 0; filter: brightness(3) blur(10px) saturate(3); transform: translateY(20px); box-shadow: 0 -20px 40px rgba(255, 80, 0, .8); }
    50% { opacity: 1; filter: brightness(2) blur(2px) saturate(2); }
    100% { opacity: 1; filter: none; transform: translateY(0); box-shadow: none; }
}

.msg-effect-burn {
    animation: msg-effect-burn-in 1s ease-out;
}

.msg-effect-shine {
    overflow: hidden;
}

@keyframes msg-effect-shine-sweep {
    from { left: -100%; }
    to { left: 150%; }
}

.msg-effect-shine::after {

    content: "";

    position: absolute;

    top: -20%;
    left: -100%;

    width: 50%;
    height: 140%;

    transform: skewX(-25deg);

    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .65), transparent);

    animation: msg-effect-shine-sweep 1.2s ease;

}

@keyframes msg-effect-wave-bob {
    0%, 100% { transform: translateY(0); }
    25% { transform: translateY(-4px); }
    50% { transform: translateY(3px); }
    75% { transform: translateY(-2px); }
}

.msg-effect-wave .message-text {
    display: inline-block;
    animation: msg-effect-wave-bob 1s ease-in-out infinite;
}

@keyframes msg-effect-bounce-drop {
    0% { transform: translateY(-250px); opacity: 0; }
    40% { transform: translateY(20px); opacity: 1; }
    60% { transform: translateY(-12px); }
    80% { transform: translateY(6px); }
    100% { transform: translateY(0); }
}

.msg-effect-bounce {
    animation: msg-effect-bounce-drop .9s cubic-bezier(.28, .84, .42, 1);
}

@keyframes msg-effect-distortion-wobble {
    0% { transform: skewX(0); filter: blur(0); }
    20% { transform: skewX(8deg) translateX(-3px); filter: blur(1px); }
    40% { transform: skewX(-7deg) translateX(3px); filter: blur(2px); }
    60% { transform: skewX(5deg); filter: blur(0); }
    80% { transform: skewX(-4deg); filter: blur(1px); }
    100% { transform: skewX(0); filter: blur(0); }
}

.msg-effect-distortion {
    animation: msg-effect-distortion-wobble .8s infinite;
}

@keyframes msg-effect-particles-burst {
    0% { opacity: 0; transform: translate(-50%, -50%) scale(.2); }
    20% { opacity: 1; }
    100% { opacity: 0; transform: translate(-50%, -120%) scale(1.8); }
}

.msg-effect-particles::before,
.msg-effect-particles::after {

    content: "✦ · ✧ · ✦ · ✧";

    position: absolute;

    left: 50%;
    top: 50%;

    width: 250px;

    pointer-events: none;

    color: #ffffff;
    font-size: 13px;

    opacity: 0;

    animation: msg-effect-particles-burst 1.2s ease-out forwards;

}

.msg-effect-particles::before {
    transform: translate(-50%, -50%);
}

.msg-effect-particles::after {
    transform: translate(-50%, -50%) rotate(180deg);
    animation-delay: .1s;
}


/* =========================================================
   RESPONSIVE
========================================================= */

@media (max-width: 420px) {

    :root {
        --font-size: 15px;
        --username-size: 15px;
    }

}


/* =========================================================
   LAYOUT EDIT MODE (drag&drop позиционирования, settings.html)
========================================================= */

.layout-draggable {

    cursor: move;

    outline: 1px dashed rgba(255, 255, 255, 0.4);
    outline-offset: 2px;

    touch-action: none;

    user-select: none;

}

.layout-draggable:hover {
    outline-color: #ffb000;
}

/* Элемент, скрытый обычной настройкой (напр. showTime=false) —
   в edit-режиме показывается полупрозрачным "призраком", чтобы
   его можно было позиционировать заранее, не включая по-настоящему. */
.layout-ghost {
    position: relative;
    opacity: 0.35;
}

.layout-ghost.layout-ghost-shown {
    opacity: 1;
}

.layout-ghost-toggle {

    position: absolute;

    margin-left: 4px;

    font-size: 12px;

    cursor: pointer;

    user-select: none;

    filter: drop-shadow(0 0 2px #000);

}

/* Ресайз-хендл на самом блоке виджета в edit-режиме (вместо
   слайдеров ширины/высоты в панели настроек). */
.chat-widget.layout-resizable {
    outline: 1px dashed rgba(255, 255, 255, 0.25);
}

.layout-resize-handle {

    position: absolute;

    right: 0;
    bottom: 0;

    width: 18px;
    height: 18px;

    cursor: nwse-resize;

    background:
        linear-gradient(135deg, transparent 0 50%, #ffb000 50% 60%, transparent 60% 70%, #ffb000 70% 80%, transparent 80%);

    z-index: 10;

}
