/* 装饰气泡（QQ 式花边气泡）。两端各一份、内容完全相同，改一边要同步另一边。
 *
 * 每个人在「···」→「气泡样式」里选一款，自己发的文字消息在两端都按这一款显示（存服务器，改了实时同步）。
 * 可选 id 由服务端白名单限定（internal/server/profile.go 的 bubbleStyles，单测会核对这里的 .bs-xxx 一一对应）；
 * 新增一款：两端 bubbles.css 加样式、两端 app.js 的 BUBBLE_STYLES 加名字、服务端白名单加 id。
 *
 * 装饰款不画小尖角（::before），改在角落贴一个小图案（::after，内联 SVG）；连续几条只在第一条上贴。
 * 每款自带文字颜色，深色模式下不变色，保证两端、两种外观看到的一样。 */
.bubble[class*=" bs-"],
.row.mine .bubble[class*=" bs-"] { color: var(--bs-text); background: var(--bs-bg); border: 2px solid var(--bs-line); }
.bubble[class*=" bs-"]::before { display: none; }
.bubble[class*=" bs-"]::after {
    content: "";
    position: absolute;
    top: -12px;
    right: -10px;
    width: 26px;
    height: 26px;
    background: var(--bs-deco) center / contain no-repeat;
    pointer-events: none;
}
.row.mine .bubble[class*=" bs-"]::after { right: auto; left: -10px; }
.row.grouped .bubble[class*=" bs-"]::after { display: none; }
/* 角标动画：只在实时到达的消息上播 3 圈就停（历史消息不动）。.deco-play 由 app.js 加；
   整体重绘时 --bs-delay 为负值，接着已经播到的进度往下播，不会每来一条消息就从头再播。 */
.bubble.deco-play[class*=" bs-"]::after {
    animation: var(--bs-anim);
    animation-iteration-count: 3;
    animation-delay: var(--bs-delay, 0ms);
}
@keyframes bs-spin { to { transform: rotate(360deg); } }
@keyframes bs-bounce { 50% { transform: translateY(-5px); } }
@keyframes bs-twinkle { 50% { transform: scale(.6) rotate(20deg); opacity: .5; } }
@keyframes bs-drift { 50% { transform: translateX(5px); } }
@keyframes bs-beat { 50% { transform: scale(1.3); } }
@keyframes bs-sway { 25% { transform: rotate(-15deg); } 75% { transform: rotate(15deg); } }
@media (prefers-reduced-motion: reduce) {
    .bubble.deco-play[class*=" bs-"]::after { animation: none; }
}
/* 气泡里的引用块用半透明底，跟着各款的底色走 */
.bubble[class*=" bs-"] .reply-quote { color: inherit; background: rgba(255, 255, 255, .45); }
.bubble.bs-star .reply-quote,
.bubble.bs-love .reply-quote { background: rgba(0, 0, 0, .18); }

.bubble.bs-sakura {
    --bs-anim: bs-spin .8s linear;
    --bs-bg: linear-gradient(135deg, #fff0f5, #ffd9e6); --bs-line: #ffb3cb; --bs-text: #6b2440;
    --bs-deco: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-12 -12 24 24'%3E%3Cg fill='%23ff8fb3' stroke='%23c2185b' stroke-width='.8'%3E%3Cellipse cx='0' cy='-6' rx='3.6' ry='5.4'/%3E%3Cellipse cx='0' cy='-6' rx='3.6' ry='5.4' transform='rotate(72)'/%3E%3Cellipse cx='0' cy='-6' rx='3.6' ry='5.4' transform='rotate(144)'/%3E%3Cellipse cx='0' cy='-6' rx='3.6' ry='5.4' transform='rotate(216)'/%3E%3Cellipse cx='0' cy='-6' rx='3.6' ry='5.4' transform='rotate(288)'/%3E%3C/g%3E%3Ccircle r='2.6' fill='%23ffd84d'/%3E%3C/svg%3E");
}
.bubble.bs-paw {
    --bs-anim: bs-bounce .8s ease-in-out;
    border-style: dashed !important;
    --bs-bg: #fff7e8; --bs-line: #e3b47a; --bs-text: #5a3a12;
    --bs-deco: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='%23c98a4b'%3E%3Cellipse cx='12' cy='15.5' rx='5.5' ry='4.5'/%3E%3Cellipse cx='5' cy='9' rx='2.3' ry='2.9'/%3E%3Cellipse cx='9.5' cy='5' rx='2.3' ry='2.9'/%3E%3Cellipse cx='14.5' cy='5' rx='2.3' ry='2.9'/%3E%3Cellipse cx='19' cy='9' rx='2.3' ry='2.9'/%3E%3C/g%3E%3C/svg%3E");
}
.bubble.bs-star {
    --bs-anim: bs-twinkle .8s ease-in-out;
    --bs-bg: linear-gradient(135deg, #2b2f6b, #4a3b8f); --bs-line: #8c7cf0; --bs-text: #f3f1ff;
    --bs-deco: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-12 -12 24 24'%3E%3Cpath d='M0-11 3-3 11-3 4.5 2 7 10 0 5-7 10-4.5 2-11-3-3-3Z' fill='%23ffd84d' stroke='%23b8860b' stroke-width='.8'/%3E%3C/svg%3E");
}
.bubble.bs-cloud {
    --bs-anim: bs-drift .8s ease-in-out;
    --bs-bg: linear-gradient(180deg, #eaf6ff, #cfe9ff); --bs-line: #ffffff; --bs-text: #1d4466;
    box-shadow: 0 0 0 2px #9ccdf5 !important;
    --bs-deco: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 20'%3E%3Cpath d='M7 18a5 5 0 0 1-.6-10A6.5 6.5 0 0 1 19 6a5.5 5.5 0 0 1 2 11.9Z' fill='%23fff' stroke='%2364a8e0' stroke-width='1.4'/%3E%3C/svg%3E");
}
.bubble.bs-lemon {
    --bs-anim: bs-spin .8s linear;
    --bs-bg: linear-gradient(135deg, #fffbe0, #fff1a8); --bs-line: #f2d24b; --bs-text: #5c4a00;
    --bs-deco: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-12 -12 24 24'%3E%3Ccircle r='10.5' fill='%23fff3a0' stroke='%23e0b400' stroke-width='1.6'/%3E%3Cg stroke='%23e0b400' stroke-width='1.2'%3E%3Cpath d='M0 0V-8M0 0 7 -4M0 0 7 4M0 0V8M0 0-7 4M0 0-7-4'/%3E%3C/g%3E%3C/svg%3E");
}
.bubble.bs-love {
    --bs-anim: bs-beat .8s ease-in-out;
    --bs-bg: linear-gradient(135deg, #ff9db8, #ff6b8b); --bs-line: #e94f76; --bs-text: #ffffff;
    --bs-deco: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-12 -12 24 24'%3E%3Cpath d='M0 9C-12 1-8-10 0-4 8-10 12 1 0 9Z' fill='%23ff3d6e' stroke='%23fff' stroke-width='1.6'/%3E%3C/svg%3E");
}
.bubble.bs-mint {
    --bs-anim: bs-sway .8s ease-in-out;
    --bs-bg: linear-gradient(135deg, #e9fbf1, #c4f0d8); --bs-line: #7fd4a6; --bs-text: #134a2e;
    --bs-deco: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-12 -12 24 24'%3E%3Cpath d='M-9 9C-9-4-1-10 9-9 10 1 4 9-9 9Z' fill='%2366cc8a' stroke='%23237a47' stroke-width='1'/%3E%3Cpath d='M-9 9 5-5' stroke='%23237a47' stroke-width='1'/%3E%3C/svg%3E");
}

/* 选择面板 */
/* 款式多了以后弹窗会比手机屏幕还高、「完成」按钮被挤出屏幕：弹窗限高，中间的款式列表自己滚动 */
.bubble-card { max-width: 420px; max-height: calc(100dvh - 40px); display: flex; flex-direction: column; }
.bubble-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 14px 10px; margin: 6px 0 14px; min-height: 0; overflow-y: auto; padding: 14px 12px 16px; }
.bubble-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 16px 6px 8px;
    font: inherit;
    color: var(--text);
    background: var(--panel);
    border: 2px solid transparent;
    border-radius: 10px;
    cursor: pointer;
}
.bubble-option.selected { border-color: var(--accent); }
/* 性别（2026-10-24）：三个选项一排，只有字 */
.gender-grid { grid-template-columns: repeat(3, 1fr); }
.gender-option { padding: 12px 6px; font-size: 15px; }
.bubble-option .bubble { max-width: 100%; font-size: 14px; white-space: nowrap; }
.bubble-option .bubble:not([class*=" bs-"]) { color: var(--bubble-mine-text); background: var(--bubble-mine); }
.bubble-option .bubble::before { display: none; }
.bubble-option .bubble[class*=" bs-"]::after { right: auto; left: -10px; }
.bubble-option-name { font-size: 13px; }
.bubble-option.selected .bubble-option-name { color: var(--accent); font-weight: 600; }

/* 贪吃蛇（小动物气泡，2026-10-05 用户从 deploy/previews/puppy-bubbles/design-preview.html 里挑中上线）：气泡就是蛇身，字越多身子越长，
 * 蛇头（::before）、尾巴（::after）挂在两端。头朝向对方：对方的消息头在右、尾在左；自己的消息左右对调，
 * 并用独立的 scale 属性镜像（动画只改 rotate，两者互不覆盖）。连续几条每条都有头尾，不像角标款只贴第一条。
 * 新消息蛇头点头、尾巴摆 3 下就停（与角标款同一套 deco-play 机制）；背景图里的 SVG 不能控制圈数，所以信子是静止的。 */
.bubble.bs-snake {
    --bs-anim: bs-snake-tail .8s ease-in-out;
    --bs-bg: radial-gradient(circle, rgba(35, 110, 40, .22) 0 2.5px, transparent 3.5px) 0 0 / 13px 13px, linear-gradient(#93e08c, #6cc96c);
    --bs-line: #2f7d32; --bs-text: #123d14;
    border-radius: 18px;
    margin: 0 44px 0 26px;
}
.row.mine .bubble.bs-snake { margin: 0 26px 0 44px; }
.bubble.bs-snake::before, .bubble.bs-snake::after {
    /* 普通气泡的小尖角也是 ::before，自带 transform: rotate(45deg) 和背景色，这里必须清掉 */
    transform: none; box-shadow: none;
    content: ""; display: block; position: absolute; top: 50%; translate: 0 -50%;
    border: 0; background: center / contain no-repeat; pointer-events: none;
}
.bubble.bs-snake::before {
    left: auto; right: -47px; width: 52px; height: 37px; transform-origin: 6% 50%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 54 38'%3E%3Cpath d='M40 22 h8 l4 -4 M48 22 l4 4' fill='none' stroke='%23e53935' stroke-width='2.2' stroke-linecap='round'/%3E%3Cpath d='M0 9 C 14 1, 36 1, 42 15 C 46 25, 36 35, 20 35 C 10 35, 2 31, 0 27 Z' fill='%237fd47f' stroke='%232f7d32' stroke-width='2'/%3E%3Ccircle cx='28' cy='14' r='5.5' fill='%23fff' stroke='%232f7d32' stroke-width='1.5'/%3E%3Ccircle cx='30' cy='14' r='2.8' fill='%231b1b1b'/%3E%3Ccircle cx='38' cy='22' r='1.2' fill='%232f7d32'/%3E%3Cellipse cx='26' cy='27' rx='4' ry='2' fill='%23ff9db2' opacity='.7'/%3E%3C/svg%3E");
}
.bubble.bs-snake::after {
    right: auto; left: -27px; width: 30px; height: 22px; transform-origin: 100% 50%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 22'%3E%3Cpath d='M30 2 C 20 3, 8 8, 0 12 C 9 14, 20 19, 30 20 Z' fill='%237fd47f' stroke='%232f7d32' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* 镜像（scale）和点头 / 摆尾（rotate）共用 transform-origin：镜像一侧必须以中心为原点，否则整张图会被甩出去 */
.row.mine .bubble.bs-snake::before { right: auto; left: -47px; scale: -1 1; transform-origin: 50% 50%; }
.row.mine .bubble.bs-snake::after { left: auto; right: -27px; scale: -1 1; transform-origin: 50% 50%; }
/* 聊天样式里连续消息会隐藏小尖角（::before）和角标（::after），蛇的头尾要每条都有 */
.row.grouped .bubble.bs-snake::before, .row.grouped .bubble.bs-snake::after { display: block; }
.bubble.deco-play.bs-snake::before { animation: bs-snake-nod .8s ease-in-out 3 var(--bs-delay, 0ms); }
@keyframes bs-snake-tail { 50% { rotate: -14deg; } }
@keyframes bs-snake-nod { 25% { rotate: -6deg; } 75% { rotate: 5deg; } }
@media (prefers-reduced-motion: reduce) {
    .bubble.deco-play.bs-snake::before { animation: none; }
}

/* 乖乖（小动物气泡里的小狗，2026-10-05 按用户给的参考图画成黑色毛茸茸小狗并改名上线）：
 * 浅奶杏色身子、深棕字（最初黑身子白字，后来浅灰带斜纹，用户都觉得影响看字）；头（::before）是毛乱乱的灰脑袋、
 * 深灰耳朵、奶白嘴套和眉毛、眯眼，头顶粉色蝴蝶结（2026-10-05 用户要求），灰尾巴（::after）挂在另一头。
 * 摆放、镜像、连续消息都显示头尾的做法与贪吃蛇相同（见上）；预览里的小爪子没有第三个伪元素可用，线上不画。
 * 新消息尾巴快速摇 6 下就停（2026-10-18 用户两次要求缩短，整体压到 2.5 秒内）。
 * 眉毛跳动（2026-10-18 用户要求，试过眨眼后改为只动眉毛、眼睛不变）：头像是两帧并排的雪碧图（左帧原样、右帧眉毛下压 1.6），
 * 背景宽 200%，播动画时用 steps() 切 background-position 到右帧 0.18 秒再切回来，每 .8 秒跳一次、跳 3 次；
 * 背景图没法改里面的路径，只能这样换帧，也不依赖 SVG 内部动画。镜像（scale: -1 1）作用在元素上，帧位置不受影响。 */
.bubble.bs-guaiguai {
    --bs-anim: bs-guai-wag .35s ease-in-out;
    /* 2026-10-05 用户：狗脸改灰、气泡框换别的颜色，灰底斜纹影响看字——身子改干净的浅奶杏色、深棕字，不要纹理 */
    --bs-bg: linear-gradient(#fffaf3, #ffeedd);
    --bs-line: #5a4a3f; --bs-text: #3a2a1f;
    border-radius: 18px;
    margin: 0 40px 0 22px;
}
.row.mine .bubble.bs-guaiguai { margin: 0 22px 0 40px; }
.bubble.bs-guaiguai::before, .bubble.bs-guaiguai::after {
    /* 普通气泡的小尖角也是 ::before，自带 transform: rotate(45deg) 和背景色，这里必须清掉 */
    transform: none; box-shadow: none;
    content: ""; display: block; position: absolute; translate: 0 -50%;
    border: 0; background: center / contain no-repeat; pointer-events: none;
}
.bubble.bs-guaiguai::before {
    top: 50%; left: auto; right: -38px; width: 48px; height: 46px;
    background-size: 200% 100%; background-position: 0 0;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 46'%3E%3Cdefs%3E%3Cg id='h'%3E%3Cg fill='%235c5c5c' stroke='%23333' stroke-width='2' stroke-linejoin='round'%3E%3Cpath d='M11 13 C 2 15, 0 31, 6 37 C 11 34, 13 23, 15 15 Z'/%3E%3Cpath d='M37 13 C 46 15, 48 31, 42 37 C 37 34, 35 23, 33 15 Z'/%3E%3C/g%3E%3Cpath d='M5 37 q2 -6 3 -12 M43 37 q-2 -6 -3 -12' stroke='%233f3f3f' stroke-width='1.1' fill='none' stroke-linecap='round'/%3E%3Cpath d='M9 22 l2 -6 3 3 2 -7 3 4 3 -6 3 5 3 -6 3 4 2 -2 2 6 3 2 C 42 31, 37 41, 24 41 C 11 41, 7 31, 9 22 Z' fill='%238c8c8c' stroke='%23333' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='M18 14 l2 5 M24 11 l.8 6 M30 13 l-1.5 5' stroke='%23dcdcdc' stroke-width='1.3' stroke-linecap='round'/%3E%3Cellipse cx='24' cy='33.5' rx='9.5' ry='6.5' fill='%23dadada'/%3E%3Cellipse cx='24' cy='35' rx='6.5' ry='4.2' fill='%23f6f6f6'/%3E%3Cpath d='M15 31 l-2 1.5 M33 31 l2 1.5 M16 35 l-2.5 .8 M32 35 l2.5 .8' stroke='%238a8a8a' stroke-width='1' stroke-linecap='round'/%3E%3Cellipse cx='24' cy='30.2' rx='4.4' ry='3.1' fill='%23111'/%3E%3Ccircle cx='22.6' cy='29.2' r='1' fill='%237a7a7a'/%3E%3Cpath d='M24 33.2 v1.8 M24 35 q-2.4 1.8 -4.4 0 M24 35 q2.4 1.8 4.4 0' fill='none' stroke='%23333' stroke-width='1.3' stroke-linecap='round'/%3E%3Cg transform='translate(24 9)' stroke='%23c2185b' stroke-width='1.2' stroke-linejoin='round'%3E%3Cpath d='M0 0 L-9 -5 Q-12 0 -9 5 Z' fill='%23ff8fb3'/%3E%3Cpath d='M0 0 L9 -5 Q12 0 9 5 Z' fill='%23ff8fb3'/%3E%3Ccircle r='2.6' fill='%23ff6b9a'/%3E%3C/g%3E%3C/g%3E%3C/defs%3E%3Cuse href='%23h'/%3E%3Cpath d='M13 21.5 q4 -3.2 8 0 M27 21.5 q4 -3.2 8 0' fill='none' stroke='%23f0f0f0' stroke-width='2.2' stroke-linecap='round'/%3E%3Cpath d='M14 25.5 q3.5 2.4 7 0 M27 25.5 q3.5 2.4 7 0' fill='none' stroke='%23333' stroke-width='1.8' stroke-linecap='round'/%3E%3Cg transform='translate(48)'%3E%3Cuse href='%23h'/%3E%3Cpath d='M13 23.1 q4 -3.2 8 0 M27 23.1 q4 -3.2 8 0' fill='none' stroke='%23f0f0f0' stroke-width='2.2' stroke-linecap='round'/%3E%3Cpath d='M14 25.5 q3.5 2.4 7 0 M27 25.5 q3.5 2.4 7 0' fill='none' stroke='%23333' stroke-width='1.8' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E");
}
.bubble.bs-guaiguai::after {
    top: 35%; right: auto; left: -20px; width: 24px; height: 26px; transform-origin: 100% 90%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 26'%3E%3Cpath d='M24 22 C 14 22, 6 16, 4 4' fill='none' stroke='%23333' stroke-width='8' stroke-linecap='round'/%3E%3Cpath d='M24 22 C 14 22, 6 16, 4 4' fill='none' stroke='%238c8c8c' stroke-width='4.5' stroke-linecap='round'/%3E%3Cpath d='M3 2 l-2 -1 M5 1.5 l0 -2 M6 3 l2 -1' stroke='%23333' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E");
}
.row.mine .bubble.bs-guaiguai::before { right: auto; left: -38px; scale: -1 1; transform-origin: 50% 50%; }
.row.mine .bubble.bs-guaiguai::after { left: auto; right: -20px; scale: -1 1; transform-origin: 50% 90%; }
.row.grouped .bubble.bs-guaiguai::before, .row.grouped .bubble.bs-guaiguai::after { display: block; }
.bubble.deco-play.bs-guaiguai::after { animation-iteration-count: 6; }
/* 播动画时眉毛跳 3 次：每轮 .8 秒，前 87% 原样、最后 0.1 秒切到眉毛下压帧 */
.bubble.deco-play.bs-guaiguai::before { animation: bs-guai-brow .8s steps(1, end) 3 var(--bs-delay, 0ms); }
@keyframes bs-guai-brow { 0%, 86% { background-position: 0 0; } 87%, 100% { background-position: 100% 0; } }
.bubble.bs-guaiguai .reply-quote { background: rgba(90, 74, 63, .08); }
@keyframes bs-guai-wag { 0%, 100% { rotate: -12deg; } 50% { rotate: 22deg; } }
@media (prefers-reduced-motion: reduce) {
    .bubble.deco-play.bs-guaiguai::before { animation: none; }
}

/* 柴柴（2026-10-05 用户给的柴犬参考图）：橙黄脸、奶白嘴边，眯眼笑，眉毛两个奶白小点，黑鼻头 ω 嘴；
 * 身子上橙下白、深棕字，卷尾巴。头尾做法同贪吃蛇 / 乖乖（见上）。新消息尾巴晃 3 下。 */
.bubble.bs-shiba {
    --bs-anim: bs-shiba-tail .6s ease-in-out;
    --bs-bg: linear-gradient(#f6c48a 0, #f9d9b0 45%, #fff4e3 60%, #fff4e3);
    --bs-line: #2b1d14; --bs-text: #4a2a10;
    border-radius: 18px;
    margin: 0 42px 0 24px;
}
.row.mine .bubble.bs-shiba { margin: 0 24px 0 42px; }
.bubble.bs-shiba::before, .bubble.bs-shiba::after {
    /* 普通气泡的小尖角也是 ::before，自带 transform: rotate(45deg) 和背景色，这里必须清掉 */
    transform: none; box-shadow: none;
    content: ""; display: block; position: absolute; translate: 0 -50%;
    border: 0; background: center / contain no-repeat; pointer-events: none;
}
.bubble.bs-shiba::before {
    top: 50%; left: auto; right: -40px; width: 50px; height: 48px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 48'%3E%3Cpath d='M8 20 L10 3 L22 12 Z' fill='%23e8a15a' stroke='%232b1d14' stroke-width='2.2' stroke-linejoin='round'/%3E%3Cpath d='M42 20 L40 3 L28 12 Z' fill='%23e8a15a' stroke='%232b1d14' stroke-width='2.2' stroke-linejoin='round'/%3E%3Cpath d='M11.5 15 L12.5 7 L18 11.5 Z M38.5 15 L37.5 7 L32 11.5 Z' fill='%23fff4e3'/%3E%3Cpath d='M25 9 C 40 9, 46 19, 46 28 C 46 39, 37 45, 25 45 C 13 45, 4 39, 4 28 C 4 19, 10 9, 25 9 Z' fill='%23e8a15a' stroke='%232b1d14' stroke-width='2.2'/%3E%3Cpath d='M8 31 C 10 24, 18 24, 25 27 C 32 24, 40 24, 42 31 C 42 39, 35 44, 25 44 C 15 44, 8 39, 8 31 Z' fill='%23fff4e3'/%3E%3Cellipse cx='16' cy='18.5' rx='2.6' ry='1.6' fill='%23fff4e3'/%3E%3Cellipse cx='34' cy='18.5' rx='2.6' ry='1.6' fill='%23fff4e3'/%3E%3Cpath d='M12 24 q4 -3 8 0 M30 24 q4 -3 8 0' fill='none' stroke='%232b1d14' stroke-width='2.2' stroke-linecap='round'/%3E%3Cellipse cx='25' cy='29' rx='3.4' ry='2.5' fill='%232b1d14'/%3E%3Cpath d='M25 31.5 v1.5 M19.5 33 q2.8 3.2 5.5 0 q2.7 3.2 5.5 0' fill='none' stroke='%232b1d14' stroke-width='1.8' stroke-linecap='round'/%3E%3Ccircle cx='17.5' cy='32' r='.7' fill='%232b1d14'/%3E%3Ccircle cx='16.5' cy='34' r='.7' fill='%232b1d14'/%3E%3Ccircle cx='32.5' cy='32' r='.7' fill='%232b1d14'/%3E%3Ccircle cx='33.5' cy='34' r='.7' fill='%232b1d14'/%3E%3C/svg%3E");
}
.bubble.bs-shiba::after {
    top: 40%; right: auto; left: -23px; width: 28px; height: 30px; transform-origin: 100% 80%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 30'%3E%3Cpath d='M28 24 C 16 26, 6 20, 6 12 C 6 5, 14 2, 18 7 C 21 11, 16 15, 13 12' fill='none' stroke='%232b1d14' stroke-width='9' stroke-linecap='round'/%3E%3Cpath d='M28 24 C 16 26, 6 20, 6 12 C 6 5, 14 2, 18 7 C 21 11, 16 15, 13 12' fill='none' stroke='%23e8a15a' stroke-width='5.5' stroke-linecap='round'/%3E%3Cpath d='M17 8 C 19 11, 16 14, 13.5 12' fill='none' stroke='%23fff4e3' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E");
}
.row.mine .bubble.bs-shiba::before { right: auto; left: -40px; scale: -1 1; transform-origin: 50% 50%; }
.row.mine .bubble.bs-shiba::after { left: auto; right: -23px; scale: -1 1; transform-origin: 50% 80%; }
.row.grouped .bubble.bs-shiba::before, .row.grouped .bubble.bs-shiba::after { display: block; }
@keyframes bs-shiba-tail { 50% { rotate: 12deg; } }

/* 长消息的中间图案（2026-10-05 用户要求）：消息超过一定字数时两端给气泡加 .bs-long（app.js：动物款 ANIMAL_LONG_CHARS=10，其余 LONG_BUBBLE_CHARS=12），
 * 小动物气泡多画一样东西——乖乖脖子上骑一只小香猪（画进头像里，见上）、柴柴戴红项圈和铃铛（挂在脖子上，即紧挨着头的那一端，并在这一端
 * 多留出位置不压字；用户反馈放在身子正中很怪）、贪吃蛇身上三道黄环（身上的花纹，留在正中，颜色偏浅）。
 * 每条都要带上 .row.mine 的写法：基础规则 .row.mine .bubble[class*=" bs-"] 的优先级更高，会把自己发的气泡背景盖回去。 */
/* 长消息：小香猪骑在乖乖脖子上（2026-10-05 用户要求，替换原来的粉围脖）。小猪是两端在长消息里多放的 <span class="bs-rider">，
   固定在气泡顶边、紧挨头的一端，身子坐在边框上、两条腿跨在边沿两侧；上方多留 16px 给它，不压字 */
.bs-rider { display: none; }
.bubble.bs-guaiguai.bs-long .bs-rider {
    display: block; position: absolute; top: -21px; right: 4px; width: 30px; height: 32px; pointer-events: none;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-15 -17 30 32'%3E%3Cpath d='M-8 6 v7 M8 6 v7' stroke='%23c96b84' stroke-width='3.2' stroke-linecap='round'/%3E%3Cellipse cx='0' cy='5' rx='11' ry='7' fill='%23ffc3cf' stroke='%23c96b84' stroke-width='1.4'/%3E%3Cpath d='M-9 -10 l-4 -6 7 2 Z M9 -10 l4 -6 -7 2 Z' fill='%23ffb3c4' stroke='%23c96b84' stroke-width='1.2' stroke-linejoin='round'/%3E%3Ccircle cx='0' cy='-5' r='9.5' fill='%23ffd0da' stroke='%23c96b84' stroke-width='1.4'/%3E%3Cellipse cx='0' cy='-2.5' rx='4.4' ry='3.2' fill='%23ff9db2' stroke='%23c96b84' stroke-width='1'/%3E%3Ccircle cx='-1.5' cy='-2.5' r='.9' fill='%23a14d63'/%3E%3Ccircle cx='1.5' cy='-2.5' r='.9' fill='%23a14d63'/%3E%3Ccircle cx='-3.8' cy='-8' r='1.2' fill='%233a2a2a'/%3E%3Ccircle cx='3.8' cy='-8' r='1.2' fill='%233a2a2a'/%3E%3Cellipse cx='-6.5' cy='-3.5' rx='1.9' ry='1.1' fill='%23ff8fa8' opacity='.8'/%3E%3Cellipse cx='6.5' cy='-3.5' rx='1.9' ry='1.1' fill='%23ff8fa8' opacity='.8'/%3E%3Cpath d='M-5 3 q-3 3 -5 2 M5 3 q3 3 5 2' fill='none' stroke='%23c96b84' stroke-width='2.2' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.row.mine .bubble.bs-guaiguai.bs-long .bs-rider { right: auto; left: 4px; scale: -1 1; }
.bubble.bs-guaiguai.bs-long, .row.mine .bubble.bs-guaiguai.bs-long { margin-top: 16px; }
/* 播动画时小香猪以屁股为轴顺时针、逆时针来回摇（2026-10-18 用户要求，不要上下晃），每轮 .7 秒、3 轮，与腿的踏步差不多长；
   只动 rotate，不碰镜像用的 scale */
.bubble.deco-play.bs-guaiguai.bs-long .bs-rider { transform-origin: 50% 90%; animation: bs-rider-rock .7s ease-in-out 3 var(--bs-delay, 0ms); }
@keyframes bs-rider-rock { 25% { rotate: 12deg; } 75% { rotate: -12deg; } }
@media (prefers-reduced-motion: reduce) {
    .bubble.deco-play.bs-guaiguai.bs-long .bs-rider { animation: none; }
}
.bubble.bs-shiba.bs-long {
    /* 挂在脖子上：紧挨着头的那一端（对方的消息在右端），并在这一端多留出位置，不压字 */
    padding-right: 36px;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='8.5' fill='%23f5b323' stroke='%232b1d14' stroke-width='1.6'/%3E%3Cpath d='M3 9.5 h14' stroke='%232b1d14' stroke-width='1.2'/%3E%3Ccircle cx='10' cy='13.5' r='1.6' fill='%232b1d14'/%3E%3Cpath d='M10 13.5 v4' stroke='%232b1d14' stroke-width='1.4'/%3E%3Cellipse cx='7' cy='6' rx='2' ry='1.2' fill='%23fff' opacity='.7'/%3E%3C/svg%3E") calc(100% - 10px) calc(100% - 1px) / 14px 14px no-repeat,
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 40' preserveAspectRatio='none'%3E%3Cg opacity='.55'%3E%3Crect width='18' height='40' fill='%23ef5350'/%3E%3Crect width='1.6' height='40' fill='%23b71c1c'/%3E%3Crect x='16.4' width='1.6' height='40' fill='%23b71c1c'/%3E%3C/g%3E%3C/svg%3E") calc(100% - 10px) 0 / 14px 100% no-repeat, var(--bs-bg);
}
.row.mine .bubble.bs-shiba.bs-long {
    padding-right: 13px; padding-left: 36px;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='8.5' fill='%23f5b323' stroke='%232b1d14' stroke-width='1.6'/%3E%3Cpath d='M3 9.5 h14' stroke='%232b1d14' stroke-width='1.2'/%3E%3Ccircle cx='10' cy='13.5' r='1.6' fill='%232b1d14'/%3E%3Cpath d='M10 13.5 v4' stroke='%232b1d14' stroke-width='1.4'/%3E%3Cellipse cx='7' cy='6' rx='2' ry='1.2' fill='%23fff' opacity='.7'/%3E%3C/svg%3E") 10px calc(100% - 1px) / 14px 14px no-repeat,
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 40' preserveAspectRatio='none'%3E%3Cg opacity='.55'%3E%3Crect width='18' height='40' fill='%23ef5350'/%3E%3Crect width='1.6' height='40' fill='%23b71c1c'/%3E%3Crect x='16.4' width='1.6' height='40' fill='%23b71c1c'/%3E%3C/g%3E%3C/svg%3E") 10px 0 / 14px 100% no-repeat, var(--bs-bg);
}
.bubble.bs-snake.bs-long,
.row.mine .bubble.bs-snake.bs-long {
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' preserveAspectRatio='none'%3E%3Cg fill='%23ffd84d' stroke='%23c9a227' stroke-width='1'%3E%3Crect x='2' width='7' height='40'/%3E%3Crect x='16.5' width='7' height='40'/%3E%3Crect x='31' width='7' height='40'/%3E%3C/g%3E%3C/svg%3E") 50% 0 / 40px 100% no-repeat, var(--bs-bg);
}

/* 四肢（2026-10-05 用户：「动物气泡可以增加四肢吗，尝试一下」）：头尾已经用掉 ::before / ::after，
 * 两端在乖乖、柴柴的气泡里多放一个 <span class="bs-limbs">（app.js 的 ANIMAL_LIMBS），它的 ::before 是靠头一端的两条前腿、
 * ::after 是靠尾巴一端的两条后腿，挂在气泡底边下面；气泡底部多留 10px 外边距给腿。新消息四条腿交替踏步几下。
 * 贪吃蛇没有腿。 */
.bs-limbs { display: none; }
.bubble.bs-guaiguai .bs-limbs, .bubble.bs-shiba .bs-limbs {
    display: block; position: absolute; left: 12px; right: 12px; bottom: -13px; height: 15px; pointer-events: none;
}
.bubble.bs-guaiguai .bs-limbs::before, .bubble.bs-guaiguai .bs-limbs::after,
.bubble.bs-shiba .bs-limbs::before, .bubble.bs-shiba .bs-limbs::after {
    content: ""; position: absolute; bottom: 0; width: 28px; height: 15px; background: center / contain no-repeat;
}
.bubble.bs-guaiguai .bs-limbs::before, .bubble.bs-guaiguai .bs-limbs::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-1 -1 30 18'%3E%3Cpath d='M1 0 v9 a5 5 0 0 0 10 0 v-9' fill='%238c8c8c' stroke='%23333' stroke-width='2' stroke-linejoin='round'/%3E%3Cellipse cx='6' cy='11' rx='3' ry='1.6' fill='%235c5c5c'/%3E%3Cpath d='M16 0 v9 a5 5 0 0 0 10 0 v-9' fill='%238c8c8c' stroke='%23333' stroke-width='2' stroke-linejoin='round'/%3E%3Cellipse cx='21' cy='11' rx='3' ry='1.6' fill='%235c5c5c'/%3E%3C/svg%3E"); }
.bubble.bs-shiba .bs-limbs::before, .bubble.bs-shiba .bs-limbs::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-1 -1 30 18'%3E%3Cpath d='M1 0 v9 a5 5 0 0 0 10 0 v-9' fill='%23fff4e3' stroke='%232b1d14' stroke-width='2' stroke-linejoin='round'/%3E%3Cellipse cx='6' cy='11' rx='3' ry='1.6' fill='%23e8a15a'/%3E%3Cpath d='M16 0 v9 a5 5 0 0 0 10 0 v-9' fill='%23fff4e3' stroke='%232b1d14' stroke-width='2' stroke-linejoin='round'/%3E%3Cellipse cx='21' cy='11' rx='3' ry='1.6' fill='%23e8a15a'/%3E%3C/svg%3E"); }
/* 前腿在头那一端：对方的消息头在右，自己的在左 */
.bs-limbs::before { right: 2px; }
.bs-limbs::after { left: 2px; }
.row.mine .bs-limbs::before { right: auto; left: 2px; }
.row.mine .bs-limbs::after { left: auto; right: 2px; }
.bubble.bs-guaiguai, .row.mine .bubble.bs-guaiguai, .bubble.bs-shiba, .row.mine .bubble.bs-shiba { margin-bottom: 12px; min-width: 104px; }
/* min-width：只有一两个字时前后腿会挤成一排。104px 含左右 2px 边框，腿区 = 104 - 4 - 24 = 76px，前后腿各 28px、离端 2px，中间留 16px 肚子 */
/* 回应徽标默认往上压进气泡底边 8px，有腿的会压到腿上：改挂在腿下面 */
.bubble.bs-guaiguai ~ .reactions, .bubble.bs-shiba ~ .reactions { margin-top: 0; }
.bubble.deco-play .bs-limbs::before { animation: bs-step .35s ease-in-out 6 var(--bs-delay, 0ms); }
.bubble.deco-play .bs-limbs::after { animation: bs-step .35s ease-in-out 6 calc(var(--bs-delay, 0ms) + .175s); }
@keyframes bs-step { 50% { translate: 0 -3px; } }
@media (prefers-reduced-motion: reduce) {
    .bubble.deco-play .bs-limbs::before, .bubble.deco-play .bs-limbs::after { animation: none; }
}

/* 非动物气泡的长条装饰（2026-10-05 用户：「给这个长条增加一点点装饰」）：长消息底边内侧点缀三个小号的本款角标图案
 * （就是右上角那个 --bs-deco），底部多留一点内边距，不压字。 */
.bubble.bs-sakura.bs-long, .bubble.bs-paw.bs-long, .bubble.bs-star.bs-long, .bubble.bs-cloud.bs-long,
.bubble.bs-lemon.bs-long, .bubble.bs-love.bs-long, .bubble.bs-mint.bs-long,
.row.mine .bubble.bs-sakura.bs-long, .row.mine .bubble.bs-paw.bs-long, .row.mine .bubble.bs-star.bs-long, .row.mine .bubble.bs-cloud.bs-long,
.row.mine .bubble.bs-lemon.bs-long, .row.mine .bubble.bs-love.bs-long, .row.mine .bubble.bs-mint.bs-long {
    padding-bottom: 20px;
    /* 只点缀三个（左、中、右），用户要的是「一点点」；排满一整行显得太密。
       定位基准用内边距区（默认值），图案落在底部多留的那段空白里；用内容区会压到最后一行字 */
    background: var(--bs-deco) 18% calc(100% - 4px) / 11px 11px no-repeat, var(--bs-deco) 50% calc(100% - 4px) / 11px 11px no-repeat,
        var(--bs-deco) 82% calc(100% - 4px) / 11px 11px no-repeat, var(--bs-bg);
}

/* 小动物冲刺入场（2026-10-05 用户：「从发起人出发，向对方移动，像一条狗往前冲一样，四肢摆动，尾巴晃动」）：
 * 实时到达的小动物气泡不用 style.css 的普通入场，由 app.js 的 applyDash 给 .bubble-wrap 加 .enter-dash——
 * 从发送方那一侧画面外跑进来（头朝对方，所以对方的消息往右跑、自己的往左跑），身子前倾，冲过头一点再刹住、往回晃两下；
 * 一路上四条腿快速前后摆、尾巴猛摇、头一点一点、身子一颠一颠，乖乖背上的小香猪往后仰着抓紧。
 * 刹住后接回原来的新消息动画（尾巴摇、腿踏步），它们的起点往后推 .8 秒。整体重绘时 --dash-delay 为负值，接着已播的进度往下播。
 * 跑进来的那段在画面外，行上横向裁掉，避免消息区一闪横向滚动条。 */
.row:has(> .bubble-wrap.enter-dash) { overflow-x: clip; }
.bubble-wrap.enter-dash { animation: bs-dash-them 1s linear both; transform-origin: 50% 100%; }
.row.mine .bubble-wrap.enter-dash { animation-name: bs-dash-mine; }
@keyframes bs-dash-them {
    0% { transform: translateX(calc(-1 * min(110vw, 520px))) rotate(3deg); animation-timing-function: cubic-bezier(.35, .55, .6, 1); }
    58% { transform: translateX(16px) rotate(3deg); animation-timing-function: ease-out; }
    72% { transform: translateX(-6px) rotate(-4deg); animation-timing-function: ease-in-out; }
    86% { transform: translateX(3px) rotate(1.5deg); }
    100% { transform: none; }
}
@keyframes bs-dash-mine {
    0% { transform: translateX(min(110vw, 520px)) rotate(-3deg); animation-timing-function: cubic-bezier(.35, .55, .6, 1); }
    58% { transform: translateX(-16px) rotate(-3deg); animation-timing-function: ease-out; }
    72% { transform: translateX(6px) rotate(4deg); animation-timing-function: ease-in-out; }
    86% { transform: translateX(-3px) rotate(-1.5deg); }
    100% { transform: none; }
}
/* 身子一颠一颠：只在跑的那段（约 .58 秒） */
.enter-dash .bubble[class*=" bs-"] { animation: bs-gallop .145s ease-in-out 4 var(--dash-delay, 0ms); }
.enter-dash .bubble.bs-snake { animation-name: bs-slither; }
@keyframes bs-gallop { 50% { translate: 0 -5px; } }
@keyframes bs-slither { 25% { translate: 0 -2px; } 75% { translate: 0 2px; } }
/* 四条腿前后大步摆，前后腿错开半拍；刹住后接回原来的踏步 */
.enter-dash .bubble .bs-limbs::before, .enter-dash .bubble .bs-limbs::after { transform-origin: 50% 0; }
.enter-dash .bubble .bs-limbs::before {
    animation: bs-run .145s ease-in-out 4 alternate var(--dash-delay, 0ms),
        bs-step .35s ease-in-out 4 calc(var(--bs-delay, 0ms) + .8s);
}
.enter-dash .bubble .bs-limbs::after {
    animation: bs-run .145s ease-in-out 4 alternate-reverse var(--dash-delay, 0ms),
        bs-step .35s ease-in-out 4 calc(var(--bs-delay, 0ms) + .975s);
}
@keyframes bs-run { from { rotate: -28deg; } to { rotate: 28deg; } }
/* 尾巴一路猛摇，刹住后接回原来的摇尾 */
.enter-dash .bubble.bs-guaiguai::after { animation: bs-guai-wag .12s ease-in-out 8 var(--dash-delay, 0ms), bs-guai-wag .35s ease-in-out 4 calc(var(--bs-delay, 0ms) + .8s); }
.enter-dash .bubble.bs-shiba::after { animation: bs-shiba-tail .12s ease-in-out 8 var(--dash-delay, 0ms), bs-shiba-tail .6s ease-in-out 2 calc(var(--bs-delay, 0ms) + .8s); }
.enter-dash .bubble.bs-snake::after { animation: bs-snake-tail .15s ease-in-out 6 var(--dash-delay, 0ms), bs-snake-tail .8s ease-in-out 2 calc(var(--bs-delay, 0ms) + .8s); }
/* 头跟着步子一点一点；乖乖刹住后接着跳眉毛（这条写在 deco-play 的眉毛规则后面、优先级相同，所以要把眉毛一起列上） */
.enter-dash .bubble.bs-shiba::before { animation: bs-head-bob .145s ease-in-out 4 alternate var(--dash-delay, 0ms); }
.enter-dash .bubble.bs-guaiguai::before { animation: bs-head-bob .145s ease-in-out 4 alternate var(--dash-delay, 0ms), bs-guai-brow .8s steps(1, end) 2 calc(var(--bs-delay, 0ms) + .8s); }
.enter-dash .bubble.bs-snake::before { animation: bs-snake-nod .3s ease-in-out 2 var(--dash-delay, 0ms), bs-snake-nod .8s ease-in-out 2 calc(var(--bs-delay, 0ms) + .8s); }
@keyframes bs-head-bob { from { rotate: -5deg; } to { rotate: 6deg; } }
/* 小香猪往后仰着抓紧，刹车时被往前一甩；甩完（1 秒）再来回摇 2 轮。两段都动 rotate，所以摇要等甩完才开始，不叠在一起 */
.enter-dash .bubble.bs-guaiguai.bs-long .bs-rider { transform-origin: 50% 90%; animation: bs-rider-hold 1s linear both var(--dash-delay, 0ms), bs-rider-rock .7s ease-in-out 2 calc(var(--bs-delay, 0ms) + 1s); }
/* 往后仰的方向跟跑的方向相反：自己的消息往左跑，--lean 取 -1 把角度全部反过来 */
.row.mine .enter-dash .bubble.bs-guaiguai.bs-long .bs-rider { --lean: -1; }
@keyframes bs-rider-hold {
    0%, 50% { rotate: calc(var(--lean, 1) * -14deg); translate: 0 -2px; }
    58% { rotate: calc(var(--lean, 1) * 16deg); translate: 0 0; }
    72% { rotate: calc(var(--lean, 1) * -6deg); }
    86% { rotate: calc(var(--lean, 1) * 3deg); }
    100% { rotate: 0deg; }
}
@media (prefers-reduced-motion: reduce) {
    .bubble-wrap.enter-dash, .enter-dash .bubble[class*=" bs-"], .enter-dash .bubble .bs-limbs::before, .enter-dash .bubble .bs-limbs::after,
    .enter-dash .bubble[class*=" bs-"]::before, .enter-dash .bubble[class*=" bs-"]::after, .enter-dash .bs-rider { animation: none; }
}

/* #34 聊天背景（会话双方共享）：加在消息区 #messages 上（预览页加在 .chat 上），纯 CSS 渐变或小图案，不上传图片。
 * 图案用 background-size 平铺，跟消息区一起固定、不随消息滚动。深色模式换成深色版本；
 * 深色底的款式顺带调亮时间条、拍一拍灰字等用到的 --text-dim / --divider-bg。 */
.cbg-sakura { background: linear-gradient(180deg, #fff3f6 0%, #ffe1ea 100%); }
.cbg-sky { background: linear-gradient(180deg, #eaf6ff 0%, #d6ecff 100%); }
.cbg-mint { background: linear-gradient(180deg, #effbf4 0%, #dcf5e6 100%); }
.cbg-sunset { background: linear-gradient(180deg, #fff1e0 0%, #ffd9d0 55%, #f6cde6 100%); }
.cbg-night {
    --text-dim: #b9c1dc; --divider-bg: rgba(255, 255, 255, .14);
    background-color: #1d2340;
    background-image:
        radial-gradient(1.5px 1.5px at 20px 30px, #fff 50%, transparent 51%),
        radial-gradient(1px 1px at 70px 80px, #fff 50%, transparent 51%),
        radial-gradient(1.2px 1.2px at 110px 20px, #ffe9a8 50%, transparent 51%),
        radial-gradient(1px 1px at 150px 110px, #fff 50%, transparent 51%),
        radial-gradient(1.4px 1.4px at 45px 120px, #cfe0ff 50%, transparent 51%),
        radial-gradient(1px 1px at 95px 55px, #fff 50%, transparent 51%),
        radial-gradient(1.2px 1.2px at 165px 60px, #fff 50%, transparent 51%),
        radial-gradient(1px 1px at 10px 85px, #ffe9a8 50%, transparent 51%),
        linear-gradient(180deg, #1d2340 0%, #2c2f5a 100%);
    background-size: 180px 140px, 180px 140px, 180px 140px, 180px 140px, 180px 140px, 180px 140px, 180px 140px, 180px 140px, 100% 100%;
}
/* 星空是唯一在浅色模式下也很深的背景：上面只让直接画在背景上的字（时间条、已读、拍一拍、设置提示、已编辑）用浅色 --text-dim。
 * 自带浅色底的部件（气泡及其引用块、气泡旁的引用 / 收藏 / 回应 / 编辑按钮、回应表情、被引用角标）恢复主题原值，
 * 否则浅色模式下浅图标落在浅底上，变成一个灰方块（2026-10-21 用户反馈）。原值在 #chat 上记下，往下继承。 */
#chat { --text-dim-base: var(--text-dim); --divider-bg-base: var(--divider-bg); }
.cbg-night :is(.bubble, .reply-action, .reaction, .quoted-badge) { --text-dim: var(--text-dim-base, #8a8a8a); --divider-bg: var(--divider-bg-base, rgba(0, 0, 0, .06)); }
.cbg-night .reply-action { background: linear-gradient(var(--panel), var(--panel)), var(--bg); }   /* 主题 A 的 --panel 半透明，叠在星空上会发灰：垫一层主题底色变成实心 */
.cbg-night .pat-line strong { color: #eef0fa; }   /* 拍一拍、设置提示里的名字：默认是主文字色，浅色模式下是深字 */
.cbg-night .new-divider { color: #c9d2f0; }       /* 「以下为新消息」：默认是主题强调色，主题 C 浅色下几乎是黑色 */
.cbg-hearts {
    background-color: #fff6f8;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='56' viewBox='0 0 56 56'%3E%3Cpath d='M14 20c-3-5-10-3-10 2 0 4 6 8 10 11 4-3 10-7 10-11 0-5-7-7-10-2z' fill='%23ffd2dd'/%3E%3Cpath d='M42 46c-2-3.5-7-2-7 1.5 0 2.8 4.2 5.6 7 7.7 2.8-2.1 7-4.9 7-7.7 0-3.5-5-5-7-1.5z' fill='%23ffe0e8'/%3E%3C/svg%3E");
    background-size: 56px 56px;
}
.cbg-paws {
    background-color: #fbf6ee;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64' viewBox='0 0 64 64'%3E%3Cg fill='%23ecdcc4'%3E%3Cellipse cx='16' cy='22' rx='6' ry='5'/%3E%3Ccircle cx='9' cy='14' r='2.4'/%3E%3Ccircle cx='14' cy='10.5' r='2.4'/%3E%3Ccircle cx='19' cy='10.5' r='2.4'/%3E%3Ccircle cx='23.5' cy='14' r='2.4'/%3E%3C/g%3E%3Cg fill='%23f1e5d2' transform='rotate(-20 46 48)'%3E%3Cellipse cx='46' cy='52' rx='5' ry='4.2'/%3E%3Ccircle cx='40' cy='45.5' r='2'/%3E%3Ccircle cx='44.5' cy='42.5' r='2'/%3E%3Ccircle cx='49' cy='42.5' r='2'/%3E%3Ccircle cx='52.5' cy='45.5' r='2'/%3E%3C/g%3E%3C/svg%3E");
    background-size: 64px 64px;
}
.cbg-grid {
    background-color: #fbfbf7;
    background-image: linear-gradient(rgba(120, 150, 200, .13) 1px, transparent 1px), linear-gradient(90deg, rgba(120, 150, 200, .13) 1px, transparent 1px);
    background-size: 22px 22px;
}
/* 2026-10-26 用户定（P-057）：每套主题独占自己的一组背景，切换主题时强制回到新主题的默认背景；分组见 app.js 的 CHAT_BGS。
 * 下面 10 款是这次新做的，现有 8 款分到各组：A 晴空、星空、晚霞；B 樱花粉、爱心、爪印；C 薄荷、方格纸。 */
/* ---------- 主题 A 玻璃清透：蓝紫渐变、磨砂玻璃 ---------- */
/* 极光：淡紫底上两条斜着的青、紫光带 */
.cbg-aurora {
    background-color: #eef0fb;
    background-image:
        radial-gradient(120% 60% at 10% 18%, rgba(120, 220, 210, .38), transparent 60%),
        radial-gradient(110% 55% at 95% 45%, rgba(170, 140, 255, .34), transparent 62%),
        radial-gradient(90% 50% at 30% 95%, rgba(130, 170, 255, .30), transparent 65%);
}
/* 光斑：浅蓝紫底，几团大而虚的圆形光斑，像隔着毛玻璃看灯 */
.cbg-bokeh {
    background-color: #f0f2fc;
    background-image:
        radial-gradient(circle at 18% 22%, rgba(150, 170, 255, .30) 0 46px, transparent 90px),
        radial-gradient(circle at 82% 34%, rgba(200, 160, 255, .26) 0 60px, transparent 120px),
        radial-gradient(circle at 30% 70%, rgba(140, 210, 255, .24) 0 52px, transparent 104px),
        radial-gradient(circle at 78% 86%, rgba(180, 170, 255, .26) 0 40px, transparent 84px);
}
/* 棱镜：极淡的斜向光线，蓝到紫 */
.cbg-prism {
    background-color: #f2f3fb;
    background-image:
        repeating-linear-gradient(115deg, rgba(140, 160, 255, .09) 0 2px, transparent 2px 46px),
        linear-gradient(160deg, #eef3ff 0%, #f1ecff 55%, #f6eefa 100%);
}

/* ---------- 主题 B 软萌果冻：奶油粉、果冻气泡 ---------- */
/* 草莓波点：奶油粉底，错落的粉色圆点 */
.cbg-dots {
    background-color: #fff5f3;
    background-image:
        radial-gradient(circle, #ffd6dc 0 5px, transparent 5.5px),
        radial-gradient(circle, #ffe4d6 0 3.5px, transparent 4px);
    background-size: 44px 44px, 44px 44px;
    background-position: 0 0, 22px 22px;
}
/* 糖果条纹：很淡的粉、杏、薄荷斜条 */
.cbg-candy {
    background-color: #fff7f4;
    background-image: repeating-linear-gradient(135deg, rgba(255, 200, 210, .32) 0 14px, transparent 14px 28px, rgba(255, 222, 196, .30) 28px 42px, transparent 42px 56px, rgba(200, 236, 220, .30) 56px 70px, transparent 70px 84px);
}
/* 棉花糖云：一朵朵圆鼓鼓的粉、杏色小云 */
.cbg-cloud {
    background-color: #fff6f5;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='96' viewBox='0 0 120 96'%3E%3Cg fill='%23ffe1e6'%3E%3Ccircle cx='22' cy='30' r='9'/%3E%3Ccircle cx='33' cy='25' r='12'/%3E%3Ccircle cx='45' cy='31' r='8'/%3E%3Crect x='14' y='30' width='38' height='9' rx='4.5'/%3E%3C/g%3E%3Cg fill='%23ffead9'%3E%3Ccircle cx='80' cy='74' r='7'/%3E%3Ccircle cx='89' cy='70' r='9.5'/%3E%3Ccircle cx='99' cy='75' r='6.5'/%3E%3Crect x='74' y='74' width='31' height='7' rx='3.5'/%3E%3C/g%3E%3C/svg%3E");
    background-size: 120px 96px;
}

/* ---------- 主题 C 简约质感：米白纸张、墨绿气泡 ---------- */
/* 亚麻：米白底上极细的经纬织纹 */
.cbg-linen {
    background-color: #f7f5ee;
    background-image:
        repeating-linear-gradient(0deg, rgba(120, 110, 80, .05) 0 1px, transparent 1px 4px),
        repeating-linear-gradient(90deg, rgba(120, 110, 80, .045) 0 1px, transparent 1px 5px);
}
/* 牛皮纸：暖棕底加细小纤维点 */
.cbg-kraft {
    background-color: #eadbc1;
    background-image:
        radial-gradient(rgba(120, 90, 50, .10) .8px, transparent 1.2px),
        radial-gradient(rgba(255, 255, 255, .20) .7px, transparent 1.1px);
    background-size: 7px 7px, 11px 11px;
    background-position: 0 0, 3px 5px;
}
/* 远山：米白底，底部两层淡墨绿的山影，像水墨 */
.cbg-hills {
    background-color: #f5f5ef;
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 120' preserveAspectRatio='none'%3E%3Cpath d='M0 70 Q60 30 120 62 T240 50 T400 58 V120 H0Z' fill='%23dfe6dd'/%3E%3Cpath d='M0 92 Q80 60 160 88 T320 80 T400 86 V120 H0Z' fill='%23cfdacf'/%3E%3C/svg%3E"),
        linear-gradient(180deg, #f7f6f0 0%, #eef1ea 100%);
    background-repeat: no-repeat, no-repeat;
    background-position: bottom, 0 0;
    background-size: 100% 160px, 100% 100%;
}
/* 点阵手帐：米白底，等距浅灰点 */
.cbg-dotgrid {
    background-color: #fafaf6;
    background-image: radial-gradient(rgba(90, 100, 90, .22) 1px, transparent 1.4px);
    background-size: 18px 18px;
}
@media (prefers-color-scheme: dark) {
    .cbg-sakura { background: linear-gradient(180deg, #2a1d22 0%, #3a2430 100%); }
    .cbg-sky { background: linear-gradient(180deg, #172433 0%, #1d3045 100%); }
    .cbg-mint { background: linear-gradient(180deg, #16261e 0%, #1c3328 100%); }
    .cbg-sunset { background: linear-gradient(180deg, #2c2118 0%, #35222a 55%, #2d2236 100%); }
    .cbg-hearts { background-color: #2a1f23; background-blend-mode: multiply; }
    .cbg-paws { background-color: #26221c; background-blend-mode: multiply; }
    .cbg-grid { background-color: #1f2023; background-image: linear-gradient(rgba(160, 180, 220, .1) 1px, transparent 1px), linear-gradient(90deg, rgba(160, 180, 220, .1) 1px, transparent 1px); }
    .cbg-aurora { background-color: #141a2e; background-image: radial-gradient(120% 60% at 10% 18%, rgba(70, 200, 180, .22), transparent 60%), radial-gradient(110% 55% at 95% 45%, rgba(140, 100, 255, .24), transparent 62%), radial-gradient(90% 50% at 30% 95%, rgba(80, 120, 240, .2), transparent 65%); }
    .cbg-bokeh { background-color: #161a2b; background-image: radial-gradient(circle at 18% 22%, rgba(110, 130, 255, .2) 0 46px, transparent 90px), radial-gradient(circle at 82% 34%, rgba(170, 120, 255, .18) 0 60px, transparent 120px), radial-gradient(circle at 30% 70%, rgba(90, 170, 255, .16) 0 52px, transparent 104px), radial-gradient(circle at 78% 86%, rgba(140, 130, 255, .18) 0 40px, transparent 84px); }
    .cbg-prism { background-color: #171a2c; background-image: repeating-linear-gradient(115deg, rgba(140, 160, 255, .07) 0 2px, transparent 2px 46px), linear-gradient(160deg, #161b2e 0%, #1b1830 55%, #221a2c 100%); }
    .cbg-dots { background-color: #2a1f24; background-image: radial-gradient(circle, #4a2f38 0 5px, transparent 5.5px), radial-gradient(circle, #3f2e2a 0 3.5px, transparent 4px); }
    .cbg-candy { background-color: #2a1f24; background-image: repeating-linear-gradient(135deg, rgba(255, 150, 170, .07) 0 14px, transparent 14px 28px, rgba(255, 190, 150, .06) 28px 42px, transparent 42px 56px, rgba(150, 220, 190, .05) 56px 70px, transparent 70px 84px); }
    .cbg-cloud { background-color: #2a1f24; background-blend-mode: multiply; }
    .cbg-linen { background-color: #1d1c19; background-image: repeating-linear-gradient(0deg, rgba(255, 245, 220, .035) 0 1px, transparent 1px 4px), repeating-linear-gradient(90deg, rgba(255, 245, 220, .03) 0 1px, transparent 1px 5px); }
    .cbg-kraft { background-color: #2b241b; background-image: radial-gradient(rgba(255, 220, 170, .06) .8px, transparent 1.2px), radial-gradient(rgba(0, 0, 0, .25) .7px, transparent 1.1px); }
    .cbg-hills { background-color: #1b1d1a; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 120' preserveAspectRatio='none'%3E%3Cpath d='M0 70 Q60 30 120 62 T240 50 T400 58 V120 H0Z' fill='%23263028'/%3E%3Cpath d='M0 92 Q80 60 160 88 T320 80 T400 86 V120 H0Z' fill='%232e3a30'/%3E%3C/svg%3E"), linear-gradient(180deg, #1b1c19 0%, #1c211d 100%); }
    .cbg-dotgrid { background-color: #1d1d1b; background-image: radial-gradient(rgba(220, 230, 215, .14) 1px, transparent 1.4px); }
}
/* 浅色背景上的灰字（时间、已读、拍一拍、气泡旁按钮等）：主题的 --text-dim 只对主题底色 ≥4.5:1，
 * 这些背景更深或带色，按各自色相再调深一档，同样 ≥4.5:1（含图案色块）；深色模式不变（2026-10-21）。 */
@media not (prefers-color-scheme: dark) {
    .cbg-sakura { --text-dim: #835f6b; }
    .cbg-sky { --text-dim: #5a6b7c; }
    .cbg-mint { --text-dim: #587363; }
    .cbg-sunset { --text-dim: #74575e; }
    .cbg-hearts { --text-dim: #7a5864; }
    .cbg-paws { --text-dim: #6c6151; }
    .cbg-grid { --text-dim: #6e6e68; }
    .cbg-aurora { --text-dim: #5b6280; }
    .cbg-bokeh { --text-dim: #5c6380; }
    .cbg-prism { --text-dim: #5d6380; }
    .cbg-dots { --text-dim: #7d5a62; }
    .cbg-candy { --text-dim: #7a5a60; }
    .cbg-cloud { --text-dim: #7c5b62; }
    .cbg-linen { --text-dim: #66645a; }
    .cbg-kraft { --text-dim: #5e4b33; }
    .cbg-hills { --text-dim: #5d665c; }
    .cbg-dotgrid { --text-dim: #66675f; }
}

/* #35 头像挂件（2026-10-06 用户选定 7 款：蝴蝶结、皇冠、猫耳、兔耳、圣诞帽、小花、狗妹的狗耳朵）：各设各的，戴在自己头像上，对方也看得到。头像里放一个 <i class="pendant pd-x">，
 * 头像加 .has-pd 后不再裁切溢出（图片自己保留圆角）；尺寸、位置都按头像边长的百分比，聊天里 34px、资料页的大头像都适用。 */
.avatar.has-pd { overflow: visible; }
.avatar.has-pd img { border-radius: inherit; }
.pendant { position: absolute; z-index: 1; pointer-events: none; background: center / contain no-repeat; }
.pd-bow { width: 62%; height: 42%; top: -20%; right: -20%; transform: rotate(18deg);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 26'%3E%3Cpath d='M20 13L4 3c-3-1-4 1-4 4v12c0 3 1 5 4 4z' fill='%23ff7aa2' stroke='%23d84a78' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M20 13L36 3c3-1 4 1 4 4v12c0 3-1 5-4 4z' fill='%23ff7aa2' stroke='%23d84a78' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M8 9l6 3M32 9l-6 3' stroke='%23ffc2d4' stroke-width='1.6' stroke-linecap='round'/%3E%3Ccircle cx='20' cy='13' r='4.5' fill='%23ff9bbb' stroke='%23d84a78' stroke-width='1.5'/%3E%3C/svg%3E"); }
.pd-crown { width: 74%; height: 44%; top: -34%; left: 13%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 24'%3E%3Cpath d='M3 22L1 5l10 8 9-12 9 12 10-8-2 17z' fill='%23ffd34d' stroke='%23c99a12' stroke-width='1.6' stroke-linejoin='round'/%3E%3Ccircle cx='20' cy='4' r='2.2' fill='%23ff6b8b'/%3E%3Ccircle cx='1.8' cy='5' r='1.8' fill='%2368c6ff'/%3E%3Ccircle cx='38.2' cy='5' r='1.8' fill='%2368c6ff'/%3E%3Crect x='3' y='18' width='34' height='4' fill='%23f2b92b'/%3E%3C/svg%3E"); }
.pd-cat { width: 104%; height: 44%; top: -30%; left: -2%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 22'%3E%3Cpath d='M2 22L6 2l13 14z' fill='%23444' stroke='%23222' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M6.5 8L8 17l6-2z' fill='%23ffb3c6'/%3E%3Cpath d='M48 22L44 2 31 16z' fill='%23444' stroke='%23222' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M43.5 8L42 17l-6-2z' fill='%23ffb3c6'/%3E%3C/svg%3E"); }
.pd-bunny { width: 80%; height: 72%; top: -62%; left: 10%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 36'%3E%3Cellipse cx='11' cy='17' rx='6' ry='16' transform='rotate(-12 11 17)' fill='%23fff' stroke='%23c9c9c9' stroke-width='1.5'/%3E%3Cellipse cx='11' cy='18' rx='3' ry='11' transform='rotate(-12 11 18)' fill='%23ffc2d4'/%3E%3Cellipse cx='29' cy='17' rx='6' ry='16' transform='rotate(12 29 17)' fill='%23fff' stroke='%23c9c9c9' stroke-width='1.5'/%3E%3Cellipse cx='29' cy='18' rx='3' ry='11' transform='rotate(12 29 18)' fill='%23ffc2d4'/%3E%3C/svg%3E"); }
.pd-santa { width: 92%; height: 70%; top: -48%; left: -6%; transform: rotate(-14deg);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 34'%3E%3Cpath d='M4 26C8 10 18 3 28 4c6 1 10 7 12 14l-4 2c-2-5-4-8-8-8C20 12 16 20 14 26z' fill='%23e53935' stroke='%23b71c1c' stroke-width='1.4' stroke-linejoin='round'/%3E%3Crect x='1' y='24' width='30' height='8' rx='4' fill='%23fff' stroke='%23d0d0d0' stroke-width='1.2'/%3E%3Ccircle cx='38' cy='21' r='4.5' fill='%23fff' stroke='%23d0d0d0' stroke-width='1.2'/%3E%3C/svg%3E"); }
.pd-flower { width: 50%; height: 50%; top: -16%; right: -16%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cg fill='%23ffd1dc' stroke='%23f08aa6' stroke-width='1.2'%3E%3Ccircle cx='15' cy='7' r='6'/%3E%3Ccircle cx='23' cy='13' r='6'/%3E%3Ccircle cx='20' cy='22' r='6'/%3E%3Ccircle cx='10' cy='22' r='6'/%3E%3Ccircle cx='7' cy='13' r='6'/%3E%3C/g%3E%3Ccircle cx='15' cy='15' r='4.5' fill='%23ffd34d' stroke='%23e0a800' stroke-width='1.2'/%3E%3C/svg%3E"); }
.pd-puppy { width: 136%; height: 78%; top: -20%; left: -18%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 40'%3E%3Cpath d='M17 5C6 2 0 17 3 31c5 3 10-6 14-17z' fill='%23F2C9A0' stroke='%231F1F1F' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='M43 5c11-3 17 12 14 26-5 3-10-6-14-17z' fill='%23F2C9A0' stroke='%231F1F1F' stroke-width='2' stroke-linejoin='round'/%3E%3Cg fill='%23FFB3C7' stroke='%23FF6B8B' stroke-width='.8'%3E%3Ccircle cx='51' cy='3.6' r='2.6'/%3E%3Ccircle cx='54.4' cy='6' r='2.6'/%3E%3Ccircle cx='53' cy='9.8' r='2.6'/%3E%3Ccircle cx='49' cy='9.8' r='2.6'/%3E%3Ccircle cx='47.6' cy='6' r='2.6'/%3E%3C/g%3E%3Ccircle cx='51' cy='6.8' r='1.8' fill='%23FFD84D'/%3E%3C/svg%3E"); }
/* 被拍一拍时挂件跟着动（2026-10-18 用户要求）：头像抖（style.css 的 .pat-shake，.6 秒）的同时，戴着的挂件按款式再加一段同长的动作——
 * 蝴蝶结、小花：放大一下再左右晃；皇冠、圣诞帽：往上跳一下落回来；猫耳、兔耳、狗耳：以根部为轴压扁又弹起（扇耳朵）。
 * 全部用 rotate / translate / scale 独立属性，不碰 .pd-bow / .pd-santa 自带的 transform 基准角度。没戴挂件时选择器不命中，什么都不发生。 */
.avatar.pat-shake .pd-bow, .avatar.pat-shake .pd-flower { animation: pd-pat-wobble .6s ease-in-out; }
.avatar.pat-shake .pd-crown, .avatar.pat-shake .pd-santa { animation: pd-pat-hop .6s ease-out; }
.avatar.pat-shake .pd-cat, .avatar.pat-shake .pd-bunny, .avatar.pat-shake .pd-puppy { transform-origin: 50% 100%; animation: pd-pat-flap .6s ease-in-out; }
@keyframes pd-pat-wobble { 20% { scale: 1.3; rotate: -16deg; } 45% { scale: 1.15; rotate: 14deg; } 70% { scale: 1; rotate: -7deg; } 100% { rotate: 0deg; } }
@keyframes pd-pat-hop { 25% { translate: 0 -40%; rotate: -10deg; } 50% { translate: 0 0; rotate: 4deg; } 68% { translate: 0 -14%; } 85% { translate: 0 0; rotate: 0deg; } }
@keyframes pd-pat-flap { 22% { scale: 1.12 .62; } 48% { scale: .94 1.18; } 70% { scale: 1.05 .9; } 100% { scale: 1; } }
@media (prefers-reduced-motion: reduce) {
    .avatar.pat-shake .pendant { animation: none; }
}
/* 挂件选择弹窗里的头像样例：留出上方空间给挂件 */
.pendant-option { padding-top: 26px; }
.avatar.pendant-sample { width: 44px; height: 44px; border-radius: 8px; font-size: 18px; }
/* 设置页、详细资料、我的账号里的大头像（64px）：兔耳、圣诞帽会高出头像 30～40px，被页面容器裁掉，戴挂件时上方多留空间 */
.profile-hero .avatar.has-pd { margin-top: 22px; }
